v4k-git-backup/engine/v4k.c

32760 lines
1.3 MiB
C
Raw Normal View History

2024-08-19 07:54:01 +00:00
/* game framework.
* - rlyeh, public domain
*
* ## V4K License
*
* This software is available under 3 licenses. Choose whichever you prefer.
* ------------------------------------------------------------------------------
* ALTERNATIVE A - Public Domain (https://unlicense.org/)
* ------------------------------------------------------------------------------
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
* software, either in source code form or as a compiled binary, for any purpose,
* commercial or non-commercial, and by any means.
*
* In jurisdictions that recognize copyright laws, the author or authors of this
* software dedicate any and all copyright interest in the software to the public
* domain. We make this dedication for the benefit of the public at large and to
* the detriment of our heirs and successors. We intend this dedication to be an
* overt act of relinquishment in perpetuity of all present and future rights to
* this software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ------------------------------------------------------------------------------
* ALTERNATIVE B - 0-BSD License (https://opensource.org/licenses/FPL-1.0.0)
* ------------------------------------------------------------------------------
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
* ------------------------------------------------------------------------------
* ALTERNATIVE C - MIT-0 (No Attribution clause)
* ------------------------------------------------------------------------------
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* ## License: Contributed Code ------------------------------------------------
*
* Dear Contributor,
*
* In order to ensure this project remains completely free and unencumbered by
* anyone's copyright monopoly, it is advisable that you dedicate your code-base
* contributions to the three licensing terms above. This removes any possible
* ambiguity as to what terms somebody might have thought they were contributing
* under, in case of a future dispute. These concerns are not unique to public
* domain software. Most large, established open-source projects have a
* Contributor License Agreement (CLA) process, of varying degrees of formality.
*
* Please add yourself to the list below before contributing.
* Thanks.
*
* --
*
* "I dedicate any and all copyright interest in this software to the three
* licensing terms listed above. I make this dedication for the benefit of the
* public at large and to the detriment of my heirs and successors. I intend
* this dedication to be an overt act of relinquishment in perpetuity of all
* present and future rights to this software under copyright law."
*
* Author (name) I agree (YES/NO) Files/Features (optional)
* ------------------------------------------------------------------------------
* @r-lyeh YES Initial codebase
* @zak@v4.games YES N/A
* ------------------------------------------------------------------------------
*/
#ifndef V4K_H
#include "v4k.h"
#endif
#if is(win32) // hack to boost exit time. there are no critical systems that need to shutdown properly on Windows,
#define atexit(...) // however Linux needs proper atexit() to deinitialize 3rd_miniaudio.h; likely OSX as well.
#endif
#ifndef V4K_3RD
#define V4K_3RD
#include "v4k"
#endif
//-----------------------------------------------------------------------------
// C files
#line 1 "v4k_begin.c"
#define do_threadlock(mutexptr) \
for( int init_ = !!(mutexptr) || (thread_mutex_init( (mutexptr) = CALLOC(1, sizeof(thread_mutex_t)) ), 1); init_; init_ = 0) \
for( int lock_ = (thread_mutex_lock( mutexptr ), 1); lock_; lock_ = (thread_mutex_unlock( mutexptr ), 0) )
#define AS_NKCOLOR(color) \
((struct nk_color){ ((color>>0))&255,((color>>8))&255,((color>>16))&255,((color>>24))&255 })
#line 0
#line 1 "v4k_ds.c"
// -----------------------------------------------------------------------------
// sort/less
int less_64_ptr(const void *a, const void *b) {
return 0[(uint64_t*)a] - 0[(uint64_t*)b];
}
int less_int_ptr(const void *a, const void *b) {
return 0[(int*)a] - 0[(int*)b];
}
int less_int(int a, int b) {
return a - b;
}
int less_64(uint64_t a, uint64_t b) {
return a > b ? +1 : -!!(a - b);
}
int less_ptr(void *a, void *b) {
return (uintptr_t)a > (uintptr_t)b ? +1 : -!!((uintptr_t)a - (uintptr_t)b);
}
int less_str(char *a, char *b) {
return strcmp((const char *)a, (const char *)b);
}
// -----------------------------------------------------------------------------
// un/hash
uint32_t unhash_32(uint32_t x) {
// Thomas Mueller at https://stackoverflow.com/questions/664014/ - says no collisions for 32bits!
x = ((x >> 16) ^ x) * 0x119de1f3;
x = ((x >> 16) ^ x) * 0x119de1f3;
x = (x >> 16) ^ x;
return x;
}
uint32_t hash_32(uint32_t x) {
// Thomas Mueller at https://stackoverflow.com/questions/664014/ - says no collisions for 32bits!
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = (x >> 16) ^ x;
return x;
}
uint64_t hash_64(uint64_t x) {
#if 1
x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb);
x = x ^ (x >> 31);
return x;
#else
// should we just use x2 hash_32?
uint32_t hi = (x >> 32ull), lo = (x & ~0u);
return (hash_32(hi) << 32ull) | hash_32(lo);
#endif
}
uint64_t hash_flt(double x) {
union { double d; uint64_t i; } c;
return c.d = x, hash_64(c.i);
}
uint64_t hash_str(const char* str) {
uint64_t hash = 14695981039346656037ULL; // hash(0),mul(131) faster than fnv1a, a few more collisions though
while( *str ) hash = ( (unsigned char)*str++ ^ hash ) * 0x100000001b3ULL;
return hash;
}
uint64_t hash_bin(const void* ptr, unsigned len) {
uint64_t hash = 14695981039346656037ULL; // hash(0),mul(131) faster than fnv1a, a few more collisions though
for( unsigned char *str = (unsigned char *)ptr; len--; )
hash = ( (unsigned char)*str++ ^ hash ) * 0x100000001b3ULL;
return hash;
}
uint64_t hash_int(int key) {
return hash_32((uint32_t)key);
}
uint64_t hash_ptr(const void *ptr) {
uint64_t key = (uint64_t)(uintptr_t)ptr;
return hash_64(key); // >> 3? needed?
}
// -----------------------------------------------------------------------------
// utils
uint64_t popcnt64(uint64_t x) {
// [src] https://en.wikipedia.org/wiki/Hamming_weight
x -= (x >> 1) & 0x5555555555555555ULL; //put count of each 2 bits into those 2 bits
x = (x & 0x3333333333333333ULL) + ((x >> 2) & 0x3333333333333333ULL); //put count of each 4 bits into those 4 bits
x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0fULL; //put count of each 8 bits into those 8 bits
return (x * 0x0101010101010101ULL) >> 56; //returns left 8 bits of x + (x<<8) + (x<<16) + (x<<24) + ...
}
// -----------------------------------------------------------------------------
// vector based allocator (x1.75 enlarge factor)
void* vrealloc( void* p, size_t sz ) {
if( !sz ) {
if( p ) {
size_t *ret = (size_t*)p - 2;
ret[0] = 0;
ret[1] = 0;
REALLOC( ret, 0 );
}
return 0;
} else {
size_t *ret;
if( !p ) {
ret = (size_t*)REALLOC( 0, sizeof(size_t) * 2 + sz );
ret[0] = sz;
ret[1] = 0;
} else {
ret = (size_t*)p - 2;
size_t osz = ret[0];
size_t ocp = ret[1];
if( sz <= (osz + ocp) ) {
ret[0] = sz;
ret[1] = ocp - (sz - osz);
} else {
ret = (size_t*)REALLOC( ret, sizeof(size_t) * 2 + sz * 1.75 );
ret[0] = sz;
ret[1] = (size_t)(sz * 1.75) - sz;
}
}
return &ret[2];
}
}
size_t vlen( void* p ) {
return p ? 0[ (size_t*)p - 2 ] : 0;
}
// -----------------------------------------------------------------------------
enum { MAP_GC_SLOT = MAP_HASHSIZE };
typedef int map_is_pow2_assert[ !(MAP_HASHSIZE & (MAP_HASHSIZE - 1)) ? 1:-1];
static int map_get_index(uint64_t hkey1) {
return hkey1 & (MAP_HASHSIZE-1);
}
void (map_init)(map* m) {
map c = {0};
*m = c;
array_resize(m->array, (MAP_HASHSIZE+1));
memset(m->array, 0, (MAP_HASHSIZE+1) * sizeof(m->array[0]) ); // array_resize() just did memset()
}
void (map_insert)(map* m, pair *p, void *key, void *value, uint64_t keyhash, void *super) {
p->keyhash = keyhash;
p->key = key;
p->value = value;
p->super = super;
/* Insert onto the beginning of the list */
int index = map_get_index(p->keyhash);
p->next = m->array[index];
m->array[index] = p;
m->is_sorted = 0;
++m->count;
}
void* (map_find)(map* m, void *key, uint64_t keyhash) {
int index = map_get_index(keyhash);
for( pair *cur = m->array[index]; cur; cur = cur->next ) {
if( cur->keyhash == keyhash ) {
char **c = (char **)cur->key;
char **k = (char **)key;
if( !m->cmp(c[0], k[0]) ) {
return cur->super;
}
}
}
return 0;
}
void (map_erase)(map* m, void *key, uint64_t keyhash) {
int index = map_get_index(keyhash);
for( pair *prev = 0, *cur = m->array[index]; cur; (prev = cur), (cur = cur->next) ) {
if( cur->keyhash == keyhash ) {
char **c = (char **)cur->key;
char **k = (char **)key;
if( !m->cmp(c[0], k[0]) ) {
if( prev ) prev->next = cur->next; else m->array[index] = cur->next ? cur->next : 0;
#if MAP_DONT_ERASE
/* Insert onto the beginning of the GC list */
cur->next = m->array[MAP_GC_SLOT];
m->array[MAP_GC_SLOT] = cur;
#else
REALLOC(cur,0);
#endif
--m->count;
m->is_sorted = 0;
return;
}
}
}
}
int (map_count)(map* m) { // clean deferred GC_SLOT only
return m->count;
int counter = 0;
for( int i = 0; i < MAP_HASHSIZE; ++i) {
for( pair *cur = m->array[i]; cur; cur = cur->next ) {
++counter;
}
}
return counter;
}
int (map_isempty)(map* m) { // clean deferred GC_SLOT only
return !m->count;
}
void (map_gc)(map* m) { // clean deferred GC_SLOT only
#if MAP_DONT_ERASE
for( pair *next, *cur = m->array[MAP_GC_SLOT]; cur; cur = next ) {
next = cur->next;
REALLOC(cur,0);
}
m->array[MAP_GC_SLOT] = 0;
#endif
}
void (map_clear)(map* m) {
for( int i = 0; i <= MAP_HASHSIZE; ++i) {
for( pair *next, *cur = m->array[i]; cur; cur = next ) {
next = cur->next;
REALLOC(cur,0);
}
m->array[i] = 0;
}
m->count = 0;
m->is_sorted = 0;
}
bool (map_sort)(map* m) {
if( m->is_sorted ) return false;
array_clear(m->sorted);
// array_reserve(m->sorted, m->count);
for( int i = 0; i < array_count(m->array); ++i) {
for( pair *cur = m->array[i]; cur; cur = cur->next ) {
array_push(m->sorted, cur);
}
}
#if 0
array_sort(m->sorted, m->cmp);
#else
// @fixme: do better than bubble sort below
for( int i = 0; i < array_count(m->sorted) - 1; ++i)
for( int j = i+1; j < array_count(m->sorted); ++j) {
pair *curi = m->sorted[i];
pair *curj = m->sorted[j];
char **c = (char **)curi->key;
char **k = (char **)curj->key;
if( m->cmp(c[0], k[0]) > 0 ) {
pair *swap = m->sorted[i];
m->sorted[i] = m->sorted[j];
m->sorted[j] = swap;
}
}
#endif
return m->is_sorted = true;
}
void (map_free)(map* m) {
(map_clear)(m);
array_free(m->array);
m->array = 0;
map c = {0};
*m = c;
}
// -----------------------------------------------------------------------------
enum { set_GC_SLOT = SET_HASHSIZE };
typedef int set_is_pow2_assert[ !(SET_HASHSIZE & (SET_HASHSIZE - 1)) ? 1:-1];
static int set_get_index(uint64_t hkey1) {
return hkey1 & (SET_HASHSIZE-1);
}
void (set_init)(set* m) {
set zero = {0};
*m = zero;
array_resize(m->array, (SET_HASHSIZE+1));
memset(m->array, 0, (SET_HASHSIZE+1) * sizeof(m->array[0]) ); // array_resize() just did memset()
}
void (set_insert)(set* m, set_item *p, void *key, uint64_t keyhash, void *super) {
p->keyhash = keyhash;
p->key = key;
p->super = super;
/* Insert onto the beginning of the list */
int index = set_get_index(p->keyhash);
p->next = m->array[index];
m->array[index] = p;
++m->count;
}
void* (set_find)(const set* m, void *key, uint64_t keyhash) {
int index = set_get_index(keyhash);
for( const set_item *cur = m->array[index]; cur; cur = cur->next ) {
if( cur->keyhash == keyhash ) {
char **c = (char **)cur->key;
char **k = (char **)key;
if( !m->cmp(c[0], k[0]) ) {
return cur->super;
}
}
}
return 0;
}
void (set_erase)(set* m, void *key, uint64_t keyhash) {
int index = set_get_index(keyhash);
for( set_item *prev = 0, *cur = m->array[index]; cur; (prev = cur), (cur = cur->next) ) {
if( cur->keyhash == keyhash ) {
char **c = (char **)cur->key;
char **k = (char **)key;
if( !m->cmp(c[0], k[0]) ) {
if (prev) prev->next = cur->next; else m->array[index] = cur->next ? cur->next : 0;
#if SET_DONT_ERASE
/* Insert onto the beginning of the GC list */
cur->next = m->array[set_GC_SLOT];
m->array[set_GC_SLOT] = cur;
#else
REALLOC(cur,0);
#endif
--m->count;
return;
}
}
}
}
int (set_count)(const set* m) { // does not include GC_SLOT
return m->count;
int counter = 0;
for( int i = 0; i < SET_HASHSIZE; ++i) {
for( const set_item *cur = m->array[i]; cur; cur = cur->next ) {
++counter;
}
}
return counter;
}
int (set_isempty)(const set *m) { // clean deferred GC_SLOT only
return !m->count;
}
void (set_gc)(set* m) { // clean deferred GC_SLOT only
#if SET_DONT_ERASE
for( set_item *next, *cur = m->array[set_GC_SLOT]; cur; cur = next ) {
next = cur->next;
REALLOC(cur,0);
}
m->array[set_GC_SLOT] = 0;
#endif
}
void (set_clear)(set* m) { // include GC_SLOT
for( int i = 0; i <= SET_HASHSIZE; ++i) {
for( set_item *next, *cur = m->array[i]; cur; cur = next ) {
next = cur->next;
REALLOC(cur,0);
}
m->array[i] = 0;
}
m->count = 0;
}
void (set_free)(set* m) {
(set_clear)(m);
array_free(m->array);
m->array = 0;
set zero = {0};
*m = zero;
}
#line 0
#line 1 "v4k_string.c"
#include <stdarg.h>
#ifndef STACK_ALLOC_SIZE
#define STACK_ALLOC_SIZE (512*1024)
#endif
char* tempvl(const char *fmt, va_list vl) {
va_list copy;
va_copy(copy, vl);
int sz = /*stbsp_*/vsnprintf( 0, 0, fmt, copy ) + 1;
va_end(copy);
int reqlen = sz;
#if 0
int heap = 0;
enum { STACK_ALLOC = 16384 };
static __thread char buf[STACK_ALLOC];
#else
int heap = 1;
static __thread int STACK_ALLOC = STACK_ALLOC_SIZE;
static __thread char *buf = 0; if(!buf) buf = REALLOC(0, STACK_ALLOC); // @leak
#endif
static __thread int cur = 0; //printf("string stack %d/%d\n", cur, STACK_ALLOC);
if( reqlen >= STACK_ALLOC ) {
tty_color(RED);
printf("no stack enough, increase STACK_ALLOC variable above (reqlen:%d) (fmt: %s)\n", reqlen, fmt);
tty_color(0);
//assert(reqlen < STACK_ALLOC);
STACK_ALLOC = reqlen * 2;
buf = REALLOC(0, STACK_ALLOC);
}
char* ptr = buf + (cur *= (cur+reqlen) < (STACK_ALLOC - 1), (cur += reqlen) - reqlen);
/*stbsp_*/vsnprintf( ptr, sz, fmt, vl );
return (char *)ptr;
}
char* tempva(const char *fmt, ...) {
va_list vl;
va_start(vl, fmt);
char *s = tempvl(fmt, vl);
va_end(vl);
return s;
}
char* (strcatf)(char **src_, const char *buf) {
char *src = *src_;
if(!buf) return src;
// if(!buf[0]) return src;
int srclen = (src ? strlen(src) : 0), buflen = strlen(buf);
src = (char*)REALLOC(src, srclen + buflen + 1 );
memcpy(src + srclen, buf, buflen + 1 );
*src_ = src;
return src;
}
// -----------------------------------------------------------------------------
// string utils
int strmatch(const char *s, const char *wildcard) {
// returns true if wildcard matches
if( *wildcard=='\0' ) return !*s;
if( *wildcard=='*' ) return strmatch(s, wildcard+1) || (*s && strmatch(s+1, wildcard));
if( *wildcard=='?' ) return *s && (*s != '.') && strmatch(s+1, wildcard+1);
return (*s == *wildcard) && strmatch(s+1, wildcard+1);
}
int strmatchi(const char *s, const char *wildcard) {
return strmatch(strlower(s), strlower(wildcard));
}
int strcmp_qsort(const void *a, const void *b) {
const char **ia = (const char **)a;
const char **ib = (const char **)b;
return strcmp(*ia, *ib);
}
int strcmpi_qsort(const void *a, const void *b) {
const char **ia = (const char **)a;
const char **ib = (const char **)b;
return strcmpi(*ia, *ib);
}
bool strbeg(const char *a, const char *b) { // returns true if both strings match at beginning. case sensitive
return strncmp(a, b, strlen(b)) ? false : true; // strstr(a,b) == a
}
bool strend(const char *a, const char *b) { // returns true if both strings match at end. case sensitive
int la = strlen(a), lb = strlen(b);
if( la < lb ) return false;
return strncmp(a + la - lb, b, lb) ? false : true;
}
/*
int main() {
printf("strbeg(abc abc) = %d\n", strbeg("abc", "abc"));
printf("strbeg(abc abcd) = %d\n", strbeg("abc", "abcd"));
printf("strbeg(abcd abc) = %d\n", strbeg("abcd", "abc"));
printf("strbeg(abc (empty)) = %d\n", strbeg("abc", ""));
printf("strbeg((empty) abc) = %d\n", strbeg("", "abc"));
printf("strbeg(123 abcd) = %d\n", strbeg("123", "abcd"));
printf("strbeg(abcd 123) = %d\n", strbeg("abcd", "123"));
puts("---");
printf("strend(abc abc) = %d\n", strend("abc", "abc"));
printf("strend(abc 0abc) = %d\n", strend("abc", "0abc"));
printf("strend(abcd bcd) = %d\n", strend("abcd", "bcd"));
printf("strend(abc (empty)) = %d\n", strend("abc", ""));
printf("strend((empty) abc) = %d\n", strend("", "abc"));
printf("strend(123 abcd) = %d\n", strend("123", "abcd"));
printf("strend(abcd 123) = %d\n", strend("abcd", "123"));
}
*/
bool strbegi(const char *a, const char *b) { // returns true if both strings match at beginning. case insensitive
int la = strlen(a), lb = strlen(b);
if( la < lb ) return 0;
if( lb == 0 ) return 1;
int len = la < lb ? la : lb;
for( int i = 0; i < len; ++i ) {
if( tolower(a[i]) != tolower(b[i]) ) {
return false;
}
}
return true;
}
bool strendi(const char *src, const char *sub) { // returns true if both strings match at end. case insensitive
int srclen = strlen(src);
int sublen = strlen(sub);
if( sublen > srclen ) return 0;
return !strcmpi(src + srclen - sublen, sub);
}
// Find substring in string, case insensitive. Alias for strcasestr()
// Returns first char of coincidence, or NULL.
const char *strstri( const char *src, const char *sub ){
while( *src++ ) {
for( const char *s = src-1, *f = sub, *c = s; ; ++f, ++c) {
if(!*f) return s;
if(!*c) return NULL;
if(tolower(*c) != tolower(*f)) break;
}
}
return NULL;
}
char *strupper(const char *str) {
char *s = va("%s", str), *bak = s;
while(*s++) s[-1] = toupper(s[-1]);
return bak;
}
char *strlower(const char *str) {
char *s = va("%s", str), *bak = s;
while(*s++) s[-1] = tolower(s[-1]);
return bak;
}
#ifndef __APPLE__ // BSD provides these
// Safely concatenate two strings. Always NUL terminates (unless dstcap == 0).
// Returns length of operation; if retval >= dstcap, truncation occurred.
size_t strlcat(char *dst, const char *src, size_t dstcap) {
int dl = strlen(dst), sl = strlen(src);
if( dstcap ) snprintf(dst + dl, dstcap - dl, "%s", src);
return dl + sl;
}
// Safely copy two strings. Always NUL terminates (unless dstcap == 0).
// Copy src to string dst of size dstcap. Copies at most dstcap-1 characters.
// Returns length of input; if retval >= dstcap, truncation occurred.
size_t strlcpy(char *dst, const char *src, size_t dstcap) {
int sl = strlen(src);
if( dstcap ) snprintf(dst, dstcap, "%*s", sl, src);
return sl;// count does not include NUL
}
#endif
char *string8(const wchar_t *str) { // from wchar16(win) to utf8/ascii
int i = 0, n = wcslen(str) * 6 - 1;
static __thread char error[1] = {0}, buffer[2048]; assert( n < 2048 );
while( *str ) {
if (*str < 0x80) {
if (i+1 > n) return error;
buffer[i++] = (char) *str++;
} else if (*str < 0x800) {
if (i+2 > n) return error;
buffer[i++] = 0xc0 + (*str >> 6);
buffer[i++] = 0x80 + (*str & 0x3f);
str += 1;
} else if (*str >= 0xd800 && *str < 0xdc00) {
uint32_t c;
if (i+4 > n) return error;
c = ((str[0] - 0xd800) << 10) + ((str[1]) - 0xdc00) + 0x10000;
buffer[i++] = 0xf0 + (c >> 18);
buffer[i++] = 0x80 + ((c >> 12) & 0x3f);
buffer[i++] = 0x80 + ((c >> 6) & 0x3f);
buffer[i++] = 0x80 + ((c ) & 0x3f);
str += 2;
} else if (*str >= 0xdc00 && *str < 0xe000) {
return error;
} else {
if (i+3 > n) return error;
buffer[i++] = 0xe0 + (*str >> 12);
buffer[i++] = 0x80 + ((*str >> 6) & 0x3f);
buffer[i++] = 0x80 + ((*str ) & 0x3f);
str += 1;
}
}
buffer[i] = 0;
return va("%s", buffer);
}
char *strrepl(char **string, const char *target, const char *replace) { // may reallocate input string if needed
//if new text is shorter than old one,then no need to heap, replace inline
int rlen = strlen(replace), tlen = strlen(target), diff = tlen - rlen;
if( diff >= 0 ) return strswap(*string, target, replace);
char *buf = 0, *aux = *string;
for( int tgtlen = tlen; tgtlen && aux[0]; ) {
char *found = strstr(aux, target);
if( found ) {
strcatf(&buf, "%.*s%s", (int)(found - aux), aux, replace);
aux += (found - aux) + tgtlen;
} else {
strcatf(&buf, "%s", aux);
break;
}
}
if( buf ) {
// strcpy(*string, buf);
char *s = *string;
if(s) s[0] = 0;
strcatf(&s, "%s", buf);
*string = s;
FREE( buf );
}
return *string;
}
char *strswap(char *copy, const char *target, const char *replace) { // replaced only if new text is shorter than old one
int rlen = strlen(replace), diff = strlen(target) - rlen;
if( diff >= 0 ) {
for( char *s = copy, *e = s + strlen(copy); /*s < e &&*/ 0 != (s = strstr(s, target)); ) {
if( rlen ) s = (char*)memcpy( s, replace, rlen ) + rlen;
if( diff ) memmove( s, s + diff, (e - (s + diff)) + 1 );
}
}
return copy;
}
char *strcut(char *copy, const char *target) {
return strswap(copy, target, "");
}
const char *strlerp(unsigned numpairs, const char **pairs, const char *str) { // using key-value pairs, null-terminated
if( !pairs[0] ) {
return str;
}
// find & replace all tokens; @fixme: optimize me
char *buf = REALLOC(0, 128*1024); strcpy(buf, str);
for( unsigned i = 0; i < numpairs; ++i ) {
const char *token = pairs[i*2+0];
const char *repl = pairs[i*2+1];
while(strstr(buf, token)) {
strrepl(&buf, token, repl);
}
}
char *ret = va("%s", buf);
FREE(buf);
return ret;
}
array(char*) strsplit(const char *str, const char *separators) {
enum { SLOTS = 32 };
static __thread int slot = 0;
static __thread char *buf[SLOTS] = {0};
static __thread array(char*) list[SLOTS] = {0};
slot = (slot+1) % SLOTS;
array_resize(list[slot], 0);
*(buf[slot] = REALLOC(buf[slot], strlen(str)*2+1)) = '\0'; // *2 to backup pathological case where input str is only separators && include == 1
for(char *dst = buf[slot]; str && *str; ) {
// count literal run && terminators
int run = strcspn(str, separators);
int end = strspn(str + run, separators);
// append literal run
if( run ) {
array_push(list[slot], dst);
memmove(dst,str,run); dst[run] = '\0'; //strncpy(dst, str, run)
dst += run + 1;
}
#if defined SPLIT_INCLUDE_SEPARATORS && SPLIT_INCLUDE_SEPARATORS
// mode: append all separators: "1++2" -> "1" "+" "+" "2"
for( int i = 0; i < end; ++i ) {
array_push(list[slot], dst);
dst[0] = str[ run + i ];
dst[1] = '\0';
dst += 2;
}
#endif
// skip both
str += run + end;
}
return list[slot];
}
char* strjoin(array(char*) list, const char *separator) {
enum { SLOTS = 16 };
static __thread int slot = 0;
static __thread char* mems[SLOTS] = {0};
slot = (slot+1) % SLOTS;
int num_list = array_count(list);
int len = 0, inc = 0, seplen = strlen(separator);
for( int i = 0; (num_list > 0 ? i < num_list : !!list[i]); ++i ) {
len += strlen(list[i]) + inc;
inc = seplen;
}
mems[slot] = REALLOC(mems[slot], len+1);
char *p = mems[slot]; *p = 0;
const char *sep = "";
for( int i = 0; (num_list > 0 ? i < num_list : !!list[i]); ++i ) {
p += sprintf(p, "%s%s", sep, list[i]);
sep = separator;
}
return mems[slot];
}
static
const char *extract_utf32(const char *s, uint32_t *out) {
/**/ if( (s[0] & 0x80) == 0x00 ) return *out = (s[0]), s + 1;
else if( (s[0] & 0xe0) == 0xc0 ) return *out = (s[0] & 31) << 6 | (s[1] & 63), s + 2;
else if( (s[0] & 0xf0) == 0xe0 ) return *out = (s[0] & 15) << 12 | (s[1] & 63) << 6 | (s[2] & 63), s + 3;
else if( (s[0] & 0xf8) == 0xf0 ) return *out = (s[0] & 7) << 18 | (s[1] & 63) << 12 | (s[2] & 63) << 6 | (s[3] & 63), s + 4;
return *out = 0, s + 0;
}
array(uint32_t) string32( const char *utf8 ) {
static __thread int slot = 0; slot = (slot+1) % 16;
static __thread array(uint32_t) out[16] = {0}; array_resize(out[slot], 0);
//int worstlen = strlen(utf8) + 1; array_reserve(out, worstlen);
while( *utf8 ) {
uint32_t unicode = 0;
utf8 = extract_utf32( utf8, &unicode );
array_push(out[slot], unicode);
}
return out[slot];
}
const char* codepoint_to_utf8(unsigned c) { //< @r-lyeh
static char s[4+1];
memset(s, 0, 5);
/**/ if (c < 0x80) s[0] = c, s[1] = 0;
else if (c < 0x800) s[0] = 0xC0 | ((c >> 6) & 0x1F), s[1] = 0x80 | ( c & 0x3F), s[2] = 0;
else if (c < 0x10000) s[0] = 0xE0 | ((c >> 12) & 0x0F), s[1] = 0x80 | ((c >> 6) & 0x3F), s[2] = 0x80 | ( c & 0x3F), s[3] = 0;
else if (c < 0x110000) s[0] = 0xF0 | ((c >> 18) & 0x07), s[1] = 0x80 | ((c >> 12) & 0x3F), s[2] = 0x80 | ((c >> 6) & 0x3F), s[3] = 0x80 | (c & 0x3F), s[4] = 0;
return s;
}
// -----------------------------------------------------------------------------
// quarks
unsigned quark_intern( quarks_db *q, const char *string ) {
if( string && string[0] ) {
int slen = strlen(string);
int qlen = array_count(q->blob);
char *found;
if( !qlen ) {
array_resize(q->blob, slen + 1 );
memcpy(found = q->blob, string, slen + 1);
} else {
found = strstr(q->blob, string);
if( !found ) {
array_resize(q->blob, qlen - 1 + slen + 1);
memcpy(found = q->blob + qlen - 1, string, slen + 1 );
}
}
// already interned? return that instead
vec2i offset_len = vec2i(found - q->blob, slen);
for( int i = 0; i < array_count(q->entries); ++i ) {
if( offset_len.x == q->entries[i].x )
if( offset_len.y == q->entries[i].y )
return i+1;
}
// else cache and return it
array_push(q->entries, offset_len);
return array_count(q->entries);
}
return 0;
}
const char *quark_string( quarks_db *q, unsigned key ) {
if( key && key <= array_count(q->entries) ) {
vec2i offset_len = q->entries[key-1];
return va("%.*s", offset_len.y, q->blob + offset_len.x);
}
return "";
}
static __thread quarks_db qdb;
unsigned intern( const char *string ) {
return quark_intern( &qdb, string );
}
const char *quark( unsigned key ) {
return quark_string( &qdb, key );
}
#if 0
AUTORUN {
test( !intern(NULL) ); // quark #0, cannot intern null string
test( !intern("") ); // quark #0, ok to intern empty string
test( !quark(0)[0] ); // empty string for quark #0
unsigned q1 = intern("Hello"); // -> quark #1
unsigned q2 = intern("happy"); // -> quark #2
unsigned q3 = intern("world."); // -> quark #3
printf("%u %u %u\n", q1, q2, q3);
test( q1 );
test( q2 );
test( q3 );
test( q1 != q2 );
test( q1 != q3 );
test( q2 != q3 );
unsigned q4 = intern("happy");
printf("%x vs %x\n", q2, q4);
test( q4 );
test( q4 == q2 );
char buf[256];
sprintf(buf, "%s %s %s", quark(q1), quark(q2), quark(q3));
test( !strcmp("Hello happy world.", buf) );
}
#endif
// ----------------------------------------------------------------------------
// localization kit
static const char *kit_lang = "enUS", *kit_langs =
"enUS,enGB,"
"frFR,"
"esES,esAR,esMX,"
"deDE,deCH,deAT,"
"itIT,itCH,"
"ptBR,ptPT,"
"zhCN,zhSG,zhTW,zhHK,zhMO,"
"ruRU,elGR,trTR,daDK,noNB,svSE,nlNL,plPL,fiFI,jaJP,"
"koKR,csCZ,huHU,roRO,thTH,bgBG,heIL"
;
static map(char*,char*) kit_ids;
static map(char*,char*) kit_vars;
#ifndef KIT_FMT_ID2
#define KIT_FMT_ID2 "%s.%s"
#endif
void kit_init() {
do_once map_init(kit_ids, less_str, hash_str);
do_once map_init(kit_vars, less_str, hash_str);
}
void kit_insert( const char *id, const char *translation) {
char *id2 = va(KIT_FMT_ID2, kit_lang, id);
char **found = map_find_or_add_allocated_key(kit_ids, STRDUP(id2), NULL);
if(*found) FREE(*found);
*found = STRDUP(translation);
}
bool kit_merge( const char *filename ) {
// @todo: xlsx2ini
return false;
}
void kit_clear() {
map_clear(kit_ids);
}
bool kit_load( const char *filename ) {
return kit_clear(), kit_merge( filename );
}
void kit_set( const char *key, const char *value ) {
value = value && value[0] ? value : "";
char **found = map_find_or_add_allocated_key(kit_vars, STRDUP(key), NULL );
if(*found) FREE(*found);
*found = STRDUP(value);
}
void kit_reset() {
map_clear(kit_vars);
}
char *kit_translate2( const char *id, const char *lang ) {
char *id2 = va(KIT_FMT_ID2, lang, id);
char **found = map_find(kit_ids, id2);
// return original [[ID]] if no translation is found
if( !found ) return va("[[%s]]", id);
// return translation if no {{moustaches}} are found
if( !strstr(*found, "{{") ) return *found;
// else replace all found {{moustaches}} with context vars
{
// make room
static __thread char *results[16] = {0};
static __thread unsigned counter = 0; counter = (counter+1) % 16;
char *buffer = results[ counter ];
if( buffer ) FREE(buffer), buffer = 0;
// iterate moustaches
const char *begin, *end, *text = *found;
while( NULL != (begin = strstr(text, "{{")) ) {
end = strstr(begin+2, "}}");
if( end ) {
char *var = va("%.*s", (int)(end - (begin+2)), begin+2);
char **found_var = map_find(kit_vars, var);
if( found_var && 0[*found_var] ) {
strcatf(&buffer, "%.*s%s", (int)(begin - text), text, *found_var);
} else {
strcatf(&buffer, "%.*s{{%s}}", (int)(begin - text), text, var);
}
text = end+2;
} else {
strcatf(&buffer, "%.*s", (int)(begin - text), text);
text = begin+2;
}
}
strcatf(&buffer, "%s", text);
return buffer;
}
}
char *kit_translate( const char *id ) {
return kit_translate2( id, kit_lang );
}
void kit_locale( const char *lang ) {
kit_lang = STRDUP(lang); // @leak
}
void kit_dump_state( FILE *fp ) {
for each_map(kit_ids, char *, k, char *, v) {
fprintf(fp, "[ID ] %s=%s\n", k, v);
}
for each_map(kit_vars, char *, k, char *, v) {
fprintf(fp, "[VAR] %s=%s\n", k, v);
}
}
/*
int main() {
kit_init();
kit_locale("enUS");
kit_insert("HELLO_PLAYERS", "Hi {{PLAYER1}} and {{PLAYER2}}!");
kit_insert("GREET_PLAYERS", "Nice to meet you.");
kit_locale("esES");
kit_insert("HELLO_PLAYERS", "Hola {{PLAYER1}} y {{PLAYER2}}!");
kit_insert("GREET_PLAYERS", "Un placer conoceros.");
kit_locale("enUS");
printf("%s %s\n", kit_translate("HELLO_PLAYERS"), kit_translate("GREET_PLAYERS")); // Hi {{PLAYER1}} and {{PLAYER2}}! Nice to meet you.
kit_locale("esES");
kit_set("PLAYER1", "John");
kit_set("PLAYER2", "Karl");
printf("%s %s\n", kit_translate("HELLO_PLAYERS"), kit_translate("GREET_PLAYERS")); // Hola John y Karl! Un placer conoceros.
assert( 0 == strcmp(kit_translate("NON_EXISTING"), "[[NON_EXISTING]]")); // [[NON_EXISTING]]
assert(~puts("Ok"));
}
*/
#line 0
#line 1 "v4k_compat.c"
//-----------------------------------------------------------------------------
// compat (unix & stdio.h)
#if is(tcc) && is(win32) // add missing symbols
const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; /* :: */
const struct in6_addr in6addr_loopback; /* ::1 */
#endif
#if is(win32)
#include <io.h>
#if is(mingw)
#include <unistd.h>
#endif
#else
#include <unistd.h>
#include <sched.h> // sched_setaffinity(), CPU_ZERO(), CPU_COUNT()
#include <sys/ioctl.h>
#endif
#if is(ems)
//#define unlink(x) ((void)(x), 0)
#endif
#if is(win32)
//#define alloca _alloca
#define atoi64 _atoi64
#define popen _popen
#define pclose _pclose
//#define strncasecmp _strnicmp
#define mkdir(p,m) mkdir(p)
#define chdir ifdef(cl, _chdir, chdir)
#if is(cl) || is(tcc)
#define ftruncate _chsize_s
#endif
#define flockfile ifdef(cl,_lock_file,ifdef(mingw,_lock_file,(void)))
#define funlockfile ifdef(cl,_unlock_file,ifdef(mingw,_unlock_file,(void)))
#else // gcc
//#include <alloca.h> // mingw64 does not have it
#include <strings.h> // strncasecmp
#define atoi64 atoll
//#define strstri strcasestr
//#define strcmpi strcasecmp
#endif
#if defined MAX_PATH
#define DIR_MAX MAX_PATH
#elif defined PATH_MAX
#define DIR_MAX PATH_MAX
#else
#define DIR_MAX 260
#endif
#if is(win32) // _MSC_VER and __MINGW64__
#include <stdio.h>
#include <windows.h>
#include <share.h>
#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>
FILE *fmemopen(void *buf, size_t len, const char *type) {
int fd = -1;
char temppath[DIR_MAX - 14], filename[DIR_MAX + 1];
if( GetTempPathA(sizeof(temppath), temppath) )
if( GetTempFileNameA(temppath, "v4k_temp", 0, filename) )
if( !_sopen_s(&fd, filename, _O_CREAT | _O_SHORT_LIVED | _O_TEMPORARY | _O_RDWR | _O_BINARY | _O_NOINHERIT, _SH_DENYRW, _S_IREAD | _S_IWRITE) )
for( FILE *fp = fd != -1 ? _fdopen(fd, "w+b") : NULL; fp; )
return fwrite(buf, len, 1, fp), rewind(fp), unlink(filename), fp; // no need to _close. fclose(on the returned FILE*) also _closes the file descriptor.
return fd != -1 ? _close(fd), NULL : NULL;
}
#endif
#if 0
#if !is(cl)
#define tmpfile file_temp
#endif
#define tmpnam(x) file_tempname()
#endif
#if 0
static
const char *pathfile_from_handle(FILE *fp) {
#if is(win32)
int fd = fileno(fp);
HANDLE handle = (HANDLE)_get_osfhandle( fd ); // <io.h>
DWORD size = GetFinalPathNameByHandleW(handle, NULL, 0, VOLUME_NAME_DOS);
wchar_t name[DIR_MAX] = L"";
size = GetFinalPathNameByHandleW(handle, name, size, VOLUME_NAME_DOS);
name[size] = L'\0';
return wchar16to8(name + 4); // skip \\?\ header
#else
// In Linux, you can use readlink on /proc/self/fd/NNN where NNN is the file descriptor
// In OSX:
// #include <sys/syslimits.h>
// #include <fcntl.h>
// char filePath[DIR_MAX];
// if (fcntl(fd, F_GETPATH, filePath) != -1) {
// // do something with the file path
// }
return 0;
#endif
}
#endif
// -----------------------------------------------------------------------------
// new C macros
#define cast(T) ifdef(c, void *, decltype(T))
#define literal(T) ifdef(c, T, (T))
// -----------------------------------------------------------------------------
void v4k_init();
static void v4k_pre_init();
static void v4k_post_init(float);
#line 0
#line 1 "v4k_ui.c"
// ----------------------------------------------------------------------------------------
// ui extensions first
static float
nk_text_width(struct nk_context *ctx, const char *str, unsigned len) {
const struct nk_style *style = &ctx->style;
const struct nk_user_font *f = style->font;
float pixels_width = f->width(f->userdata, f->height, str, len ? (int)len : (int)strlen(str));
return pixels_width + 10; // 10 -> internal widget padding
}
static nk_bool
nk_hovered_text(struct nk_context *ctx, const char *str, int len,
nk_flags align, nk_bool value)
{
struct nk_window *win;
struct nk_panel *layout;
const struct nk_input *in;
const struct nk_style *style;
enum nk_widget_layout_states state;
struct nk_rect bounds;
NK_ASSERT(ctx);
NK_ASSERT(ctx->current);
NK_ASSERT(ctx->current->layout);
if (!ctx || !ctx->current || !ctx->current->layout)
return 0;
win = ctx->current;
layout = win->layout;
style = &ctx->style;
state = nk_widget(&bounds, ctx);
if (!state) return 0;
in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
#if 1 //< @r-lyeh: sim button logic
struct nk_rect touch;
touch.x = bounds.x - style->selectable.touch_padding.x;
touch.y = bounds.y - style->selectable.touch_padding.y;
touch.w = bounds.w + style->selectable.touch_padding.x * 2;
touch.h = bounds.h + style->selectable.touch_padding.y * 2;
int clicked = !!nk_button_behavior(&ctx->last_widget_state, touch, in, NK_BUTTON_DEFAULT);
in = 0; //< @r-lyeh: do not pass any input
#endif
nk_do_selectable(&ctx->last_widget_state, &win->buffer, bounds,
str, len, align, &value, &style->selectable, in, style->font);
return clicked; //< @r-lyeh: return sim button logic instead of prev function call
}
#define ui_push_hspace(px) \
(int xx = px; xx; xx = 0) \
for(struct nk_context *ctx = (struct nk_context*)ui_handle(); ctx; ctx = 0 ) \
for(struct nk_panel *layout = ui_ctx->current->layout; layout; ) \
for( xx = (layout->at_x += px, layout->bounds.w -= px, 0); layout; layout->at_x -= px, layout->bounds.w += px, layout = 0 )
// helper macros to instance an overlayed toolbar within the regions of an existing widget
#define UI_TOOLBAR_OVERLAY_DECLARE(...) \
__VA_ARGS__; \
struct nk_rect toolbar_bounds; nk_layout_peek(&toolbar_bounds, ui_ctx); \
struct nk_vec2 item_padding = ui_ctx->style.text.padding; \
struct nk_text text; \
text.padding.x = item_padding.x; \
text.padding.y = item_padding.y; \
text.background = ui_ctx->style.window.background;
#define UI_TOOLBAR_OVERLAY(CHOICE,TEXT,COLOR,ALIGNMENT) \
do { \
text.text = COLOR; \
nk_widget_text(&ui_ctx->current->buffer, toolbar_bounds, TEXT, strlen(TEXT), &text, ALIGNMENT, ui_ctx->style.font); \
int clicked_x = input_down(MOUSE_L) && nk_input_is_mouse_hovering_rect(&ui_ctx->input, toolbar_bounds); \
if( clicked_x ) clicked_x = (int)((ui_ctx->input.mouse.pos.x - toolbar_bounds.x) - (ALIGNMENT == NK_TEXT_RIGHT ? bounds.w : 0) ); \
CHOICE = 1 + (ALIGNMENT == NK_TEXT_RIGHT ? -1 : +1) * clicked_x / (UI_ICON_FONTSIZE + UI_ICON_SPACING_X); /* divided by px per ICON_MD_ glyph approximately */ \
int glyphs = strlen(TEXT) / 4 /*3:MD,4:MDI*/; CHOICE *= !!clicked_x * (CHOICE <= glyphs); } while(0)
// menu macros that work not only standalone but also contained within a panel or window
static int ui_using_v2_menubar = 0;
#define UI_MENU(N, ...) do { \
enum { MENUROW_HEIGHT = 25 }; \
int embedded = !!ui_ctx->current; \
struct nk_rect total_space = {0,0,window_width(),window_height()}; \
if( embedded ) total_space = nk_window_get_bounds(ui_ctx), total_space.w -= 10; \
int created = !embedded && nk_begin(ui_ctx, "MENU_" STRINGIZE(__COUNTER__), nk_rect(0, 0, window_width(), UI_MENUROW_HEIGHT), NK_WINDOW_NO_SCROLLBAR); \
if ( embedded || created ) { \
ui_using_v2_menubar = 1; \
int align = NK_TEXT_LEFT, Nth = (N), ITEM_WIDTH = 30, span = 0; \
nk_menubar_begin(ui_ctx); \
nk_layout_row_begin(ui_ctx, NK_STATIC, MENUROW_HEIGHT, Nth); \
__VA_ARGS__; \
nk_menubar_end(ui_ctx); \
if( created ) nk_end(ui_ctx); \
} } while(0)
#define UI_MENU_POPUP(title, px, ...) { \
int hspace = maxi(ITEM_WIDTH, nk_text_width(ui_ctx,(title),0)); \
nk_layout_row_push(ui_ctx, hspace); span += hspace; \
if (nk_menu_begin_label(ui_ctx, (title), align, nk_vec2(px.x>1?px.x:px.x*total_space.w,px.y>1?px.y:px.y*total_space.h))) { \
__VA_ARGS__; \
nk_menu_end(ui_ctx); \
}}
#define UI_MENU_ITEM(title, ...) { \
int hspace = maxi(ITEM_WIDTH, nk_text_width(ui_ctx,(title),0)); \
nk_layout_row_push(ui_ctx, hspace); span += hspace; \
if (nk_menu_begin_label(ui_ctx, (title), align, nk_vec2(1,1))) { \
__VA_ARGS__; \
nk_menu_close(ui_ctx); \
nk_menu_end(ui_ctx); \
}}
#define UI_MENU_ALIGN_RIGHT(px, ...) { \
int hspace = total_space.w - span - (px) - 1.5 * ITEM_WIDTH; \
nk_layout_row_push(ui_ctx, hspace); span += hspace; \
if (nk_menu_begin_label(ui_ctx, (title), align = NK_TEXT_RIGHT, nk_vec2(1,1))) { \
__VA_ARGS__; \
nk_menu_close(ui_ctx); \
nk_menu_end(ui_ctx); \
}}
// ----------------------------------------------------------------------------------------
// ui
#ifndef UI_ICONS_SMALL
//#define UI_ICONS_SMALL 1
#endif
#define UI_FONT_ENUM(carlito,b612) b612 // carlito
#define UI_FONT_REGULAR UI_FONT_ENUM("Carlito", "B612") "-Regular.ttf"
#define UI_FONT_HEADING UI_FONT_ENUM("Carlito", "B612") "-BoldItalic.ttf"
#define UI_FONT_TERMINAL UI_FONT_ENUM("Inconsolata", "B612Mono") "-Regular.ttf"
#if UI_LESSER_SPACING
enum { UI_SEPARATOR_HEIGHT = 5, UI_MENUBAR_ICON_HEIGHT = 20, UI_ROW_HEIGHT = 22, UI_MENUROW_HEIGHT = 32 };
#else
enum { UI_SEPARATOR_HEIGHT = 10, UI_MENUBAR_ICON_HEIGHT = 25, UI_ROW_HEIGHT = 32, UI_MENUROW_HEIGHT = 32 };
#endif
#if UI_FONT_LARGE
#define UI_FONT_REGULAR_SIZE UI_FONT_ENUM(18,17)
#define UI_FONT_HEADING_SIZE UI_FONT_ENUM(20,19)
#define UI_FONT_TERMINAL_SIZE UI_FONT_ENUM(14,14)
#elif UI_FONT_SMALL
#define UI_FONT_REGULAR_SIZE UI_FONT_ENUM(13,14)
#define UI_FONT_HEADING_SIZE UI_FONT_ENUM(14.5,15)
#define UI_FONT_TERMINAL_SIZE UI_FONT_ENUM(14,14)
#else
#define UI_FONT_REGULAR_SIZE UI_FONT_ENUM(14.5,16)
#define UI_FONT_HEADING_SIZE UI_FONT_ENUM(16,17.5)
#define UI_FONT_TERMINAL_SIZE UI_FONT_ENUM(14,14)
#endif
#define UI_FONT_REGULAR_SAMPLING UI_FONT_ENUM(vec3(1,1,1),vec3(1,1,1))
#define UI_FONT_HEADING_SAMPLING UI_FONT_ENUM(vec3(1,1,1),vec3(1,1,1))
#define UI_FONT_TERMINAL_SAMPLING UI_FONT_ENUM(vec3(1,1,1),vec3(1,1,1))
#if UI_ICONS_SMALL
#define UI_ICON_FONTSIZE UI_FONT_ENUM(16.5f,16.5f)
#define UI_ICON_SPACING_X UI_FONT_ENUM(-2,-2)
#define UI_ICON_SPACING_Y UI_FONT_ENUM(4.5f,3.5f)
#else
#define UI_ICON_FONTSIZE UI_FONT_ENUM(20,20)
#define UI_ICON_SPACING_X UI_FONT_ENUM(0,0)
#define UI_ICON_SPACING_Y UI_FONT_ENUM(6.5f,5.0f)
#endif
#define MAX_VERTEX_MEMORY 512 * 1024
#define MAX_ELEMENT_MEMORY 128 * 1024
static struct nk_context *ui_ctx;
static struct nk_glfw nk_glfw = {0};
void* ui_handle() {
return ui_ctx;
}
static void nk_config_custom_fonts() {
#define UI_ICON_MIN ICON_MD_MIN
#define UI_ICON_MED ICON_MD_MAX_16
#define UI_ICON_MAX ICON_MD_MAX
#define ICON_BARS ICON_MD_MENU
#define ICON_FILE ICON_MD_INSERT_DRIVE_FILE
#define ICON_TRASH ICON_MD_DELETE
struct nk_font *font = NULL;
struct nk_font_atlas *atlas = NULL;
nk_glfw3_font_stash_begin(&nk_glfw, &atlas); // nk_sdl_font_stash_begin(&atlas);
// Default font(#1)...
int datalen = 0;
for( char *data = vfs_load(UI_FONT_REGULAR, &datalen); data; data = 0 ) {
float font_size = UI_FONT_REGULAR_SIZE;
struct nk_font_config cfg = nk_font_config(font_size);
cfg.oversample_h = UI_FONT_REGULAR_SAMPLING.x;
cfg.oversample_v = UI_FONT_REGULAR_SAMPLING.y;
cfg.pixel_snap = UI_FONT_REGULAR_SAMPLING.z;
#if UI_LESSER_SPACING
cfg.spacing.x -= 1.0;
#endif
// win32: struct nk_font *arial = nk_font_atlas_add_from_file(atlas, va("%s/fonts/arial.ttf",getenv("windir")), font_size, &cfg); font = arial ? arial : font;
// struct nk_font *droid = nk_font_atlas_add_from_file(atlas, "nuklear/extra_font/DroidSans.ttf", font_size, &cfg); font = droid ? droid : font;
struct nk_font *regular = nk_font_atlas_add_from_memory(atlas, data, datalen, font_size, &cfg); font = regular ? regular : font;
}
// ...with icons embedded on it.
static struct icon_font {
const char *file; int yspacing; vec3 sampling; nk_rune range[3];
} icons[] = {
{"MaterialIconsSharp-Regular.otf", UI_ICON_SPACING_Y, {1,1,1}, {UI_ICON_MIN, UI_ICON_MED /*MAX*/, 0}}, // "MaterialIconsOutlined-Regular.otf" "MaterialIcons-Regular.ttf"
{"materialdesignicons-webfont.ttf", 2, {1,1,1}, {0xF68C /*ICON_MDI_MIN*/, 0xF1CC7/*ICON_MDI_MAX*/, 0}},
};
for( int f = 0; f < countof(icons); ++f )
for( char *data = vfs_load(icons[f].file, &datalen); data; data = 0 ) {
struct nk_font_config cfg = nk_font_config(UI_ICON_FONTSIZE);
cfg.range = icons[f].range; // nk_font_default_glyph_ranges();
cfg.merge_mode = 1;
cfg.spacing.x += UI_ICON_SPACING_X;
cfg.spacing.y += icons[f].yspacing;
// cfg.font->ascent += ICON_ASCENT;
// cfg.font->height += ICON_HEIGHT;
cfg.oversample_h = icons[f].sampling.x;
cfg.oversample_v = icons[f].sampling.y;
cfg.pixel_snap = icons[f].sampling.z;
#if UI_LESSER_SPACING
cfg.spacing.x -= 1.0;
#endif
struct nk_font *icons = nk_font_atlas_add_from_memory(atlas, data, datalen, UI_ICON_FONTSIZE, &cfg);
}
// Monospaced font. Used in terminals or consoles.
for( char *data = vfs_load(UI_FONT_TERMINAL, &datalen); data; data = 0 ) {
const float font_size = UI_FONT_TERMINAL_SIZE;
static const nk_rune icon_range[] = {32, 127, 0};
struct nk_font_config cfg = nk_font_config(font_size);
cfg.range = icon_range;
cfg.oversample_h = UI_FONT_TERMINAL_SAMPLING.x;
cfg.oversample_v = UI_FONT_TERMINAL_SAMPLING.y;
cfg.pixel_snap = UI_FONT_TERMINAL_SAMPLING.z;
#if UI_LESSER_SPACING
cfg.spacing.x -= 1.0;
#endif
// struct nk_font *proggy = nk_font_atlas_add_default(atlas, font_size, &cfg);
struct nk_font *bold = nk_font_atlas_add_from_memory(atlas, data, datalen, font_size, &cfg);
}
// Extra optional fonts from here...
for( char *data = vfs_load(UI_FONT_HEADING, &datalen); data; data = 0 ) {
struct nk_font_config cfg = nk_font_config(UI_FONT_HEADING_SIZE);
cfg.oversample_h = UI_FONT_HEADING_SAMPLING.x;
cfg.oversample_v = UI_FONT_HEADING_SAMPLING.y;
cfg.pixel_snap = UI_FONT_HEADING_SAMPLING.z;
#if UI_LESSER_SPACING
cfg.spacing.x -= 1.0;
#endif
struct nk_font *bold = nk_font_atlas_add_from_memory(atlas, data, datalen, UI_FONT_HEADING_SIZE, &cfg);
// font = bold ? bold : font;
}
nk_glfw3_font_stash_end(&nk_glfw); // nk_sdl_font_stash_end();
// ASSERT(font);
if(font) nk_style_set_font(ui_ctx, &font->handle);
// Load Cursor: if you uncomment cursor loading please hide the cursor
// nk_style_load_all_cursors(ctx, atlas->cursors); glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
}
static void nk_config_custom_theme() {
#ifdef UI_HUE
float default_hue = UI_HUE;
#else
// 0.09 orange, 0.14 yellow, 0.40 green, 0.45 turquoise, 0.50 cyan, 0.52 default, 0.55 blue, 0.80 purple, 0.92 cherry, 0.96 red
float hues[] = { 0.40,0.45,0.50,0.52,0.55,0.80,0.92 };
float default_hue = hues[ (int)((((date() / 10000) % 100) / 24.f) * countof(hues)) ]; // YYYYMMDDhhmmss -> hh as 0..1
default_hue = 0.52;
#endif
float hue = clampf( optionf("--ui-hue", default_hue /*= 0.52*/), 0, 1 );
struct nk_color main_hue = nk_hsv_f(hue+0.025, 0.80, 0.400); // washed
struct nk_color hover_hue = nk_hsv_f(hue+0.025, 1.00, 0.600); // vivid
struct nk_color active_hue = nk_hsv_f(hue-0.010, 1.00, 0.600); // bright; same /S/V than vivid, but H/ slighty biased towards a different luma on spectrum
struct nk_color main = nk_hsv_f( 0.600, 0.00, 0.125); // washed b/w
struct nk_color hover = nk_hsv_f( 0.900, 0.00, 0.000); // vivid b/w
struct nk_color active = nk_hsv_f( 0.600, 0.00, 0.150); // bright b/w
struct nk_color table[NK_COLOR_COUNT] = {0};
table[NK_COLOR_TEXT] = nk_rgba(210, 210, 210, 255);
table[NK_COLOR_WINDOW] = nk_rgba(42, 42, 42, 245);
table[NK_COLOR_HEADER] = nk_rgba(51, 51, 56, 245);
table[NK_COLOR_BORDER] = nk_rgba(46, 46, 46, 255);
table[NK_COLOR_BUTTON] = main;
table[NK_COLOR_BUTTON_HOVER] = hover;
table[NK_COLOR_BUTTON_ACTIVE] = active;
// ok
table[NK_COLOR_TOGGLE] = nk_rgba(45*1.2, 53*1.2, 56*1.2, 255); // table[NK_COLOR_WINDOW]; // nk_rgba(45/1.2, 53/1.2, 56/1.2, 255);
table[NK_COLOR_TOGGLE_HOVER] = active;
table[NK_COLOR_TOGGLE_CURSOR] = main; // vivid_blue;
table[NK_COLOR_SCROLLBAR] = nk_rgba(50, 58, 61, 255);
table[NK_COLOR_SCROLLBAR_CURSOR] = main_hue;
table[NK_COLOR_SCROLLBAR_CURSOR_HOVER] = hover_hue;
table[NK_COLOR_SCROLLBAR_CURSOR_ACTIVE] = active_hue;
table[NK_COLOR_SLIDER] = nk_rgba(50, 58, 61, 255);
table[NK_COLOR_SLIDER_CURSOR] = main_hue;
table[NK_COLOR_SLIDER_CURSOR_HOVER] = hover_hue;
table[NK_COLOR_SLIDER_CURSOR_ACTIVE] = active_hue;
table[NK_COLOR_EDIT] = nk_rgba(50, 58, 61, 225);
table[NK_COLOR_EDIT_CURSOR] = nk_rgba(210, 210, 210, 255);
// table[NK_COLOR_COMBO] = nk_rgba(50, 58, 61, 255);
// table[NK_COLOR_PROPERTY] = nk_rgba(50, 58, 61, 255);
table[NK_COLOR_CHART] = nk_rgba(50, 58, 61, 255);
table[NK_COLOR_CHART_COLOR] = main_hue;
table[NK_COLOR_CHART_COLOR_HIGHLIGHT] = hover_hue; // nk_rgba(255, 0, 0, 255);
// table[NK_COLOR_TAB_HEADER] = main;
// table[NK_COLOR_SELECT] = nk_rgba(57, 67, 61, 255);
// table[NK_COLOR_SELECT_ACTIVE] = main;
// table[NK_COLOR_SELECT] = nk_rgba(255,255,255,255);
table[NK_COLOR_SELECT_ACTIVE] = main_hue;
// @transparent
#if !is(ems)
if( glfwGetWindowAttrib(window_handle(), GLFW_TRANSPARENT_FRAMEBUFFER) == GLFW_TRUE ) {
table[NK_COLOR_WINDOW].a =
table[NK_COLOR_HEADER].a = 255;
}
#endif
// @transparent
nk_style_default(ui_ctx);
nk_style_from_table(ui_ctx, table);
if(1)
{
struct nk_style_selectable *select;
select = &ui_ctx->style.selectable;
// nk_zero_struct(*select);
// select->hover.data.color = hover_hue;
// select->normal_active = nk_style_item_color(table[NK_COLOR_SELECT_ACTIVE]);
select->text_hover = nk_rgba(0,192,255,255);
select->text_hover_active = select->text_hover;
select->text_normal_active = select->text_hover; // nk_style_item_color(table[NK_COLOR_SELECT_ACTIVE]).data.color;
select->rounding = 2.0f;
}
struct nk_style *s = &ui_ctx->style;
s->window.spacing = nk_vec2(4,0);
s->window.combo_border = 0.f;
s->window.scrollbar_size = nk_vec2(5,5);
s->property.rounding = 0;
s->combo.border = 0;
s->combo.button_padding.x = -18;
s->button.border = 1;
s->edit.border = 0;
if( UI_ROW_HEIGHT < 32 ) { // UI_LESSER_SPACING
s->window.header.label_padding.y /= 2; // 2
s->window.header.padding.y /= 2; // /= 4 -> 1
}
}
static float ui_alpha = 1;
static array(float) ui_alphas;
static void ui_alpha_push(float alpha) {
array_push(ui_alphas, ui_alpha);
ui_alpha = alpha;
struct nk_color c;
struct nk_style *s = &ui_ctx->style;
c = s->window.background; c.a = alpha * 255; nk_style_push_color(ui_ctx, &s->window.background, c);
c = s->text.color; c.a = alpha * 255; nk_style_push_color(ui_ctx, &s->text.color, c);
c = s->window.fixed_background.data.color; c.a = alpha * 255; nk_style_push_style_item(ui_ctx, &s->window.fixed_background, nk_style_item_color(c));
}
static void ui_alpha_pop() {
if( array_count(ui_alphas) ) {
nk_style_pop_style_item(ui_ctx);
nk_style_pop_color(ui_ctx);
nk_style_pop_color(ui_ctx);
ui_alpha = *array_back(ui_alphas);
array_pop(ui_alphas);
}
}
// -----------------------------------------------------------------------------
// ui menu
typedef struct ui_item_t {
char *buf;
int bufcap;
int type; // 0xED17 'edit' for a writeable inputbox buffer, else read-only label
} ui_item_t;
static array(ui_item_t) ui_items; // queued menu names. to be evaluated during next frame
static vec2 ui_results = {0}; // clicked menu items from last frame
int ui_item() {
return ui_items ? (ui_results.x == array_count(ui_items) ? ui_results.y : 0) : 0;
}
int ui_menu(const char *items) { // semicolon- or comma-separated items
array_push(ui_items, ((ui_item_t){STRDUP(items),0,0}));
return ui_item();
}
int ui_menu_editbox(char *buf, int bufcap) {
array_push(ui_items, ((ui_item_t){buf,bufcap,0xED17}));
return ui_item();
}
int ui_has_menubar() {
return ui_using_v2_menubar || !!ui_items; // ? UI_MENUROW_HEIGHT + 8 : 0; // array_count(ui_items) > 0;
}
static
void ui_separator_line() {
struct nk_rect space; nk_layout_peek(&space, ui_ctx); // bounds.w *= 0.95f;
struct nk_command_buffer *canvas = nk_window_get_canvas(ui_ctx);
nk_stroke_line(canvas, space.x+0,space.y+0,space.x+space.w,space.y+0, 3.0, nk_rgb(128,128,128));
}
NK_API nk_bool
nk_menu_begin_text_styled(struct nk_context *ctx, const char *title, int len,
nk_flags align, struct nk_vec2 size, struct nk_style_button *style_button) //< @r-lyeh: added style_button param
{
struct nk_window *win;
const struct nk_input *in;
struct nk_rect header;
int is_clicked = nk_false;
nk_flags state;
NK_ASSERT(ctx);
NK_ASSERT(ctx->current);
NK_ASSERT(ctx->current->layout);
if (!ctx || !ctx->current || !ctx->current->layout)
return 0;
win = ctx->current;
state = nk_widget(&header, ctx);
if (!state) return 0;
in = (state == NK_WIDGET_ROM || win->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
if (nk_do_button_text(&ctx->last_widget_state, &win->buffer, header,
title, len, align, NK_BUTTON_DEFAULT, style_button, in, ctx->style.font))
is_clicked = nk_true;
return nk_menu_begin(ctx, win, title, is_clicked, header, size);
}
static
vec2 ui_toolbar_(array(ui_item_t) ui_items, vec2 ui_results) {
// adjust size for all the upcoming UI elements
// old method: nk_layout_row_dynamic(ui_ctx, UI_MENUBAR_ICON_HEIGHT/*h*/, array_count(ui_items));
{
const struct nk_style *style = &ui_ctx->style;
nk_layout_row_template_begin(ui_ctx, UI_MENUBAR_ICON_HEIGHT/*h*/);
for(int i = 0; i < array_count(ui_items); ++i) {
char first_token[512];
sscanf(ui_items[i].buf, "%[^,;|]", first_token); // @fixme: vsnscanf
char *tooltip = strchr(first_token, '@');
int len = tooltip ? (int)(tooltip - first_token /*- 1*/) : strlen(first_token);
float pixels_width = nk_text_width(ui_ctx, first_token, len);
pixels_width += style->window.header.label_padding.x * 2 + style->window.header.padding.x * 2;
if( pixels_width < 5 ) pixels_width = 5;
nk_layout_row_template_push_static(ui_ctx, pixels_width);
}
nk_layout_row_template_end(ui_ctx);
}
// display the UI elements
bool has_popups = ui_popups();
for( int i = 0, end = array_count(ui_items); i < end; ++i ) {
array(char*) ids = strsplit(ui_items[i].buf, ",;|");
// transparent style
static struct nk_style_button transparent_style;
do_once transparent_style = ui_ctx->style.button;
do_once transparent_style.normal.data.color = nk_rgba(0,0,0,0);
do_once transparent_style.border_color = nk_rgba(0,0,0,0);
do_once transparent_style.active = transparent_style.normal;
do_once transparent_style.hover = transparent_style.normal;
do_once transparent_style.hover.data.color = nk_rgba(0,0,0,127);
transparent_style.text_alignment = NK_TEXT_ALIGN_CENTERED|NK_TEXT_ALIGN_MIDDLE; // array_count(ids) > 1 ? NK_TEXT_ALIGN_LEFT : NK_TEXT_ALIGN_CENTERED;
char *tooltip = strchr(ids[0], '@');
int len = tooltip ? (int)(tooltip - ids[0]) : strlen(ids[0]);
// single button
if( array_count(ids) == 1 ) {
// tooltip
if( tooltip && !has_popups ) {
struct nk_rect bounds = nk_widget_bounds(ui_ctx);
if (nk_input_is_mouse_hovering_rect(&ui_ctx->input, bounds) && nk_window_has_focus(ui_ctx)) {
nk_tooltip(ui_ctx, tooltip+1);
}
}
// input...
if( ui_items[i].type == 0xED17 ) {
int active = nk_edit_string_zero_terminated(ui_ctx, NK_EDIT_AUTO_SELECT|NK_EDIT_CLIPBOARD|NK_EDIT_FIELD/*NK_EDIT_BOX*/|NK_EDIT_SIG_ENTER, ui_items[i].buf, ui_items[i].bufcap, nk_filter_default);
if( !!(active & NK_EDIT_COMMITED) ) ui_results = vec2(i+1, 0+1), nk_edit_unfocus(ui_ctx);
}
else
// ... else text
if( nk_button_text_styled(ui_ctx, &transparent_style, ids[0], len) ) {
ui_results = vec2(i+1, 0+1);
}
}
else {
struct nk_vec2 dims = {120, array_count(ids) * UI_MENUROW_HEIGHT};
const struct nk_style *style = &ui_ctx->style;
const struct nk_user_font *f = style->font;
static array(float) lens = 0; array_resize(lens, array_count(ids));
lens[0] = len;
for( int j = 1; j < array_count(ids); ++j ) {
lens[j] = strlen(ids[j]);
float width_px = f->width(f->userdata, f->height, ids[j], lens[j]);
dims.x = maxf(dims.x, width_px);
}
dims.x += 2 * style->window.header.label_padding.x;
// dropdown menu
if( nk_menu_begin_text_styled(ui_ctx, ids[0], lens[0], NK_TEXT_ALIGN_CENTERED|NK_TEXT_ALIGN_MIDDLE, dims, &transparent_style) ) {
nk_layout_row_dynamic(ui_ctx, 0, 1);
for( int j = 1; j < array_count(ids); ++j ) {
char *item = ids[j];
if( *item == '-' ) {
while(*item == '-') ++item, --lens[j];
//nk_menu_item_label(ui_ctx, "---", NK_TEXT_LEFT);
ui_separator_line();
}
if( nk_menu_item_text(ui_ctx, item, lens[j], NK_TEXT_LEFT) ) {
ui_results = vec2(i+1, j+1-1);
}
}
nk_menu_end(ui_ctx);
}
}
}
return ui_results;
}
int ui_toolbar(const char *icons) { // usage: int clicked_icon = ui_toolbar( ICON_1 ";" ICON_2 ";" ICON_3 ";" ICON_4 );
vec2 results = {0};
array(char*) items = strsplit(icons, ",;|");
static array(ui_item_t) temp = 0;
array_resize(temp, array_count(items));
for( int i = 0; i < array_count(items); ++i ) temp[i].buf = items[i], temp[i].bufcap = 0, temp[i].type = 0;
return ui_toolbar_(temp, results).x;
}
// UI Windows handlers. These are not OS Windows but UI Windows instead. For OS Windows check window_*() API.
#ifndef WINDOWS_INI
#define WINDOWS_INI editor_path("windows.ini")
#endif
static map(char*,unsigned) ui_windows = 0;
static void ui_init() {
do_once {
nk_config_custom_fonts();
nk_config_custom_theme();
map_init(ui_windows, less_str, hash_str);
}
}
static int ui_window_register(const char *panel_or_window_title) {
unsigned *state = map_find_or_add_allocated_key(ui_windows, STRDUP(panel_or_window_title), 0);
// check for visibility flag on first call
int visible = 0;
if( *state == 0 ) {
static ini_t i = 0;
do_once i = ini(WINDOWS_INI); // @leak
char **found = i ? map_find(i, va("%s.visible", panel_or_window_title)) : NULL;
if( found ) visible = (*found)[0] == '1';
}
*state |= 2;
return visible;
}
int ui_visible(const char *panel_or_window_title) {
return *map_find_or_add_allocated_key(ui_windows, STRDUP(panel_or_window_title), 0) & 1;
}
int ui_show(const char *panel_or_window_title, int enabled) {
unsigned *found = map_find_or_add_allocated_key(ui_windows, STRDUP(panel_or_window_title), 0);
if( enabled ) {
*found |= 1;
nk_window_collapse(ui_ctx, panel_or_window_title, NK_MAXIMIZED); // in case windows was previously collapsed
} else {
*found &= ~1;
}
return !!enabled;
}
int ui_dims(const char *panel_or_window_title, float width, float height) {
nk_window_set_size(ui_ctx, panel_or_window_title, (struct nk_vec2){width, height});
return 0;
}
vec2 ui_get_dims() {
return (vec2){nk_window_get_width(ui_ctx), nk_window_get_height(ui_ctx)};
}
static char *ui_build_window_list() {
char *build_windows_menu = 0;
strcatf(&build_windows_menu, "%s;", ICON_MD_VIEW_QUILT); // "Windows");
for each_map_ptr_sorted(ui_windows, char*, k, unsigned, v) {
strcatf(&build_windows_menu, "%s %s;", ui_visible(*k) ? ICON_MD_CHECK_BOX : ICON_MD_CHECK_BOX_OUTLINE_BLANK, *k); // ICON_MD_VISIBILITY : ICON_MD_VISIBILITY_OFF, *k); // ICON_MD_TOGGLE_ON : ICON_MD_TOGGLE_OFF, *k);
}
strcatf(&build_windows_menu, "-%s;%s", ICON_MD_RECYCLING " Reset layout", ICON_MD_SAVE_AS " Save layout");
return build_windows_menu; // @leak if discarded
}
static int ui_layout_all_reset(const char *mask);
static int ui_layout_all_save_disk(const char *mask);
static int ui_layout_all_load_disk(const char *mask);
static
void ui_menu_render() {
// clean up from past frame
ui_results = vec2(0,0);
if( !ui_items ) return;
if( !array_count(ui_items) ) return;
// artificially inject Windows menu on the first icon
bool show_window_menu = !!array_count(ui_items);
if( show_window_menu ) {
array_push_front(ui_items, ((ui_item_t){ui_build_window_list(), 0, 0}));
}
// process menus
if( nk_begin(ui_ctx, "Menu", nk_rect(0, 0, window_width(), UI_MENUROW_HEIGHT), NK_WINDOW_NO_SCROLLBAR/*|NK_WINDOW_BACKGROUND*/)) {
if( ui_ctx->current ) {
nk_menubar_begin(ui_ctx);
ui_results = ui_toolbar_(ui_items, ui_results);
//nk_layout_row_end(ui_ctx);
nk_menubar_end(ui_ctx);
}
}
nk_end(ui_ctx);
if( show_window_menu ) {
// if clicked on first menu (Windows)
if( ui_results.x == 1 ) {
array(char*) split = strsplit(ui_items[0].buf,";"); // *array_back(ui_items), ";");
const char *title = split[(int)ui_results.y]; title += title[0] == '-'; title += 3 * (title[0] == '\xee'); title += title[0] == ' '; /*skip separator+emoji+space*/
// toggle window unless clicked on lasts items {"reset layout", "save layout"}
bool clicked_reset_layout = ui_results.y == array_count(split) - 2;
bool clicked_save_layout = ui_results.y == array_count(split) - 1;
/**/ if( clicked_reset_layout ) ui_layout_all_reset("*");
else if( clicked_save_layout ) file_delete(WINDOWS_INI), ui_layout_all_save_disk("*");
else ui_show(title, ui_visible(title) ^ true);
// reset value so developers don't catch this click
ui_results = vec2(0,0);
}
// restore state prior to previously injected Windows menu
else
ui_results.x = ui_results.x > 0 ? ui_results.x - 1 : 0;
}
// clean up for next frame
for( int i = 0; i < array_count(ui_items); ++i ) {
if(ui_items[i].type != 0xED17)
FREE(ui_items[i].buf);
}
array_resize(ui_items, 0);
}
// -----------------------------------------------------------------------------
static int ui_dirty = 1;
static int ui_has_active_popups = 0;
static float ui_hue = 0; // hue
static int ui_is_hover = 0;
static int ui_is_active = 0;
static uint64_t ui_active_mask = 0;
int ui_popups() {
return ui_has_active_popups;
}
int ui_hover() {
return ui_is_hover;
}
int ui_active() {
return ui_is_active; //window_has_cursor() && nk_window_is_any_hovered(ui_ctx) && nk_item_is_any_active(ui_ctx);
}
static
int ui_set_enable_(int enabled) {
static struct nk_style off, on;
do_once {
off = on = ui_ctx->style;
float alpha = 0.5f;
off.text.color.a *= alpha;
#if 0
off.button.normal.data.color.a *= alpha;
off.button.hover.data.color.a *= alpha;
off.button.active.data.color.a *= alpha;
off.button.border_color.a *= alpha;
off.button.text_background.a *= alpha;
off.button.text_normal.a *= alpha;
off.button.text_hover.a *= alpha;
off.button.text_active.a *= alpha;
off.contextual_button.normal.data.color.a *= alpha;
off.contextual_button.hover.data.color.a *= alpha;
off.contextual_button.active.data.color.a *= alpha;
off.contextual_button.border_color.a *= alpha;
off.contextual_button.text_background.a *= alpha;
off.contextual_button.text_normal.a *= alpha;
off.contextual_button.text_hover.a *= alpha;
off.contextual_button.text_active.a *= alpha;
#endif
off.menu_button.normal.data.color.a *= alpha;
off.menu_button.hover.data.color.a *= alpha;
off.menu_button.active.data.color.a *= alpha;
off.menu_button.border_color.a *= alpha;
off.menu_button.text_background.a *= alpha;
off.menu_button.text_normal.a *= alpha;
off.menu_button.text_hover.a *= alpha;
off.menu_button.text_active.a *= alpha;
#if 0
off.option.normal.data.color.a *= alpha;
off.option.hover.data.color.a *= alpha;
off.option.active.data.color.a *= alpha;
off.option.border_color.a *= alpha;
off.option.cursor_normal.data.color.a *= alpha;
off.option.cursor_hover.data.color.a *= alpha;
off.option.text_normal.a *= alpha;
off.option.text_hover.a *= alpha;
off.option.text_active.a *= alpha;
off.option.text_background.a *= alpha;
off.checkbox.normal.data.color.a *= alpha;
off.checkbox.hover.data.color.a *= alpha;
off.checkbox.active.data.color.a *= alpha;
off.checkbox.border_color.a *= alpha;
off.checkbox.cursor_normal.data.color.a *= alpha;
off.checkbox.cursor_hover.data.color.a *= alpha;
off.checkbox.text_normal.a *= alpha;
off.checkbox.text_hover.a *= alpha;
off.checkbox.text_active.a *= alpha;
off.checkbox.text_background.a *= alpha;
off.selectable.normal.data.color.a *= alpha;
off.selectable.hover.data.color.a *= alpha;
off.selectable.pressed.data.color.a *= alpha;
off.selectable.normal_active.data.color.a *= alpha;
off.selectable.hover_active.data.color.a *= alpha;
off.selectable.pressed_active.data.color.a *= alpha;
off.selectable.text_normal.a *= alpha;
off.selectable.text_hover.a *= alpha;
off.selectable.text_pressed.a *= alpha;
off.selectable.text_normal_active.a *= alpha;
off.selectable.text_hover_active.a *= alpha;
off.selectable.text_pressed_active.a *= alpha;
off.selectable.text_background.a *= alpha;
off.slider.normal.data.color.a *= alpha;
off.slider.hover.data.color.a *= alpha;
off.slider.active.data.color.a *= alpha;
off.slider.border_color.a *= alpha;
off.slider.bar_normal.a *= alpha;
off.slider.bar_hover.a *= alpha;
off.slider.bar_active.a *= alpha;
off.slider.bar_filled.a *= alpha;
off.slider.cursor_normal.data.color.a *= alpha;
off.slider.cursor_hover.data.color.a *= alpha;
off.slider.cursor_active.data.color.a *= alpha;
off.slider.dec_button.normal.data.color.a *= alpha;
off.slider.dec_button.hover.data.color.a *= alpha;
off.slider.dec_button.active.data.color.a *= alpha;
off.slider.dec_button.border_color.a *= alpha;
off.slider.dec_button.text_background.a *= alpha;
off.slider.dec_button.text_normal.a *= alpha;
off.slider.dec_button.text_hover.a *= alpha;
off.slider.dec_button.text_active.a *= alpha;
off.slider.inc_button.normal.data.color.a *= alpha;
off.slider.inc_button.hover.data.color.a *= alpha;
off.slider.inc_button.active.data.color.a *= alpha;
off.slider.inc_button.border_color.a *= alpha;
off.slider.inc_button.text_background.a *= alpha;
off.slider.inc_button.text_normal.a *= alpha;
off.slider.inc_button.text_hover.a *= alpha;
off.slider.inc_button.text_active.a *= alpha;
off.progress.normal.data.color.a *= alpha;
off.progress.hover.data.color.a *= alpha;
off.progress.active.data.color.a *= alpha;
off.progress.border_color.a *= alpha;
off.progress.cursor_normal.data.color.a *= alpha;
off.progress.cursor_hover.data.color.a *= alpha;
off.progress.cursor_active.data.color.a *= alpha;
off.progress.cursor_border_color.a *= alpha;
#endif
off.property.normal.data.color.a *= alpha;
off.property.hover.data.color.a *= alpha;
off.property.active.data.color.a *= alpha;
off.property.border_color.a *= alpha;
off.property.label_normal.a *= alpha;
off.property.label_hover.a *= alpha;
off.property.label_active.a *= alpha;
off.property.edit.normal.data.color.a *= alpha;
off.property.edit.hover.data.color.a *= alpha;
off.property.edit.active.data.color.a *= alpha;
off.property.edit.border_color.a *= alpha;
off.property.edit.cursor_normal.a *= alpha;
off.property.edit.cursor_hover.a *= alpha;
off.property.edit.cursor_text_normal.a *= alpha;
off.property.edit.cursor_text_hover.a *= alpha;
off.property.edit.text_normal.a *= alpha;
off.property.edit.text_hover.a *= alpha;
off.property.edit.text_active.a *= alpha;
off.property.edit.selected_normal.a *= alpha;
off.property.edit.selected_hover.a *= alpha;
off.property.edit.selected_text_normal.a *= alpha;
off.property.edit.selected_text_hover.a *= alpha;
off.property.dec_button.normal.data.color.a *= alpha;
off.property.dec_button.hover.data.color.a *= alpha;
off.property.dec_button.active.data.color.a *= alpha;
off.property.dec_button.border_color.a *= alpha;
off.property.dec_button.text_background.a *= alpha;
off.property.dec_button.text_normal.a *= alpha;
off.property.dec_button.text_hover.a *= alpha;
off.property.dec_button.text_active.a *= alpha;
off.property.inc_button.normal.data.color.a *= alpha;
off.property.inc_button.hover.data.color.a *= alpha;
off.property.inc_button.active.data.color.a *= alpha;
off.property.inc_button.border_color.a *= alpha;
off.property.inc_button.text_background.a *= alpha;
off.property.inc_button.text_normal.a *= alpha;
off.property.inc_button.text_hover.a *= alpha;
off.property.inc_button.text_active.a *= alpha;
off.edit.normal.data.color.a *= alpha;
off.edit.hover.data.color.a *= alpha;
off.edit.active.data.color.a *= alpha;
off.edit.border_color.a *= alpha;
off.edit.cursor_normal.a *= alpha;
off.edit.cursor_hover.a *= alpha;
off.edit.cursor_text_normal.a *= alpha;
off.edit.cursor_text_hover.a *= alpha;
off.edit.text_normal.a *= alpha;
off.edit.text_hover.a *= alpha;
off.edit.text_active.a *= alpha;
off.edit.selected_normal.a *= alpha;
off.edit.selected_hover.a *= alpha;
off.edit.selected_text_normal.a *= alpha;
off.edit.selected_text_hover.a *= alpha;
#if 0
off.chart.background.data.color.a *= alpha;
off.chart.border_color.a *= alpha;
off.chart.selected_color.a *= alpha;
off.chart.color.a *= alpha;
off.scrollh.normal.data.color.a *= alpha;
off.scrollh.hover.data.color.a *= alpha;
off.scrollh.active.data.color.a *= alpha;
off.scrollh.border_color.a *= alpha;
off.scrollh.cursor_normal.data.color.a *= alpha;
off.scrollh.cursor_hover.data.color.a *= alpha;
off.scrollh.cursor_active.data.color.a *= alpha;
off.scrollh.cursor_border_color.a *= alpha;
off.scrollv.normal.data.color.a *= alpha;
off.scrollv.hover.data.color.a *= alpha;
off.scrollv.active.data.color.a *= alpha;
off.scrollv.border_color.a *= alpha;
off.scrollv.cursor_normal.data.color.a *= alpha;
off.scrollv.cursor_hover.data.color.a *= alpha;
off.scrollv.cursor_active.data.color.a *= alpha;
off.scrollv.cursor_border_color.a *= alpha;
off.tab.background.data.color.a *= alpha;
off.tab.border_color.a *= alpha;
off.tab.text.a *= alpha;
#endif
off.combo.normal.data.color.a *= alpha;
off.combo.hover.data.color.a *= alpha;
off.combo.active.data.color.a *= alpha;
off.combo.border_color.a *= alpha;
off.combo.label_normal.a *= alpha;
off.combo.label_hover.a *= alpha;
off.combo.label_active.a *= alpha;
off.combo.symbol_normal.a *= alpha;
off.combo.symbol_hover.a *= alpha;
off.combo.symbol_active.a *= alpha;
off.combo.button.normal.data.color.a *= alpha;
off.combo.button.hover.data.color.a *= alpha;
off.combo.button.active.data.color.a *= alpha;
off.combo.button.border_color.a *= alpha;
off.combo.button.text_background.a *= alpha;
off.combo.button.text_normal.a *= alpha;
off.combo.button.text_hover.a *= alpha;
off.combo.button.text_active.a *= alpha;
#if 0
off.window.fixed_background.data.color.a *= alpha;
off.window.background.a *= alpha;
off.window.border_color.a *= alpha;
off.window.popup_border_color.a *= alpha;
off.window.combo_border_color.a *= alpha;
off.window.contextual_border_color.a *= alpha;
off.window.menu_border_color.a *= alpha;
off.window.group_border_color.a *= alpha;
off.window.tooltip_border_color.a *= alpha;
off.window.scaler.data.color.a *= alpha;
off.window.header.normal.data.color.a *= alpha;
off.window.header.hover.data.color.a *= alpha;
off.window.header.active.data.color.a *= alpha;
#endif
}
static struct nk_input input;
if (!enabled) {
ui_alpha_push(0.5);
ui_ctx->style = off; // .button = off.button;
input = ui_ctx->input;
memset(&ui_ctx->input, 0, sizeof(ui_ctx->input));
} else {
ui_alpha_pop();
ui_ctx->style = on; // .button = on.button;
ui_ctx->input = input;
}
return enabled;
}
static int ui_is_enabled = 1;
int ui_enable() {
return ui_is_enabled == 1 ? 0 : ui_set_enable_(ui_is_enabled = 1);
}
int ui_disable() {
return ui_is_enabled == 0 ? 0 : ui_set_enable_(ui_is_enabled = 0);
}
int ui_enabled() {
return ui_is_enabled;
}
static
void ui_destroy(void) {
if(ui_ctx) {
nk_glfw3_shutdown(&nk_glfw); // nk_sdl_shutdown();
ui_ctx = 0;
}
}
static
void ui_create() {
do_once atexit(ui_destroy);
if( ui_dirty ) {
nk_glfw3_new_frame(&nk_glfw); //g->nk_glfw);
ui_dirty = 0;
ui_enable();
}
}
enum {
UI_NOTIFICATION_1 = 32, // sets panel as 1-story notification. used by ui_notify()
UI_NOTIFICATION_2 = 64, // sets panel as 2-story notification. used by ui_notify()
};
struct ui_notify {
char *title;
char *body; // char *icon;
float timeout;
float alpha;
int used;
};
static array(struct ui_notify) ui_notifications; // format=("%d*%s\n%s", timeout, title, body)
static
void ui_notify_render() {
// draw queued notifications
if( array_count(ui_notifications) ) {
struct ui_notify *n = array_back(ui_notifications);
static double timeout = 0;
timeout += window_delta(); // @fixme: use editor_time() instead
ui_alpha_push( timeout >= n->timeout ? 1 - clampf(timeout - n->timeout,0,1) : 1 );
if( timeout < (n->timeout + 1) ) { // N secs display + 1s fadeout
if(n->used++ < 3) nk_window_set_focus(ui_ctx, "!notify");
if( ui_panel( "!notify", n->title && n->body ? UI_NOTIFICATION_2 : UI_NOTIFICATION_1 ) ) {
if(n->title) ui_label(n->title);
if(n->body) ui_label(n->body);
ui_panel_end();
}
}
if( timeout >= (n->timeout + 2) ) { // 1s fadeout + 1s idle
timeout = 0;
if(n->title) FREE(n->title);
if(n->body) FREE(n->body);
array_pop(ui_notifications);
}
ui_alpha_pop();
}
}
static
void ui_hue_cycle( unsigned num_cycles ) {
// cycle color (phi ratio)
for( unsigned i = 0; i < num_cycles; ++i ) {
//ui_hue = (ui_hue+0.61803f)*1.61803f; while(ui_hue > 1) ui_hue -= 1;
ui_hue *= 1.61803f / 1.85f; while(ui_hue > 1) ui_hue -= 1;
}
}
static bool win_debug_visible = true;
static
void ui_render() {
// draw queued menus
ui_notify_render();
ui_menu_render();
/* IMPORTANT: `nk_sdl_render` modifies some global OpenGL state
* with blending, scissor, face culling, depth test and viewport and
* defaults everything back into a default state.
* Make sure to either a.) save and restore or b.) reset your own state after
* rendering the UI. */
//nk_sdl_render(NK_ANTI_ALIASING_ON, MAX_VERTEX_MEMORY, MAX_ELEMENT_MEMORY);
if (win_debug_visible) {
GLfloat bkColor[4]; glGetFloatv(GL_COLOR_CLEAR_VALUE, bkColor); // @transparent
glClearColor(0,0,0,1); // @transparent
glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,!bkColor[3] ? GL_TRUE : GL_FALSE); // @transparent
nk_glfw3_render(&nk_glfw, NK_ANTI_ALIASING_ON, MAX_VERTEX_MEMORY, MAX_ELEMENT_MEMORY);
glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE); // @transparent
} else {
nk_clear(&nk_glfw.ctx);
}
#if is(ems)
glFinish();
#endif
ui_dirty = 1;
ui_hue = 0;
ui_is_hover = nk_window_is_any_hovered(ui_ctx) && window_has_cursor();
if(input_down(MOUSE_L))
ui_is_active = (ui_is_hover && nk_item_is_any_active(ui_ctx));
if(input_up(MOUSE_L))
ui_is_active = 0;
}
// -----------------------------------------------------------------------------
// save/restore all window layouts on every framebuffer resize
#define UI_SNAP_PX 1 /*treshold of pixels when snapping panels/windows to the application borders [1..N]*/
#define UI_ANIM_ALPHA 0.9 /*animation alpha used when restoring panels/windows state from application resizing events: [0..1]*/
//#define UI_MENUBAR_Y 32 // menubar and row
typedef struct ui_layout {
const char *title;
bool is_panel;
vec2 desktop;
vec2 p0,p1;
float l0,l1;
float alpha;
float anim_timer;
} ui_layout;
static array(ui_layout) ui_layouts[2] = {0};
static
int ui_layout_find(const char *title, bool is_panel) {
int i = 0;
for each_array_ptr(ui_layouts[is_panel], ui_layout, s) {
if( !strcmp(title, s->title) ) return i;
++i;
}
ui_layout s = {0};
s.is_panel = is_panel;
s.title = STRDUP(title);
array_push(ui_layouts[is_panel], s);
return array_count(ui_layouts[is_panel]) - 1;
}
static
void ui_layout_save_mem(int idx, vec2 desktop, float workarea_h, struct nk_rect *xywh_, bool is_panel) {
struct nk_rect xywh = *xywh_; //< workaround for a (tcc-0.9.27+lubuntu16) bug, where xywh_ is never populated (ie, empty always) when passed by-copy
ui_layout *s = &ui_layouts[is_panel][idx];
struct nk_window *win = nk_window_find(ui_ctx, s->title);
// if(win && win->flags & NK_WINDOW_FULLSCREEN) return; // skip if maximized
s->desktop = desktop;
float excess = 0;
if( win && (win->flags & NK_WINDOW_MINIMIZED)) {
excess = xywh.h - UI_MENUROW_HEIGHT;
xywh.h = UI_MENUROW_HEIGHT;
}
// sanity checks
if(xywh.x<0) xywh.x = 0;
if(xywh.w>desktop.w-UI_SNAP_PX) xywh.w = desktop.w-UI_SNAP_PX-1;
if(xywh.y<workarea_h) xywh.y = workarea_h;
if(xywh.h>desktop.h-workarea_h-UI_SNAP_PX) xywh.h = desktop.h-workarea_h-UI_SNAP_PX-1;
if((xywh.x+xywh.w)>desktop.w) xywh.x-= xywh.x+xywh.w-desktop.w;
if((xywh.y+xywh.h)>desktop.h) xywh.y-= xywh.y+xywh.h-desktop.h;
if( win && (win->flags & NK_WINDOW_MINIMIZED)) {
xywh.h += excess;
}
// build reconstruction vectors from bottom-right corner
s->p0 = vec2(xywh.x/s->desktop.x,xywh.y/s->desktop.y);
s->p1 = vec2(xywh.w/s->desktop.x,xywh.h/s->desktop.y);
s->p0 = sub2(s->p0, vec2(1,1)); s->l0 = len2(s->p0);
s->p1 = sub2(s->p1, vec2(1,1)); s->l1 = len2(s->p1);
}
static
struct nk_rect ui_layout_load_mem(int idx, vec2 desktop, bool is_panel) {
ui_layout *s = &ui_layouts[is_panel][idx];
// extract reconstruction coords from bottom-right corner
vec2 p0 = mul2(add2(vec2(1,1), scale2(norm2(s->p0), s->l0)), desktop);
vec2 p1 = mul2(add2(vec2(1,1), scale2(norm2(s->p1), s->l1)), desktop);
return nk_rect( p0.x, p0.y, p1.x, p1.y );
}
static
int ui_layout_all_reset(const char *mask) {
ui_layout z = {0};
vec2 desktop = vec2(window_width(), window_height());
float workarea_h = ui_has_menubar()*UI_MENUROW_HEIGHT; // @fixme workarea -> reserved_area
for( int is_panel = 0; is_panel < 2; ++is_panel ) {
for( int j = 0; j < array_count(ui_layouts[is_panel]); ++j ) {
if( ui_layouts[is_panel][j].title ) {
if( nk_window_is_hidden(ui_ctx, ui_layouts[is_panel][j].title) ) continue;
struct nk_rect xywh = { 0, workarea_h + j * UI_MENUROW_HEIGHT, desktop.w / 3.333, UI_MENUROW_HEIGHT };
if( is_panel ) {
xywh.x = 0;
xywh.y = workarea_h + j * UI_MENUROW_HEIGHT;
xywh.w = desktop.w / 4;
xywh.h = desktop.h / 3;
} else {
xywh.x = desktop.w / 3.00 + j * UI_MENUROW_HEIGHT;
xywh.y = workarea_h + j * UI_MENUROW_HEIGHT;
xywh.w = desktop.w / 4;
xywh.h = desktop.h / 3;
}
nk_window_set_focus(ui_ctx, ui_layouts[is_panel][j].title);
nk_window_collapse(ui_ctx, ui_layouts[is_panel][j].title, is_panel ? 0 : 1);
struct nk_window* win = is_panel ? 0 : nk_window_find(ui_ctx, ui_layouts[is_panel][j].title );
if(win) win->flags &= ~NK_WINDOW_FULLSCREEN;
if(win) win->flags &= ~NK_WINDOW_MINIMIZED;
ui_layout_save_mem(j, desktop, workarea_h, &xywh, is_panel);
ui_layouts[is_panel][j].anim_timer = 1.0;
}
}
}
return 1;
}
static
int ui_layout_all_save_disk(const char *mask) {
float w = window_width(), h = window_height();
for each_map_ptr_sorted(ui_windows, char*, k, unsigned, v) {
struct nk_window *win = nk_window_find(ui_ctx, *k);
if( win && strmatchi(*k, mask) ) {
ini_write(WINDOWS_INI, *k, "x", va("%f", win->bounds.x / w ));
ini_write(WINDOWS_INI, *k, "y", va("%f", win->bounds.y / h ));
ini_write(WINDOWS_INI, *k, "w", va("%f", win->bounds.w / w ));
ini_write(WINDOWS_INI, *k, "h", va("%f", win->bounds.h / h ));
ini_write(WINDOWS_INI, *k, "visible", ui_visible(*k) ? "1":"0");
}
}
return 1;
}
static
const char *ui_layout_load_disk(const char *title, const char *mask, ini_t i, struct nk_rect *r) {
if(!i) return 0;
const char *dot = strrchr(title, '.');
if( dot ) title = va("%.*s", (int)(dot - title), title);
if( !strmatchi(title, mask) ) return 0;
char **x = map_find(i, va("%s.x", title));
char **y = map_find(i, va("%s.y", title));
char **w = map_find(i, va("%s.w", title));
char **h = map_find(i, va("%s.h", title));
if( x && y && w && h ) {
float ww = window_width(), wh = window_height();
r->x = atof(*x) * ww;
r->y = atof(*y) * wh;
r->w = atof(*w) * ww;
r->h = atof(*h) * wh;
char **on = map_find(i, va("%s.visible", title));
return title;
}
return 0;
}
static
int ui_layout_all_load_disk(const char *mask) {
ini_t i = ini(WINDOWS_INI); // @leak
if( !i ) return 0;
for each_map(i, char*, k, char*, v) {
struct nk_rect out = {0};
const char *title = ui_layout_load_disk(k, mask, i, &out);
if( title ) {
struct nk_window *win = nk_window_find(ui_ctx, title);
if( win ) {
win->bounds.x = out.x;
win->bounds.y = out.y;
win->bounds.w = out.w;
win->bounds.h = out.h;
}
}
}
return 1;
}
// -----------------------------------------------------------------------------
// shared code for both panels and windows. really messy.
static
int ui_begin_panel_or_window_(const char *title, int flags, bool is_window) {
struct nk_window *win = nk_window_find(ui_ctx, title);
int is_panel = !is_window;
bool starts_minimized = is_panel ? !(flags & PANEL_OPEN) : 0;
bool is_closable = is_window;
bool is_scalable = true;
bool is_movable = true;
bool is_auto_minimizes = starts_minimized; // false;
bool is_pinned = win && (win->flags & NK_WINDOW_PINNED);
if( is_pinned ) {
// is_closable = false;
is_auto_minimizes = false;
is_scalable = false;
// is_movable = false;
}
ui_create();
uint64_t hash = 14695981039346656037ULL, mult = 0x100000001b3ULL;
for(int i = 0; title[i]; ++i) hash = (hash ^ title[i]) * mult;
ui_hue = (hash & 0x3F) / (float)0x3F; ui_hue += !ui_hue;
int idx = ui_layout_find(title, is_panel);
ui_layout *s = &ui_layouts[is_panel][idx];
vec2 desktop = vec2(window_width(), window_height());
float workarea_h = ui_has_menubar()*UI_MENUROW_HEIGHT;
int row = idx + !!ui_has_menubar(); // add 1 to skip menu
vec2 offset = vec2(0, UI_ROW_HEIGHT*row);
float w = desktop.w / 3.33, h = (flags & UI_NOTIFICATION_2 ? UI_MENUROW_HEIGHT*2 : (flags & UI_NOTIFICATION_1 ? UI_MENUROW_HEIGHT : desktop.h - offset.y * 2 - 1)); // h = desktop.h * 0.66; //
struct nk_rect start_coords = {offset.x, offset.y, offset.x+w, offset.y+h};
if(is_window) {
w = desktop.w / 1.5;
h = desktop.h / 1.5;
start_coords.x = (desktop.w-w)/2;
start_coords.y = (desktop.h-h)/2 + workarea_h/2;
start_coords.w = w;
start_coords.h = h;
}
static vec2 edge = {0}; static int edge_type = 0; // [off],L,R,U,D
do_once edge = vec2(desktop.w * 0.33, desktop.h * 0.66);
// do not snap windows and/or save windows when using may be interacting with UI
int is_active = 0;
int mouse_pressed = !!input(MOUSE_L) && ui_ctx->active == win;
if( win ) {
// update global window activity bitmask
is_active = ui_ctx->active == win;
ui_active_mask = is_active ? ui_active_mask | (1ull << idx) : ui_active_mask & ~(1ull << idx);
}
// struct nk_style *s = &ui_ctx->style;
// nk_style_push_color(ui_ctx, &s->window.header.normal.data.color, nk_hsv_f(ui_hue,0.6,0.8));
// adjust inactive edges automatically
if( win ) {
bool group1_any = !is_active; // && !input(MOUSE_L);
bool group2_not_resizing = is_active && !win->is_window_resizing;
bool group2_interacting = is_active && input(MOUSE_L);
#if 0
if( group1_any ) {
// cancel self-adjust if this window is not overlapping the active one that is being resized at the moment
struct nk_window *parent = ui_ctx->active;
struct nk_rect a = win->bounds, b = parent->bounds;
bool overlap = a.x <= (b.x+b.w) && b.x <= (a.x+a.w) && a.y <= (b.y+b.h) && b.y <= (a.y+a.h);
group1_any = overlap;
}
#else
if( group1_any )
group1_any = !(win->flags & NK_WINDOW_PINNED);
#endif
if( group1_any ) {
float mouse_x = clampf(input(MOUSE_X), 0, desktop.w);
float mouse_y = clampf(input(MOUSE_Y), 0, desktop.h);
float distance_x = absf(mouse_x - win->bounds.x) / desktop.w;
float distance_y = absf(mouse_y - win->bounds.y) / desktop.h;
float alpha_x = sqrt(sqrt(distance_x)); // amplify signals a little bit: 0.1->0.56,0.5->0.84,0.98->0.99,etc
float alpha_y = sqrt(sqrt(distance_y));
/**/ if( (edge_type & 1) && win->bounds.x <= UI_SNAP_PX ) {
win->bounds.w = win->bounds.w * alpha_y + edge.w * (1-alpha_y);
}
else if( (edge_type & 2) && (win->bounds.x + win->bounds.w) >= (desktop.w-UI_SNAP_PX) ) {
win->bounds.w = win->bounds.w * alpha_y + edge.w * (1-alpha_y);
win->bounds.x = desktop.w - win->bounds.w;
}
if( (edge_type & 8) && (win->bounds.y + (win->flags & NK_WINDOW_MINIMIZED ? UI_ROW_HEIGHT : win->bounds.h)) >= (desktop.h-UI_SNAP_PX) ) {
win->bounds.h = win->bounds.h * alpha_x + edge.h * (1-alpha_x);
win->bounds.y = desktop.h - (win->flags & NK_WINDOW_MINIMIZED ? UI_ROW_HEIGHT : win->bounds.h);
}
}
// skip any saving if window is animating (moving) and/or maximized
bool anim_in_progress = s->anim_timer > 1e-3;
s->anim_timer *= anim_in_progress * UI_ANIM_ALPHA;
if( group1_any || !group2_interacting || anim_in_progress ) {
struct nk_rect target = ui_layout_load_mem(idx, desktop, is_panel);
float alpha = len2sq(sub2(s->desktop, desktop)) ? 0 : UI_ANIM_ALPHA; // smooth unless we're restoring a desktop change
#if 1
if( is_window && win->flags & NK_WINDOW_FULLSCREEN ) {
target.x = 1;
target.w = desktop.w - 1;
target.y = workarea_h + 1;
target.h = desktop.h - workarea_h - 2;
}
if( is_window && win->is_window_restoring > 1e-2) {
win->is_window_restoring = win->is_window_restoring * alpha + 0 * (1 - alpha);
target.w = desktop.w / 2;
target.h = (desktop.h - workarea_h) / 2;
target.x = (desktop.w - target.w) / 2;
target.y = ((desktop.h - workarea_h) - target.h) / 2;
}
#endif
win->bounds = nk_rect(
win->bounds.x * alpha + target.x * (1 - alpha),
win->bounds.y * alpha + target.y * (1 - alpha),
win->bounds.w * alpha + target.w * (1 - alpha),
win->bounds.h * alpha + target.h * (1 - alpha)
);
}
if(!anim_in_progress)
ui_layout_save_mem(idx, desktop, workarea_h, &win->bounds, is_panel);
} else { // if(!win)
ui_layout_save_mem(idx, desktop, workarea_h, &start_coords, is_panel);
}
int window_flags = NK_WINDOW_PINNABLE | NK_WINDOW_MINIMIZABLE | NK_WINDOW_NO_SCROLLBAR_X | (is_window ? NK_WINDOW_MAXIMIZABLE : 0);
if( starts_minimized ) window_flags |= (win ? 0 : NK_WINDOW_MINIMIZED);
if( is_auto_minimizes ) window_flags |= is_active ? 0 : !!starts_minimized * NK_WINDOW_MINIMIZED;
if( is_movable ) window_flags |= NK_WINDOW_MOVABLE;
if( is_closable ) window_flags |= NK_WINDOW_CLOSABLE;
if( is_scalable ) {
window_flags |= NK_WINDOW_SCALABLE;
if(win) window_flags |= input(MOUSE_X) < (win->bounds.x + win->bounds.w/2) ? NK_WINDOW_SCALE_LEFT : 0;
if(win) window_flags |= input(MOUSE_Y) < (win->bounds.y + win->bounds.h/2) ? NK_WINDOW_SCALE_TOP : 0;
}
// if( is_pinned )
window_flags |= NK_WINDOW_BORDER;
if( is_panel && win && !is_active ) {
if( !is_pinned && is_auto_minimizes ) {
window_flags |= NK_WINDOW_MINIMIZED;
}
}
// if( is_modal ) window_flags &= ~(NK_WINDOW_MINIMIZED | NK_WINDOW_MINIMIZABLE);
if( is_panel && win ) {
// if( win->bounds.x > 0 && (win->bounds.x+win->bounds.w) < desktop.w-1 ) window_flags &= ~NK_WINDOW_MINIMIZED;
}
if(!win) { // if newly created window (!win)
// first time, try to restore from WINDOWS_INI file
static ini_t i; do_once i = ini(WINDOWS_INI); // @leak
ui_layout_load_disk(title, "*", i, &start_coords);
ui_layout_save_mem(idx, desktop, workarea_h, &start_coords, is_panel);
}
bool is_notify = flags & (UI_NOTIFICATION_1 | UI_NOTIFICATION_2);
if( is_notify ) {
window_flags = NK_WINDOW_MOVABLE | NK_WINDOW_NOT_INTERACTIVE | NK_WINDOW_NO_SCROLLBAR;
start_coords = nk_rect(desktop.w / 2 - w / 2, -h, w, h);
}
if( nk_begin(ui_ctx, title, start_coords, window_flags) ) {
// set width for all inactive panels
struct nk_rect bounds = nk_window_get_bounds(ui_ctx);
if( mouse_pressed && win && win->is_window_resizing ) {
edge = vec2(bounds.w, bounds.h);
// push direction
int top = !!(win->is_window_resizing & NK_WINDOW_SCALE_TOP);
int left = !!(win->is_window_resizing & NK_WINDOW_SCALE_LEFT), right = !left;
edge_type = 0;
/**/ if( right && (win->bounds.x <= UI_SNAP_PX) ) edge_type |= 1;
else if( left && (win->bounds.x + win->bounds.w) >= (desktop.w-UI_SNAP_PX) ) edge_type |= 2;
/**/ if( top && (win->bounds.y + win->bounds.h) >= (desktop.h-UI_SNAP_PX) ) edge_type |= 8;
// @fixme
// - if window is in a corner (sharing 2 edges), do not allow for multi edges. either vert or horiz depending on the clicked scaler
// - or maybe, only propagate edge changes to the other windows that overlapping our window.
}
return 1;
} else {
if(is_panel) {
ui_panel_end();
} else ui_window_end();
return 0;
}
}
static const char *ui_last_title = 0;
static int *ui_last_enabled = 0;
static int ui_has_window = 0;
static int ui_window_has_menubar = 0;
int ui_window(const char *title, int *enabled) {
if( window_width() <= 0 ) return 0;
if( window_height() <= 0 ) return 0;
if( !ui_ctx || !ui_ctx->style.font ) return 0;
bool forced_creation = enabled && *enabled; // ( enabled ? *enabled : !ui_has_menubar() );
forced_creation |= ui_window_register(title);
if(!ui_visible(title)) {
if( !forced_creation ) return 0;
ui_show(title, forced_creation);
}
ui_last_enabled = enabled;
ui_last_title = title;
ui_has_window = 1;
return ui_begin_panel_or_window_(title, /*flags*/0, true);
}
int ui_window_end() {
if(ui_window_has_menubar) nk_menubar_end(ui_ctx), ui_window_has_menubar = 0;
nk_end(ui_ctx), ui_has_window = 0;
int closed = 0;
if( nk_window_is_hidden(ui_ctx, ui_last_title) ) {
nk_window_close(ui_ctx, ui_last_title);
ui_show(ui_last_title, false);
if( ui_last_enabled ) *ui_last_enabled = 0; // clear developers' flag
closed = 1;
}
// @transparent
#if !is(ems)
static bool has_transparent_attrib = 0; do_once has_transparent_attrib = glfwGetWindowAttrib(window_handle(), GLFW_TRANSPARENT_FRAMEBUFFER) == GLFW_TRUE;
if( closed && has_transparent_attrib && !ui_has_menubar() ) {
bool any_open = 0;
for each_map_ptr(ui_windows, char*, k, unsigned, v) any_open |= *v & 1;
if( !any_open ) glfwSetWindowShouldClose(window_handle(), GLFW_TRUE);
}
#endif
// @transparent
return 0;
}
int ui_panel(const char *title, int flags) {
if( window_width() <= 0 ) return 0;
if( window_height() <= 0 ) return 0;
if( !ui_ctx || !ui_ctx->style.font ) return 0;
if( ui_has_window ) {
// transparent style
static struct nk_style_button transparent_style;
do_once transparent_style = ui_ctx->style.button;
do_once transparent_style.normal.data.color = nk_rgba(0,0,0,0);
do_once transparent_style.border_color = nk_rgba(0,0,0,0);
do_once transparent_style.active = transparent_style.normal;
do_once transparent_style.hover = transparent_style.normal;
do_once transparent_style.hover.data.color = nk_rgba(0,0,0,127);
transparent_style.text_alignment = NK_TEXT_ALIGN_CENTERED|NK_TEXT_ALIGN_MIDDLE;
if(!ui_window_has_menubar) nk_menubar_begin(ui_ctx);
if(!ui_window_has_menubar) nk_layout_row_begin(ui_ctx, NK_STATIC, UI_MENUBAR_ICON_HEIGHT, 4);
if(!ui_window_has_menubar) nk_layout_row_push(ui_ctx, 70);
ui_window_has_menubar = 1;
return nk_menu_begin_text_styled(ui_ctx, title, strlen(title), NK_TEXT_ALIGN_CENTERED|NK_TEXT_ALIGN_MIDDLE, nk_vec2(220, 200), &transparent_style);
}
return ui_begin_panel_or_window_(title, flags, false);
}
int ui_panel_end() {
if( ui_has_window ) {
nk_menu_end(ui_ctx);
return 0;
}
nk_end(ui_ctx);
// nk_style_pop_color(ui_ctx);
return 0;
}
static unsigned ui_collapse_state = 0;
int ui_collapse(const char *label, const char *id) { // mask: 0(closed),1(open),2(created)
int start_open = label[0] == '!'; label += start_open;
uint64_t hash = 14695981039346656037ULL, mult = 0x100000001b3ULL;
for(int i = 0; id[i]; ++i) hash = (hash ^ id[i]) * mult;
ui_hue = (hash & 0x3F) / (float)0x3F; ui_hue += !ui_hue;
int forced = ui_filter && ui_filter[0];
enum nk_collapse_states forced_open = NK_MAXIMIZED;
ui_collapse_state = nk_tree_base_(ui_ctx, NK_TREE_NODE, 0, label, start_open ? NK_MAXIMIZED : NK_MINIMIZED, forced ? &forced_open : NULL, id, strlen(id), 0);
return ui_collapse_state & 1; // |1 open, |2 clicked, |4 toggled
}
int ui_collapse_clicked() {
return ui_collapse_state >> 1; // |1 clicked, |2 toggled
}
int ui_collapse_end() {
return nk_tree_pop(ui_ctx), 1;
}
int ui_contextual() {
#if 0
struct nk_rect bounds = nk_widget_bounds(ui_ctx); // = nk_window_get_bounds(ui_ctx);
bounds.y -= 25;
return ui_popups() ? 0 : nk_contextual_begin(ui_ctx, 0, nk_vec2(150, 300), bounds);
#else
return ui_popups() ? 0 : nk_contextual_begin(ui_ctx, 0, nk_vec2(300, 220), nk_window_get_bounds(ui_ctx));
#endif
}
int ui_contextual_end(int close) {
if(close) nk_contextual_close(ui_ctx);
nk_contextual_end(ui_ctx);
return 1;
}
int ui_submenu(const char *options) {
int choice = 0;
if( ui_contextual() ) {
array(char*) tokens = strsplit(options, ";");
for( int i = 0; i < array_count(tokens) ; ++i ) {
if( ui_button_transparent(tokens[i]) ) choice = i + 1;
}
ui_contextual_end(0);
}
return choice;
}
// -----------------------------------------------------------------------------
// code for all the widgets
static
int nk_button_transparent(struct nk_context *ctx, const char *text) {
static struct nk_style_button transparent_style;
do_once transparent_style = ctx->style.button;
do_once transparent_style.text_alignment = NK_TEXT_ALIGN_CENTERED|NK_TEXT_ALIGN_MIDDLE;
do_once transparent_style.normal.data.color = nk_rgba(0,0,0,0);
do_once transparent_style.border_color = nk_rgba(0,0,0,0);
do_once transparent_style.active = transparent_style.normal;
do_once transparent_style.hover = transparent_style.normal;
do_once transparent_style.hover.data.color = nk_rgba(0,0,0,127);
transparent_style.text_background.a = 255 * ui_alpha;
transparent_style.text_normal.a = 255 * ui_alpha;
transparent_style.text_hover.a = 255 * ui_alpha;
transparent_style.text_active.a = 255 * ui_alpha;
return nk_button_label_styled(ctx, &transparent_style, text);
}
// internal vars for our editor. @todo: maybe expose these to the end-user as well?
bool ui_label_icon_highlight;
vec2 ui_label_icon_clicked_L; // left
vec2 ui_label_icon_clicked_R; // right
static
int ui_label_(const char *label, int alignment) {
// beware: assuming label can start with any ICON_MD_ glyph, which I consider them to be a 3-bytes utf8 sequence.
// done for optimization reasons because this codepath is called a lot!
const char *icon = label ? label : ""; while( icon[0] == '!' || icon[0] == '*' ) ++icon;
int has_icon = (unsigned)icon[0] > 127, icon_len = 3, icon_width_px = 1*24;
struct nk_rect bounds = nk_widget_bounds(ui_ctx);
const struct nk_input *input = &ui_ctx->input;
int is_hovering = nk_input_is_mouse_hovering_rect(input, bounds) && !ui_has_active_popups;
if( is_hovering ) {
struct nk_rect winbounds = nk_window_get_bounds(ui_ctx);
is_hovering &= nk_input_is_mouse_hovering_rect(input, winbounds);
struct nk_window *win = ui_ctx->current;
bool has_contextual = !win->name; // contextual windows are annonymous
is_hovering &= has_contextual || nk_window_has_focus(ui_ctx);
}
int skip_color_tab = label && label[0] == '!';
if( skip_color_tab) label++;
int spacing = 8; // between left colorbar and content
struct nk_window *win = ui_ctx->current;
struct nk_panel *layout = win->layout;
layout->at_x += spacing;
layout->bounds.w -= spacing;
if( !skip_color_tab ) {
float w = is_hovering ? 4 : 2; // spacing*3/4 : spacing/2-1;
bounds.w = w;
bounds.h -= 1;
struct nk_command_buffer *canvas = nk_window_get_canvas(ui_ctx);
nk_fill_rect(canvas, bounds, 0, nk_hsva_f(ui_hue, 0.75f, 0.8f, ui_alpha) );
}
if(!label || !label[0]) {
nk_label(ui_ctx, "", alignment);
layout->at_x -= spacing;
layout->bounds.w += spacing;
return 0;
}
const char *split = strchr(label, '@');
char buffer[128]; if( split ) label = (snprintf(buffer, 128, "%.*s", (int)(split-label), label), buffer);
struct nk_style *style = &ui_ctx->style;
bool bold = label[0] == '*'; label += bold;
struct nk_font *font = bold && nk_glfw.atlas.fonts->next ? nk_glfw.atlas.fonts->next->next /*3rd font*/ : NULL; // list
if( !has_icon ) {
// set bold style and color if needed
if( font && nk_style_push_font(ui_ctx, &font->handle) ) {} else font = 0;
if( font ) nk_style_push_color(ui_ctx, &style->text.color, nk_rgba(255, 255, 255, 255 * ui_alpha));
nk_label(ui_ctx, label, alignment);
} else {
char *icon_glyph = va("%.*s", icon_len, icon);
// @todo: implement nk_push_layout()
// nk_rect bounds = {..}; nk_panel_alloc_space(bounds, ctx);
struct nk_window *win = ui_ctx->current;
struct nk_panel *layout = win->layout, copy = *layout;
struct nk_rect before; nk_layout_peek(&before, ui_ctx);
nk_label_colored(ui_ctx, icon_glyph, alignment, nk_rgba(255, 255, 255, (64 + 192 * ui_label_icon_highlight) * ui_alpha) );
struct nk_rect after; nk_layout_peek(&after, ui_ctx);
*layout = copy;
layout->at_x += icon_width_px; layout->bounds.w -= icon_width_px; // nk_layout_space_push(ui_ctx, nk_rect(0,0,icon_width_px,0));
// set bold style and color if needed
if( font && nk_style_push_font(ui_ctx, &font->handle) ) {} else font = 0;
if( font ) nk_style_push_color(ui_ctx, &style->text.color, nk_rgba(255, 255, 255, 255 * ui_alpha));
nk_label(ui_ctx, icon+icon_len, alignment);
layout->at_x -= icon_width_px; layout->bounds.w += icon_width_px;
}
if( font ) nk_style_pop_color(ui_ctx);
if( font ) nk_style_pop_font(ui_ctx);
if (split && is_hovering && !ui_has_active_popups && nk_window_has_focus(ui_ctx)) {
nk_tooltip(ui_ctx, split + 1); // @fixme: not working under ui_disable() state
}
layout->at_x -= spacing;
layout->bounds.w += spacing;
// old way
// ui_labeicon_l_icked_L.x = is_hovering ? nk_input_has_mouse_click_down_in_rect(input, NK_BUTTON_LEFT, layout->bounds, nk_true) : 0;
// new way
// this is an ugly hack to detect which icon (within a label) we're clicking on.
// @todo: figure out a better way to detect this... would it be better to have a ui_label_toolbar(lbl,bar) helper function instead?
ui_label_icon_clicked_L.x = is_hovering ? ( (int)((input->mouse.pos.x - bounds.x) - (alignment == NK_TEXT_RIGHT ? bounds.w : 0) ) * nk_input_is_mouse_released(input, NK_BUTTON_LEFT)) : 0;
return ui_label_icon_clicked_L.x;
}
int ui_label(const char *label) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
int align = label[0] == '>' ? (label++, NK_TEXT_RIGHT) : label[0] == '=' ? (label++, NK_TEXT_CENTERED) : label[0] == '<' ? (label++, NK_TEXT_LEFT) : NK_TEXT_LEFT;
nk_layout_row_dynamic(ui_ctx, 0, 1);
return ui_label_(label, align);
}
static int nk_label_(struct nk_context *ui_ctx, const char *text_, int align2 ) {
const struct nk_input *input = &ui_ctx->input;
struct nk_rect bounds = nk_widget_bounds(ui_ctx);
int is_hovering = nk_input_is_mouse_hovering_rect(input, bounds) && !ui_has_active_popups;
if( is_hovering ) {
struct nk_rect winbounds = nk_window_get_bounds(ui_ctx);
is_hovering &= nk_input_is_mouse_hovering_rect(input, winbounds);
is_hovering &= nk_window_has_focus(ui_ctx);
}
nk_label(ui_ctx, text_, align2);
// this is an ugly hack to detect which icon (within a label) we're clicking on.
// @todo: figure out a better way to detect this... would it be better to have a ui_label_toolbar(lbl,bar) helper function instead?
ui_label_icon_clicked_R.x = is_hovering ? ( (int)((input->mouse.pos.x - bounds.x) - (align2 == NK_TEXT_RIGHT ? bounds.w : 0) ) * nk_input_is_mouse_released(input, NK_BUTTON_LEFT)) : 0;
return ui_label_icon_clicked_R.x;
}
int ui_label2(const char *label, const char *text_) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
int align1 = NK_TEXT_LEFT;
int align2 = NK_TEXT_LEFT;
if( label ) align1 = label[0] == '>' ? (label++, NK_TEXT_RIGHT) : label[0] == '=' ? (label++, NK_TEXT_CENTERED) : label[0] == '<' ? (label++, NK_TEXT_LEFT) : NK_TEXT_LEFT;
if( text_ ) align2 = text_[0] == '>' ? (text_++, NK_TEXT_RIGHT) : text_[0] == '=' ? (text_++, NK_TEXT_CENTERED) : text_[0] == '<' ? (text_++, NK_TEXT_LEFT) : NK_TEXT_LEFT;
ui_label_(label, align1);
return nk_label_(ui_ctx, text_, align2);
}
int ui_label2_bool(const char *text, bool value) {
bool b = !!value;
return ui_bool(text, &b), 0;
}
int ui_label2_float(const char *text, float value) {
float f = (float)value;
return ui_float(text, &f), 0;
}
int ui_label2_wrap(const char *label, const char *str) { // @fixme: does not work (remove dynamic layout?)
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
nk_text_wrap(ui_ctx, str, strlen(str));
return 0;
}
int ui_label2_toolbar(const char *label, const char *icons) {
int mouse_click = ui_label2(label, va(">%s", icons));
int choice = !mouse_click ? 0 : 1 + -mouse_click / (UI_ICON_FONTSIZE + UI_ICON_SPACING_X); // divided by px per ICON_MD_ glyph approximately
int glyphs = strlen(icons) / 3;
return choice > glyphs ? 0 : choice;
}
int ui_notify(const char *title, const char *body) {
app_beep();
struct ui_notify n = {0};
n.title = title && title[0] ? stringf("*%s", title) : 0;
n.body = body && body[0] ? STRDUP(body) : 0;
n.timeout = 2; // 4s = 2s timeout (+ 1s fade + 1s idle)
n.alpha = 1;
array_push_front(ui_notifications, n);
return 1;
}
int ui_button_transparent(const char *text) {
nk_layout_row_dynamic(ui_ctx, 0, 1);
int align = text[0] == '>' ? (text++, NK_TEXT_RIGHT) : text[0] == '=' ? (text++, NK_TEXT_CENTERED) : text[0] == '<' ? (text++, NK_TEXT_LEFT) : NK_TEXT_CENTERED;
return !!nk_contextual_item_label(ui_ctx, text, align);
}
#ifndef UI_BUTTON_MONOCHROME
#define UI_BUTTON_MONOCHROME 0
#endif
static
int ui_button_(const char *label) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
if( 1 ) {
#if UI_BUTTON_MONOCHROME
nk_style_push_color(ui_ctx, &ui_ctx->style.button.text_normal, nk_rgba(0,0,0,ui_alpha));
nk_style_push_color(ui_ctx, &ui_ctx->style.button.text_hover, nk_rgba(0,0,0,ui_alpha));
nk_style_push_color(ui_ctx, &ui_ctx->style.button.text_active, nk_rgba(0,0,0,ui_alpha));
nk_style_push_color(ui_ctx, &ui_ctx->style.button.normal.data.color, nk_hsva_f(ui_hue,0.0,0.8*ui_alpha));
nk_style_push_color(ui_ctx, &ui_ctx->style.button.hover.data.color, nk_hsva_f(ui_hue,0.0,1.0*ui_alpha));
nk_style_push_color(ui_ctx, &ui_ctx->style.button.active.data.color, nk_hsva_f(ui_hue,0.0,0.4*ui_alpha));
#elif 0 // old
nk_style_push_color(ui_ctx, &ui_ctx->style.button.text_normal, nk_rgba(0,0,0,ui_alpha));
nk_style_push_color(ui_ctx, &ui_ctx->style.button.text_hover, nk_rgba(0,0,0,ui_alpha));
nk_style_push_color(ui_ctx, &ui_ctx->style.button.text_active, nk_rgba(0,0,0,ui_alpha));
nk_style_push_color(ui_ctx, &ui_ctx->style.button.normal.data.color, nk_hsva_f(ui_hue,0.75,0.8*ui_alpha));
nk_style_push_color(ui_ctx, &ui_ctx->style.button.hover.data.color, nk_hsva_f(ui_hue,1.00,1.0*ui_alpha));
nk_style_push_color(ui_ctx, &ui_ctx->style.button.active.data.color, nk_hsva_f(ui_hue,0.60,0.4*ui_alpha));
#else // new
nk_style_push_color(ui_ctx, &ui_ctx->style.button.text_normal, nk_rgba_f(0.00,0.00,0.00,ui_alpha));
nk_style_push_color(ui_ctx, &ui_ctx->style.button.text_hover, nk_rgba_f(0.11,0.11,0.11,ui_alpha));
nk_style_push_color(ui_ctx, &ui_ctx->style.button.text_active, nk_rgba_f(0.00,0.00,0.00,ui_alpha));
nk_style_push_color(ui_ctx, &ui_ctx->style.button.normal.data.color, nk_hsva_f(ui_hue,0.80,0.6,0.90*ui_alpha));
nk_style_push_color(ui_ctx, &ui_ctx->style.button.hover.data.color, nk_hsva_f(ui_hue,0.85,0.9,0.90*ui_alpha));
nk_style_push_color(ui_ctx, &ui_ctx->style.button.active.data.color, nk_hsva_f(ui_hue,0.80,0.6,0.90*ui_alpha));
#endif
}
struct nk_rect bounds = nk_widget_bounds(ui_ctx);
const char *split = strchr(label, '@'), *tooltip = split + 1;
int ret = nk_button_text(ui_ctx, label, split ? (int)(split - label) : strlen(label) );
const struct nk_input *in = &ui_ctx->input;
if (split && nk_input_is_mouse_hovering_rect(in, bounds) && !ui_has_active_popups && nk_window_has_focus(ui_ctx)) {
nk_tooltip(ui_ctx, tooltip);
}
if( 1 ) {
nk_style_pop_color(ui_ctx);
nk_style_pop_color(ui_ctx);
nk_style_pop_color(ui_ctx);
nk_style_pop_color(ui_ctx);
nk_style_pop_color(ui_ctx);
nk_style_pop_color(ui_ctx);
}
return ret;
}
int ui_buttons(int buttons, ...) {
static array(char*) args = 0;
array_resize(args, 0);
int num_skips = 0;
va_list list;
va_start(list, buttons);
for( int i = 0; i < buttons; ++i ) {
const char *label = va_arg(list, const char*);
int skip = ui_filter && ui_filter[0] && !strstri(label, ui_filter);
array_push(args, skip ? NULL : (char*)label);
num_skips += skip;
}
va_end(list);
if( num_skips == array_count(args) ) return 0;
buttons = array_count(args) - num_skips;
nk_layout_row_dynamic(ui_ctx, 0, buttons);
float ui_hue_old = ui_hue;
int indent = 8;
struct nk_window *win = ui_ctx->current;
struct nk_panel *layout = win->layout;
struct nk_panel copy = *layout;
ui_label_("", NK_TEXT_LEFT);
*layout = copy;
layout->at_x += indent;
layout->bounds.w -= indent;
int rc = 0;
for( int i = 0, end = array_count(args); i < end; ++i ) {
if( args[i] && ui_button_( args[i] ) ) rc = i+1;
ui_hue_cycle( 3 );
}
va_end(list);
layout->at_x -= indent;
layout->bounds.w += indent;
ui_hue = ui_hue_old;
return rc;
}
int ui_button(const char *s) {
return ui_buttons(1, s);
}
int ui_toggle(const char *label, bool *value) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
// nk_label(ui_ctx, label, alignment);
int rc = nk_button_transparent(ui_ctx, *value ? ICON_MD_TOGGLE_ON : ICON_MD_TOGGLE_OFF);
return rc ? (*value ^= 1), rc : rc;
}
static enum color_mode {COL_RGB, COL_HSV} ui_color_mode = COL_RGB;
int ui_color4f(const char *label, float *color) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
struct nk_colorf after = { color[0]*ui_alpha, color[1]*ui_alpha, color[2]*ui_alpha, color[3]*ui_alpha }, before = after;
struct nk_colorf clamped = { clampf(after.r,0,1), clampf(after.g,0,1), clampf(after.b,0,1), clampf(after.a,0,1) };
if (nk_combo_begin_color(ui_ctx, nk_rgb_cf(clamped), nk_vec2(200,400))) {
nk_layout_row_dynamic(ui_ctx, 120, 1);
after = nk_color_picker(ui_ctx, after, NK_RGB);
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_color_mode = nk_option_label(ui_ctx, "RGB", ui_color_mode == COL_RGB) ? COL_RGB : ui_color_mode;
ui_color_mode = nk_option_label(ui_ctx, "HSV", ui_color_mode == COL_HSV) ? COL_HSV : ui_color_mode;
nk_layout_row_dynamic(ui_ctx, 0, 1);
if (ui_color_mode == COL_RGB) {
after.r = nk_propertyf(ui_ctx, "#R:", -FLT_MAX, after.r, FLT_MAX, 0.01f,0.005f);
after.g = nk_propertyf(ui_ctx, "#G:", -FLT_MAX, after.g, FLT_MAX, 0.01f,0.005f);
after.b = nk_propertyf(ui_ctx, "#B:", -FLT_MAX, after.b, FLT_MAX, 0.01f,0.005f);
} else {
float hsva[4];
nk_colorf_hsva_fv(hsva, after);
hsva[0] = nk_propertyf(ui_ctx, "#H:", -FLT_MAX, hsva[0], FLT_MAX, 0.01f,0.005f);
hsva[1] = nk_propertyf(ui_ctx, "#S:", -FLT_MAX, hsva[1], FLT_MAX, 0.01f,0.005f);
hsva[2] = nk_propertyf(ui_ctx, "#V:", -FLT_MAX, hsva[2], FLT_MAX, 0.01f,0.005f);
after = nk_hsva_colorfv(hsva);
}
nk_label(ui_ctx, va("#%02X%02X%02X", (unsigned)clampf(after.r*255,0,255), (unsigned)clampf(after.g*255,0,255), (unsigned)clampf(after.b*255,0,255)), NK_TEXT_CENTERED);
color[0] = after.r;
color[1] = after.g;
color[2] = after.b;
color[3] = after.a;
nk_combo_end(ui_ctx);
}
return !!memcmp(&before.r, &after.r, sizeof(struct nk_colorf));
}
int ui_color4(const char *label, unsigned *color) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
unsigned a = *color >> 24;
unsigned b =(*color >> 16)&255;
unsigned g =(*color >> 8)&255;
unsigned r = *color & 255;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
struct nk_colorf after = { r*ui_alpha/255, g*ui_alpha/255, b*ui_alpha/255, a*ui_alpha/255 }, before = after;
if (nk_combo_begin_color(ui_ctx, nk_rgb_cf(after), nk_vec2(200,400))) {
nk_layout_row_dynamic(ui_ctx, 120, 1);
after = nk_color_picker(ui_ctx, after, NK_RGBA);
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_color_mode = nk_option_label(ui_ctx, "RGB", ui_color_mode == COL_RGB) ? COL_RGB : ui_color_mode;
ui_color_mode = nk_option_label(ui_ctx, "HSV", ui_color_mode == COL_HSV) ? COL_HSV : ui_color_mode;
nk_layout_row_dynamic(ui_ctx, 0, 1);
if (ui_color_mode == COL_RGB) {
after.r = nk_propertyi(ui_ctx, "#R:", 0, after.r * 255, 255, 1,1) / 255.f;
after.g = nk_propertyi(ui_ctx, "#G:", 0, after.g * 255, 255, 1,1) / 255.f;
after.b = nk_propertyi(ui_ctx, "#B:", 0, after.b * 255, 255, 1,1) / 255.f;
after.a = nk_propertyi(ui_ctx, "#A:", 0, after.a * 255, 255, 1,1) / 255.f;
} else {
float hsva[4];
nk_colorf_hsva_fv(hsva, after);
hsva[0] = nk_propertyi(ui_ctx, "#H:", 0, hsva[0] * 255, 255, 1,1) / 255.f;
hsva[1] = nk_propertyi(ui_ctx, "#S:", 0, hsva[1] * 255, 255, 1,1) / 255.f;
hsva[2] = nk_propertyi(ui_ctx, "#V:", 0, hsva[2] * 255, 255, 1,1) / 255.f;
hsva[3] = nk_propertyi(ui_ctx, "#A:", 0, hsva[3] * 255, 255, 1,1) / 255.f;
after = nk_hsva_colorfv(hsva);
}
r = after.r * 255;
g = after.g * 255;
b = after.b * 255;
a = after.a * 255;
*color = rgba(r,g,b,a);
nk_label(ui_ctx, va("#%02X%02X%02X%02X", r, g, b, a), NK_TEXT_CENTERED);
nk_combo_end(ui_ctx);
}
return !!memcmp(&before.r, &after.r, sizeof(struct nk_colorf));
}
int ui_color3f(const char *label, float *color) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
struct nk_colorf after = { color[0]*ui_alpha, color[1]*ui_alpha, color[2]*ui_alpha, color[3]*ui_alpha }, before = after;
struct nk_colorf clamped = { clampf(after.r,0,1), clampf(after.g,0,1), clampf(after.b,0,1), ui_alpha };
if (nk_combo_begin_color(ui_ctx, nk_rgb_cf(clamped), nk_vec2(200,400))) {
nk_layout_row_dynamic(ui_ctx, 120, 1);
after = nk_color_picker(ui_ctx, after, NK_RGB);
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_color_mode = nk_option_label(ui_ctx, "RGB", ui_color_mode == COL_RGB) ? COL_RGB : ui_color_mode;
ui_color_mode = nk_option_label(ui_ctx, "HSV", ui_color_mode == COL_HSV) ? COL_HSV : ui_color_mode;
nk_layout_row_dynamic(ui_ctx, 0, 1);
if (ui_color_mode == COL_RGB) {
after.r = nk_propertyf(ui_ctx, "#R:", -FLT_MAX, after.r, FLT_MAX, 0.01f,0.005f);
after.g = nk_propertyf(ui_ctx, "#G:", -FLT_MAX, after.g, FLT_MAX, 0.01f,0.005f);
after.b = nk_propertyf(ui_ctx, "#B:", -FLT_MAX, after.b, FLT_MAX, 0.01f,0.005f);
} else {
float hsva[4];
nk_colorf_hsva_fv(hsva, after);
hsva[0] = nk_propertyf(ui_ctx, "#H:", -FLT_MAX, hsva[0], FLT_MAX, 0.01f,0.005f);
hsva[1] = nk_propertyf(ui_ctx, "#S:", -FLT_MAX, hsva[1], FLT_MAX, 0.01f,0.005f);
hsva[2] = nk_propertyf(ui_ctx, "#V:", -FLT_MAX, hsva[2], FLT_MAX, 0.01f,0.005f);
after = nk_hsva_colorfv(hsva);
}
nk_label(ui_ctx, va("#%02X%02X%02X", (unsigned)clampf(after.r*255,0,255), (unsigned)clampf(after.g*255,0,255), (unsigned)clampf(after.b*255,0,255)), NK_TEXT_CENTERED);
color[0] = after.r;
color[1] = after.g;
color[2] = after.b;
nk_combo_end(ui_ctx);
}
return !!memcmp(&before.r, &after.r, sizeof(struct nk_colorf));
}
int ui_color3(const char *label, unsigned *color) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
unsigned a = *color >> 24;
unsigned b =(*color >> 16)&255;
unsigned g =(*color >> 8)&255;
unsigned r = *color & 255;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
struct nk_colorf after = { r*ui_alpha/255, g*ui_alpha/255, b*ui_alpha/255, ui_alpha }, before = after;
if (nk_combo_begin_color(ui_ctx, nk_rgb_cf(after), nk_vec2(200,400))) {
nk_layout_row_dynamic(ui_ctx, 120, 1);
after = nk_color_picker(ui_ctx, after, NK_RGB);
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_color_mode = nk_option_label(ui_ctx, "RGB", ui_color_mode == COL_RGB) ? COL_RGB : ui_color_mode;
ui_color_mode = nk_option_label(ui_ctx, "HSV", ui_color_mode == COL_HSV) ? COL_HSV : ui_color_mode;
nk_layout_row_dynamic(ui_ctx, 0, 1);
if (ui_color_mode == COL_RGB) {
after.r = nk_propertyi(ui_ctx, "#R:", 0, after.r * 255, 255, 1,1) / 255.f;
after.g = nk_propertyi(ui_ctx, "#G:", 0, after.g * 255, 255, 1,1) / 255.f;
after.b = nk_propertyi(ui_ctx, "#B:", 0, after.b * 255, 255, 1,1) / 255.f;
} else {
float hsva[4];
nk_colorf_hsva_fv(hsva, after);
hsva[0] = nk_propertyi(ui_ctx, "#H:", 0, hsva[0] * 255, 255, 1,1) / 255.f;
hsva[1] = nk_propertyi(ui_ctx, "#S:", 0, hsva[1] * 255, 255, 1,1) / 255.f;
hsva[2] = nk_propertyi(ui_ctx, "#V:", 0, hsva[2] * 255, 255, 1,1) / 255.f;
after = nk_hsva_colorfv(hsva);
}
r = after.r * 255;
g = after.g * 255;
b = after.b * 255;
*color = rgba(r,g,b,a);
nk_label(ui_ctx, va("#%02X%02X%02X", r, g, b), NK_TEXT_CENTERED);
nk_combo_end(ui_ctx);
}
return !!memcmp(&before.r, &after.r, sizeof(struct nk_colorf));
}
int ui_list(const char *label, const char **items, int num_items, int *selector) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
int val = nk_combo(ui_ctx, items, num_items, *selector, UI_ROW_HEIGHT, nk_vec2(200,200));
int chg = val != *selector;
*selector = val;
return chg;
}
int ui_slider(const char *label, float *slider) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
// return ui_slider2(label, slider, va("%.2f ", *slider));
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
nk_size val = *slider * 1000;
int chg = nk_progress(ui_ctx, &val, 1000, NK_MODIFIABLE);
*slider = val / 1000.f;
return chg;
}
int ui_slider2(const char *label, float *slider, const char *caption) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
struct nk_window *win = ui_ctx->current;
const struct nk_style *style = &ui_ctx->style;
struct nk_rect bounds; nk_layout_peek(&bounds, ui_ctx); bounds.w -= 10; // bounds.w *= 0.95f;
struct nk_vec2 item_padding = style->text.padding;
struct nk_text text;
text.padding.x = item_padding.x;
text.padding.y = item_padding.y;
text.background = style->window.background;
text.text = nk_rgba_f(1,1,1,ui_alpha);
nk_size val = *slider * 1000;
int chg = nk_progress(ui_ctx, &val, 1000, NK_MODIFIABLE);
*slider = val / 1000.f;
chg |= input(MOUSE_L) && nk_input_is_mouse_hovering_rect(&ui_ctx->input, bounds); // , true);
nk_widget_text(&win->buffer, bounds, caption, strlen(caption), &text, NK_TEXT_RIGHT, style->font);
return chg;
}
int ui_bool(const char *label, bool *enabled ) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
int val = *enabled;
#if 0
int chg = !!nk_checkbox_label(ui_ctx, "", &val);
#else
int chg = !!nk_button_transparent(ui_ctx, val ? ICON_MD_CHECK_BOX : ICON_MD_CHECK_BOX_OUTLINE_BLANK );
#endif
*enabled ^= chg;
return chg;
}
static int ui_num_signs = 0;
int ui_int(const char *label, int *v) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
int prev = *v;
*v = nk_propertyi(ui_ctx, "#", INT_MIN, *v, INT_MAX, 1,1);
return prev != *v;
}
int ui_unsigned(const char *label, unsigned *v) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
unsigned prev = *v;
*v = (unsigned)nk_propertyd(ui_ctx, "#", 0, *v, UINT_MAX, 1,1);
return prev != *v;
}
int ui_unsigned2(const char *label, unsigned *v) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
char *buffer = ui_num_signs ?
--ui_num_signs, va("+%2u +%2u", v[0], v[1]) :
va("%2u, %2u", v[0], v[1]);
if (nk_combo_begin_label(ui_ctx, buffer, nk_vec2(200,200))) {
nk_layout_row_dynamic(ui_ctx, 0, 1);
unsigned prev0 = v[0]; nk_property_int(ui_ctx, "#X:", 0, &v[0], INT_MAX, 1,0.5f);
unsigned prev1 = v[1]; nk_property_int(ui_ctx, "#Y:", 0, &v[1], INT_MAX, 1,0.5f);
nk_combo_end(ui_ctx);
return prev0 != v[0] || prev1 != v[1];
}
return 0;
}
int ui_unsigned3(const char *label, unsigned *v) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
char *buffer = ui_num_signs ?
--ui_num_signs, va("+%2u +%2u +%2u", v[0], v[1], v[2]) :
va("%2u, %2u, %2u", v[0], v[1], v[2]);
if (nk_combo_begin_label(ui_ctx, buffer, nk_vec2(200,200))) {
nk_layout_row_dynamic(ui_ctx, 0, 1);
unsigned prev0 = v[0]; nk_property_int(ui_ctx, "#X:", 0, &v[0], INT_MAX, 1,0.5f);
unsigned prev1 = v[1]; nk_property_int(ui_ctx, "#Y:", 0, &v[1], INT_MAX, 1,0.5f);
unsigned prev2 = v[2]; nk_property_int(ui_ctx, "#Z:", 0, &v[2], INT_MAX, 1,0.5f);
nk_combo_end(ui_ctx);
return prev0 != v[0] || prev1 != v[1] || prev2 != v[2];
}
return 0;
}
int ui_short(const char *label, short *v) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
int i = *v, ret = ui_int( label, &i );
return *v = (short)i, ret;
}
int ui_float(const char *label, float *v) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
float prev = v[0]; v[0] = nk_propertyf(ui_ctx, "#", -FLT_MAX, v[0], FLT_MAX, 0.01f,0.005f);
return prev != v[0];
}
int ui_double(const char *label, double *v) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
double prev = v[0]; v[0] = nk_propertyd(ui_ctx, "#", -DBL_MAX, v[0], DBL_MAX, 0.01f,0.005f);
return prev != v[0];
}
int ui_clampf(const char *label, float *v, float minf, float maxf) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
if( minf > maxf ) return ui_clampf(label, v, maxf, minf);
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
float prev = v[0]; v[0] = nk_propertyf(ui_ctx, "#", minf, v[0], maxf, 0.1f,0.05f);
return prev != v[0];
}
int ui_float2(const char *label, float *v) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
char *buffer = ui_num_signs ?
--ui_num_signs, va("%+.3f %+.3f", v[0], v[1]) :
va("%.3f, %.3f", v[0], v[1]);
if (nk_combo_begin_label(ui_ctx, buffer, nk_vec2(200,200))) {
nk_layout_row_dynamic(ui_ctx, 0, 1);
float prev0 = v[0]; nk_property_float(ui_ctx, "#X:", -FLT_MAX, &v[0], FLT_MAX, 1,0.5f);
float prev1 = v[1]; nk_property_float(ui_ctx, "#Y:", -FLT_MAX, &v[1], FLT_MAX, 1,0.5f);
nk_combo_end(ui_ctx);
return prev0 != v[0] || prev1 != v[1];
}
return 0;
}
int ui_float3(const char *label, float *v) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
char *buffer = ui_num_signs ?
--ui_num_signs, va("%+.2f %+.2f %+.2f", v[0], v[1], v[2]) :
va("%.2f, %.2f, %.2f", v[0], v[1], v[2]);
if (nk_combo_begin_label(ui_ctx, buffer, nk_vec2(200,200))) {
nk_layout_row_dynamic(ui_ctx, 0, 1);
float prev0 = v[0]; nk_property_float(ui_ctx, "#X:", -FLT_MAX, &v[0], FLT_MAX, 1,0.5f);
float prev1 = v[1]; nk_property_float(ui_ctx, "#Y:", -FLT_MAX, &v[1], FLT_MAX, 1,0.5f);
float prev2 = v[2]; nk_property_float(ui_ctx, "#Z:", -FLT_MAX, &v[2], FLT_MAX, 1,0.5f);
nk_combo_end(ui_ctx);
return prev0 != v[0] || prev1 != v[1] || prev2 != v[2];
}
return 0;
}
int ui_float4(const char *label, float *v) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 2);
ui_label_(label, NK_TEXT_LEFT);
char *buffer = ui_num_signs ?
--ui_num_signs, va("%+.2f %+.2f %+.2f %+.2f", v[0], v[1], v[2], v[3]) :
va("%.2f,%.2f,%.2f,%.2f", v[0], v[1], v[2], v[3]);
if (nk_combo_begin_label(ui_ctx, buffer, nk_vec2(200,200))) {
nk_layout_row_dynamic(ui_ctx, 0, 1);
float prev0 = v[0]; nk_property_float(ui_ctx, "#X:", -FLT_MAX, &v[0], FLT_MAX, 1,0.5f);
float prev1 = v[1]; nk_property_float(ui_ctx, "#Y:", -FLT_MAX, &v[1], FLT_MAX, 1,0.5f);
float prev2 = v[2]; nk_property_float(ui_ctx, "#Z:", -FLT_MAX, &v[2], FLT_MAX, 1,0.5f);
float prev3 = v[3]; nk_property_float(ui_ctx, "#W:", -FLT_MAX, &v[3], FLT_MAX, 1,0.5f);
nk_combo_end(ui_ctx);
return prev0 != v[0] || prev1 != v[1] || prev2 != v[2] || prev3 != v[3];
}
return 0;
}
int ui_mat33(const char *label, float M[9]) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
ui_num_signs = 3;
int changed = 0;
changed |= ui_label(label);
changed |= ui_float3(NULL, M);
changed |= ui_float3(NULL, M+3);
changed |= ui_float3(NULL, M+6);
return changed;
}
int ui_mat34(const char *label, float M[12]) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
ui_num_signs = 3;
int changed = 0;
changed |= ui_label(label);
changed |= ui_float4(NULL, M);
changed |= ui_float4(NULL, M+4);
changed |= ui_float4(NULL, M+8);
return changed;
}
int ui_mat44(const char *label, float M[16]) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
ui_num_signs = 4;
int changed = 0;
changed |= ui_label(label);
changed |= ui_float4(NULL, M);
changed |= ui_float4(NULL, M+4);
changed |= ui_float4(NULL, M+8);
changed |= ui_float4(NULL, M+12);
return changed;
}
int ui_buffer(const char *label, char *buffer, int buflen) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, 0, 1 + (label && label[0]));
if(label && label[0]) ui_label_(label, NK_TEXT_LEFT);
int active = nk_edit_string_zero_terminated(ui_ctx, NK_EDIT_AUTO_SELECT|NK_EDIT_CLIPBOARD|NK_EDIT_FIELD/*NK_EDIT_BOX*/|NK_EDIT_SIG_ENTER, buffer, buflen, nk_filter_default);
return !!(active & NK_EDIT_COMMITED) ? nk_edit_unfocus(ui_ctx), 1 : 0;
}
int ui_string(const char *label, char **str) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
char *bak = va("%s%c", *str ? *str : "", '\0');
int rc = ui_buffer(label, bak, strlen(bak)+2);
if( *str ) 0[*str] = '\0';
strcatf(str, "%s", bak);
return rc;
}
int ui_separator() {
if( /*label &&*/ ui_filter && ui_filter[0] ) return 0; // if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, UI_SEPARATOR_HEIGHT, 1);
ui_hue_cycle( 1 );
struct nk_command_buffer *canvas;
struct nk_input *input = &ui_ctx->input;
canvas = nk_window_get_canvas(ui_ctx);
struct nk_rect space;
enum nk_widget_layout_states state;
state = nk_widget(&space, ui_ctx);
if (state) nk_fill_rect(canvas, space, 0, ui_ctx->style.window.header.normal.data.color );
return 1;
}
int ui_subimage(const char *label, handle id, unsigned iw, unsigned ih, unsigned sx, unsigned sy, unsigned sw, unsigned sh) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
nk_layout_row_dynamic(ui_ctx, sh < 30 || id == texture_checker().id ? 0 : sh, 1 + (label && label[0]));
if( label && label[0] ) ui_label_(label, NK_TEXT_LEFT);
struct nk_rect bounds; nk_layout_peek(&bounds, ui_ctx); bounds.w -= 10; // bounds.w *= 0.95f;
int ret = nk_button_image_styled(ui_ctx, &ui_ctx->style.button, nk_subimage_id((int)id, iw, ih, nk_rect(sx,sy,sw,sh)));
if( !ret ) {
ret |= input(MOUSE_L) && nk_input_is_mouse_hovering_rect(&ui_ctx->input, bounds); // , true);
}
if( ret ) {
int px = 100 * (ui_ctx->input.mouse.pos.x - bounds.x ) / (float)bounds.w;
int py = 100 * (ui_ctx->input.mouse.pos.y - bounds.y ) / (float)bounds.h;
return px * 100 + py; // printf("%d %d xy:%d\n", px, py, (px * 100) + py);
}
return ret; // !!ret;
}
int ui_image(const char *label, handle id, unsigned w, unsigned h) {
return ui_subimage(label, id, w,h, 0,0,w,h);
}
int ui_texture(const char *label, texture_t t) {
return ui_subimage(label, t.id, t.w,t.h, 0,0,t.w,t.h);
}
int ui_subtexture(const char *label, texture_t t, unsigned x, unsigned y, unsigned w, unsigned h) {
return ui_subimage(label, t.id, t.w,t.h, x,y,w,h);
}
int ui_colormap( const char *label, colormap_t *cm ) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
int ret = 0;
if( !cm->texture ) {
const char *title = va("%s (no image)", label);
if( ui_image( title, texture_checker().id, 0,0 ) ) {
ret = 2;
}
} else {
unsigned w = cm->texture->w, h = cm->texture->h;
ui_label(va("%s (%s)", label, cm->texture->filename) ); // @fixme: labelf would crash?
const char *fmt[] = { "", "R", "RG", "RGB", "RGBA" };
const char *title = va("%s %dx%d %s", label, w, h, fmt[cm->texture->n]);
if( ui_image( title, cm->texture->id, 128, 128 ) ) {
ret = 2;
}
}
if( ui_color4f( va("%s Color", label), (float *) &cm->color ) ) {
ret = 1;
}
return ret;
}
int ui_radio(const char *label, const char **items, int num_items, int *selector) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
int ret = 0;
if( label && label[0] ) ui_label(label);
for( int i = 0; i < num_items; i++ ) {
bool enabled = *selector == i;
if( ui_bool( va("%s%s", label && label[0] ? " ":"", items[i]), &enabled ) ) {
*selector = i;
ret |= 1;
}
}
return ret;
}
int ui_section(const char *label) {
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
//ui_separator();
return ui_label(va("*%s", label));
}
int ui_dialog(const char *label, const char *text, int choices, bool *show) { // @fixme: return
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0;
ui_has_active_popups = 0;
if(*show) {
static struct nk_rect s = {0, 0, 300, 190};
if (nk_popup_begin(ui_ctx, NK_POPUP_STATIC, label, NK_WINDOW_BORDER|NK_WINDOW_CLOSABLE, s)) {
nk_layout_row_dynamic(ui_ctx, 20, 1);
for( char t[1024]; *text && sscanf(text, "%[^\r\n]", t); ) {
nk_label(ui_ctx, t, NK_TEXT_LEFT);
text += strlen(t); while(*text && *text < 32) text++;
}
if( choices ) {
if( ui_buttons(choices > 1 ? 2 : 1, "OK", "Cancel") ) {
*show = 0;
nk_popup_close(ui_ctx);
}
}
nk_popup_end(ui_ctx);
} else {
*show = nk_false;
}
ui_has_active_popups = *show;
}
return *show;
}
#define ui_bitmask_template(X) \
int ui_bitmask##X(const char *label, uint##X##_t *enabled) { \
if( label && ui_filter && ui_filter[0] ) if( !strstri(label, ui_filter) ) return 0; \
\
/* @fixme: better way to retrieve widget width? nk_layout_row_dynamic() seems excessive */ \
nk_layout_row_dynamic(ui_ctx, 1, 1); \
struct nk_rect bounds = nk_widget_bounds(ui_ctx); \
\
/* actual widget: label + X checkboxes */ \
nk_layout_row_begin(ui_ctx, NK_STATIC, 0, 1+X); \
\
int offset = bounds.w > (15*X) ? bounds.w - (15*X) : 0; /* bits widget below needs at least 118px wide */ \
nk_layout_row_push(ui_ctx, offset); \
ui_label_(label, NK_TEXT_LEFT); \
\
uint8_t copy = *enabled; \
for( int i = 0; i < X; ++i ) { \
int b = (X-1-i); \
nk_layout_row_push(ui_ctx, 10); \
/* bit */ \
int val = (*enabled >> b) & 1; \
int chg = nk_checkbox_label(ui_ctx, "", &val); \
*enabled = (*enabled & ~(1 << b)) | ((!!val) << b); \
/* tooltip */ \
struct nk_rect bb = { offset + 10 + i * 14, bounds.y, 14, 30 }; /* 10:padding,14:width,30:height */ \
if (nk_input_is_mouse_hovering_rect(&ui_ctx->input, bb) && !ui_has_active_popups && nk_window_has_focus(ui_ctx)) { \
nk_tooltipf(ui_ctx, "Bit %d", b); \
} \
} \
\
nk_layout_row_end(ui_ctx); \
return copy ^ *enabled; \
}
ui_bitmask_template(8);
ui_bitmask_template(16);
//ui_bitmask_template(32);
int ui_console() { // @fixme: buggy
static char *cmd = 0;
static int enter = 0;
struct nk_font *font = nk_glfw.atlas.fonts->next /*2nd font*/;
if( font && nk_style_push_font(ui_ctx, &font->handle) ) {} else font = 0;
struct nk_rect bounds = {0,0,400,300}; // @fixme: how to retrieve inlined region below? (inlined)
if( 1 ) /*if( windowed || (!windowed && *inlined) )*/ bounds = nk_window_get_content_region(ui_ctx);
else { struct nk_rect b; nk_layout_peek(&b, ui_ctx); bounds.w = b.w; }
nk_layout_row_static(ui_ctx, bounds.h - UI_ROW_HEIGHT, bounds.w, 1);
static array(char*) lines = 0;
for( int i = 0; i < array_count(lines); ++i )
if(lines[i]) nk_label_wrap(ui_ctx, lines[i]); // "This is a very long line to hopefully get this text to be wrapped into multiple lines to show line wrapping");
if( enter ) {
array_push(lines, 0);
for( FILE *fp = popen(cmd, "r"); fp; pclose(fp), fp = 0) {
int ch;
do {
ch = fgetc(fp);
if( strchr("\r\n\t\b", ch) ) {
array_push(lines,0);
continue;
}
if( ch >= ' ' ) strcatf(array_back(lines), "%c", ch);
} while(ch > 0);
}
cmd[0] = 0;
}
enter = ui_string("", &cmd);
if( font ) nk_style_pop_font(ui_ctx);
return enter;
}
int ui_browse(const char **output, bool *inlined) {
static struct browser_media media = {0};
static struct browser browsers[2] = {0}; // 2 instances max: 0=inlined, 1=windowed
static char *results[2] = {0}; // 2 instances max: 0=inlined, 1=windowed
do_once {
const int W = 96, H = 96; // 2048x481 px, 21x5 cells
texture_t i = texture("icons/suru.png", TEXTURE_RGBA|TEXTURE_MIPMAPS);
browser_config_dir(icon_load_rect(i.id, i.w, i.h, W, H, 16, 3), BROWSER_FOLDER); // default group
browser_config_dir(icon_load_rect(i.id, i.w, i.h, W, H, 2, 4), BROWSER_HOME);
browser_config_dir(icon_load_rect(i.id, i.w, i.h, W, H, 17, 3), BROWSER_COMPUTER);
browser_config_dir(icon_load_rect(i.id, i.w, i.h, W, H, 1, 4), BROWSER_PROJECT);
browser_config_dir(icon_load_rect(i.id, i.w, i.h, W, H, 0, 4), BROWSER_DESKTOP);
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 8, 0), "");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 10, 2), ".txt.md.doc.license" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 8, 1), ".ini.cfg" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 8, 3), ".xlsx" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 9, 0), ".c" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 4, 1), ".h.hpp.hh.hxx" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 4, 2), ".fs.vs.gs.fx.glsl.shader" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 12, 0), ".cpp.cc.cxx" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 15, 0), ".json" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 15, 2), ".bat.sh" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 6, 1), ".htm.html" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 20, 1), ".xml" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 12, 1), ".js" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 0, 3), ".ts" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 6, 2), ".py" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 16, 1), ".lua" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 10, 0), ".css.doc" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 6, 0), ".wav.flac.ogg.mp1.mp3.mod.xm.s3m.it.sfxr.mid.fur" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 1, 3), ".ttf.ttc.otf" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 7, 1), ".jpg.jpeg.png.bmp.psd.pic.pnm.ico.ktx.pvr.dds.astc.basis.hdr.tga.gif" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 4, 3), ".mp4.mpg.ogv.mkv.wmv.avi" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 2, 1), ".iqm.iqe.gltf.gltf2.glb.fbx.obj.dae.blend.md3.md5.ms3d.smd.x.3ds.bvh.dxf.lwo" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 0, 1), ".exe" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 7, 0), ".bin.dSYM.pdb.o.lib.dll" ".");
browser_config_type(icon_load_rect(i.id, i.w, i.h, W, H, 15, 3), ".zip.rar.7z.pak" ".");
for( int j = 0; j < countof(browsers); ++j ) browser_init(&browsers[j]);
browsers[0].listing = 1; // inlined version got listing by default, as there is not much space for layouting
}
// at_exit: browser_free(&browser);
int clicked = 0;
bool windowed = !inlined;
if( windowed || (!windowed && *inlined) ) {
struct browser *browser = browsers + windowed; // select instance
char **result = results + windowed; // select instance
struct nk_rect bounds = {0}; // // {0,0,400,300};
// #define P(b) printf(FILELINE " (%3d,%3d) %3d,%3d\n", (int)b.x,(int)b.y, (int)b.w,(int)b.h)
// if(ui_ctx->current) bounds = nk_layout_space_bounds(ui_ctx), P(bounds);
// if(ui_ctx->current) bounds = nk_layout_widget_bounds(ui_ctx), P(bounds);
// if(ui_ctx->current) bounds = nk_widget_bounds(ui_ctx), P(bounds);
// if(ui_ctx->current) bounds = nk_window_get_bounds(ui_ctx), P(bounds);
// if(ui_ctx->current) bounds = nk_window_get_content_region(ui_ctx), P(bounds);
// if(ui_ctx->current) nk_layout_peek(&bounds, ui_ctx), P(bounds);
// if(ui_ctx->current) nk_layout_widget_space(&bounds, ui_ctx, ui_ctx->current, nk_false), P(bounds); // note: cant be used within a panel
// #undef P
// panel
// v4k_ui.c:2497 ( 6, 34) 310, 24
// v4k_ui.c:2498 ( 16, 62) 286, 24
// v4k_ui.c:2499 ( 16, 86) 296, 24
// v4k_ui.c:2500 ( 0, 0) 327,613
// v4k_ui.c:2501 ( 6, 34) 310,572 << ok
// v4k_ui.c:2502 ( 16, 86) 296, 24
// v4k_ui.c:2503 (316, 62) 297, 24
// window
// v4k_ui.c:2497 (188,152) 711, 4
// v4k_ui.c:2498 (188,152) 711, 4
// v4k_ui.c:2499 (-2147483648,156) -2147483648, 4
// v4k_ui.c:2500 (182,118) 728,409
// v4k_ui.c:2501 (188,152) 711,368 << ok
// v4k_ui.c:2502 (-2147483648,156) -2147483648, 4
// v4k_ui.c:2503 (-2147483648,152) -2147483648, 4
// popup
// v4k_ui.c:2497 ( 9, 30) 350, 24
// v4k_ui.c:2498 ( 19, 58) 326, 24
// v4k_ui.c:2499 ( 19, 82) 336, 24
// v4k_ui.c:2500 ( 4, 29) 360,460
// v4k_ui.c:2501 ( 9, 30) 350,458 << ok
// v4k_ui.c:2502 ( 19, 82) 336, 24
// v4k_ui.c:2503 (359, 58) 336, 24
bounds = nk_window_get_content_region(ui_ctx);
if( !windowed && *inlined ) bounds.h *= 0.80;
clicked = browser_run(ui_ctx, browser, windowed, bounds);
if( clicked ) {
strcatf(result, "%d", 0);
(*result)[0] = '\0';
strcatf(result, "%s", browser->file);
if( inlined ) *inlined = 0;
const char *target = ifdef(win32, "/", "\\"), *replace = ifdef(win32, "\\", "/");
strswap(*result, target, replace);
if( output ) *output = *result;
}
}
return clicked;
}
/*
// demo:
static const char *file;
if( ui_panel("inlined", 0)) {
static bool show_browser = 0;
if( ui_button("my button") ) { show_browser = true; }
if( ui_browse(&file, &show_browser) ) puts(file);
ui_panel_end();
}
if( ui_window("windowed", 0) ) {
if( ui_browse(&file, NULL) ) puts(file);
ui_window_end();
}
*/
// ----------------------------------------------------------------------------
int ui_demo(int do_windows) {
static int integer = 42;
static bool toggle = true;
static bool boolean = true;
static float floating = 3.14159;
static float float2[2] = {1,2};
static float float3[3] = {1,2,3};
static float float4[4] = {1,2,3,4};
static float rgbf[3] = {0.84,0.67,0.17};
static float rgbaf[4] = {0.67,0.90,0.12,1};
static unsigned rgb = CYAN;
static unsigned rgba = PINK;
static float slider = 0.5f;
static float slider2 = 0.5f;
static char string[64] = "hello world 123";
static int item = 0; const char *list[] = {"one","two","three"};
static bool show_dialog = false;
static bool show_browser = false;
static const char* browsed_file = "";
static uint8_t bitmask = 0x55;
static int hits = 0;
static int window1 = 0, window2 = 0, window3 = 0;
static int disable_all = 0;
if( ui_panel("UI", 0) ) {
int choice = ui_toolbar("Browser;Toast@Notify;Toggle on/off");
if(choice == 1) show_browser = true;
if(choice == 2) ui_notify(va("My random toast (%d)", rand()), va("This is notification #%d", ++hits));
if(choice == 3) disable_all ^= 1;
if( disable_all ) ui_disable();
if( ui_browse(&browsed_file, &show_browser) ) puts(browsed_file);
if( ui_section("Labels")) {}
if( ui_label("my label")) {}
if( ui_label("my label with tooltip@built on " __DATE__ " " __TIME__)) {}
if( ui_label2_toolbar("my toolbar", ICON_MD_STAR ICON_MD_STAR_OUTLINE ICON_MD_BOOKMARK ICON_MD_BOOKMARK_BORDER) ) {}
//if( ui_label2_wrap("my long label", "and some long long long long text wrapped")) {}
if( ui_section("Types")) {}
if( ui_bool("my bool", &boolean) ) puts("bool changed");
if( ui_int("my int", &integer) ) puts("int changed");
if( ui_float("my float", &floating) ) puts("float changed");
if( ui_buffer("my string", string, 64) ) puts("string changed");
if( ui_section("Vectors") ) {}
if( ui_float2("my float2", float2) ) puts("float2 changed");
if( ui_float3("my float3", float3) ) puts("float3 changed");
if( ui_float4("my float4", float4) ) puts("float4 changed");
if( ui_section("Lists")) {}
if( ui_list("my list", list, 3, &item ) ) puts("list changed");
if( ui_section("Colors")) {}
if( ui_color3("my color3", &rgb) ) puts("color3 changed");
if( ui_color4("my color4@this is a tooltip", &rgba) ) puts("color4 changed");
if( ui_color3f("my color3f", rgbf) ) puts("color3f changed");
if( ui_color4f("my color4f@this is a tooltip", rgbaf) ) puts("color4f changed");
if( ui_section("Sliders")) {}
if( ui_slider("my slider", &slider)) puts("slider changed");
if( ui_slider2("my slider 2", &slider2, va("%.2f", slider2))) puts("slider2 changed");
if( do_windows ) {
if( ui_section("Windows")) {}
int show = ui_buttons(3, "Container", "SubPanel", "SubRender");
if( show == 1 ) window1 = 1;
if( show == 2 ) window2 = 1;
if( show == 3 ) window3 = 1;
}
if( ui_section("Others")) {}
if( ui_bitmask8("my bitmask", &bitmask) ) printf("bitmask changed %x\n", bitmask);
if( ui_toggle("my toggle", &toggle) ) printf("toggle %s\n", toggle ? "on":"off");
if( ui_image("my image", texture_checker().id, 0, 0) ) { puts("image clicked"); }
if( ui_separator() ) {}
if( ui_button("my button") ) { puts("button clicked"); show_dialog = true; }
if( ui_buttons(2, "yes", "no") ) { puts("button clicked"); }
if( ui_buttons(3, "yes", "no", "maybe") ) { puts("button clicked"); }
if( ui_dialog("my dialog", __FILE__ "\n" __DATE__ "\n" "Public Domain.", 2/*two buttons*/, &show_dialog) ) {}
if( disable_all ) ui_enable(); // restore enabled state
ui_panel_end();
}
if( !do_windows ) return 0;
// window api showcasing
if( ui_window("Container demo", &window1) ) {
ui_label("label #1");
if( ui_bool("my bool", &boolean) ) puts("bool changed");
if( ui_int("my int", &integer) ) puts("int changed");
if( ui_float("my float", &floating) ) puts("float changed");
if( ui_buffer("my string", string, 64) ) puts("string changed");
ui_window_end();
}
if( ui_window("SubPanel demo", &window2) ) {
if( ui_panel("panel #2", 0) ) {
ui_label("label #2");
if( ui_bool("my bool", &boolean) ) puts("bool changed");
if( ui_int("my int", &integer) ) puts("int changed");
if( ui_float("my float", &floating) ) puts("float changed");
if( ui_buffer("my string", string, 64) ) puts("string changed");
ui_panel_end();
}
ui_window_end();
}
if( ui_window("SubRender demo", &window3) ) {
if( ui_panel("panel #3A", 0) ) {
if( ui_bool("my bool", &boolean) ) puts("bool changed");
if( ui_int("my int", &integer) ) puts("int changed");
if( ui_float("my float", &floating) ) puts("float changed");
if( ui_buffer("my string", string, 64) ) puts("string changed");
if( ui_separator() ) {}
if( ui_slider("my slider", &slider)) puts("slider changed");
if( ui_slider2("my slider 2", &slider2, va("%.2f", slider2))) puts("slider2 changed");
ui_panel_end();
}
if( ui_panel("panel #3B", 0) ) {
if( ui_bool("my bool", &boolean) ) puts("bool changed");
if( ui_int("my int", &integer) ) puts("int changed");
if( ui_float("my float", &floating) ) puts("float changed");
if( ui_buffer("my string", string, 64) ) puts("string changed");
if( ui_separator() ) {}
if( ui_slider("my slider", &slider)) puts("slider changed");
if( ui_slider2("my slider 2", &slider2, va("%.2f", slider2))) puts("slider2 changed");
ui_panel_end();
}
const char *title = "SubRender demo";
struct nk_window *win = nk_window_find(ui_ctx, title);
if( win ) {
enum { menubar_height = 65 }; // title bar (~32) + menu bounds (~25)
struct nk_rect bounds = win->bounds; bounds.y += menubar_height; bounds.h -= menubar_height;
#if 1
ddraw_flush();
// @fixme: this is breaking rendering when post-fxs are in use. edit: cannot reproduce
static texture_t tx = {0};
if( texture_rec_begin(&tx, bounds.w, bounds.h )) {
glClearColor(0.15,0.15,0.15,1);
glClear(GL_COLOR_BUFFER_BIT);
ddraw_grid(10);
ddraw_flush();
texture_rec_end(&tx);
}
struct nk_image image = nk_image_id((int)tx.id);
nk_draw_image_flipped(nk_window_get_canvas(ui_ctx), bounds, &image, nk_white);
#else
static video_t *v = NULL;
do_once v = video( "bjork-all-is-full-of-love.mp4", VIDEO_RGB );
texture_t *textures = video_decode( v );
struct nk_image image = nk_image_id((int)textures[0].id);
nk_draw_image(nk_window_get_canvas(ui_ctx), bounds, &image, nk_white);
#endif
}
ui_window_end();
}
return 0;
}
#line 0
#line 1 "v4k_audio.c"
// @fixme: really shutdown audio & related threads before quitting. ma_dr_wav crashes.
#if is(win32) && !is(gcc)
#include <windows.h>
#include <mmeapi.h> // midi
static HMIDIOUT midi_out_handle = 0;
#elif is(osx)
static AudioUnit midi_out_handle = 0;
#endif
static void midi_init() {
#if is(win32) && !is(gcc)
if( midiOutGetNumDevs() != 0 ) {
midiOutOpen(&midi_out_handle, 0, 0, 0, 0);
}
#elif is(osx)
AUGraph graph;
AUNode outputNode, mixerNode, dlsNode;
NewAUGraph(&graph);
AudioComponentDescription output = {'auou','ahal','appl',0,0};
AUGraphAddNode(graph, &output, &outputNode);
AUGraphOpen(graph);
AUGraphInitialize(graph);
AUGraphStart(graph);
AudioComponentDescription dls = {'aumu','dls ','appl',0,0};
AUGraphAddNode(graph, &dls, &dlsNode);
AUGraphNodeInfo(graph, dlsNode, NULL, &midi_out_handle);
AudioComponentDescription mixer = {'aumx','smxr','appl',0,0};
AUGraphAddNode(graph, &mixer, &mixerNode);
AUGraphConnectNodeInput(graph,mixerNode,0,outputNode,0);
AUGraphConnectNodeInput(graph,dlsNode,0,mixerNode,0);
AUGraphUpdate(graph,NULL);
#endif
}
static void midi_quit() {
#if is(win32) && !is(gcc)
if (midi_out_handle) midiOutClose(midi_out_handle);
#endif
// @fixme: osx
// https://developer.apple.com/library/archive/samplecode/PlaySoftMIDI/Listings/main_cpp.html#//apple_ref/doc/uid/DTS40008635-main_cpp-DontLinkElementID_4
}
void midi_send(unsigned midi_msg) {
#if is(win32) && !is(gcc)
if( midi_out_handle ) {
midiOutShortMsg(midi_out_handle, midi_msg);
}
#elif is(osx)
if( midi_out_handle ) {
MusicDeviceMIDIEvent(midi_out_handle, (midi_msg) & 0xFF, (midi_msg >> 8) & 0xFF, (midi_msg >> 16) & 0xFF, 0);
}
#endif
}
// encapsulate ma_dr_wav,ma_dr_mp3,stbvorbis and some buffer with the sts_mixer_stream_t
enum { UNK, WAV, OGG, MP1, MP3 };
typedef struct {
int type;
union {
ma_dr_wav wav;
stb_vorbis *ogg;
void *opaque;
ma_dr_mp3 mp3_;
};
sts_mixer_stream_t stream; // mixer stream
union {
int32_t data[4096*2]; // static sample buffer
float dataf[4096*2];
};
bool rewind;
bool loop;
} mystream_t;
static void downsample_to_mono_flt( int channels, float *buffer, int samples ) {
if( channels > 1 ) {
float *output = buffer;
while( samples-- > 0 ) {
float mix = 0;
for( int i = 0; i < channels; ++i ) mix += *buffer++;
*output++ = (float)(mix / channels);
}
}
}
static void downsample_to_mono_s16( int channels, short *buffer, int samples ) {
if( channels > 1 ) {
short *output = buffer;
while( samples-- > 0 ) {
float mix = 0;
for( int i = 0; i < channels; ++i ) mix += *buffer++;
*output++ = (short)(mix / channels);
}
}
}
// the callback to refill the (stereo) stream data
static bool refill_stream(sts_mixer_sample_t* sample, void* userdata) {
mystream_t* stream = (mystream_t*)userdata;
switch( stream->type ) {
default:
break; case WAV: {
int sl = sample->length / 2; /*sample->channels*/;
if( stream->rewind ) stream->rewind = 0, ma_dr_wav_seek_to_pcm_frame(&stream->wav, 0);
if (ma_dr_wav_read_pcm_frames_s16(&stream->wav, sl, (short*)stream->data) < sl) {
ma_dr_wav_seek_to_pcm_frame(&stream->wav, 0);
if (!stream->loop) return false;
}
}
break; case MP3: {
int sl = sample->length / 2; /*sample->channels*/;
if( stream->rewind ) stream->rewind = 0, ma_dr_mp3_seek_to_pcm_frame(&stream->mp3_, 0);
if (ma_dr_mp3_read_pcm_frames_f32(&stream->mp3_, sl, stream->dataf) < sl) {
ma_dr_mp3_seek_to_pcm_frame(&stream->mp3_, 0);
if (!stream->loop) return false;
}
}
break; case OGG: {
stb_vorbis *ogg = (stb_vorbis*)stream->ogg;
if( stream->rewind ) stream->rewind = 0, stb_vorbis_seek(stream->ogg, 0);
if( stb_vorbis_get_samples_short_interleaved(ogg, 2, (short*)stream->data, sample->length) == 0 ) {
stb_vorbis_seek(stream->ogg, 0);
if (!stream->loop) return false;
}
}
}
return true;
}
static void reset_stream(mystream_t* stream) {
if( stream ) memset( stream->data, 0, sizeof(stream->data) ), stream->rewind = 1;
}
// load a (stereo) stream
static bool load_stream(mystream_t* stream, const char *filename) {
int datalen;
char *data = vfs_load(filename, &datalen); if(!data) return false;
int error;
int HZ = 44100;
stream->type = UNK;
stream->loop = true;
if( stream->type == UNK && (stream->ogg = stb_vorbis_open_memory((const unsigned char *)data, datalen, &error, NULL)) ) {
stb_vorbis_info info = stb_vorbis_get_info(stream->ogg);
if( info.channels != 2 ) { puts("cannot stream ogg file. stereo required."); goto end; } // @fixme: upsample
stream->type = OGG;
stream->stream.sample.frequency = info.sample_rate;
stream->stream.sample.audio_format = STS_MIXER_SAMPLE_FORMAT_16;
}
if( stream->type == UNK && ma_dr_wav_init_memory(&stream->wav, data, datalen, NULL)) {
if( stream->wav.channels != 2 ) { puts("cannot stream wav file. stereo required."); goto end; } // @fixme: upsample
stream->type = WAV;
stream->stream.sample.frequency = stream->wav.sampleRate;
stream->stream.sample.audio_format = STS_MIXER_SAMPLE_FORMAT_16;
}
ma_dr_mp3_config mp3_cfg = { 2, HZ };
if( stream->type == UNK && (ma_dr_mp3_init_memory(&stream->mp3_, data, datalen, NULL/*&mp3_cfg*/) != 0) ) {
stream->type = MP3;
stream->stream.sample.frequency = stream->mp3_.sampleRate;
stream->stream.sample.audio_format = STS_MIXER_SAMPLE_FORMAT_FLOAT;
}
if( stream->type == UNK ) {
return false;
}
end:;
stream->stream.userdata = stream;
stream->stream.callback = refill_stream;
stream->stream.sample.length = sizeof(stream->data) / sizeof(stream->data[0]);
stream->stream.sample.data = stream->data;
refill_stream(&stream->stream.sample, stream);
return true;
}
// load a (mono) sample
static bool load_sample(sts_mixer_sample_t* sample, const char *filename) {
int datalen;
char *data = vfs_load(filename, &datalen); if(!data) return false;
int error;
int channels = 0;
if( !channels ) for( ma_dr_wav w = {0}, *wav = &w; wav && ma_dr_wav_init_memory(wav, data, datalen, NULL); wav = 0 ) {
channels = wav->channels;
sample->frequency = wav->sampleRate;
sample->audio_format = STS_MIXER_SAMPLE_FORMAT_16;
sample->length = wav->totalPCMFrameCount;
sample->data = REALLOC(0, sample->length * sizeof(short) * channels);
ma_dr_wav_read_pcm_frames_s16(wav, sample->length, (short*)sample->data);
ma_dr_wav_uninit(wav);
}
if( !channels ) for( stb_vorbis *ogg = stb_vorbis_open_memory((const unsigned char *)data, datalen, &error, NULL); ogg; ogg = 0 ) {
stb_vorbis_info info = stb_vorbis_get_info(ogg);
channels = info.channels;
sample->frequency = info.sample_rate;
sample->audio_format = STS_MIXER_SAMPLE_FORMAT_16;
sample->length = (int)stb_vorbis_stream_length_in_samples(ogg);
stb_vorbis_close(ogg);
short *buffer;
int sample_rate;
stb_vorbis_decode_memory((const unsigned char *)data, datalen, &channels, &sample_rate, (short **)&buffer);
sample->data = buffer;
}
ma_dr_mp3_config mp3_cfg = { 2, 44100 };
ma_uint64 mp3_fc;
if( !channels ) for( short *fbuf = ma_dr_mp3_open_memory_and_read_pcm_frames_s16(data, datalen, &mp3_cfg, &mp3_fc, NULL); fbuf ; fbuf = 0 ) {
channels = mp3_cfg.channels;
sample->frequency = mp3_cfg.sampleRate;
sample->audio_format = STS_MIXER_SAMPLE_FORMAT_16;
sample->length = mp3_fc; // / sizeof(float) / mp3_cfg.channels;
sample->data = fbuf;
}
if( !channels ) {
short *output = 0;
int outputSize, hz, mp1channels;
bool ok = jo_read_mp1(data, datalen, &output, &outputSize, &hz, &mp1channels);
if( ok ) {
channels = mp1channels;
sample->frequency = hz;
sample->audio_format = STS_MIXER_SAMPLE_FORMAT_16;
sample->length = outputSize / sizeof(int16_t) / channels;
sample->data = output; // REALLOC(0, sample->length * sizeof(int16_t) * channels );
// memcpy( sample->data, output, outputSize );
}
}
if( !channels ) {
return false;
}
if( channels > 1 ) {
if( sample->audio_format == STS_MIXER_SAMPLE_FORMAT_FLOAT ) {
downsample_to_mono_flt( channels, sample->data, sample->length );
sample->data = REALLOC( sample->data, sample->length * sizeof(float));
}
else
if( sample->audio_format == STS_MIXER_SAMPLE_FORMAT_16 ) {
downsample_to_mono_s16( channels, sample->data, sample->length );
sample->data = REALLOC( sample->data, sample->length * sizeof(short));
}
else {
puts("error!"); // @fixme
}
}
return true;
}
// -----------------------------------------------------------------------------
static ma_device device;
static ma_context context;
static sts_mixer_t mixer;
// This is the function that's used for sending more data to the device for playback.
static void audio_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) {
int len = frameCount;
sts_mixer_mix_audio(&mixer, pOutput, len / (sizeof(int32_t) / 4));
(void)pDevice; (void)pInput;
// return len / (sizeof(int32_t) / 4);
}
void audio_drop(void) {
ma_device_stop(&device);
ma_device_uninit(&device);
ma_context_uninit(&context);
}
int audio_init( int flags ) {
atexit(audio_drop);
// init sts_mixer
sts_mixer_init(&mixer, 44100, STS_MIXER_SAMPLE_FORMAT_32);
// The prioritization of backends can be controlled by the application. You need only specify the backends
// you care about. If the context cannot be initialized for any of the specified backends ma_context_init()
// will fail.
ma_backend backends[] = {
#if 1
ma_backend_wasapi, // Higest priority.
ma_backend_dsound,
ma_backend_winmm,
ma_backend_coreaudio,
ma_backend_pulseaudio,
ma_backend_alsa,
ma_backend_oss,
ma_backend_jack,
ma_backend_opensl,
// ma_backend_webaudio,
// ma_backend_openal,
//ma_backend_sdl,
ma_backend_null // Lowest priority.
#else
// Highest priority
ma_backend_wasapi, // WASAPI | Windows Vista+
ma_backend_dsound, // DirectSound | Windows XP+
ma_backend_winmm, // WinMM | Windows XP+ (may work on older versions, but untested)
ma_backend_coreaudio, // Core Audio | macOS, iOS
ma_backend_pulseaudio, // PulseAudio | Cross Platform (disabled on Windows, BSD and Android)
ma_backend_alsa, // ALSA | Linux
ma_backend_oss, // OSS | FreeBSD
ma_backend_jack, // JACK | Cross Platform (disabled on BSD and Android)
ma_backend_opensl, // OpenSL ES | Android (API level 16+)
ma_backend_webaudio, // Web Audio | Web (via Emscripten)
ma_backend_sndio, // sndio | OpenBSD
ma_backend_audio4, // audio(4) | NetBSD, OpenBSD
ma_backend_aaudio, // AAudio | Android 8+
ma_backend_custom, // Custom | Cross Platform
ma_backend_null, // Null | Cross Platform (not used on Web)
// Lowest priority
#endif
};
if (ma_context_init(backends, countof(backends), NULL, &context) != MA_SUCCESS) {
PRINTF("%s\n", "Failed to initialize audio context.");
return false;
}
ma_device_config config = ma_device_config_init(ma_device_type_playback); // Or ma_device_type_capture or ma_device_type_duplex.
config.playback.pDeviceID = NULL; // &myPlaybackDeviceID; // Or NULL for the default playback device.
config.playback.format = ma_format_s32;
config.playback.channels = 2;
config.sampleRate = 44100;
config.dataCallback = (void*)audio_callback; //< @r-lyeh add void* cast
config.pUserData = NULL;
if (ma_device_init(NULL, &config, &device) != MA_SUCCESS) {
printf("Failed to open playback device.");
ma_context_uninit(&context);
return false;
}
(void)flags;
ma_device_start(&device);
return true;
}
typedef struct audio_handle {
bool is_clip;
bool is_stream;
union {
sts_mixer_sample_t clip;
mystream_t stream;
};
} audio_handle;
static array(audio_handle*) audio_instances;
audio_t audio_clip( const char *pathfile ) {
audio_handle *a = REALLOC(0, sizeof(audio_handle) );
memset(a, 0, sizeof(audio_handle));
a->is_clip = load_sample( &a->clip, pathfile );
array_push(audio_instances, a);
return a;
}
audio_t audio_stream( const char *pathfile ) {
audio_handle *a = REALLOC(0, sizeof(audio_handle) );
memset(a, 0, sizeof(audio_handle));
a->is_stream = load_stream( &a->stream, pathfile );
array_push(audio_instances, a);
return a;
}
static float volume_clip = 1, volume_stream = 1, volume_master = 1;
float audio_volume_clip(float gain) {
if( gain >= 0 && gain <= 1 ) volume_clip = gain * gain;
// patch all live clips
for(int i = 0, active = 0; i < STS_MIXER_VOICES; ++i) {
if(mixer.voices[i].state != STS_MIXER_VOICE_STOPPED) // is_active?
if( mixer.voices[i].sample ) // is_sample?
mixer.voices[i].gain = volume_clip;
}
return sqrt( volume_clip );
}
float audio_volume_stream(float gain) {
if( gain >= 0 && gain <= 1 ) volume_stream = gain * gain;
// patch all live streams
for(int i = 0, active = 0; i < STS_MIXER_VOICES; ++i) {
if(mixer.voices[i].state != STS_MIXER_VOICE_STOPPED) // is_active?
if( mixer.voices[i].stream ) // is_stream?
mixer.voices[i].gain = volume_stream;
}
return sqrt( volume_stream );
}
float audio_volume_master(float gain) {
if( gain >= 0 && gain <= 1 ) volume_master = gain * gain;
// patch global mixer
mixer.gain = volume_master;
return sqrt( volume_master );
}
int audio_mute(int mute) {
static bool muted = 0; do_once muted = flag("--mute") || flag("--muted");
if( mute >= 0 && mute <= 1 ) muted = mute;
return muted;
}
int audio_muted() {
return audio_mute(-1);
}
int audio_play_gain_pitch_pan( audio_t a, int flags, float gain, float pitch, float pan ) {
if(audio_muted()) return 1;
if( flags & AUDIO_IGNORE_MIXER_GAIN ) {
// do nothing, gain used as-is
} else {
// apply mixer gains on top
gain += a->is_clip ? volume_clip : volume_stream;
}
if( flags & AUDIO_SINGLE_INSTANCE ) {
audio_stop( a );
}
// gain: [0..+1], pitch: (0..N], pan: [-1..+1]
if( a->is_clip ) {
int voice = sts_mixer_play_sample(&mixer, &a->clip, gain, pitch, pan);
if( voice == -1 ) return 0; // all voices busy
}
if( a->is_stream ) {
int voice = sts_mixer_play_stream(&mixer, &a->stream.stream, gain);
if( voice == -1 ) return 0; // all voices busy
}
return 1;
}
int audio_play_gain_pitch( audio_t a, int flags, float gain, float pitch ) {
return audio_play_gain_pitch_pan(a, flags, gain, pitch, 0);
}
int audio_play_gain( audio_t a, int flags, float gain ) {
return audio_play_gain_pitch(a, flags, gain, 1.f);
}
int audio_play( audio_t a, int flags ) {
return audio_play_gain(a, flags & ~AUDIO_IGNORE_MIXER_GAIN, 0.f);
}
int audio_stop( audio_t a ) {
if( a->is_clip ) {
sts_mixer_stop_sample(&mixer, &a->clip);
}
if( a->is_stream ) {
sts_mixer_stop_stream(&mixer, &a->stream.stream);
reset_stream(&a->stream);
}
return 1;
}
void audio_loop( audio_t a, bool loop ) {
if ( a->is_stream ) {
a->stream.loop = loop;
}
}
bool audio_playing( audio_t a ) {
if( a->is_clip ) {
return !sts_mixer_sample_stopped(&mixer, &a->clip);
}
if( a->is_stream ) {
return !sts_mixer_stream_stopped(&mixer, &a->stream.stream);
}
return false;
}
// -----------------------------------------------------------------------------
// audio queue
#ifndef AUDIO_QUEUE_BUFFERING_MS
#define AUDIO_QUEUE_BUFFERING_MS 50 // 10 // 100
#endif
#ifndef AUDIO_QUEUE_MAX
#define AUDIO_QUEUE_MAX 2048
#endif
#ifndef AUDIO_QUEUE_TIMEOUT
#define AUDIO_QUEUE_TIMEOUT ifdef(win32, THREAD_QUEUE_WAIT_INFINITE, 500)
#endif
typedef struct audio_queue_t {
int cursor;
int avail;
unsigned flags;
char data[0];
} audio_queue_t;
static thread_queue_t queue_mutex;
static void audio_queue_init() {
static void* audio_queues[AUDIO_QUEUE_MAX] = {0};
do_once thread_queue_init(&queue_mutex, countof(audio_queues), audio_queues, 0);
}
static bool audio_queue_callback(sts_mixer_sample_t* sample, void* userdata) {
(void)userdata;
int sl = sample->length / 2; // 2 ch
int bytes = sl * 2 * (sample->audio_format == STS_MIXER_SAMPLE_FORMAT_16 ? 2 : 4);
char *dst = sample->data;
static audio_queue_t *aq = 0;
do {
while( !aq ) aq = (audio_queue_t*)thread_queue_consume(&queue_mutex, THREAD_QUEUE_WAIT_INFINITE);
int len = aq->avail > bytes ? bytes : aq->avail;
memcpy(dst, (char*)aq->data + aq->cursor, len);
dst += len;
bytes -= len;
aq->cursor += len;
aq->avail -= len;
if( aq->avail <= 0 ) {
FREE(aq); // @fixme: mattias' original thread_queue_consume() implementation crashes here on tcc+win because of a double free on same pointer. using mcmp for now
aq = 0;
}
} while( bytes > 0 );
return 1;
}
static int audio_queue_voice = -1;
void audio_queue_clear() {
do_once audio_queue_init();
sts_mixer_stop_voice(&mixer, audio_queue_voice);
audio_queue_voice = -1;
}
int audio_queue( const void *samples, int num_samples, int flags ) {
do_once audio_queue_init();
float gain = 1; // [0..1]
float pitch = 1; // (0..N]
float pan = 0; // [-1..1]
int bits = flags & AUDIO_8 ? 8 : flags & (AUDIO_32|AUDIO_FLOAT) ? 32 : 16;
int channels = flags & AUDIO_2CH ? 2 : 1;
int bytes_per_sample = channels * (bits / 8);
int bytes = num_samples * bytes_per_sample;
static sts_mixer_stream_t q = { 0 };
if( audio_queue_voice < 0 ) {
void *reuse_ptr = q.sample.data;
q = ((sts_mixer_stream_t){0});
q.sample.data = reuse_ptr;
q.callback = audio_queue_callback;
q.sample.frequency = flags & AUDIO_8KHZ ? 8000 : flags & AUDIO_11KHZ ? 11025 : flags & AUDIO_44KHZ ? 44100 : flags & AUDIO_32KHZ ? 32000 : 22050;
q.sample.audio_format = flags & AUDIO_FLOAT ? STS_MIXER_SAMPLE_FORMAT_FLOAT : STS_MIXER_SAMPLE_FORMAT_16;
q.sample.length = q.sample.frequency / (1000 / AUDIO_QUEUE_BUFFERING_MS); // num_samples;
int bytes = q.sample.length * 2 * (flags & AUDIO_FLOAT ? 4 : 2);
q.sample.data = memset(REALLOC(q.sample.data, bytes), 0, bytes);
audio_queue_voice = sts_mixer_play_stream(&mixer, &q, gain * 1.f);
if( audio_queue_voice < 0 ) return 0;
}
audio_queue_t *aq = MALLOC(sizeof(audio_queue_t) + (bytes << (channels == 1))); // dupe space if going to be converted from mono to stereo
aq->cursor = 0;
aq->avail = bytes;
aq->flags = flags;
if( !samples ) {
memset(aq->data, 0, bytes);
} else {
// @todo: convert from other source formats to target format in here: add AUDIO_8, AUDIO_32
if( channels == 1 ) {
// mixer accepts stereo samples only; so resample mono to stereo if needed
for( int i = 0; i < num_samples; ++i ) {
memcpy((char*)aq->data + (i*2+0) * bytes_per_sample, (char*)samples + i * bytes_per_sample, bytes_per_sample );
memcpy((char*)aq->data + (i*2+1) * bytes_per_sample, (char*)samples + i * bytes_per_sample, bytes_per_sample );
}
} else {
memcpy(aq->data, samples, bytes);
}
}
while( !thread_queue_produce(&queue_mutex, aq, THREAD_QUEUE_WAIT_INFINITE) ) {}
return audio_queue_voice;
}
int ui_audio() {
int changed = 0;
float sfx = sqrt(volume_clip), bgm = sqrt(volume_stream), master = sqrt(volume_master);
if( ui_slider2("BGM volume", &bgm, va("%.2f", bgm))) changed = 1, audio_volume_stream(bgm);
if( ui_slider2("SFX volume", &sfx, va("%.2f", sfx))) changed = 1, audio_volume_clip(sfx);
if( ui_slider2("Master volume", &master, va("%.2f", master))) changed = 1, audio_volume_master(master);
ui_separator();
int num_voices = sts_mixer_get_active_voices(&mixer);
ui_label2("Format", mixer.audio_format == 0 ? "None" : mixer.audio_format == 1 ? "8-bit" : mixer.audio_format == 2 ? "16-bit" : mixer.audio_format == 3 ? "32-bit integer" : "32-bit float");
ui_label2("Frequency", va("%4.1f KHz", mixer.frequency / 1000.0));
ui_label2("Voices", va("%d/%d", num_voices, STS_MIXER_VOICES));
ui_separator();
for( int i = 0; i < STS_MIXER_VOICES; ++i ) {
if( mixer.voices[i].state != STS_MIXER_VOICE_STOPPED ) { // PLAYING || STREAMING
ui_label(va("Voice %d", i+1));
// float mul = mixer.voices[i].state == STS_MIXER_VOICE_STREAMING ? 2 : 1;
// float div = mixer.voices[i].state == STS_MIXER_VOICE_STREAMING ? mixer.voices[i].stream->sample.length : mixer.voices[i].sample->length;
// float pct = mixer.voices[i].position * mul / div;
// if(ui_slider2("Position", &pct, va("%5.2f", pct))) changed = 1;
if(ui_slider2("Gain", &mixer.voices[i].gain, va("%5.2f", mixer.voices[i].gain))) changed = 1;
if(ui_slider2("Pitch", &mixer.voices[i].pitch, va("%5.2f", mixer.voices[i].pitch))) changed = 1;
if(ui_slider2("Pan", &mixer.voices[i].pan, va("%5.2f", mixer.voices[i].pan))) changed = 1;
ui_separator();
}
}
return changed;
}
#line 0
#line 1 "v4k_collide.c"
/* poly */
poly poly_alloc(int cnt) {
poly p = {0};
p.cnt = cnt;
p.verts = REALLOC(p.verts, sizeof(p.verts[0]) * cnt); // array_resize(p.verts, cnt);
return p;
}
void poly_free(poly *p) {
REALLOC(p->verts, 0); // array_free(p->verts);
poly z = {0};
*p = z;
}
/* plane */
vec4 plane4(vec3 p, vec3 n) {
return vec34(n, -dot3(n,p));
}
/* pyramid */
poly pyramid(vec3 from, vec3 to, float size) {
/* calculate axis */
vec3 up, right, forward = norm3( sub3(to, from) );
ortho3(&right, &up, forward);
/* calculate extend */
vec3 xext = scale3(right, size);
vec3 yext = scale3(up, size);
vec3 nxext = scale3(right, -size);
vec3 nyext = scale3(up, -size);
/* calculate base vertices */
poly p = {0};
p.verts = REALLOC(p.verts, sizeof(p.verts[0]) * (5+1)); p.cnt = 5; /*+1 for diamond case*/ // array_resize(p.verts, 5+1); p.cnt = 5;
p.verts[0] = add3(add3(from, xext), yext); /*a*/
p.verts[1] = add3(add3(from, xext), nyext); /*b*/
p.verts[2] = add3(add3(from, nxext), nyext); /*c*/
p.verts[3] = add3(add3(from, nxext), yext); /*d*/
p.verts[4] = to; /*r*/
return p;
}
/* pyramid */
poly diamond(vec3 from, vec3 to, float size) {
vec3 mid = add3(from, scale3(sub3(to, from), 0.5f));
poly p = pyramid(mid, to, size);
p.verts[5] = from; p.cnt = 6;
return p;
}
// ---
static void transform_(vec3 *r, vec3 v, const float *r33, vec3 t3) {
for (int i = 0; i < 3; ++i) {
i[&r->x] = i[&v.x] * r33[i*3+0];
i[&r->x] += i[&v.x] * r33[i*3+1];
i[&r->x] += i[&v.x] * r33[i*3+2];
i[&r->x] += i[&t3.x];
}
}
static void transformS(vec3 *v, const float *r33, vec3 t3) {
vec3 tmp = *v;
transform_(v, tmp, r33, t3);
}
static void transformT(vec3 *r, vec3 v, const float *r33, vec3 t3) {
for (int i = 0; i < 3; ++i) {
float p = i[&v.x] - i[&t3.x];
i[&r->x] = p * r33[0*3+i];
i[&r->x] += p * r33[1*3+i];
i[&r->x] += p * r33[2*3+i];
}
}
static void transformST(vec3 *v, const float *r33, vec3 t3) {
vec3 tmp = *v;
transformT(v, tmp, r33, t3);
}
/* ============================================================================
*
* COLLISION
*
* =========================================================================== */
static __thread hit hits[16] = {0};
static __thread int hit_index = -1;
#define hit_next() &hits[ (++hit_index) & 15 ]
static float line_closest_line_(float *t1, float *t2, vec3 *c1, vec3 *c2, line l, line m) {
vec3 r, d1, d2;
d1 = sub3(l.b, l.a); /* direction vector segment s1 */
d2 = sub3(m.b, m.a); /* direction vector segment s2 */
r = sub3(l.a, m.a);
float i = dot3(d1, d1);
float e = dot3(d2, d2);
float f = dot3(d2, r);
if (i <= C_EPSILON && e <= C_EPSILON) {
/* both segments degenerate into points */
vec3 d12;
*t1 = *t2 = 0.0f;
*c1 = l.a;
*c2 = m.a;
d12 = sub3(*c1, *c2);
return dot3(d12,d12);
}
if (i > C_EPSILON) {
float c = dot3(d1,r);
if (e > C_EPSILON) {
/* non-degenerate case */
float b = dot3(d1,d2);
float denom = i*e - b*b;
/* compute closest point on L1/L2 if not parallel else pick any t2 */
if (denom != 0.0f)
*t1 = clampf(0.0f, (b*f - c*e) / denom, 1.0f);
else *t1 = 0.0f;
/* cmpute point on L2 closest to S1(s) */
*t2 = (b*(*t1) + f) / e;
if (*t2 < 0.0f) {
*t2 = 0.0f;
*t1 = clampf(0.0f, -c/i, 1.0f);
} else if (*t2 > 1.0f) {
*t2 = 1.0f;
*t1 = clampf(0.0f, (b-c)/i, 1.0f);
}
} else {
/* second segment degenerates into a point */
*t1 = clampf(0.0f, -c/i, 1.0f);
*t2 = 0.0f;
}
} else {
/* first segment degenerates into a point */
*t2 = clampf(0.0f, f/e, 1.0f);
*t1 = 0.0f;
}
/* calculate closest points */
vec3 n, d12;
n = scale3(d1, *t1);
*c1 = add3(l.a, n);
n = scale3(d2, *t2);
*c2 = add3(m.a, n);
/* calculate squared distance */
d12 = sub3(*c1, *c2);
return dot3(d12,d12);
}
vec3 line_closest_point(line l, vec3 p) {
vec3 ab = sub3(l.b,l.a), pa = sub3(p,l.a);
float t = dot3(pa,ab) / dot3(ab,ab);
return add3(l.a, scale3(ab, t < 0 ? 0 : t > 1 ? 1 : t));
}
float line_distance2_point(line l, vec3 p) {
vec3 ab = sub3(l.a,l.b), ap = sub3(l.a,p), bp = sub3(l.b,p);
/* handle cases p proj outside ab */
float e = dot3(ap,ab); if (e <= 0) return dot3(ap,ap);
float f = dot3(ab,ab); if (e >= f) return dot3(bp,bp);
return dot3(ap,ap) - (e*e)/f;
}
float ray_test_plane(ray r, vec4 plane) {
/* Ray: P = origin + rd * t
* Plane: plane_normal * P + d = 0
*
* Substitute:
* normal * (origin + rd*t) + d = 0
*
* Solve for t:
* plane_normal * origin + plane_normal * rd*t + d = 0
* -(plane_normal*rd*t) = plane_normal * origin + d
*
* plane_normal * origin + d
* t = -1 * -------------------------
* plane_normal * rd
*
* Result:
* Behind: t < 0
* Infront: t >= 0
* Parallel: t = 0
* Intersection point: ro + rd * t
*/
vec3 p = ptr3(&plane.x);
float n = -(dot3(p,r.p) + plane.w);
if (fabs(n) < 0.0001f) return 0.0f;
return n/(dot3(p,r.d));
}
float ray_test_triangle(ray r, triangle tr) {
float t = 0;
vec3 di0, di1, di2;
vec3 d21, d02, in;
vec3 n, d10, d20;
vec3 in0, in1, in2;
/* calculate triangle normal */
d10 = sub3(tr.p1,tr.p0);
d20 = sub3(tr.p2,tr.p0);
d21 = sub3(tr.p2,tr.p1);
d02 = sub3(tr.p0,tr.p2);
n = cross3(d10,d20);
/* check for plane intersection */
vec4 p = plane4(tr.p0, n);
t = ray_test_plane(r, p);
if (t <= 0.0f) return t;
/* intersection point */
in = scale3(r.d,t);
in = add3(in,r.p);
/* check if point inside triangle in plane */
di0 = sub3(in, tr.p0);
di1 = sub3(in, tr.p1);
di2 = sub3(in, tr.p2);
in0 = cross3(d10, di0);
in1 = cross3(d21, di1);
in2 = cross3(d02, di2);
if (dot3(in0,n) < 0.0f)
return -1;
if (dot3(in1,n) < 0.0f)
return -1;
if (dot3(in2,n) < 0.0f)
return -1;
return t;
}
int ray_test_sphere(float *t0, float *t1, ray r, sphere s) {
vec3 a;
float tc,td,d2,r2;
a = sub3(s.c,r.p);
tc = dot3(r.d,a);
if (tc < 0) return 0;
r2 = s.r*s.r;
d2 = dot3(a,a) - tc*tc;
if (d2 > r2) return 0;
td = sqrtf(r2 - d2);
*t0 = tc - td;
*t1 = tc + td;
return 1;
}
int ray_test_aabb(float *t0, float *t1, ray r, aabb a) {
float t0x = (a.min.x - r.p.x) / r.d.x;
float t0y = (a.min.y - r.p.y) / r.d.y;
float t0z = (a.min.z - r.p.z) / r.d.z;
float t1x = (a.max.x - r.p.x) / r.d.x;
float t1y = (a.max.y - r.p.y) / r.d.y;
float t1z = (a.max.z - r.p.z) / r.d.z;
float tminx = minf(t0x, t1x);
float tminy = minf(t0y, t1y);
float tminz = minf(t0z, t1z);
float tmaxx = maxf(t0x, t1x);
float tmaxy = maxf(t0y, t1y);
float tmaxz = maxf(t0z, t1z);
if (tminx > tmaxy || tminy > tmaxx)
return 0;
*t0 = maxf(tminx, tminy);
*t1 = minf(tmaxy, tmaxx);
if (*t0 > tmaxz || tminz> *t1)
return 0;
*t0 = maxf(*t0, tminz);
*t1 = minf(*t1, tmaxz);
return 1;
}
vec3 sphere_closest_point(sphere s, vec3 p) {
vec3 d = norm3(sub3(p, s.c));
return add3(s.c, scale3(d,s.r));
}
int sphere_test_sphere(sphere a, sphere b) {
vec3 d = sub3(b.c, a.c);
float r = a.r + b.r;
if (dot3(d,d) > r*r)
return 0;
return 1;
}
hit *sphere_hit_sphere(sphere a, sphere b) {
vec3 d = sub3(b.c, a.c);
float r = a.r + b.r;
float d2 = dot3(d,d);
if (d2 > r*r) return 0;
hit *m = hit_next();
float l = sqrtf(d2);
float linv = 1.0f / ((l != 0) ? l: 1.0f);
m->normal = scale3(d, linv);
m->depth = r - l;
d = scale3(m->normal, b.r);
m->contact_point = sub3(b.c, d);
return m;
}
int sphere_test_aabb(sphere s, aabb a) {
return aabb_test_sphere(a, s);
}
hit *sphere_hit_aabb(sphere s, aabb a) {
/* find closest aabb point to sphere center point */
vec3 ap = aabb_closest_point(a, s.c);
vec3 d = sub3(s.c, ap);
float d2 = dot3(d, d);
if (d2 > s.r*s.r) return 0;
hit *m = hit_next();
/* calculate distance vector between sphere and aabb center points */
vec3 ac = add3(a.min, scale3(sub3(a.max, a.min), 0.5f));
d = sub3(ac, s.c);
/* normalize distance vector */
float l2 = dot3(d,d);
float l = l2 != 0.0f ? sqrtf(l2): 1.0f;
float linv = 1.0f/l;
d = scale3(d, linv);
m->normal = d;
m->contact_point = scale3(m->normal, s.r);
m->contact_point = add3(s.c, m->contact_point);
/* calculate penetration depth */
vec3 sp = sphere_closest_point(s, ap);
d = sub3(sp, ap);
m->depth = sqrtf(dot3(d,d)) - l;
return m;
}
int sphere_test_capsule(sphere s, capsule c) {
return capsule_test_sphere(c, s);
}
hit *sphere_hit_capsule(sphere s, capsule c) {
#if 0
// original code
/* find closest capsule point to sphere center point */
hit *m = hit_next();
vec3 cp = capsule_closest_point(c, s.c);
m->normal = sub3(cp, s.c);
float d2 = dot3(m->normal, m->normal);
if (d2 > s.r*s.r) return 0;
/* normalize hit normal vector */
m->normal = norm3(m->normal);
/* calculate penetration depth */
m->depth = d2 - s.r*s.r;
m->depth = m->depth != 0.0f ? sqrtf(m->depth): 0.0f;
m->contact_point = add3(s.c, scale3(m->normal, s.r));
return m;
#else
// aproximation of I would expect this function to return instead
vec3 l = sub3(c.a,c.b); float len = len3(l);
vec3 d = norm3(l);
ray r = ray(add3(c.a,scale3(d,-2*len)), d);
s.r += c.r;
hit *h = ray_hit_sphere(r, s);
if(!h) return 0;
s.r -= c.r;
h->contact_point = add3(s.c,scale3(norm3(sub3(h->contact_point,s.c)),s.r));
return h;
#endif
}
void aabb_rebalance_transform(aabb *b, aabb a, mat33 m, vec3 t) {
for (int i = 0; i < 3; ++i) {
i[&b->min.x] = i[&b->max.x] = i[&t.x];
for (int j = 0; j < 3; ++j) {
float e = m[i*3+j] * j[&a.min.x];
float f = m[i*3+j] * j[&a.max.x];
if (e < f) {
i[&b->min.x] += e;
i[&b->max.x] += f;
} else {
i[&b->min.x] += f;
i[&b->max.x] += e;
}
}
}
}
vec3 aabb_closest_point(aabb a, vec3 p) {
vec3 res;
for (int i = 0; i < 3; ++i) {
float v = i[&p.x];
if (v < i[&a.min.x]) v = i[&a.min.x];
if (v > i[&a.max.x]) v = i[&a.max.x];
i[&res.x] = v;
}
return res;
}
float aabb_distance2_point(aabb a, vec3 p) {
float r = 0;
for (int i = 0; i < 3; ++i) {
float v = i[&p.x];
if (v < i[&a.min.x]) r += (i[&a.min.x]-v) * (i[&a.min.x]-v);
if (v > i[&a.max.x]) r += (v-i[&a.max.x]) * (v-i[&a.max.x]);
} return r;
}
int aabb_contains_point(aabb a, vec3 p) {
if (p.x < a.min.x || p.x > a.max.x) return 0;
if (p.y < a.min.y || p.y > a.max.y) return 0;
if (p.z < a.min.z || p.z > a.max.z) return 0;
return 1;
}
int aabb_test_aabb(aabb a, aabb b) {
if (a.max.x < b.min.x || a.min.x > b.max.x) return 0;
if (a.max.y < b.min.y || a.min.y > b.max.y) return 0;
if (a.max.z < b.min.z || a.min.z > b.max.z) return 0;
return 1;
}
hit *aabb_hit_aabb(aabb a, aabb b) {
if (!aabb_test_aabb(a, b))
return 0;
hit *m = hit_next();
/* calculate distance vector between both aabb center points */
vec3 ac, bc, d;
ac = sub3(a.max, a.min);
bc = sub3(b.max, b.min);
ac = scale3(ac, 0.5f);
bc = scale3(bc, 0.5f);
ac = add3(a.min, ac);
bc = add3(b.min, bc);
d = sub3(bc, ac);
/* normalize distance vector */
float l2 = dot3(d,d);
float l = l2 != 0.0f ? sqrtf(l2): 1.0f;
float linv = 1.0f/l;
d = scale3(d, linv);
/* calculate contact point */
m->normal = d;
m->contact_point = aabb_closest_point(a, bc);
d = sub3(m->contact_point, ac);
/* calculate penetration depth */
float r2 = dot3(d,d);
float r = sqrtf(r2);
m->depth = r - l;
return m;
}
int aabb_test_sphere(aabb a, sphere s) {
/* compute squared distance between sphere center and aabb */
float d2 = aabb_distance2_point(a, s.c);
/* intersection if distance is smaller/equal sphere radius*/
return d2 <= s.r*s.r;
}
hit *aabb_hit_sphere(aabb a, sphere s) {
/* find closest aabb point to sphere center point */
hit *m = hit_next();
m->contact_point = aabb_closest_point(a, s.c);
vec3 d = sub3(s.c, m->contact_point);
float d2 = dot3(d, d);
if (d2 > s.r*s.r) return 0;
/* calculate distance vector between aabb and sphere center points */
vec3 ac = add3(a.min, scale3(sub3(a.max, a.min), 0.5f));
d = sub3(s.c, ac);
/* normalize distance vector */
float l2 = dot3(d,d);
float l = l2 != 0.0f ? sqrtf(l2): 1.0f;
float linv = 1.0f/l;
d = scale3(d, linv);
/* calculate penetration depth */
m->normal = d;
d = sub3(m->contact_point, ac);
m->depth = sqrtf(dot3(d,d));
return m;
}
int aabb_test_capsule(aabb a, capsule c) {
return capsule_test_aabb(c, a);
}
hit *aabb_hit_capsule(aabb a, capsule c) {
/* calculate aabb center point */
vec3 ac = add3(a.min, scale3(sub3(a.max, a.min), 0.5f));
/* calculate closest point from aabb to point on capsule and check if inside aabb */
vec3 cp = capsule_closest_point(c, ac);
if (!aabb_contains_point(a, cp))
return 0;
hit *m = hit_next();
/* vector and distance between both capsule closests point and aabb center*/
vec3 d; float d2;
d = sub3(cp, ac);
d2 = dot3(d,d);
/* calculate penetration depth from closest aabb point to capsule */
vec3 ap = aabb_closest_point(a, cp);
vec3 dt = sub3(ap, cp);
m->depth = sqrtf(dot3(dt,dt));
/* calculate normal */
float l = sqrtf(d2);
float linv = 1.0f / ((l != 0.0f) ? l: 1.0f);
m->normal = scale3(d, linv);
m->contact_point = ap;
return m;
}
float capsule_distance2_point(capsule c, vec3 p) {
float d2 = line_distance2_point(line(c.a,c.b), p);
return d2 - (c.r*c.r);
}
vec3 capsule_closest_point(capsule c, vec3 p) {
/* calculate closest point to internal capsule segment */
vec3 pp = line_closest_point(line(c.a,c.b), p);
/* extend point out by radius in normal direction */
vec3 d = norm3(sub3(p,pp));
return add3(pp, scale3(d, c.r));
}
int capsule_test_capsule(capsule a, capsule b) {
float t1, t2;
vec3 c1, c2;
float d2 = line_closest_line_(&t1, &t2, &c1, &c2, line(a.a,a.b), line(b.a,b.b));
float r = a.r + b.r;
return d2 <= r*r;
}
hit *capsule_hit_capsule(capsule a, capsule b) {
float t1, t2;
vec3 c1, c2;
float d2 = line_closest_line_(&t1, &t2, &c1, &c2, line(a.a,a.b), line(b.a,b.b));
float r = a.r + b.r;
if (d2 > r*r) return 0;
hit *m = hit_next();
/* calculate normal from both closest points for each segement */
vec3 cp, d;
m->normal = sub3(c2, c1);
m->normal = norm3(m->normal);
/* calculate contact point from closest point and depth */
m->contact_point = capsule_closest_point(a, c2);
cp = capsule_closest_point(b, c1);
d = sub3(c1, cp);
m->depth = sqrtf(dot3(d,d));
return m;
}
int capsule_test_sphere(capsule c, sphere s) {
/* squared distance bwetween sphere center and capsule line segment */
float d2 = line_distance2_point(line(c.a,c.b), s.c);
float r = s.r + c.r;
return d2 <= r * r;
}
hit *capsule_hit_sphere(capsule c, sphere s) {
/* find closest capsule point to sphere center point */
hit *m = hit_next();
m->contact_point = capsule_closest_point(c, s.c);
m->normal = sub3(s.c, m->contact_point);
float d2 = dot3(m->normal, m->normal);
if (d2 > s.r*s.r) return 0;
/* normalize hit normal vector */
float l = d2 != 0.0f ? sqrtf(d2): 1;
float linv = 1.0f/l;
m->normal = scale3(m->normal, linv);
/* calculate penetration depth */
m->depth = d2 - s.r*s.r;
m->depth = m->depth != 0.0f ? sqrtf(m->depth): 0.0f;
return m;
}
int capsule_test_aabb(capsule c, aabb a) {
/* calculate aabb center point */
vec3 ac = scale3(sub3(a.max, a.min), 0.5f);
/* calculate closest point from aabb to point on capsule and check if inside aabb */
vec3 p = capsule_closest_point(c, ac);
return aabb_contains_point(a, p);
}
hit *capsule_hit_aabb(capsule c, aabb a) {
/* calculate aabb center point */
vec3 ac = add3(a.min, scale3(sub3(a.max, a.min), 0.5f));
/* calculate closest point from aabb to point on capsule and check if inside aabb */
vec3 cp = capsule_closest_point(c, ac);
if (!aabb_contains_point(a, cp))
return 0;
hit *m = hit_next();
/* vector and distance between both capsule closests point and aabb center*/
vec3 d; float d2;
d = sub3(ac, cp);
d2 = dot3(d,d);
/* calculate penetration depth from closest aabb point to capsule */
vec3 ap = aabb_closest_point(a, cp);
vec3 dt = sub3(ap, cp);
m->depth = sqrtf(dot3(dt,dt));
/* calculate normal */
float l = sqrtf(d2);
float linv = 1.0f / ((l != 0.0f) ? l: 1.0f);
m->normal = scale3(d, linv);
m->contact_point = cp;
return m;
}
/* ============================================================================
*
* COLLISION VOLUME
*
* =========================================================================== */
hit *ray_hit_plane(ray r, plane p) {
vec4 pf = plane4(p.p, p.n);
float t = ray_test_plane(r, pf);
if (t <= 0.0f) return 0;
hit *o = hit_next();
o->p = add3(r.p, scale3(r.d, t));
o->t0 = o->t1 = t;
o->n = scale3(p.n, -1.0f);
return o;
}
hit *ray_hit_triangle(ray r, triangle tr) {
float t = ray_test_triangle(r, tr);
if (t <= 0) return 0;
hit *o = hit_next();
o->t0 = o->t1 = t;
o->p = add3(r.p, scale3(r.d, t));
o->n = norm3(cross3(sub3(tr.p1,tr.p0),sub3(tr.p2,tr.p0)));
return o;
}
hit *ray_hit_sphere(ray r, sphere s) {
hit *o = hit_next();
if (!ray_test_sphere(&o->t0, &o->t1, r, s))
return 0;
o->p = add3(r.p, scale3(r.d, minf(o->t0,o->t1)));
o->n = norm3(sub3(o->p, s.c));
return o;
}
hit *ray_hit_aabb(ray r, aabb a) {
hit *o = hit_next();
vec3 pnt, ext, c;
float d, min;
if (!ray_test_aabb(&o->t0, &o->t1, r, a))
return 0;
o->p = add3(r.p, scale3(r.d, minf(o->t0,o->t1)));
ext = sub3(a.max, a.min);
c = add3(a.min, scale3(ext,0.5f));
pnt = sub3(o->p, c);
min = fabs(ext.x - fabs(pnt.x));
o->n = scale3(vec3(1,0,0), signf(pnt.x));
d = fabs(ext.y - fabs(pnt.y));
if (d < min) {
min = d;
o->n = scale3(vec3(0,1,0), signf(pnt.y));
}
d = fabs(ext.z - fabs(pnt.z));
if (d < min)
o->n = scale3(vec3(0,0,1), signf(pnt.z));
return o;
}
frustum frustum_build(mat44 pv) {
frustum f;
f.l = vec4(pv[ 3]+pv[ 0], pv[ 7]+pv[ 4], pv[11]+pv[ 8], pv[15]+pv[12]);
f.r = vec4(pv[ 3]-pv[ 0], pv[ 7]-pv[ 4], pv[11]-pv[ 8], pv[15]-pv[12]);
f.t = vec4(pv[ 3]-pv[ 1], pv[ 7]-pv[ 5], pv[11]-pv[ 9], pv[15]-pv[13]);
f.b = vec4(pv[ 3]+pv[ 1], pv[ 7]+pv[ 5], pv[11]+pv[ 9], pv[15]+pv[13]);
f.n = vec4(pv[ 3]+pv[ 2], pv[ 7]+pv[ 6], pv[11]+pv[10], pv[15]+pv[14]);
f.f = vec4(pv[ 3]-pv[ 2], pv[ 7]-pv[ 6], pv[11]-pv[10], pv[15]-pv[14]);
for (int i = 0; i < 6; i++) f.pl[i] = scale4(f.pl[i], 1 / len3(f.pl[i].xyz));
return f;
}
int frustum_test_sphere(frustum f, sphere s) {
for(int i = 0; i < 6; i++) {
if((dot3(f.pl[i].xyz, s.c) + f.pl[i].w + s.r) < 0) return 0;
}
return 1;
}
int frustum_test_aabb(frustum f, aabb a) {
for(int i = 0; i < 6; i++) {
vec3 v = vec3(f.pl[i].x > 0 ? a.max.x : a.min.x, f.pl[i].y > 0 ? a.max.y : a.min.y, f.pl[i].z > 0 ? a.max.z : a.min.z);
if((dot3(f.pl[i].xyz, v) + f.pl[i].w) < 0) return 0;
}
return 1;
}
void collide_demo() { // debug draw collisions
// animation
static float dx = 0, dy = 0;
float delta = (0.25f / 60.f);
dx = dx + delta * 2.0f;
dy = dy + delta * 0.8f;
#if 0
// 3D
glEnable(GL_DEPTH_TEST);
// grid
ddraw_grid(0);
#endif
{
// Triangle-Ray Intersection*/
vec3 ro, rd;
int suc;
triangle tri = { vec3(-9,1,28), vec3(-10,0,28), vec3(-11,1,28) };
// ray
ro = vec3(-10,-1,20);
rd = vec3(-10+0.4f*sin(dx), 2.0f*cos(dy), 29.81023f);
rd = sub3(rd, ro);
rd = norm3(rd);
ray r = ray(ro, rd);
hit *hit = ray_hit_triangle(r, tri);
if (hit) {
// point of intersection
ddraw_color(RED);
ddraw_box(hit->p, vec3(0.10f, 0.10f, 0.10f));
// intersection normal
ddraw_color(BLUE);
vec3 v = add3(hit->p, hit->n);
ddraw_arrow(hit->p, v);
}
// line
ddraw_color(RED);
rd = scale3(rd,10);
rd = add3(ro,rd);
ddraw_line(ro, rd);
// triangle
if (hit) ddraw_color(RED);
else ddraw_color(WHITE);
ddraw_triangle(tri.p0,tri.p1,tri.p2);
}
{
// Plane-Ray Intersection*/
vec3 ro, rd;
mat33 rot;
// ray
static float d = 0;
d += delta * 2.0f;
ro = vec3(0,-1,20);
rd = vec3(0.1f, 0.5f, 9.81023f);
rd = sub3(rd, ro);
rd = norm3(rd);
// rotation
rotation33(rot, deg(d), 0,1,0);
rd = mulv33(rot, rd);
// intersection
ray r = ray(ro, rd);
plane pl = plane(vec3(0,0,28), vec3(0,0,1));
hit *hit = ray_hit_plane(r, pl);
if (hit) {
// point of intersection
ddraw_color(RED);
ddraw_box(hit->p, vec3(0.10f, 0.10f, 0.10f));
// intersection normal
ddraw_color(BLUE);
vec3 v = add3(hit->p, hit->n);
ddraw_arrow(hit->p, v);
ddraw_color(RED);
}
// line
ddraw_color(RED);
rd = scale3(rd,9);
rd = add3(ro,rd);
ddraw_line(ro, rd);
// plane
if (hit) ddraw_color(RED);
else ddraw_color(WHITE);
ddraw_plane(vec3(0,0,28), vec3(0,0,1), 3.0f);
}
{
// Sphere-Ray Intersection*/
vec3 ro, rd;
sphere s;
// ray
ro = vec3(0,-1,0);
rd = vec3(0.4f*sin(dx), 2.0f*cos(dy), 9.81023f);
rd = sub3(rd, ro);
rd = norm3(rd);
ray r = ray(ro, rd);
s = sphere(vec3(0,0,8), 1);
hit *hit = ray_hit_sphere(r, s);
if(hit) {
// points of intersection
vec3 in = add3(ro,scale3(rd,hit->t0));
ddraw_color(GREEN);
ddraw_box(in, vec3(0.05f, 0.05f, 0.05f));
in = add3(ro,scale3(rd,hit->t1));
ddraw_color(YELLOW);
ddraw_box(in, vec3(0.05f, 0.05f, 0.05f));
// intersection normal
ddraw_color(BLUE);
vec3 v = add3(hit->p, hit->n);
ddraw_arrow(hit->p, v);
ddraw_color(RED);
}
// line
ddraw_color(RED);
rd = scale3(rd,10);
rd = add3(ro,rd);
ddraw_line(ro, rd);
// sphere
if (hit) ddraw_color(RED);
else ddraw_color(WHITE);
ddraw_sphere(vec3(0,0,8), 1);
}
{ // ray-aabb
aabb bounds = aabb(vec3(10-0.5f,-0.5f,7.5f), vec3(10.5f,0.5f,8.5f));
vec3 ro = vec3(10,-1,0);
vec3 rd = vec3(10+0.4f*sin(dx), 2.0f*cos(dy), 9.81023f);
rd = norm3(sub3(rd, ro));
ray r = ray(ro, rd);
hit *hit = ray_hit_aabb(r, bounds);
if(hit) {
// points of intersection
vec3 in;
in = scale3(rd,hit->t0);
in = add3(ro,in);
ddraw_color(RED);
ddraw_box(in, vec3(0.05f, 0.05f, 0.05f));
in = scale3(rd,hit->t1);
in = add3(ro,in);
ddraw_color(RED);
ddraw_box(in, vec3(0.05f, 0.05f, 0.05f));
// intersection normal
ddraw_color(BLUE);
vec3 v = add3(hit->p, hit->n);
ddraw_arrow(hit->p, v);
ddraw_color(RED);
} else ddraw_color(WHITE);
ddraw_box(vec3(10,0,8), vec3(1,1,1));
// line
ddraw_color(RED);
rd = scale3(rd,10);
rd = add3(ro,rd);
ddraw_line(ro, rd);
}
{
// Sphere-Sphere intersection*/
sphere a = sphere(vec3(-10,0,8), 1);
sphere b = sphere(vec3(-10+0.6f*sin(dx), 3.0f*cos(dy),8), 1);
hit *m = sphere_hit_sphere(a, b);
if (m) {
vec3 v;
ddraw_color(BLUE);
ddraw_box(m->contact_point, vec3(0.05f, 0.05f, 0.05f));
v = add3(m->contact_point, m->normal);
ddraw_arrow(m->contact_point, v);
ddraw_color(RED);
} else ddraw_color(WHITE);
ddraw_sphere(a.c, 1);
ddraw_sphere(b.c, 1);
}
{
// AABB-AABB intersection*/
const float x = 10+0.6f*sin(dx);
const float y = 3.0f*cos(dy);
const float z = 20.0f;
aabb a = aabb(vec3(10-0.5f,-0.5f,20-0.5f), vec3(10+0.5f,0.5f,20.5f));
aabb b = aabb(vec3(x-0.5f,y-0.5f,z-0.5f), vec3(x+0.5f,y+0.5f,z+0.5f));
hit *m = aabb_hit_aabb(a, b);
if(m) {
vec3 v;
ddraw_color(BLUE);
ddraw_box(m->contact_point, vec3(0.05f, 0.05f, 0.05f));
v = add3(m->contact_point, m->normal);
ddraw_arrow(m->contact_point, v);
ddraw_color(RED);
} else ddraw_color(WHITE);
ddraw_box(vec3(10,0,20), vec3(1,1,1));
ddraw_box(vec3(x,y,z), vec3(1,1,1));
}
{
// Capsule-Capsule intersection*/
const float x = 20+0.4f*sin(dx);
const float y = 3.0f*cos(dy);
const float z = 28.5f;
capsule a = capsule(vec3(20.0f,-1.0f,28.0f), vec3(20.0f,1.0f,28.0f), 0.2f);
capsule b = capsule(vec3(x,y-1.0f,z), vec3(x,y+1.0f,z-1.0f), 0.2f);
hit *m = capsule_hit_capsule(a, b);
if( m ) {
vec3 v;
ddraw_color(BLUE);
ddraw_box(m->contact_point, vec3(0.05f, 0.05f, 0.05f));
v = add3(m->contact_point, m->normal);
ddraw_arrow(m->contact_point, v);
ddraw_color(RED);
} else ddraw_color(WHITE);
ddraw_capsule(vec3(x,y-1.0f,z), vec3(x,y+1.0f,z-1.0f), 0.2f);
ddraw_capsule(vec3(20.0f,-1.0f,28.0f), vec3(20.0f,1.0f,28.0f), 0.2f);
}
{
// AABB-Sphere intersection*/
aabb a = aabb(vec3(20-0.5f,-0.5f,7.5f), vec3(20.5f,0.5f,8.5f));
sphere s = sphere(vec3(20+0.6f*sin(dx), 3.0f*cos(dy),8), 1);
hit *m = aabb_hit_sphere(a, s);
if(m) {
vec3 v;
ddraw_color(BLUE);
ddraw_box(m->contact_point, vec3(0.05f, 0.05f, 0.05f));
v = add3(m->contact_point, m->normal);
ddraw_arrow(m->contact_point, v);
ddraw_color(RED);
} else ddraw_color(WHITE);
ddraw_box(vec3(20,0,8), vec3(1,1,1));
ddraw_sphere(s.c, 1);
}
{
// Sphere-AABB intersection*/
const float x = 10+0.6f*sin(dx);
const float y = 3.0f*cos(dy);
const float z = -8.0f;
sphere s = sphere(vec3(10,0,-8), 1);
aabb a = aabb(vec3(x-0.5f,y-0.5f,z-0.5f), vec3(x+0.5f,y+0.5f,z+0.5f));
hit *m = sphere_hit_aabb(s, a);
if(m) {
vec3 v;
ddraw_color(BLUE);
ddraw_box(m->contact_point, vec3(0.05f, 0.05f, 0.05f));
v = add3(m->contact_point, m->normal);
ddraw_arrow(m->contact_point, v);
ddraw_color(RED);
} else ddraw_color(WHITE);
ddraw_box(vec3(x,y,z), vec3(1,1,1));
ddraw_sphere(s.c, 1);
}
{
// Capsule-Sphere intersection*/
capsule c = capsule(vec3(-20.5f,-1.0f,7.5f), vec3(-20+0.5f,1.0f,8.5f), 0.2f);
sphere b = sphere(vec3(-20+0.6f*sin(dx), 3.0f*cos(dy),8), 1);
hit *m = capsule_hit_sphere(c, b);
if(m) {
vec3 v;
ddraw_color(BLUE);
ddraw_box(m->contact_point, vec3(0.05f, 0.05f, 0.05f));
v = add3(m->contact_point, m->normal);
ddraw_arrow(m->contact_point, v);
ddraw_color(RED);
} else ddraw_color(WHITE);
ddraw_sphere(b.c, 1);
ddraw_capsule(vec3(-20.5f,-1.0f,7.5f), vec3(-20+0.5f,1.0f,8.5f), 0.2f);
}
{
// Sphere-Capsule intersection*/
const float x = 20+0.4f*sin(dx);
const float y = 3.0f*cos(dy);
const float z = -8;
sphere s = sphere(vec3(20,0,-8), 1);
capsule c = capsule(vec3(x,y-1.0f,z), vec3(x,y+1.0f,z-1.0f), 0.2f);
hit *m = sphere_hit_capsule(s, c);
if(m) {
vec3 v;
ddraw_color(BLUE);
ddraw_box(m->contact_point, vec3(0.05f, 0.05f, 0.05f));
v = add3(m->contact_point, m->normal);
ddraw_arrow(m->contact_point, v);
ddraw_color(RED);
} else ddraw_color(WHITE);
ddraw_capsule(vec3(x,y-1.0f,z), vec3(x,y+1.0f,z-1.0f), 0.2f);
ddraw_sphere(s.c, 1);
}
{
// Capsule-AABB intersection*/
const float x = -20+0.6f*sin(dx);
const float y = 3.0f*cos(dy);
const float z = 28.0f;
capsule c = capsule(vec3(-20.5f,-1.0f,27.5f), vec3(-20+0.5f,1.0f,28.5f), 0.2f);
aabb b = aabb(vec3(x-0.5f,y-0.5f,z-0.5f), vec3(x+0.5f,y+0.5f,z+0.5f));
hit *m = capsule_hit_aabb(c, b);
if(m) {
vec3 v;
ddraw_color(BLUE);
ddraw_box(m->contact_point, vec3(0.05f, 0.05f, 0.05f));
v = add3(m->contact_point, m->normal);
ddraw_arrow(m->contact_point, v);
ddraw_color(RED);
} else ddraw_color(WHITE);
ddraw_box(vec3(x,y,z), vec3(1,1,1));
ddraw_capsule(vec3(-20.5f,-1.0f,27.5f), vec3(-20+0.5f,1.0f,28.5f), 0.2f);
}
{
// AABB-Capsule intersection*/
const float x = 0.4f*sin(dx);
const float y = 3.0f*cos(dy);
const float z = -8;
aabb a = aabb(vec3(-0.5f,-0.5f,-8.5f), vec3(0.5f,0.5f,-7.5f));
capsule c = capsule(vec3(x,y-1.0f,z), vec3(x,y+1.0f,z-1.0f), 0.2f);
hit *m = aabb_hit_capsule(a, c);
if(m) {
ddraw_color(RED);
ddraw_box(m->contact_point, vec3(0.05f, 0.05f, 0.05f));
ddraw_arrow(m->contact_point, add3(m->contact_point, m->normal));
} else ddraw_color(WHITE);
ddraw_capsule(vec3(x,y-1.0f,z), vec3(x,y+1.0f,z-1.0f), 0.2f);
ddraw_box(vec3(0,0,-8.0f), vec3(1,1,1));
}
}
#line 0
#line 1 "v4k_cook.c"
// data pipeline
// - rlyeh, public domain.
// ----------------------------------------------------------------------------
// @todo: threads should steal workloads from job queue
// @todo: restore errno/errorlevel checks
// @todo: +=, -=, that_asset.ini
// @todo: @dae FLAGS+=-U
// @todo: SF2_SOUNDBANK=TOOLS/soundbank.sf2
// @fixme: leaks (worth?)
// -----------------------------------------------------------------------------
#ifndef COOK_INI_PATHFILE
#define COOK_INI_PATHFILE "tools/cook.ini"
#endif
const char *ART = "art/";
const char *TOOLS = "tools/bin/";
const char *EDITOR = "tools/";
const char *COOK_INI = COOK_INI_PATHFILE;
static unsigned ART_SKIP_ROOT; // number of chars to skip the base root in ART folder
static unsigned ART_LEN; // dupe
typedef struct cook_subscript_t {
char *infile;
char *outfile; // can be either infile, or a totally different file
char *script;
char *outname;
int compress_level;
uint64_t pass_ns, gen_ns, exe_ns, zip_ns;
} cook_subscript_t;
typedef struct cook_script_t {
cook_subscript_t cs[8];
int num_passes;
uint64_t pass_ns, gen_ns, exe_ns, zip_ns;
} cook_script_t;
static
cook_script_t cook_script(const char *rules, const char *infile, const char *outfile) {
cook_script_t mcs = { 0 };
// pass loop: some asset rules may require multiple cook passes
for( int pass = 0; pass < countof(mcs.cs); ++pass ) {
// by default, assume:
// - no script is going to be generated (empty script)
// - if no script is going to be generated, output is in fact input file.
// - no compression is going to be required.
cook_subscript_t cs = { 0 };
cs.gen_ns -= time_ns();
// reuse script heap from last call if possible (optimization)
static __thread char *script = 0;
if(script) script[0] = 0;
// reuse parsing maps if possible (optimization)
static __thread map(char*, char*) symbols = 0; if(!symbols) map_init_str(symbols);
static __thread map(char*, char*) groups = 0; if(!groups) map_init_str(groups);
static __thread set(char*) passes = 0; if(!passes) set_init_str(passes);
map_clear(symbols);
map_clear(groups);
map_find_or_add(symbols, "INFILE", STRDUP(infile));
map_find_or_add(symbols, "INPUT", STRDUP(infile));
map_find_or_add(symbols, "PRETTY", STRDUP(infile + ART_SKIP_ROOT)); // pretty (truncated) input (C:/prj/V4K/art/file.wav -> file.wav)
map_find_or_add(symbols, "OUTPUT", STRDUP(outfile));
map_find_or_add(symbols, "TOOLS", STRDUP(TOOLS));
map_find_or_add(symbols, "EDITOR", STRDUP(EDITOR));
map_find_or_add(symbols, "PROGRESS", STRDUP(va("%03d", cook_progress())));
// clear pass counter
set_clear(passes);
// start parsing. parsing is enabled by default
int enabled = 1;
array(char*)lines = strsplit(rules, "\r\n");
for( int i = 0, end = array_count(lines); i < end; ++i ) {
// skip blanks
int blanks = strspn(lines[i], " \t");
char *line = lines[i] + blanks;
// discard full comments
if( line[0] == ';' ) continue;
// truncate inline comments
if( strstr(line, ";") ) *strstr(line, ";") = 0;
// trim ending spaces
char *eos = line + strlen(line); while(eos > line && eos[-1] == ' ' ) *--eos = 0;
// discard non-specific lines
if( line[0] == '@' ) {
int with_wine = flag("--cook-wine") && !!strstr(line, "@win");
int parse = 0
| ifdef(win32, (!!strstr(line, "@win")), 0)
| ifdef(linux, (!!strstr(line, "@lin") ? 1 : with_wine), 0)
| ifdef(osx, (!!strstr(line, "@osx") ? 1 : with_wine), 0);
if( !parse ) continue;
line = strchr(line+1, ' ');
if(!line) continue;
line += strspn(line, " \t");
}
// execute `shell` commands
if( line[0] == '`' ) {
char *eos = strrchr(++line, '`');
if( eos ) *eos = 0;
// replace all symbols
char* nl = STRDUP(line); // @leak
for each_map(symbols, char*, key, char*, val) {
strrepl(&nl, key, val);
}
lines[i] = line = nl;
#if 0
static thread_mutex_t lock, *init = 0; if(!init) thread_mutex_init(init = &lock);
thread_mutex_lock( &lock );
system(line); // strcatf(&script, "%s\n", line);
thread_mutex_unlock( &lock );
#else
// append line
strcatf(&script, "%s\n", line);
#endif
continue;
}
// process [sections]
if( line[0] == '[' ) {
enabled = 1;
int is_cook = !!strstr(line, "[cook]");
int is_compress = !!strstr(line, "[compress]");
if( !is_cook && !is_compress ) { // if not a special section...
// remove hint cook tag if present. that's informative only.
if(strbegi(line, "[cook ") ) memcpy(line+1, " ", 4); // line += 6;
// start parsing expressions like `[media && !avi && mp3]`
array(char*) tags = strsplit(line, " []&");
// let's check whether INPUT belongs to tags above
char **INPUT = map_find(symbols, "INPUT");
bool found_in_set = true;
for( int i = 0, end = array_count(tags); i < end; ++i) {
bool negate = false;
char *tag = tags[i];
while(*tag == '!') negate ^= 1, ++tag;
// find tag in groups map
// either a group or an extension
char **is_group = map_find(groups, tag);
if( is_group ) {
char *list = *is_group;
char *INPUT_EXT = file_ext(infile); INPUT_EXT = strrchr(INPUT_EXT, '.'); // .ext1.ext -> .ext
char *ext = INPUT_EXT; ext += ext[0] == '.'; // dotless
bool in_list = strbegi(list, ext) || strendi(list, va(",%s",ext)) || strstri(list, va(",%s,",ext));
if( !in_list ^ negate ) { found_in_set = false; break; }
} else {
char *ext = va(".%s", tag);
bool found = !!strendi(*INPUT, ext);
if( !found ^ negate ) { found_in_set = false; break; }
}
}
if( found_in_set ) {
// inc pass
set_find_or_add(passes, STRDUP(*tags)); // @leak
// check whether we keep searching
int num_passes = set_count(passes);
found_in_set = ( pass == (num_passes-1) );
}
//
enabled = found_in_set ? 1 : 0;
}
}
// either SYMBOL=, group=, or regular script line
if( enabled && line[0] != '[' ) {
enum { group, symbol, regular } type = regular;
int tokenlen = strspn(line, "-+_.|0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
char *token = va("%.*s", tokenlen, line);
char *equal = strchr(line, '=');
if( equal ) {
if( equal == &line[tokenlen] ) { // if key=value expression found
// discriminate: symbols are uppercase and never begin with digits. groups are [0-9]+[|][a-z].
type = strcmp(strupper(token), token) || isdigit(token[0]) ? group : symbol;
}
}
if( type == group ) map_find_or_add(groups, token, STRDUP(equal+1));
if( type == symbol ) {
// @todo: perform the replacement/union/intersection on set here
bool is_add = strendi(token, "+");
bool is_del = strendi(token, "-");
// if present, remove last sign from token -> (FLAGS1+)=, (FLAGS1-)=
if(is_add || is_del) token[strlen(token) - 1] = 0;
map_find_or_add(symbols, token, STRDUP(equal+1));
}
// for each_map(symbols, char*, key, char*, val) printf("%s=%s,", key, val); puts("");
// for each_map(groups, char*, key, char*, val) printf("%s=%s,", key, val); puts("");
// if( type != regular ) printf("%s found >> %s\n", type == group ? "group" : "symbol", line);
if( type == regular ) {
char** INPUT = map_find(symbols, "INPUT");
char** OUTPUT = map_find(symbols, "OUTPUT");
// parse return code
char *has_errorlevel = strstr(line, "=="); //==N form
int errorlevel = has_errorlevel ? atoi(has_errorlevel + 2) : 0;
if( has_errorlevel ) memcpy(has_errorlevel, " ", 3);
// detect if newer extension or filename is present, and thus update OUTPUT if needed
char *newer_extension = strstr(line, "->"); if(newer_extension) {
*newer_extension = 0;
newer_extension += 2 + strspn(newer_extension + 2, " ");
if( strchr(newer_extension, '.') ) {
// newer filename
cs.outname = stringf("%s@%s", cs.outname ? cs.outname : infile, newer_extension); // @leak // special char (multi-pass cooks)
newer_extension = NULL;
} else {
strcatf(&*OUTPUT, ".%s", newer_extension);
}
}
// replace all symbols
char* nl = STRDUP(line); // @leak
for each_map(symbols, char*, key, char*, val) {
strrepl(&nl, key, val);
}
lines[i] = line = nl;
// convert slashes
ifdef(win32,
strswap(line, "/", "\\")
, // else
strswap(line, "\\", "/")
);
// append line
strcatf(&script, "%s\n", line);
// handle return code here
// if(has_errorlevel)
// strcatf(&script, "IF NOT '%%ERRORLEVEL%%'=='%d' echo ERROR!\n", errorlevel);
// rename output->input for further chaining, in case it is needed
if( newer_extension ) {
*INPUT[0] = 0;
strcatf(&*INPUT, "%s", *OUTPUT);
}
}
}
}
char** OUTPUT = map_find(symbols, "OUTPUT");
int ext_num_groups = 0;
// compression
if( 1 ) {
char* ext = file_ext(infile); ext = strrchr(ext, '.'); ext += ext[0] == '.'; // dotless INPUT_EXT
char* belongs_to = 0;
for each_map(groups, char*, key, char*, val) {
if( !isdigit(key[0]) ) {
char *comma = va(",%s,", ext);
if( !strcmpi(val,ext) || strbegi(val, comma+1) || strstri(val, comma) || strendi(val, va(",%s", ext))) {
belongs_to = key;
ext_num_groups++;
}
}
}
char *compression = 0;
for each_map_ptr_sorted(groups, char*, key, char*, val) { // sorted iteration, so hopefully '0' no compression gets evaluated first
if( !compression && isdigit((*key)[0]) ) {
char *comma = va(",%s,", ext);
if( !strcmpi(*val,ext) || strbegi(*val, comma+1) || strstri(*val, comma) || strendi(*val, va(",%s", ext))) {
compression = (*key);
}
comma = va(",%s,", belongs_to);
if( !strcmpi(*val,ext) || strbegi(*val, comma+1) || strstri(*val, comma) || strendi(*val, va(",%s", ext))) {
compression = (*key);
}
}
}
cs.compress_level = 0;
if( compression ) {
// last chance to optionally override the compressor at command-line level
static const char *compressor_override;
do_once compressor_override = option("--cook-compressor", "");
if( compressor_override[0] ) compression = (char*)compressor_override;
/**/ if(strstri(compression, "PPP")) cs.compress_level = atoi(compression) | PPP;
else if(strstri(compression, "ULZ")) cs.compress_level = atoi(compression) | ULZ;
else if(strstri(compression, "LZ4")) cs.compress_level = atoi(compression) | LZ4X;
else if(strstri(compression, "CRSH")) cs.compress_level = atoi(compression) | CRSH;
else if(strstri(compression, "DEFL")) cs.compress_level = isdigit(compression[0]) ? atoi(compression) : 6 /*| DEFL*/;
//else if(strstri(compression, "LZP")) cs.compress_level = atoi(compression) | LZP1; // not supported
else if(strstri(compression, "LZMA")) cs.compress_level = atoi(compression) | LZMA;
else if(strstri(compression, "BALZ")) cs.compress_level = atoi(compression) | BALZ;
else if(strstri(compression, "LZW")) cs.compress_level = atoi(compression) | LZW3;
else if(strstri(compression, "LZSS")) cs.compress_level = atoi(compression) | LZSS;
else if(strstri(compression, "BCM")) cs.compress_level = atoi(compression) | BCM;
else cs.compress_level = isdigit(compression[0]) ? atoi(compression) : 6 /*| DEFL*/;
}
}
// if script was generated...
if( script && script[0] && strstr(script, ifdef(win32, file_normalize(va("%s",infile)), infile )) ) {
// update outfile
cs.outfile = *OUTPUT;
// amalgamate script
array(char*) lines = strsplit(script, "\r\n");
#if is(win32)
char *joint = strjoin(lines, " && ");
cs.script = joint;
#else
if( flag("--cook-wine") ) {
// dear linux/osx/bsd users:
// tools going wrong for any reason? cant compile them maybe?
// small hack to use win32 pipeline tools instead
char *joint = strjoin(lines, " && wine " );
cs.script = va("wine %s", /*TOOLS,*/ joint);
} else {
char *joint = strjoin(lines, " && " );
cs.script = va("export LD_LIBRARY_PATH=%s && %s", TOOLS, joint);
}
#endif
} else {
// if( script && script[0] ) system(script); //< @todo: un-comment this line if we want to get the shell command prints invoked per entry
// ... else bypass infile->outfile
char** INFILE = map_find(symbols, "INFILE");
cs.outfile = *INFILE;
// and return an empty script
cs.script = "";
}
cs.outname = cs.outname ? cs.outname : (char*)infile;
cs.gen_ns += time_ns();
ASSERT(mcs.num_passes < countof(mcs.cs));
mcs.cs[mcs.num_passes++] = cs;
bool next_pass_required = mcs.num_passes < ext_num_groups;
if( !next_pass_required ) break;
}
return mcs;
}
// ----------------------------------------------------------------------------
struct fs {
char *fname, status;
uint64_t stamp;
uint64_t bytes;
};
static array(struct fs) fs_now;
static __thread array(char*) added;
static __thread array(char*) changed;
static __thread array(char*) deleted;
static __thread array(char*) uncooked;
static
array(struct fs) zipscan_filter(int threadid, int numthreads) {
// iterate all previously scanned files
array(struct fs) fs = 0;
for( int i = 0, end = array_count(fs_now); i < end; ++i ) {
// during workload distribution, we assign random files to specific thread buckets.
// we achieve this by hashing the basename of the file. we used to hash also the path
// long time ago but that is less resilient to file relocations across the repository.
// excluding the file extension from the hash also helps from external file conversions.
char *fname = file_name(fs_now[i].fname);
char *sign = strrchr(fname, '@'); if(sign) *sign = '\0'; // special char (multi-pass cooks)
char *dot = strrchr(fname, '.'); if(dot) *dot = '\0';
// skip if list item does not belong to this thread bucket
uint64_t hash = hash_str(fname);
unsigned bucket = (hash /*>> 32*/) % numthreads;
if(bucket != threadid) continue;
array_push(fs, fs_now[i]);
}
return fs;
}
static
int zipscan_diff( zip* old, array(struct fs) now ) {
array_free(added);
array_free(changed);
array_free(deleted);
array_free(uncooked);
// if not zipfile is present, all files are new and must be added
if( !old ) {
for( int i = 0; i < array_count(now); ++i ) {
array_push(uncooked, STRDUP(now[i].fname));
}
return 1;
}
// compare for new & changed files
for( int i = 0; i < array_count(now); ++i ) {
int found = zip_find(old, now[i].fname);
if( found < 0 ) {
array_push(added, STRDUP(now[i].fname));
array_push(uncooked, STRDUP(now[i].fname));
} else {
uint64_t oldsize = atoi64(zip_comment(old,found)); // zip_size(old, found); returns sizeof processed asset. return original size of unprocessed asset, which we store in comment section
uint64_t oldstamp = atoi64(zip_modt(old,found)+20); // format is "YYYY/MM/DD hh:mm:ss", then +20 chars later a hidden epoch timestamp in base10 can be found
int64_t diffstamp = oldstamp < now[i].stamp ? now[i].stamp - oldstamp : oldstamp - now[i].stamp;
if( oldsize != now[i].bytes || diffstamp > 1 ) { // @fixme: should use hash instead. hashof(tool) ^ hashof(args used) ^ hashof(rawsize) ^ hashof(rawdate)
printf("%s:\t%u vs %u, %llu vs %llu\n", now[i].fname, (unsigned)oldsize,(unsigned)now[i].bytes, (long long unsigned)oldstamp, (long long unsigned)now[i].stamp);
array_push(changed, STRDUP(now[i].fname));
array_push(uncooked, STRDUP(now[i].fname));
}
}
}
// compare for deleted files
for( int i = 0; i < zip_count(old); ++i ) {
char *oldname = zip_name(old, i);
//if( strchr(oldname, '@') ) oldname = va("%*.s", (int)(strchr(oldname, '@') - oldname), oldname ); // special char (multi-pass cooks)
int idx = zip_find(old, oldname); // find latest versioned file in zip
unsigned oldsize = zip_size(old, idx);
if (!oldsize) continue;
struct fs *found = 0; // zipscan_locate(now, oldname);
for(int j = 0; j < array_count(now); ++j) {
if( !strcmp(now[j].fname,oldname)) {
found = &now[j];
break;
}
}
if( !found ) {
array_push(deleted, STRDUP(oldname));
}
}
return 1;
}
// ----------------------------------------------------------------------------
typedef struct cook_worker {
const char **files;
const char *rules;
int threadid, numthreads;
thread_ptr_t self;
volatile int progress;
thread_mutex_t *lock;
} cook_worker;
enum { JOBS_MAX = 256 };
static cook_worker jobs[JOBS_MAX] = {0};
static volatile bool cook_cancelable = false, cook_cancelling = false, cook_debug = false;
#ifndef COOK_ON_DEMAND
#define COOK_ON_DEMAND ifdef(cook, optioni("--cook-on-demand", 0), false)
#endif
static
int cook(void *userdata) {
cook_worker *job = (cook_worker*)userdata;
// start progress
volatile int *progress = &job->progress;
*progress = 0;
// preload a few large binaries
// dll("tools/furnace.exe", 0);
// dll("tools/assimp-vc143-mt.dll", 0);
// dll("tools/ffmpeg.exe", 0);
// scan disk from fs_now snapshot
array(struct fs) filtered = zipscan_filter(job->threadid, job->numthreads);
//printf("Scanned: %d items found\n", array_count(now));
// prepare out tempname
char COOK_TMPFILE[64]; snprintf(COOK_TMPFILE, 64, "temp_%02d", job->threadid);
// prepare zip
char zipfile[64]; snprintf(zipfile, 64, ".art[%02x].zip", job->threadid);
if( file_size(zipfile) == 0 ) unlink(zipfile);
// populate added/deleted/changed arrays by examining current disk vs last cache
zip *z;
{
z = zip_open(zipfile, "r+b");
zipscan_diff(z, filtered);
if( z ) zip_close(z);
fflush(0);
z = zip_open(zipfile, "a+b");
if( !z ) {
unlink(zipfile);
z = zip_open(zipfile, "a+b"); // try again
if(!z) PANIC("cannot open file for updating: %s", zipfile);
}
}
// deleted files. --cook-additive runs are append-only, so they skip this block
if( !flag("--cook-additive") )
for( int i = 0, end = array_count(deleted); i < end; ++i ) {
printf("Deleting %03d%% %s\n", (i+1) == end ? 100 : (i * 100) / end, deleted[i]);
FILE* out = fopen(COOK_TMPFILE, "wb"); fclose(out);
FILE* in = fopen(COOK_TMPFILE, "rb");
char *comment = "0";
zip_append_file/*_timeinfo*/(z, deleted[i], comment, in, 0/*, tm_now*/);
fclose(in);
}
// if(array_count(uncooked))
// PRINTF("cook_jobs[%d]=%d\n", job->threadid, array_count(uncooked));
// generate cook metrics. you usually do `game.exe --cook-stats && (type *.csv | sort /R > cook.csv)`
static __thread FILE *statsfile = 0;
if(flag("--cook-stats"))
fseek(statsfile = fopen(va("cook%d.csv",job->threadid), "a+t"), 0L, SEEK_END);
if(statsfile && !job->threadid && ftell(statsfile) == 0) fprintf(statsfile,"%10s,%10s,%10s,%10s,%10s, %s\n","+total_ms","gen_ms","exe_ms","zip_ms","pass","file");
// added or changed files
for( int i = 0, end = array_count(uncooked); i < end && !cook_cancelling; ++i ) {
*progress = ((i+1) == end ? 90 : (i * 90) / end); // (i+i>0) * 100.f / end;
// start cook
const char *infile = uncooked[i]; //job->files[j];
int inlen = file_size(infile);
// generate a cooking script for this asset
cook_script_t mcs = cook_script(job->rules, infile, COOK_TMPFILE);
// puts(cs.script);
for(int pass = 0; pass < mcs.num_passes; ++pass) {
cook_subscript_t cs = mcs.cs[pass];
// log to batch file for forensic purposes, if explicitly requested
static __thread int logging = -1; if(logging < 0) logging = !!flag("--cook-debug") || cook_debug;
if( logging ) {
static __thread FILE *logfile = 0; if(!logfile) fseek(logfile = fopen(va("cook%d.cmd",job->threadid), "a+t"), 0L, SEEK_END);
if( logfile ) {
fprintf(logfile, "@rem %s\n%s\n", cs.outname, cs.script);
fprintf(logfile, "for %%%%i in (\"%s\") do md _cook\\%%%%~pi\\%%%%~ni%%%%~xi 1>nul 2>nul\n", infile);
fprintf(logfile, "for %%%%i in (\"%s\") do xcopy /y %s _cook\\%%%%~pi\\%%%%~ni%%%%~xi\n\n", infile, file_normalize(cs.outfile));
}
}
// invoke cooking script
mcs.cs[pass].exe_ns -= time_ns();
// invoke cooking script
const char *rc_output = app_exec(cs.script);
// recap status
int rc = atoi(rc_output);
// int outlen = file_size(cs.outfile);
int failed = rc; // cs.script[0] ? rc || !outlen : 0;
// print errors
if( failed ) {
PRINTF("Import failed: %s while executing:\n%s\nReturned:\n%s\n", cs.outname, cs.script, rc_output);
continue;
}
if( pass > 0 ) { // (multi-pass cook)
// newly generated file: refresh values
// ensure newly created files by cook are also present on repo/disc for further cook passes
file_delete(cs.outname);
file_move(cs.outfile, cs.outname);
inlen = file_size(infile = cs.outfile = cs.outname);
}
mcs.cs[pass].exe_ns += time_ns();
// process only if included. may include optional compression.
mcs.cs[pass].zip_ns -= time_ns();
if( cs.compress_level >= 0 ) {
FILE *in = fopen(cs.outfile ? cs.outfile : infile, "rb");
if(!in) in = fopen(infile, "rb");
char *comment = va("%d", inlen);
if( !zip_append_file(z, infile, comment, in, cs.compress_level) ) {
PANIC("failed to add processed file into %s: %s(%s)", zipfile, cs.outname, infile);
}
fclose(in);
}
mcs.cs[pass].zip_ns += time_ns();
// stats per subscript
mcs.cs[pass].pass_ns = mcs.cs[pass].gen_ns + mcs.cs[pass].exe_ns + mcs.cs[pass].zip_ns;
if(statsfile) fprintf(statsfile, "%10.f,%10.f,%10.f,%10.f,%10d, \"%s\"\n", mcs.cs[pass].pass_ns/1e6, mcs.cs[pass].gen_ns/1e6, mcs.cs[pass].exe_ns/1e6, mcs.cs[pass].zip_ns/1e6, pass+1, infile);
}
}
zip_close(z);
// end progress
if( file_size(zipfile) == 0 ) unlink(zipfile);
*progress = 100;
return 1;
}
static
int cook_async( void *userdata ) {
#if COOK_FROM_TERMINAL
// nothing to do...
#else
while(!window_handle()) sleep_ms(100); // wait for window handle to be created
#endif
// boost cook thread #0, which happens to be the only spawn thread when num_jobs=1 (tcc case, cook-sync case).
// also in multi-threaded scenarios, it is not bad at all to have one high priority thread...
// in any case, game view is not going to look bad because the game will be displaying a progress bar at that time.
cook_worker *job = (cook_worker*)userdata;
if( job->threadid == 0 ) thread_set_high_priority();
// tcc: only a single running thread shall pass, because of racing shared state due to missing thread_local support at compiler level
ifdef(tcc, thread_mutex_lock( job->lock ));
ifdef(osx, thread_mutex_lock( job->lock )); // @todo: remove silicon mac M1 hack
int ret = cook(userdata);
// tcc: only a single running thread shall pass, because of racing shared state due to missing thread_local support at compiler level
ifdef(osx, thread_mutex_unlock( job->lock )); // @todo: remove silicon mac M1 hack
ifdef(tcc, thread_mutex_unlock( job->lock ));
thread_exit( ret );
return ret;
}
bool cook_start( const char *cook_ini, const char *masks, int flags ) {
cook_ini = cook_ini ? cook_ini : COOK_INI;
char *rules_ = file_read(cook_ini);
if(!rules_ || rules_[0] == 0) return false;
static char *rules; do_once rules = STRDUP(rules_);
do_once {
#if 0
const char *HOME = file_pathabs(cook_ini); // ../tools/cook.ini -> c:/prj/v4k/tools/cook.ini
if( strbeg(HOME, app_path() ) ) HOME = STRDUP( file_path( HOME += strlen(app_path()) ) ); // -> tools/ @leak
#else
char *HOME = STRDUP(file_pathabs(cook_ini)); // ../tools/cook.ini -> c:/prj/v4k/tools/cook.ini
HOME[ strlen(HOME) - strlen(file_name(cook_ini)) ] = '\0'; // -> tools/ @leak
#endif
ART_LEN = 0; //strlen(app_path());
/* = MAX_PATH;
for each_substring(ART, ",", art_folder) {
ART_LEN = mini(ART_LEN, strlen(art_folder));
}*/
if( strstr(rules, "ART=") ) {
ART = va( "%s", strstr(rules, "ART=") + 4 );
char *r = strchr( ART, '\r' ); if(r) *r = 0;
char *n = strchr( ART, '\n' ); if(n) *n = 0;
char *s = strchr( ART, ';' ); if(s) *s = 0;
char *w = strchr( ART, ' ' ); if(w) *w = 0;
char *out = 0; const char *sep = "";
for each_substring(ART, ",", t) {
char *tmp = file_pathabs(va("%s%s", HOME, t)) + ART_LEN;
for(int i = 0; tmp[i]; ++i) if(tmp[i]=='\\') tmp[i] = '/';
strcatf(&out, "%s%s%s", sep, tmp, strendi(tmp, "/") ? "" : "/");
assert( out[strlen(out) - 1] == '/' );
sep = ",";
}
ART = out; // @leak
}
if( strstr(rules, "TOOLS=") ) {
TOOLS = va( "%s", strstr(rules, "TOOLS=") + 6 );
char *r = strchr( TOOLS, '\r' ); if(r) *r = 0;
char *n = strchr( TOOLS, '\n' ); if(n) *n = 0;
char *s = strchr( TOOLS, ';' ); if(s) *s = 0;
char *w = strchr( TOOLS, ' ' ); if(w) *w = 0;
char *cat = va("%s%s", HOME, TOOLS), *out = 0;
for(int i = 0; cat[i]; ++i) if(cat[i]=='\\') cat[i] = '/';
strcatf(&out, "%s%s", cat, strend(cat, "/") ? "" : "/");
TOOLS = out; // @leak
assert( TOOLS[strlen(TOOLS) - 1] == '/' );
// last chance to autodetect tools folder (from cook.ini path)
if( !file_directory(TOOLS) ) {
out = STRDUP(cook_ini);
for(int i = 0; out[i]; ++i) if(out[i]=='\\') out[i] = '/';
TOOLS = out; // @leak
}
}
if( strstr(rules, "EDITOR=") ) {
EDITOR = va( "%s", strstr(rules, "EDITOR=") + 7 );
char *r = strchr( EDITOR, '\r' ); if(r) *r = 0;
char *n = strchr( EDITOR, '\n' ); if(n) *n = 0;
char *s = strchr( EDITOR, ';' ); if(s) *s = 0;
char *w = strchr( EDITOR, ' ' ); if(w) *w = 0;
char *cat = va("%s%s", HOME, EDITOR), *out = 0;
for(int i = 0; cat[i]; ++i) if(cat[i]=='\\') cat[i] = '/';
strcatf(&out, "%s%s", cat, strend(cat, "/") ? "" : "/");
EDITOR = out; // @leak
assert( EDITOR[strlen(EDITOR) - 1] == '/' );
}
// small optimization for upcoming parser: remove whole comments from file
array(char*) lines = strsplit(rules, "\r\n");
for( int i = 0; i < array_count(lines); ) {
if( lines[i][0] == ';' ) array_erase_slow(lines, i);
else ++i;
}
rules = STRDUP( strjoin(lines, "\n") );
}
if( !masks ) {
return true; // nothing to do
}
// estimate ART_SKIP_ROOT (C:/prj/v4k/demos/assets/file.png -> strlen(C:/prj/v4k/) -> 11)
{
array(char*) dirs = 0;
for each_substring(ART, ",", art_folder) {
array_push(dirs, file_pathabs(art_folder));
}
if( array_count(dirs) > 1 ) {
for( int ok = 1, ch = dirs[0][ART_SKIP_ROOT]; ch && ok; ch = dirs[0][++ART_SKIP_ROOT] ) {
for( int i = 1; i < array_count(dirs) && ok; ++i ) {
ok = dirs[i][ART_SKIP_ROOT] == ch;
}
}
}
while( ART_SKIP_ROOT > 0 && !strchr("\\/", dirs[0][ART_SKIP_ROOT-1]) ) --ART_SKIP_ROOT;
array_free(dirs);
}
if( COOK_ON_DEMAND ) {
return true; // cooking is deferred
}
// scan disk: all subfolders in ART (comma-separated)
static array(char *) list = 0; // @leak
for each_substring(ART, ",", art_folder) {
array(char *) glob = file_list(va("%s**",art_folder)); // art_folder ends with '/'
for( unsigned i = 0, end = array_count(glob); i < end; ++i ) {
const char *fname = glob[i];
if( !strmatchi(fname, masks)) continue;
// skip special files, folders and internal files like .art.zip
const char *dir = file_path(fname);
if( dir[0] == '.' ) continue; // discard system dirs and hidden files
if( strbegi(dir, TOOLS) ) continue; // discard tools folder
if( !file_ext(fname)[0] ) continue; // discard extensionless entries
if( !file_size(fname)) continue; // skip dirs and empty files
// exclude vc c/c++ .obj files. they're not 3d wavefront .obj files
if( strend(fname, ".obj") ) {
char header[4] = {0};
for( FILE *in = fopen(fname, "rb"); in; fclose(in), in = NULL) {
fread(header, 2, 1, in);
}
if( !memcmp(header, "\x64\x86", 2) ) continue;
if( !memcmp(header, "\x00\x00", 2) ) continue;
}
char *dot = strrchr(fname, '.');
if( dot ) {
char extdot[32];
snprintf(extdot, 32, "%s.", dot); // .png -> .png.
// exclude vc/gcc/clang files
if( strstr(fname, ".a.o.pdb.lib.ilk.exp.dSYM.") ) // must end with dot
continue;
}
// @todo: normalize path & rebase here (absolute to local)
// [...]
// fi.normalized = ; tolower->to_underscore([]();:+ )->remove_extra_underscores
if (file_name(fname)[0] == '.') continue; // skip system files
if (file_name(fname)[0] == ';') continue; // skip comment files
array_push(list, STRDUP(fname));
}
}
// inspect disk
for( int i = 0, end = array_count(list); i < end; ++i ) {
char *fname = list[i];
struct fs fi = {0};
fi.fname = fname; // STRDUP(fname);
fi.bytes = file_size(fname);
fi.stamp = file_stamp10(fname); // timestamp in base10(yyyymmddhhmmss)
array_push(fs_now, fi);
}
cook_debug = !!( flags & COOK_DEBUGLOG );
cook_cancelable = !!( flags & COOK_CANCELABLE );
// spawn all the threads
int num_jobs = cook_jobs();
for( int i = 0; i < num_jobs; ++i ) {
jobs[i].self = 0;
jobs[i].threadid = i;
jobs[i].numthreads = flags & COOK_ASYNC ? num_jobs : 1;
jobs[i].files = (const char **)list;
jobs[i].rules = rules;
jobs[i].progress = -1;
static thread_mutex_t lock; do_once thread_mutex_init(&lock);
jobs[i].lock = &lock;
}
for( int i = 0; i < num_jobs; ++i ) {
if( flags & COOK_ASYNC ) {
jobs[i].self = thread_init(cook_async, &jobs[i], "cook_async()", 0/*STACK_SIZE*/);
continue;
}
if(!cook(&jobs[i])) return false;
}
return true;
}
void cook_stop() {
// join all threads
int num_jobs = cook_jobs();
for( int i = 0; i < num_jobs; ++i ) {
if(jobs[i].self) thread_join(jobs[i].self);
}
// remove all temporary outfiles
for each_array(file_list("temp_*"), char*, tempfile) unlink(tempfile);
}
int cook_progress() {
int count = 0, sum = 0;
for( int i = 0, end = cook_jobs(); i < end; ++i ) {
sum += jobs[i].progress;
++count;
}
return cook_jobs() ? sum / (count+!count) : 100;
}
void cook_cancel() {
if( cook_cancelable ) cook_cancelling = true;
}
int cook_jobs() {
int num_jobs = optioni("--cook-jobs", maxf(1.15,app_cores()) * 1.75), max_jobs = countof(jobs);
ifdef(ems, num_jobs = 0);
ifdef(retail, num_jobs = 0);
ifdef(nocook, num_jobs = 0);
return clampi(num_jobs, 0, max_jobs);
}
void cook_config( const char *pathfile_to_cook_ini ) { // @todo: test run-from-"bin/" case on Linux.
COOK_INI = pathfile_to_cook_ini;
ASSERT( file_exist(COOK_INI) );
}
bool have_tools() {
static bool found; do_once found = file_exist(COOK_INI);
return ifdef(retail, false, found);
}
#line 0
#line 1 "v4k_data.c"
static array(json5) roots;
static array(char*) sources;
bool json_push(const char *source) {
char *source_rw = STRDUP(source);
json5 root = {0};
char *error = json5_parse(&root, source_rw, 0);
if( error ) {
FREE(source_rw);
return false;
} else {
array_push(sources, source_rw);
array_push(roots, root);
return true;
}
}
bool json_pop() {
if( array_count(roots) > 0 ) {
FREE(*array_back(sources));
array_pop(sources);
json5_free(array_back(roots));
array_pop(roots);
return true;
}
return false;
}
json5* json_node(const char *keypath) {
json5 *j = array_back(roots), *r = j;
for each_substring( keypath, "/[.]", key ) {
r = 0;
/**/ if( j->type == JSON5_ARRAY ) r = j = &j->array[atoi(key)];
else if( j->type == JSON5_OBJECT && isdigit(key[0]) )
for( int i = 0, seq = atoi(key); !r && i < j->count; ++i ) {
if( i == seq ) {
r = j = &j->nodes[i];
break;
}
}
else if( j->type == JSON5_OBJECT )
for( int i = 0; !r && i < j->count; ++i ) {
if( j->nodes[i].name && !strcmp(j->nodes[i].name, key) ) {
r = j = &j->nodes[i];
break;
}
}
if( !j ) break;
}
return r;
}
int (json_count)(const char *keypath) {
json5* j = json_node(keypath);
return j ? j->count : 0;
}
json_t *json_find(const char *type_keypath) {
char type = type_keypath[0];
const char *key = type_keypath+1;
json5 *j = json_node(key);
if( !j ) return NULL;
static __thread int slot = 0;
static __thread json_t buf[128] = {0};
slot = (slot+1) % 128;
json_t *v = &buf[slot];
v->i = j ? j->integer : 0;
if(type == 's' && (!v->p || j->type == JSON5_NULL)) v->s = ""; // if_null_string
if(type == 'f' && j && j->type == JSON5_INTEGER) v->f = j->integer;
return v;
}
json_t json_get(const char *type_keypath) {
char type = type_keypath[0];
const char *key = type_keypath+1;
json5 *j = json_node(key);
json_t v = {0};
v.i = j ? j->integer : 0;
if(type == 's' && (!v.p || j->type == JSON5_NULL)) v.s = ""; // if_null_string
if(type == 'f' && j && j->type == JSON5_INTEGER) v.f = j->integer;
return v;
}
const char *(json_key)(const char *keypath) {
json5 *j = json_node(keypath);
if( !j ) return "";
return j->name;
}
// xml impl
static __thread array(char *) xml_sources;
static __thread array(struct xml *) xml_docs;
int xml_push(const char *xml_source) {
if( xml_source ) {
char *src = STRDUP(xml_source), *error = 0;
for( struct xml *doc = xml_parse(src, 0, &error); doc && !error; ) {
array_push(xml_docs, doc);
array_push(xml_sources, src);
return 1;
}
if( error ) PRINTF("%s\n", error);
FREE(src);
}
return 0;
}
void xml_pop() {
if( array_count(xml_docs) ) {
xml_free( *array_back(xml_docs) );
array_pop(xml_docs);
FREE( *array_back(xml_sources) );
array_pop(xml_sources);
}
}
static void *xml_path(struct xml *node, char *path, int down) {
if( !path || !path[0] ) return node;
if( node ) {
char type = path[0];
if( type == '/' ) {
int sep = strcspn(++path, "/[@$");
if( !sep ) type = path[0];
else
if( 1 ) { // path[ sep ] ) {
char tag[32]; snprintf(tag, 32, "%.*s", sep, path);
// Find the first sibling with the given tag name (may be the same node)
struct xml *next = down ? xml_find_down(node, tag) : xml_find(node, tag);
return xml_path(next, &path[ sep ], 1);
}
}
if( type == '$' ) {
return (void*)( node->down ? xml_text( node->down ) : xml_tag( node ) );
}
if( type == '@' ) {
return (void*)xml_att(node, ++path);
}
if( type == '[' ) {
for( int i = 0, end = atoi(++path); i < end; ++i ) { node = xml_find_next(node, xml_tag(node)); if(!node) return NULL; }
while( isdigit(path[0]) ) ++path;
return xml_path(node, ++path, 1);
}
}
return NULL;
}
const char *(xml_string)(char *key) {
struct xml *node = xml_path(*array_back(xml_docs), key, 0);
if( node && strchr(key, '@') ) return (const char *)node;
if( node && strchr(key, '$') ) return (const char *)node;
return "";
}
unsigned (xml_count)(char *key) {
struct xml *node = xml_path(*array_back(xml_docs), key, 0);
if( !node ) return 0;
const char *tag = xml_tag(node);
unsigned count = 1;
while( (node = xml_find_next(node, tag)) != 0) ++count;
return count;
}
array(char) (xml_blob)(char *key) { // base64 blob
struct xml *node = xml_path(*array_back(xml_docs), key, 0);
if( !node ) return 0;
if( !strchr(key, '$') ) return 0;
const char *data = (const char*)node;
array(char) out = base64_decode(data, strlen(data)); // either array of chars (ok) or null (error)
return out;
}
bool data_tests() {
// data tests (json5)
const char json5[] =
" /* json5 */ // comment\n"
" abc: 42.67, def: true, integer:0x100 \n"
" huge: 2.2239333e5, \n"
" hello: 'world /*comment in string*/ //again', \n"
" children : { a: 1, b: 2, c: 3 },\n"
" array: [+1,2,-3,4,5], \n"
" invalids : [ nan, NaN, -nan, -NaN, inf, Infinity, -inf, -Infinity ],";
if( json_push(json5) ) {
assert( json_float("/abc") == 42.67 );
assert( json_int("/def") == 1 );
assert( json_int("/integer") == 0x100 );
assert( json_float("/huge") > 2.22e5 );
assert( strlen(json_string("/hello")) == 35 );
assert( json_int("/children/a") == 1 );
assert( json_int("/children.b") == 2 );
assert( json_int("/children[c]") == 3 );
assert( json_int("/array[%d]", 2) == -3 );
assert( json_count("/invalids") == 8 );
assert( isnan(json_float("/invalids[0]")) );
assert( !json_find("/non_existing") );
assert( PRINTF("json5 tests OK\n") );
json_pop();
}
// data tests (xml)
const char *xml = // vfs_read("test1.xml");
"<!-- XML representation of a person record -->"
"<person created=\"2006-11-11T19:23\" modified=\"2006-12-31T23:59\">"
" <firstName>Robert</firstName>"
" <lastName>Smith</lastName>"
" <address type=\"home\">"
" <street>12345 Sixth Ave</street>"
" <city>Anytown</city>"
" <state>CA</state>"
" <postalCode>98765-4321</postalCode>"
" </address>"
"</person>";
if( xml_push(xml) ) {
assert( !strcmp("Robert", xml_string("/person/firstName/$")) );
assert( !strcmp("Smith", xml_string("/person/lastName/$")) );
assert( !strcmp("home", xml_string("/person/address/@type")) );
assert( PRINTF("xml tests OK\n") );
xml_pop();
}
return true;
}
#line 0
#line 1 "v4k_extend.c"
// dll ------------------------------------------------------------------------
/* deprecated
#if is(win32)
# include <winsock2.h>
# define dlopen(name,flags) (void*)( (name) ? LoadLibraryA(name) : GetModuleHandleA(NULL))
# define dlsym(handle,symbol) GetProcAddress((HMODULE)(handle), symbol )
# define dlclose(handle) 0
#else
# include <dlfcn.h>
# define dlopen(name,flags) (void*)( (name) ? dlopen(name, flags) : NULL )
# define dlsym(handle,symbol) dlsym( (handle) ? (handle) : ifdef(osx,RTLD_SELF,NULL), symbol )
#endif
*/
void* dll(const char *fname, const char *symbol) {
if( fname && !file_exist(fname) ) {
char *buf, *path = file_path(fname), *base = file_base(fname);
if( file_exist(buf = va("%s%s.dll", path, base)) ||
file_exist(buf = va("%s%s.so", path, base)) ||
file_exist(buf = va("%slib%s.so", path, base)) ||
file_exist(buf = va("%s%s.dylib", path, base)) ) {
fname = (const char *)buf;
} else {
return NULL;
}
}
#if is(win32)
return (void*)GetProcAddress(fname ? LoadLibraryA(fname) : GetModuleHandleA(NULL), symbol);
#else
return dlsym(fname ? dlopen(fname, RTLD_NOW|RTLD_LOCAL) : ifdef(osx, RTLD_SELF, NULL), symbol);
#endif
}
#if 0 // demo: cl demo.c /LD && REM dll
EXPORT int add2(int a, int b) { return a + b; }
int main() { int (*adder)() = dll("demo.dll", "add2"); printf("%d\n", adder(2,3)); }
#endif
// script ---------------------------------------------------------------------
typedef lua_State lua;
// the Lua interpreter(s)
static array(lua*) Ls;
// the **current** Lua interpreter
static lua *L;
#if is(linux)
void luaopen_libv4k(lua_State *L) {}
#endif
static void* script__realloc(void *userdef, void *ptr, size_t osize, size_t nsize) {
(void)userdef;
return ptr = REALLOC( ptr, /* (osize+1) * */ nsize );
}
static int script__traceback(lua_State *L) {
if (!lua_isstring(L, 1)) { // try metamethod if non-string error object
if (lua_isnoneornil(L, 1) ||
!luaL_callmeta(L, 1, "__tostring") ||
!lua_isstring(L, -1))
return 1; // return non-string error object
lua_remove(L, 1); // replace object with result of __tostring metamethod
}
luaL_traceback(L, L, lua_tostring(L, 1), 1);
return 1;
}
static void script__error(lua_State *L, int status) {
if (status != 0) {
const char *errormsg = lua_tostring(L, -1);
PRINTF( "!-- %s\n", errormsg);
lua_pop(L, 1); // remove error message
}
}
static int script__call(lua_State *L, int narg, int clear) {
#if ENABLE_FASTCALL_LUA
lua_call(L, 0, 0);
return 0;
#else
int base = lua_gettop(L) - narg; // function index
lua_pushcfunction(L, script__traceback); // push traceback function
lua_insert(L, base); // put it under chunk and args
int status = lua_pcall(L, narg, (clear ? 0 : LUA_MULTRET), base);
script__error(L, status);
lua_remove(L, base); // remove traceback function
if (status != 0) lua_gc(L, LUA_GCCOLLECT, 0); // force gc in case of errors
return status;
#endif
}
void script_bind_function(const char *c_name, void *c_function) {
lua_pushcfunction( L, c_function );
lua_setglobal( L, c_name );
}
void script_call(const char *lua_function) {
lua_getglobal( L, lua_function );
script__call( L, 0, 1 );
}
void script_bind_class(const char *classname, int num, const char **methods, void **functions) {
lua_newtable( L );
for( int i = 0; i < num; ++i) {
lua_pushcfunction( L, functions[i] );
lua_setfield( L, 1, methods[i] );
}
lua_setglobal( L, classname );
}
void script_run(const char *script) {
int ret = luaL_dostring(L, script);
if( ret != LUA_OK ) {
PRINTF("!Script failed to run: %s\n", lua_tostring(L, -1));
lua_pop(L, 1); // pop error message
}
}
void script_runfile(const char *pathfile) {
PRINTF( "Loading script '%s'\n", pathfile );
int loadResult = luaL_loadfile( L, pathfile );
/**/ if( loadResult == LUA_OK ) {
script__call( L, 0, 1 );
}
else if( loadResult == LUA_ERRSYNTAX ) {
PRINTF("!Script failed to load (LUA_ERRSYNTAX, '%s'): %s\n", lua_tostring( L, 1 ), pathfile );
// lua_pop(L, 1); // pop error message
}
else if( loadResult == LUA_ERRMEM ) {
PRINTF("!Script failed to load (LUA_ERRMEM): %s\n", pathfile);
}
else {
PRINTF("!Script failed to load: %s\n", pathfile );
}
}
// syntax sugars
/* usage:
int window_create_lua(lua *L) {
window_create(arg_float(1), arg_int(2));
return_void(0);
}
int window_swap_lua(lua *L) {
int r = window_swap();
return_int(r);
}
*/
#define arg_int(nth) lua_tointeger(L, nth)
#define arg_bool(nth) lua_toboolean(L, nth)
#define arg__Bool(nth) lua_toboolean(L, nth)
#define arg_float(nth) (float)lua_tonumber(L, nth)
#define arg_double(nth) lua_tonumber(L, nth)
#define arg_string(nth) lua_tolstring(L, nth, 0)
#define return_void(x) return ((x), 0)
#define return_bool(x) return (lua_pushboolean(L, x), 1)
#define return__Bool(x) return (lua_pushboolean(L, x), 1)
#define return_int(x) return (lua_pushinteger(L, x), 1)
#define return_float(x) return (lua_pushnumber(L, x), 1)
#define return_double(x) return (lua_pushnumber(L, x), 1)
#define return_string(x) return (lua_pushstring(L, x), 1)
#define WRAP_ALL(...) EXPAND(WRAP_ALL, __VA_ARGS__)
#define WRAP_ALL2(rc, func) int func##_lua(lua*L) { return_##rc(func()); }
#define WRAP_ALL3(rc, func, a1) int func##_lua(lua*L) { return_##rc(func(arg_##a1(1))); }
#define WRAP_ALL4(rc, func, a1,a2) int func##_lua(lua*L) { return_##rc(func(arg_##a1(1),arg_##a2(2))); }
#define BIND_ALL(...) EXPAND(BIND_ALL, __VA_ARGS__);
#define BIND_ALL2(rc,func) script_bind_function(#func, func##_lua)
#define BIND_ALL3(rc,func,a1) script_bind_function(#func, func##_lua)
#define BIND_ALL4(rc,func,a1,a2) script_bind_function(#func, func##_lua)
#define XMACRO(X) /* @fixme: add all remaining V4K functions */ \
X(bool, window_create, float, int ) \
X(bool, window_swap ) \
X(void, ddraw_grid, float ) \
X(bool, ui_panel, string, int ) \
X(bool, ui_notify, string, string ) \
X(void, ui_panel_end )
XMACRO(WRAP_ALL)
void script_quit(void) {
if( L ) {
lua_close(L);
L = 0;
}
}
void script_init() {
if( !L ) {
// v4k_init();
// initialize Lua
L = lua_newstate(script__realloc, 0); // L = luaL_newstate();
// load various Lua libraries
luaL_openlibs(L);
luaopen_base(L);
luaopen_table(L);
luaopen_io(L);
luaopen_string(L);
luaopen_math(L);
#if !is(ems)
// enable ffi (via luaffi)
luaopen_ffi(L);
#endif
// @fixme: workaround that prevents script binding on lua 5.4.3 on top of luajit 2.1.0-beta3 on linux. lua_setglobal() crashing when accessing null L->l_G
if(L->l_G) {
XMACRO(BIND_ALL);
}
atexit(script_quit);
}
}
bool script_tests() {
// script test (lua)
script_run( "-- Bye.lua\nio.write(\"script test: Bye world!, from \", _VERSION, \"\\n\")" );
return true;
}
#undef XMACRO
// script v2 ------------------------------------------------------------------
#define luaL_dostringsafe(L, str) \
luaL_dostring(L, \
"xpcall(function()\n" \
str \
"end, function(err)\n" \
" print('Error: ' .. tostring(err))\n" \
" print(debug.traceback(nil, 2))\n" \
" if core and core.on_error then\n" \
" pcall(core.on_error, err)\n" \
" end\n" \
" os.exit(1)\n" \
"end)" \
);
static int f_vfs_read(lua_State *L) {
char *file = file_normalize(luaL_checkstring(L, 1));
if( strbegi(file, app_path()) ) file += strlen(app_path());
strswap(file+1, ".", "/");
strswap(file+1, "/lua", ".lua");
int len; char *data = vfs_load(file, &len);
if( len ) {
data = memcpy(MALLOC(len+1), data, len), data[len] = 0;
//tty_color(data ? GREEN : RED);
//printf("%s (%s)\n%s", file, data ? "ok" : "failed", data);
//tty_color(0);
}
return lua_pushstring(L, data), 1; // "\n\tcannot find `%s` within mounted zipfiles", file), 1;
}
// add our zip loader at the end of package.loaders
void lua_add_ziploader(lua_State* L) {
lua_pushcfunction( L, f_vfs_read );
lua_setglobal( L, "vfs_read" );
luaL_dostringsafe(L,
// "package.path = [[;<?>;<<?.lua>>;]]\n" // .. package.path\n"
"package.searchers[#package.searchers + 1] = function(libraryname)\n"
" for pattern in string.gmatch( package.path, '[^;]+' ) do\n"
" local proper_path = string.gsub(pattern, '?', libraryname)\n"
" local f = vfs_read(proper_path)\n"
" if f ~= nil then\n"
" return load(f, proper_path)\n"
" end\n"
" end\n"
" return nil\n"
"end\n"
);
}
void *script_init_env(unsigned flags) {
if( flags & SCRIPT_LUA ) {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
if( flags & SCRIPT_DEBUGGER ) {
// Register debuggers/inspectors
// luaL_dostringsafe(L, "I = require('inspect').inspect\n");
dbg_setup_default(L);
}
lua_add_ziploader(L);
return L;
}
return 0;
}
bool script_push(void *env) {
array_push(Ls, L = env);
return true;
}
bool script_pop() {
L = array_count(Ls) && (array_pop(Ls), array_count(Ls)) ? *array_back(Ls) : NULL;
return !!array_count(Ls);
}
#line 0
#line 1 "v4k_file.c"
// -----------------------------------------------------------------------------
// file
#if 0 // ifdef _WIN32
#include <winsock2.h>
#if is(tcc)
#define CP_UTF8 65001
int WINAPI MultiByteToWideChar();
int WINAPI WideCharToMultiByte();
#endif
// widen() ? string1252() ? string16() ? stringw() ?
wchar_t *widen(const char *utf8) { // wide strings (win only)
int chars = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
char *buf = va("%.*s", (int)(chars * sizeof(wchar_t)), "");
return MultiByteToWideChar(CP_UTF8, 0, utf8, -1, (void*)buf, chars), (wchar_t *)buf;
}
#define open8(path,mode) ifdef(win, _wopen(widen(path)) , open(path, mode) )
#define fopen8(path,mode) ifdef(win, _wfopen(widen(path),widen(mode)) , fopen(path,mode) )
#define remove8(path) ifdef(win, _wremove(widen(path)) , remove(path) )
#define rename8(path) ifdef(win, _wrename(widen(path)) , rename(path) )
#define stat8(path,st) ifdef(win, _wstat(widen(path),st) , stat(path,st) ) // _stati64()
#define stat8_t ifdef(win, _stat , stat_t ) // struct _stati64
#endif
char *file_name(const char *pathfile) {
char *s = strrchr(pathfile, '/'), *t = strrchr(pathfile, '\\');
return va("%s", s > t ? s+1 : t ? t+1 : pathfile);
}
char *file_base(const char *pathfile) {
char *s = file_name(pathfile);
char *e = file_ext(pathfile);
return s[ strlen(s) - strlen(e) ] = '\0', s;
}
char *file_pathabs( const char *pathfile ) {
char *out = va("%*.s", DIR_MAX+1, "");
#if is(win32)
_fullpath(out, pathfile, DIR_MAX);
#else
realpath(pathfile, out);
#endif
return out;
}
char *file_path(const char *pathfile) {
return va("%.*s", (int)(strlen(pathfile)-strlen(file_name(pathfile))), pathfile);
}
char *file_load(const char *filename, int *len) { // @todo: 32 counters/thread enough?
static __thread array(char) buffer[32] = {0}, *invalid[1];
static __thread unsigned i = 0; i = (i+1) % 32;
FILE *fp = filename[0] ? fopen(filename, "rb") : NULL;
if( fp ) {
fseek(fp, 0L, SEEK_END);
size_t sz = ftell(fp);
fseek(fp, 0L, SEEK_SET);
array_resize(buffer[i], sz+1);
sz *= fread(buffer[i],sz,1,fp) == 1;
buffer[i][sz] = 0;
if(len) *len = (int)sz;
fclose(fp);
return buffer[i]; // @fixme: return 0 on error instead?
}
if (len) *len = 0;
return 0;
}
char *file_read(const char *filename) { // @todo: fix leaks
return file_load(filename, NULL);
}
bool file_write(const char *name, const void *ptr, int len) {
bool ok = 0;
for( FILE *fp = name && ptr && len >= 0 ? fopen(name, "wb") : NULL; fp; fclose(fp), fp = 0) {
ok = fwrite(ptr, len,1, fp) == 1;
}
return ok;
}
bool file_append(const char *name, const void *ptr, int len) {
bool ok = 0;
for( FILE *fp = name && ptr && len >= 0 ? fopen(name, "a+b") : NULL; fp; fclose(fp), fp = 0) {
ok = fwrite(ptr, len,1, fp) == 1;
}
return ok;
}
static // not exposed
bool file_stat(const char *fname, struct stat *st) {
// remove ending slashes. win32+tcc does not like them.
int l = strlen(fname), m = l;
while( l && (fname[l-1] == '/' || fname[l-1] == '\\') ) --l;
fname = l == m ? fname : va("%.*s", l, fname);
return stat(fname, st) >= 0;
}
uint64_t file_stamp(const char *fname) {
struct stat st;
return !file_stat(fname, &st) ? 0ULL : st.st_mtime;
}
uint64_t file_stamp10(const char *fname) {
time_t mtime = (time_t)file_stamp(fname);
struct tm *ti = localtime(&mtime);
return atoi64(va("%04d%02d%02d%02d%02d%02d",ti->tm_year+1900,ti->tm_mon+1,ti->tm_mday,ti->tm_hour,ti->tm_min,ti->tm_sec));
}
uint64_t file_size(const char *fname) {
struct stat st;
return !file_stat(fname, &st) ? 0ULL : st.st_size;
}
bool file_directory( const char *pathfile ) {
struct stat st;
return !file_stat(pathfile, &st) ? 0 : S_IFDIR == ( st.st_mode & S_IFMT );
}
bool file_exist(const char *fname) {
struct stat st;
return !file_stat(fname, &st) ? false : true;
}
char *file_normalize(const char *name) {
char *copy = va("%s", name), *s = copy, c;
#if is(win32)
for( int i = 0; copy[i]; ++i ) { if(copy[i] == '/') copy[i] = '\\'; if(copy[i] == '\'') copy[i] = '\"'; }
#else
for( int i = 0; copy[i]; ++i ) { if(copy[i] == '\\') copy[i] = '/'; if(copy[i] == '\"') copy[i] = '\''; }
#endif
return copy;
}
#if 0
char *file_normalize(const char *name) {
char *copy = va("%s", name), *s = copy, c;
// lowercases+digits+underscores+slashes only. anything else is truncated.
for( ; *name ; ++name ) {
/**/ if( *name >= 'a' && *name <= 'z' ) *s++ = *name;
else if( *name >= 'A' && *name <= 'Z' ) *s++ = *name - 'A' + 'a';
else if( *name >= '0' && *name <= '9' ) *s++ = *name;
else if( *name == '/' || *name == '\\') *s++ = '/';
else if( *name <= ' ' || *name == '.' ) *s++ = '_';
} *s++ = 0;
// remove dupe slashes
for( name = s = copy, c = '/'; *name ; ) {
while( *name && *name != c ) *s++ = *name++;
if( *name ) *s++ = c;
while( *name && *name == c ) name++;
} *s++ = 0;
// remove dupe underlines
for( name = s = copy, c = '_'; *name ; ) {
while( *name && *name != c ) *s++ = *name++;
if( *name ) *s++ = c;
while( *name && *name == c ) name++;
} *s++ = 0;
return copy;
}
char *file_normalize_with_folder(const char *name) {
char *s = file_normalize(name);
char *slash = strrchr(s, '/'); if(slash) *slash = 0;
char *penultimate = strrchr(s, '/'); if(slash) *slash = '/';
return penultimate ? penultimate+1 : /*slash ? slash+1 :*/ s;
}
#endif
char *file_ext(const char *name) {
char *b = file_name(name), *s = strchr(b, '.'); //strrchr(name, '.');
return va("%s", s ? s : "" ); // , name );
}
char *file_id(const char *pathfile) {
char *dir = file_path(pathfile); for(int i=0;dir[i];++i) dir[i]=tolower(dir[i]);
char *base = file_name(pathfile); for(int i=0;base[i];++i) base[i]=tolower(base[i]);
#if 0 // extensionless, larry.mid and larry.txt will collide, diffuse.tga and diffuse.png will match
char *ext = strchr(base, '.'); if (ext) ext[0] = '\0'; // remove all extensions
#else // extensionless for audio/images only (materials: diffuse.tga and diffuse.png will match)
char *ext = strrchr(base, '.'); //if (ext) ext[0] = '\0'; // remove all extensions
if(ext) if( strstr(".jpg.png.bmp.tga.hdr"".", ext) || strstr(".ogg.mp3.wav.mod.xm.flac"".", ext) || strstr(".mp4.ogv.avi.mkv.wmv.mpg.mpeg"".", ext) ) {
ext = strchr(base, '.');
ext[0] = '\0'; //strcpy(ext, "_xxx");
}
#endif
// if (!dir[0]) return base;
char *stem = va("%s/%s", dir, base); // file_name(dir);
// /path2/path1/file2_file1 -> file1_file2/path1/path2
int len = 0;
int ids_count = 0;
char ids[64][64] = { 0 };
// split path stems
for each_substring(stem, "/\\@", key) {
int tokens_count = 0;
char* tokens[64] = { 0 };
// split tokens
for each_substring(key, "[]()_ ", it) {
tokens[tokens_count++] = va("%s", it);
}
// sort alphabetically
if( tokens_count > 1 ) qsort(tokens, tokens_count, sizeof(char *), strcmp_qsort);
// concat sorted token1_token2_...
char built[256]; *built = 0;
for( int i = 0; i < tokens_count; ++i ) {
strlcat(built, "_", 256);
strlcat(built, tokens[i], 256);
}
strncpy( ids[ ids_count ], &built[1], 64 );
len += strlen( ids[ ids_count++ ] );
}
// concat in inverse order: file/path1/path2/...
char buffer[DIR_MAX]; buffer[0] = 0;
for( int it = ids_count; --it >= 0; ) {
strcat(buffer, ids[it]);
strcat(buffer, "/");
}
return va("%s", buffer);
}
array(char*) file_list(const char *pathmasks) {
static __thread array(char*) list = 0; // @fixme: add 16 slots
for( int i = 0; i < array_count(list); ++i ) {
FREE(list[i]);
}
array_resize(list, 0);
for each_substring(pathmasks,";",pathmask) {
char *cwd = 0, *masks = 0;
char *slash = strrchr(pathmask, '/');
if( !slash ) cwd = "./", masks = pathmask;
else {
masks = va("%s", slash+1);
cwd = pathmask, slash[1] = '\0';
}
if( !masks[0] ) masks = "*";
ASSERT(strend(cwd, "/"), "Error: dirs like '%s' must end with slash", cwd);
int recurse = strstr(cwd, "**") || strstr(masks, "**");
strswap(cwd, "**", "./");
dir *d = dir_open(cwd, recurse ? "r" : "");
if( d ) {
for( int i = 0; i < dir_count(d); ++i ) {
if( dir_file(d,i) ) {
// dir_name() should return full normalized paths "C:/prj/v4k/demos/art/fx/fxBloom.fs". should exclude system dirs as well
char *entry = dir_name(d,i);
char *fname = file_name(entry);
int allowed = 0;
for each_substring(masks,";",mask) {
allowed |= strmatch(fname, mask);
}
if( !allowed ) continue;
// if( strstr(fname, "/.") ) continue; // @fixme: still needed? useful?
// insert copy
char *copy = STRDUP(entry);
array_push(list, copy);
}
}
dir_close(d);
}
}
array_sort(list, strcmp);
return list;
}
bool file_move(const char *src, const char *dst) {
bool ok = file_exist(src) && !file_exist(dst) && 0 == rename(src, dst);
return ok;
}
bool file_delete(const char *pathfile) {
if( file_exist(pathfile) ) {
for( int i = 0; i < 10; ++i ) {
bool ok = 0 == unlink(pathfile);
if( ok ) return true;
sleep_ms(10);
}
return false;
}
return true;
}
bool file_copy(const char *src, const char *dst) {
int ok = 0, BUFSIZE = 1 << 20; // 1 MiB
static __thread char *buffer = 0; do_once buffer = REALLOC(0, BUFSIZE); // @leak
for( FILE *in = fopen(src, "rb"); in; fclose(in), in = 0) {
for( FILE *out = fopen(dst, "wb"); out; fclose(out), out = 0, ok = 1) {
for( int n; !!(n = fread( buffer, 1, BUFSIZE, in )); ){
if(fwrite( buffer, 1, n, out ) != n)
return fclose(in), fclose(out), false;
}
}
}
return ok;
}
char* file_tempname() {
static __thread int id;
return va("%s/v4k-temp.%s.%p.%d", app_temp(), getenv(ifdef(win32, "username", "USER")), &id, rand());
}
FILE *file_temp(void) {
const char *fname = file_tempname();
FILE *fp = fopen(fname, "w+b");
if( fp ) unlink(fname);
return fp;
}
char *file_counter(const char *name) {
static __thread char outfile[DIR_MAX], init = 0;
static __thread map(char*, int) ext_counters;
if(!init) map_init(ext_counters, less_str, hash_str), init = '\1';
char *base = va("%s",name), *ext = file_ext(name);
if(ext && ext[0]) *strstr(base, ext) = '\0';
int *counter = map_find_or_add(ext_counters, ext, 0);
while( *counter >= 0 ) {
*counter = *counter + 1;
sprintf(outfile, "%s(%03d)%s", base, *counter, ext);
if( !file_exist(outfile) ) {
return va("%s", outfile);
}
}
return 0;
}
enum { MD5_HASHLEN = 16 };
enum { SHA1_HASHLEN = 20 };
enum { CRC32_HASHLEN = 4 };
void* file_sha1(const char *file) { // 20bytes
hash_state hs = {0};
sha1_init(&hs);
for( FILE *fp = fopen(file, "rb"); fp; fclose(fp), fp = 0) {
char buf[8192];
for( int inlen; (inlen = sizeof(buf) * fread(buf, sizeof(buf), 1, fp)); ) {
sha1_process(&hs, (const unsigned char *)buf, inlen);
}
}
unsigned char *hash = va("%.*s", SHA1_HASHLEN, "");
sha1_done(&hs, hash);
return hash;
}
void* file_md5(const char *file) { // 16bytes
hash_state hs = {0};
md5_init(&hs);
for( FILE *fp = fopen(file, "rb"); fp; fclose(fp), fp = 0) {
char buf[8192];
for( int inlen; (inlen = sizeof(buf) * fread(buf, sizeof(buf), 1, fp)); ) {
md5_process(&hs, (const unsigned char *)buf, inlen);
}
}
unsigned char *hash = va("%.*s", MD5_HASHLEN, "");
md5_done(&hs, hash);
return hash;
}
void* file_crc32(const char *file) { // 4bytes
unsigned crc = 0;
for( FILE *fp = fopen(file, "rb"); fp; fclose(fp), fp = 0) {
char buf[8192];
for( int inlen; (inlen = sizeof(buf) * fread(buf, sizeof(buf), 1, fp)); ) {
crc = zip__crc32(crc, buf, inlen); // unsigned int stbiw__crc32(unsigned char *buffer, int len)
}
}
unsigned char *hash = va("%.*s", (int)sizeof(crc), "");
memcpy(hash, &crc, sizeof(crc));
return hash;
}
#if 0
void* crc32_mem(const void *ptr, int inlen) { // 4bytes
unsigned hash = 0;
hash = zip__crc32(hash, ptr, inlen); // unsigned int stbiw__crc32(unsigned char *buffer, int len)
return hash;
}
void* md5_mem(const void *ptr, int inlen) { // 16bytes
hash_state hs = {0};
md5_init(&hs);
md5_process(&hs, (const unsigned char *)ptr, inlen);
unsigned char *hash = va("%.*s", MD5_HASHLEN, "");
md5_done(&hs, hash);
return hash;
}
void* sha1_mem(const void *ptr, int inlen) { // 20bytes
hash_state hs = {0};
sha1_init(&hs);
sha1_process(&hs, (const unsigned char *)ptr, inlen);
unsigned char *hash = va("%.*s", SHA1_HASHLEN, "");
sha1_done(&hs, hash);
return hash;
}
unsigned crc32_mem(unsigned h, const void *ptr_, unsigned len) {
// based on public domain code by Karl Malbrain
const uint8_t *ptr = (const uint8_t *)ptr_;
if (!ptr) return 0;
const unsigned tbl[16] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };
for(h = ~h; len--; ) { uint8_t b = *ptr++; h = (h >> 4) ^ tbl[(h & 15) ^ (b & 15)]; h = (h >> 4) ^ tbl[(h & 15) ^ (b >> 4)]; }
return ~h;
}
uint64_t crc64_mem(uint64_t h, const void *ptr, uint64_t len) {
// based on public domain code by Lasse Collin
// also, use poly64 0xC96C5795D7870F42 for crc64-ecma
static uint64_t crc64_table[256];
static uint64_t poly64 = UINT64_C(0x95AC9329AC4BC9B5);
if( poly64 ) {
for( int b = 0; b < 256; ++b ) {
uint64_t r = b;
for( int i = 0; i < 8; ++i ) {
r = r & 1 ? (r >> 1) ^ poly64 : r >> 1;
}
crc64_table[ b ] = r;
//printf("%016llx\n", crc64_table[b]);
}
poly64 = 0;
}
const uint8_t *buf = (const uint8_t *)ptr;
uint64_t crc = ~h; // ~crc;
while( len != 0 ) {
crc = crc64_table[(uint8_t)crc ^ *buf++] ^ (crc >> 8);
--len;
}
return ~crc;
}
// https://en.wikipedia.org/wiki/MurmurHash
static inline uint32_t murmur3_scramble(uint32_t k) {
return k *= 0xcc9e2d51, k = (k << 15) | (k >> 17), k *= 0x1b873593;
}
uint32_t murmur3_mem(const uint8_t* key, size_t len, uint32_t seed) {
uint32_t h = seed;
uint32_t k;
/* Read in groups of 4. */
for (size_t i = len >> 2; i; i--) {
// Here is a source of differing results across endiannesses.
// A swap here has no effects on hash properties though.
k = *((uint32_t*)key);
key += sizeof(uint32_t);
h ^= murmur3_scramble(k);
h = (h << 13) | (h >> 19);
h = h * 5 + 0xe6546b64;
}
/* Read the rest. */
k = 0;
for (size_t i = len & 3; i; i--) {
k <<= 8;
k |= key[i - 1];
}
// A swap is *not* necessary here because the preceeding loop already
// places the low bytes in the low places according to whatever endianness
// we use. Swaps only apply when the memory is copied in a chunk.
h ^= murmur3_scramble(k);
/* Finalize. */
h ^= len;
h ^= h >> 16;
h *= 0x85ebca6b;
h ^= h >> 13;
h *= 0xc2b2ae35;
h ^= h >> 16;
return h;
}
#endif
// -----------------------------------------------------------------------------
// storage (emscripten only)
void storage_mount(const char* folder) {
#if is(ems)
emscripten_run_script(va("FS.mkdir('%s'); FS.mount(IDBFS, {}, '%s');", folder, folder));
#else
(void)folder;
#endif
}
void storage_read() {
#if is(ems)
EM_ASM(
/* sync from persisted state into memory */
FS.syncfs(true, function (err) {
assert(!err);
});
);
#endif
}
void storage_flush() {
#if is(ems)
EM_ASM(
FS.syncfs(false, function (err) {
assert(!err);
});
);
#endif
}
// -----------------------------------------------------------------------------
// compressed archives
// return list of files inside zipfile
array(char*) file_zip_list(const char *zipfile) {
static __thread array(char*) list[16] = {0};
static __thread int count = 0;
count = (count+1) % 16;
array_resize(list[count], 0);
for( zip *z = zip_open(zipfile, "rb"); z; zip_close(z), z = 0) {
for( unsigned i = 0; i < zip_count(z); ++i ) {
array_push( list[count], zip_name(z, i) );
}
}
return list[count];
}
// extract single file content from zipfile
array(char) file_zip_extract(const char *zipfile, const char *filename) {
static __thread array(char) list[16] = {0};
static __thread int count = 0;
array(char) out = list[count = (count+1) % 16];
array_resize(out, 0);
for( zip *z = zip_open(zipfile, "rb"); z; zip_close(z), z = 0) {
int index = zip_find(z, filename); // convert entry to index. returns <0 if not found.
if( index < 0 ) return zip_close(z), out;
unsigned outlen = zip_size(z, index);
unsigned excess = zip_excess(z, index);
array_resize(out, outlen + 1 + excess);
unsigned ret = zip_extract_inplace(z, index, out, array_count(out));
if(ret) { out[outlen] = '\0'; array_resize(out, outlen); } else { array_resize(out, 0); }
}
return out;
}
// append single file into zipfile. compress with DEFLATE|6. Other compressors are also supported (try LZMA|5, ULZ|9, LZ4X|3, etc.)
bool file_zip_append(const char *zipfile, const char *filename, int clevel) {
bool ok = false;
for( zip *z = zip_open(zipfile, "a+b"); z; zip_close(z), z = 0) {
for( FILE *fp = fopen(filename, "rb"); fp; fclose(fp), fp = 0) {
ok = zip_append_file(z, filename, "", fp, clevel);
}
}
return ok;
}
// append mem blob into zipfile. compress with DEFLATE|6. Other compressors are also supported (try LZMA|5, ULZ|9, LZ4X|3, etc.)
// @fixme: implement zip_append_mem() and use that instead
bool file_zip_appendmem(const char *zipfile, const char *entryname, const void *ptr, unsigned len, int clevel) {
bool ok = false;
if( ptr )
for( zip *z = zip_open(zipfile, "a+b"); z; zip_close(z), z = 0) {
ok = zip_append_mem(z, entryname, "", ptr, len, clevel);
}
return ok;
}
// -----------------------------------------------------------------------------
// archives
enum { is_zip, is_tar, is_pak, is_dir };
typedef struct archive_dir {
char* path;
union {
int type;
int size; // for cache only
};
union {
void *archive;
void *data; // for cache only
zip* zip_archive;
tar* tar_archive;
pak* pak_archive;
};
struct archive_dir *next;
} archive_dir;
static archive_dir *dir_mount;
static archive_dir *dir_cache;
#ifndef MAX_CACHED_FILES // @todo: should this be MAX_CACHED_SIZE (in MiB) instead?
#define MAX_CACHED_FILES 32 // @todo: should we cache the cooked contents instead? ie, stbi() result instead of file.png?
#endif
struct vfs_entry {
const char *name;
const char *id;
unsigned size;
};
static array(struct vfs_entry) vfs_hints; // mounted raw assets
static array(struct vfs_entry) vfs_entries; // mounted cooked assets
static bool vfs_mount_hints(const char *path);
void vfs_reload() {
const char *app = app_name();
array_resize(vfs_hints, 0); // @leak
array_resize(vfs_entries, 0); // @leak
// mount virtual filesystems later (mounting order matters: low -> to -> high priority)
#if defined(EMSCRIPTEN)
vfs_mount("index.zip");
#else
// mount fused executables
vfs_mount(va("%s%s%s", app_path(), app_name(), ifdef(win32, ".exe", "")));
// mount all zipfiles
for each_array( file_list("*.zip"), char*, file ) vfs_mount(file);
#endif
// vfs_resolve() will use these art_folder locations as hints when cook-on-demand is in progress.
// cook-on-demand will not be able to resolve a virtual pathfile if there are no cooked assets on disk,
// unless there is a record of what's actually on disk somewhere, and that's where the hints belong to.
if( COOK_ON_DEMAND )
for each_substring(ART,",",art_folder) {
vfs_mount_hints(art_folder);
}
}
#define ARK1 0x41724B31 // 'ArK1' in le, 0x314B7241 41 72 4B 31 otherwise
#define ARK1_PADDING (512 - 40) // 472
#define ARK_PRINTF(f,...) 0 // printf(f,__VA_ARGS__)
#define ARK_SWAP32(x) (x)
#define ARK_SWAP64(x) (x)
#define ARK_REALLOC REALLOC
static uint64_t ark_fget64( FILE *in ) { uint64_t v; fread( &v, 8, 1, in ); return ARK_SWAP64(v); }
void ark_list( const char *infile, zip **z ) {
for( FILE *in = fopen(infile, "rb"); in; fclose(in), in = 0 )
while(!feof(in)) {
if( 0 != (ftell(in) % ARK1_PADDING) ) fseek(in, ARK1_PADDING - (ftell(in) % ARK1_PADDING), SEEK_CUR);
ARK_PRINTF("Reading at #%d\n", (int)ftell(in));
uint64_t mark = ark_fget64(in);
if( mark != ARK1 ) continue;
uint64_t stamp = ark_fget64(in);
uint64_t datalen = ark_fget64(in);
uint64_t datahash = ark_fget64(in);
uint64_t namelen = ark_fget64(in);
*z = zip_open_handle(in, "rb");
return;
}
}
static
bool vfs_mount_(const char *path, array(struct vfs_entry) *entries) {
const char *path_bak = path;
zip *z = NULL; tar *t = NULL; pak *p = NULL; dir *d = NULL;
int is_folder = ('/' == path[strlen(path)-1]);
if( is_folder ) d = dir_open(path, "rb");
if( is_folder && !d ) return 0;
if( !is_folder ) z = zip_open(path, "rb");
if( !is_folder && !z ) t = tar_open(path, "rb");
if( !is_folder && !z && !t ) p = pak_open(path, "rb");
if( !is_folder && !z && !t && !p ) ark_list(path, &z); // last resort. try as .ark
if( !is_folder && !z && !t && !p ) return 0;
// normalize input -> "././" to ""
while (path[0] == '.' && path[1] == '/') path += 2;
path = STRDUP(path);
if( z || t || p ) {
// save local path for archives, so we can subtract those from upcoming requests
if(strrchr(path,'/')) strrchr(path,'/')[1] = '\0';
} else if(d) 0[(char*)path] = 0;
// append to mounted archives
archive_dir *prev = dir_mount, zero = {0};
*(dir_mount = REALLOC(0, sizeof(archive_dir))) = zero;
dir_mount->next = prev;
dir_mount->path = (char*)path;
dir_mount->archive = z ? (void*)z : t ? (void*)t : p ? (void*)p : (void*)d;
dir_mount->type = is_folder ? is_dir : z ? is_zip : t ? is_tar : p ? is_pak : -1;
ASSERT(dir_mount->type >= 0 && dir_mount->type < 4);
// append list of files to internal listing
for( archive_dir *dir = dir_mount; dir ; dir = 0 ) { // for(archive_dir *dir = dir_mount; dir; dir = dir->next) {
assert(dir->type >= 0 && dir->type < 4);
unsigned (*fn_count[4])(void*) = {(void*)zip_count, (void*)tar_count, (void*)pak_count, (void*)dir_count};
char* (*fn_name[4])(void*, unsigned index) = {(void*)zip_name, (void*)tar_name, (void*)pak_name, (void*)dir_name};
unsigned (*fn_size[4])(void*, unsigned index) = {(void*)zip_size, (void*)tar_size, (void*)pak_size, (void*)dir_size};
for( unsigned idx = 0, end = fn_count[dir->type](dir->archive); idx < end; ++idx ) {
assert(idx < end);
const char *filename = STRDUP( fn_name[dir->type](dir->archive, idx) );
const char *fileid = STRDUP( file_id(filename) );
unsigned filesize = fn_size[dir->type](dir->archive, idx);
// printf("%u) %s %u [%s]\n", idx, filename, filesize, fileid);
// append to list
array_push(*entries, (struct vfs_entry){filename, fileid, filesize});
}
// PRINTF("Mounted VFS volume '%s' (%u entries)\n", path_bak, fn_count[dir->type](dir->archive) );
}
return 1;
}
static
bool vfs_mount_hints(const char *path) {
return vfs_mount_(path, &vfs_hints);
}
bool vfs_mount(const char *path) {
return vfs_mount_(path, &vfs_entries);
}
array(char*) vfs_list(const char *masks) {
static __thread array(char*) list = 0; // @fixme: add 16 slots
for( int i = 0; i < array_count(list); ++i ) {
FREE(list[i]);
}
array_resize(list, 0);
for each_substring(masks,";",it) {
if( COOK_ON_DEMAND ) // edge case: any game using only vfs api + cook-on-demand flag will never find any file
for each_array(file_list(it), char*, item) {
// insert copy
char *copy = STRDUP(item);
array_push(list, copy);
}
it = va("*/%s", it);
// int recurse = !!strstr(it, "**"); // @fixme: support non-recursive
for( unsigned i = 0; i < array_count(vfs_entries); ++i ) {
const char *name = vfs_entries[i].name;
if( strmatch(name, it) ) {
// insert copy
char *copy = STRDUP(name);
array_push(list, copy);
}
}
}
// sort alphabetically then remove dupes
array_sort(list, strcmp);
array_unique(list, strcmp_qsort);
return list;
}
static
char *vfs_unpack(const char *pathfile, int *size) { // must FREE() after use
// @todo: add cache here
char *data = NULL;
for(archive_dir *dir = dir_mount; dir && !data; dir = dir->next) {
if( dir->type == is_dir ) {
#if 0 // sandboxed
char buf[DIR_MAX];
snprintf(buf, sizeof(buf), "%s%s", dir->path, pathfile);
data = file_load(buf, size);
#endif
} else {
int (*fn_find[3])(void *, const char *) = {(void*)zip_find, (void*)tar_find, (void*)pak_find};
void* (*fn_unpack[3])(void *, unsigned) = {(void*)zip_extract, (void*)tar_extract, (void*)pak_extract};
unsigned (*fn_size[3])(void *, unsigned) = {(void*)zip_size, (void*)tar_size, (void*)pak_size};
#if 0
const char* cleanup = pathfile + strbegi(pathfile, dir->path) * strlen(dir->path);
while (cleanup[0] == '/') ++cleanup;
#else
const char *cleanup = pathfile;
#endif
int index = fn_find[dir->type](dir->archive, cleanup);
data = fn_unpack[dir->type](dir->archive, index);
if( size ) *size = fn_size[dir->type](dir->archive, index);
}
// printf("%c trying %s in %s ...\n", data ? 'Y':'N', pathfile, dir->path);
}
//wait_ms(1000); // <-- simulate slow hdd
return data;
}
const char *vfs_resolve(const char *pathfile) {
// we dont resolve absolute paths. they dont belong to the vfs
// if( pathfile[0] == '/' || pathfile[0] == '\\' || pathfile[1] == ':' ) return pathfile;
char* id = file_id(pathfile);
// find best match (vfs_entries first)
for (int i = array_count(vfs_entries); --i >= 0; ) {
if (strbegi(vfs_entries[i].id, id) ) {
return vfs_entries[i].name;
}
}
// find best match (vfs_hints later)
for (int i = array_count(vfs_hints); --i >= 0; ) {
if (strbegi(vfs_hints[i].id, id) ) {
return vfs_hints[i].name;
}
}
return pathfile;
}
char* vfs_load(const char *pathfile, int *size_out) { // @todo: fix leaks, vfs_unpack()
// @fixme: handle \\?\ absolute path (win)
if (!pathfile[0]) return file_load(pathfile, size_out);
while( pathfile[0] == '.' && (pathfile[1] == '/' || pathfile[1] == '\\') ) pathfile += 2;
// if (pathfile[0] == '/' || pathfile[1] == ':') return file_load(pathfile, size_out); // @fixme: handle current cooked /home/V4K or C:/V4K path cases within zipfiles
if( size_out ) *size_out = 0;
if( strend(pathfile, "/") ) return 0; // it's a dir
static __thread map(char*,int) misses = 0, *init = 0; if(!init) init = misses, map_init(misses, less_str, hash_str);
int *found = map_find_or_add_allocated_key(misses, STRDUP(pathfile), -1); // [-1]non-init,[false]could not cook,[true]cooked
if( found && *found == 0 ) {
return 0;
}
//{
// exclude garbage from material names
// @todo: exclude double slashs in paths
char *base = file_name(pathfile); if(strchr(base,'+')) base = strchr(base, '+')+1;
if(base[0] == '\0') return 0; // it's a dir
char *folder = file_path(pathfile);
pathfile = va("%s%s", folder, base);
// solve virtual path
pathfile = va("%s", vfs_resolve(pathfile));
base = file_name(pathfile);
if(base[0] == '\0') return 0; // it's a dir
folder = file_path(pathfile);
// ease folders reading by shortening them: /home/rlyeh/prj/v4k/art/demos/audio/coin.wav -> demos/audio/coin.wav
// or C:/prj/v4k/engine/art/fonts/B612-BoldItalic.ttf -> fonts/B612-BoldItalic.ttf
static __thread array(char*) art_paths = 0;
if(!art_paths) for each_substring(ART,",",stem) array_push(art_paths, STRDUP(stem));
char* pretty_folder = "";
if( folder ) for( int i = 0; i < array_count(art_paths); ++i ) {
if( strbeg(folder, art_paths[i]) ) { pretty_folder = folder + strlen(art_paths[i]); break; }
}
//}
int size = 0;
void *ptr = 0;
#if 0
// clean pathfile
while (pathfile[0] == '.' && pathfile[1] == '/') pathfile += 2;
while (pathfile[0] == '/') ++pathfile;
#endif
const char *lookup_id = /*file_normalize_with_folder*/(pathfile);
// search (last item)
static __thread char last_item[256] = { 0 };
static __thread void *last_ptr = 0;
static __thread int last_size = 0;
if( !strcmpi(lookup_id, last_item)) {
ptr = last_ptr;
size = last_size;
}
// search (cache)
if( !ptr && !is(osx) ) { // @todo: remove silicon mac M1 hack
ptr = cache_lookup(lookup_id, &size);
}
if( ptr ) {
PRINTF("Loading VFS (%s)%s (cached)\n", pretty_folder, base);
} else {
PRINTF("Loading VFS (%s)%s\n", pretty_folder, base);
}
// read cooked asset from mounted disks
if( !ptr ) {
ptr = vfs_unpack(pathfile, &size);
// asset not found? maybe it has not been cooked yet at this point (see --cook-on-demand)
if( !ptr && COOK_ON_DEMAND ) {
static thread_mutex_t mutex, *init = 0; if(!init) thread_mutex_init(init = &mutex);
thread_mutex_lock(&mutex);
// this block saves some boot time (editor --cook-on-demand: boot 1.50s -> 0.90s)
#if 1 // EXPERIMENTAL_DONT_COOK_NON_EXISTING_ASSETS
static set(char*) disk = 0;
if(!disk) { set_init_str(disk); for each_substring(ART,",",art_folder) for each_array(file_list(va("%s**", art_folder)), char*, item) set_insert(disk, STRDUP(item)); } // art_folder ends with '/'
int found = !!set_find(disk, (char*)pathfile);
if( found )
#endif
{
// technically, we should only cook `base` asset at this point. however, cooks on demand can be very
// expensive. not only because we're invoking an external tool/cook app in here, which is scanning all the
// cook folders at every call, but also because there is this expensive vfs_reload() call at end of current scope.
// so, in order to minimize the number of upcoming cook requests, we cook more stuff than needed at this point;
// just in anticipation of what's likely going to happen in the next frames.
// so, when asked to cook "models/model.fbx" we actually:
// - do cook "models/model.* (so, "model.tga","model.png","model.jpg","model.mtl",etc. are also cooked)
// - do cook "models/*.fbx" as well
char *dir = file_path(pathfile + ART_SKIP_ROOT);
char *group1 = dir[0] ? va("\"*/%s%s.*\"", dir, file_base(pathfile)) : base; // -> "*/models/model.*"
char *group2 = dir[0] ? va("\"*/%s*%s\"", dir, file_ext(pathfile)) : ""; // -> "*/models/*.fbx"
char *cmd = va("%scook" ifdef(osx,".osx",ifdef(linux,".linux",".exe"))" %s %s --cook-ini=%s --cook-additive --cook-jobs=1 --quiet", TOOLS, group1, group2, COOK_INI);
// cook groups
int rc = system(cmd); // atoi(app_exec(cmd));
if(rc < 0) PANIC("cannot invoke `%scook` (return code %d)", TOOLS, rc);
vfs_reload(); // @todo: optimize me. it is waaay inefficent to reload the whole VFS layout after cooking a single asset
}
thread_mutex_unlock(&mutex);
// finally, try again
pathfile = va("%s", vfs_resolve(pathfile));
ptr = vfs_unpack(pathfile, &size);
}
if( ptr ) {
cache_insert(lookup_id, ptr, size);
}
}
if( ptr && size )
if( ptr != last_ptr) {
snprintf(last_item, 256, "%s", lookup_id);
last_ptr = ptr;
last_size = size;
}
// yet another last resort: redirect vfs_load() calls to file_load()
// (for environments without tools or cooked assets)
if(!ptr) {
if( !have_tools() ) {
ptr = file_load(pathfile, size_out);
}
}
if(!ptr) {
PRINTF("Loading %s (not found)\n", pathfile);
}
*found = ptr ? true : false;
if( size_out ) *size_out = ptr ? size : 0;
return ptr;
}
char* vfs_read(const char *pathfile) {
return vfs_load(pathfile, NULL);
}
int vfs_size(const char *pathfile) {
int sz;
return vfs_load(pathfile, &sz), sz;
}
FILE* vfs_handle(const char *pathfile) {
// @fixme: non-compressible assets (adpcm wavs,mp3,ogg,mp4,avi,...) are taking a suboptimal code path here.
// no need to unzip them. just seek within the zipfile and return the *fp at that position
int sz;
char *buf = vfs_load(pathfile, &sz);
FILE *fp = fmemopen(buf ? buf : "", buf ? sz : 0, "r+b");
ASSERT( fp, "cannot create tempfile" );
return fp;
}
#if 0
const char *vfs_extract(const char *pathfile) { // extract a vfs file into the local (temp) filesystem
#if 0
FILE* fp = vfs_handle(pathfile);
return fp ? pathfile_from_handle(fp) : "";
#else
int sz;
char *buf = vfs_load(pathfile, &sz);
if( !buf ) return "";
// pool of temp files. recycles after every loop
enum { MAX_TEMP_FILES = 16 };
static __thread char temps[MAX_TEMP_FILES][DIR_MAX] = {0};
static __thread int i = 0;
if( temps[i][0] ) unlink(temps[i]);
i = (i + 1) % MAX_TEMP_FILES;
if(!temps[i][0] ) snprintf(temps[i], DIR_MAX, "%s", file_tempname());
char *name = temps[i];
FILE *tmp = fopen(name, "wb"); //unlink(name);
ASSERT( tmp, "cannot create tempfile %s", name );
fwrite(buf ? buf : "", 1, buf ? sz : 0, tmp);
fclose(tmp);
return name;
#endif
}
#endif
// -----------------------------------------------------------------------------
// cache
static thread_mutex_t cache_mutex; AUTORUN{ thread_mutex_init(&cache_mutex); }
void* cache_lookup(const char *pathfile, int *size) { // find key->value
if( !MAX_CACHED_FILES ) return 0;
void* data = 0;
thread_mutex_lock(&cache_mutex);
for(archive_dir *dir = dir_cache; dir; dir = dir->next) {
if( !strcmp(dir->path, pathfile) ) {
if(size) *size = dir->size;
data = dir->data;
break;
}
}
thread_mutex_unlock(&cache_mutex);
return data;
}
void* cache_insert(const char *pathfile, void *ptr, int size) { // append key/value; return LRU or NULL
if( !MAX_CACHED_FILES ) return 0;
if( !ptr || !size ) return 0;
// keep cached files within limits
thread_mutex_lock(&cache_mutex);
// append to cache
archive_dir zero = {0}, *old = dir_cache;
*(dir_cache = REALLOC(0, sizeof(archive_dir))) = zero;
dir_cache->next = old;
dir_cache->path = STRDUP(pathfile);
dir_cache->size = size;
dir_cache->data = REALLOC(0, size+1);
memcpy(dir_cache->data, ptr, size); size[(char*)dir_cache->data] = 0; // copy+terminator
void *found = 0;
static int added = 0;
if( added < MAX_CACHED_FILES ) {
++added;
} else {
// remove oldest cache entry
for( archive_dir *prev = dir_cache, *dir = prev; dir ; prev = dir, dir = dir->next ) {
if( !dir->next ) {
prev->next = 0; // break link
found = dir->data;
dir->path = REALLOC(dir->path, 0);
dir->data = REALLOC(dir->data, 0);
dir = REALLOC(dir, 0);
break;
}
}
}
thread_mutex_unlock(&cache_mutex);
return found;
}
// ----------------------------------------------------------------------------
// ini
/* ini+, extended ini format
// - rlyeh, public domain
//
// # spec
//
// ; line comment
// [user] ; map section name (optional)
// name=john ; key and value (mapped here as user.name=john)
// +surname=doe jr. ; sub-key and value (mapped here as user.name.surname=doe jr.)
// age=30 ; numbers
// color=240 ; key and value \
// color=253 ; key and value |> array: color[0], color[1] and color[2]
// color=255 ; key and value /
// color= ; remove key/value(s)
// color=white ; recreate key; color[1] and color[2] no longer exist
// [] ; unmap section
// -note=keys may start with symbols (except plus and semicolon)
// -note=linefeeds are either \r, \n or \r\n.
// -note=utf8 everywhere.
*/
static
char *ini_parse( const char *s ) {
char *map = 0;
int mapcap = 0, maplen = 0;
enum { DEL, REM, TAG, KEY, SUB, VAL } fsm = DEL;
const char *cut[6] = {0}, *end[6] = {0};
while( *s ) {
while( *s && (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n') ) ++s;
/**/ if( *s == ';' ) cut[fsm = REM] = ++s;
else if( *s == '[' ) cut[fsm = TAG] = ++s;
else if( *s == '+' ) cut[fsm = SUB] = ++s;
else if( *s == '=' ) cut[fsm = VAL] = ++s;
else if( *s > ' ' && *s <= 'z' && *s != ']' ) cut[fsm = KEY] = cut[SUB] = end[SUB] = s;
else { if( *s ) ++s; continue; }
/**/ if( fsm == REM ) { while(*s && *s != '\r'&& *s != '\n') ++s; }
else if( fsm == TAG ) { while(*s && *s != '\r'&& *s != '\n'&& *s != ']') ++s; end[TAG] = s; }
else if( fsm == KEY ) { while(*s && *s > ' ' && *s <= 'z' && *s != '=') ++s; end[KEY] = s; }
else if( fsm == SUB ) { while(*s && *s > ' ' && *s <= 'z' && *s != '=') ++s; end[SUB] = s; }
else if( fsm == VAL ) { while(*s && *s >= ' ' && *s <= 127 && *s != ';') ++s; end[VAL] = s;
while( end[VAL][-1] <= ' ' ) { --end[VAL]; }
char buf[256] = {0}, *key = buf;
if( end[TAG] - cut[TAG] ) key += sprintf(key, "%.*s.", (int)(end[TAG] - cut[TAG]), cut[TAG] );
if( end[KEY] - cut[KEY] ) key += sprintf(key, "%.*s", (int)(end[KEY] - cut[KEY]), cut[KEY] );
if( end[SUB] - cut[SUB] ) key += sprintf(key, ".%.*s", (int)(end[SUB] - cut[SUB]), cut[SUB] );
int reqlen = (key - buf) + 1 + (end[VAL] - cut[VAL]) + 1 + 1;
if( (reqlen + maplen) >= mapcap ) map = REALLOC( map, mapcap += reqlen + 512 );
sprintf( map + maplen, "%.*s%c%.*s%c%c", (int)(key - buf), buf, 0, (int)(end[VAL] - cut[VAL]), cut[VAL], 0, 0 );
maplen += reqlen - 1;
}
}
return map;
}
// @todo: evaluate alt api
// int count = ini_count(filename);
// char *key = ini_key(filename, id);
// char *val = ini_val(filename, id);
void ini_destroy(ini_t x) {
for each_map(x, char*, k, char*, v) {
FREE(k);
FREE(v);
}
map_free(x);
}
ini_t ini_from_mem(const char *data) {
if( !data || !data[0] ) return 0;
char *kv = ini_parse(data);
if( !kv ) return 0;
ini_t map = 0;
map_init(map, less_str, hash_str);
for( char *iter = kv; iter[0]; ) {
char *key = iter; while( *iter++ );
char *val = iter; while( *iter++ );
char **found = map_find(map, key);
if( !found ) map_insert(map, STRDUP(key), STRDUP(val));
assert( map_find(map,key) );
}
FREE( kv );
return map;
}
ini_t ini(const char *filename) {
char *kv = file_read(filename);
if(!kv) kv = vfs_read(filename);
return ini_from_mem(kv);
}
bool ini_write(const char *filename, const char *section, const char *key, const char *value) {
// this is a little hacky {
char *data = file_read(filename);
if( data && data[0] ) {
char *begin = strrchr(data, '[');
char *end = strrchr(data, ']');
if( begin && end && begin < end ) {
char *last_section = va("%.*s", (int)(end - begin - 1), begin + 1);
if( !strcmpi(last_section, section) ) section = 0;
}
}
// }
char *s = va("%s%s=%s\r\n", section ? va("[%s]\r\n", section) : "", key, value);
return file_append(filename, s, strlen(s));
}
#line 0
#line 1 "v4k_font.c"
// font framework. original code by Vassvik (UNLICENSED)
// - rlyeh, public domain.
//
// [x] embedded default font (bm mini).
// [x] oversampling, texture dimensions.
// [x] utf8, unicode ranges.
// [x] markup opcodes.
// [x] faces (italic, bold, regular, cjk), colors and sizes.
// [x] unicode ranges from dear-imgui (@ocornut allowed to mit-0 relicense the data tables).
// [*] alignment. kinda hacky. revisit some day.
// [ ] underlining, soft/hard shadows, outlines.
// [ ] clip/wrap/overflow regions.
// [ ] text-shaping, text-layout.
// [ ] text-wrapping.
// [ ] optimizations.
//
// ## language families that could be merged on a single texture alias
// - EU+EL+RU
// - AR+HE+RU
// - TH+VI+TW
// - ZH
// - JP
// - KR
// -----------------------------------------------------------------------------
// bm-mini.zip (public domain font)
// http://bitmapmania.m78.com
// cooz@m78.com
static const char bm_mini_ttf[] = {
/*000000*/ 0x00,0x01,0x00,0x00,0x00,0x0e,0x00,0x30,0x00,0x03,0x00,0xb0,0x4f,0x53,0x2f,0x32,
/*000010*/ 0x80,0x00,0x6d,0x88,0x00,0x00,0x4e,0x04,0x00,0x00,0x00,0x4e,0x63,0x6d,0x61,0x70,
/*000020*/ 0xf1,0x89,0xe8,0x81,0x00,0x00,0x45,0x54,0x00,0x00,0x02,0x28,0x63,0x76,0x74,0x20,
/*000030*/ 0x5a,0x9b,0xfa,0x82,0x00,0x00,0x03,0xe8,0x00,0x00,0x00,0x32,0x66,0x70,0x67,0x6d,
/*000040*/ 0x83,0x33,0xc2,0x4f,0x00,0x00,0x03,0xd4,0x00,0x00,0x00,0x14,0x67,0x6c,0x79,0x66,
/*000050*/ 0x05,0xa3,0xba,0x6b,0x00,0x00,0x04,0x64,0x00,0x00,0x3c,0xec,0x68,0x64,0x6d,0x78,
/*000060*/ 0x0d,0xad,0x0b,0x14,0x00,0x00,0x47,0x7c,0x00,0x00,0x06,0x88,0x68,0x65,0x61,0x64,
/*000070*/ 0xce,0xfe,0xc6,0xae,0x00,0x00,0x4e,0x54,0x00,0x00,0x00,0x36,0x68,0x68,0x65,0x61,
/*000080*/ 0x05,0x15,0x02,0xee,0x00,0x00,0x4e,0x8c,0x00,0x00,0x00,0x24,0x68,0x6d,0x74,0x78,
/*000090*/ 0xae,0x38,0x00,0xf4,0x00,0x00,0x42,0xe0,0x00,0x00,0x01,0x8c,0x6c,0x6f,0x63,0x61,
/*0000a0*/ 0x00,0x0b,0xe4,0xba,0x00,0x00,0x41,0x50,0x00,0x00,0x01,0x90,0x6d,0x61,0x78,0x70,
/*0000b0*/ 0x00,0xfe,0x01,0x4d,0x00,0x00,0x4e,0xb0,0x00,0x00,0x00,0x20,0x6e,0x61,0x6d,0x65,
/*0000c0*/ 0xd6,0xe2,0x1a,0x1f,0x00,0x00,0x00,0xec,0x00,0x00,0x02,0xe5,0x70,0x6f,0x73,0x74,
/*0000d0*/ 0x09,0x85,0x09,0xff,0x00,0x00,0x44,0x6c,0x00,0x00,0x00,0xe8,0x70,0x72,0x65,0x70,
/*0000e0*/ 0xc9,0x0f,0xd2,0x13,0x00,0x00,0x04,0x1c,0x00,0x00,0x00,0x48,0x00,0x00,0x00,0x15,
/*0000f0*/ 0x01,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x76,0x00,0x3b,0x00,0x00,
/*000100*/ 0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x0e,0x00,0xb8,0x00,0x00,0x00,0x00,0x00,0x00,
/*000110*/ 0x00,0x02,0x00,0x04,0x00,0xc8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x4e,
/*000120*/ 0x01,0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x14,0x00,0xd6,0x00,0x00,
/*000130*/ 0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x48,0x01,0x83,0x00,0x00,0x00,0x00,0x00,0x00,
/*000140*/ 0x00,0x06,0x00,0x10,0x01,0xd3,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3b,
/*000150*/ 0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x07,0x00,0xb1,0x00,0x01,
/*000160*/ 0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x02,0x00,0xc6,0x00,0x01,0x00,0x00,0x00,0x00,
/*000170*/ 0x00,0x03,0x00,0x27,0x00,0xea,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x0a,
/*000180*/ 0x00,0xcc,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x24,0x01,0x5f,0x00,0x01,
/*000190*/ 0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x08,0x01,0xcb,0x00,0x03,0x00,0x01,0x04,0x09,
/*0001a0*/ 0x00,0x00,0x00,0x76,0x00,0x3b,0x00,0x03,0x00,0x01,0x04,0x09,0x00,0x01,0x00,0x0e,
/*0001b0*/ 0x00,0xb8,0x00,0x03,0x00,0x01,0x04,0x09,0x00,0x02,0x00,0x04,0x00,0xc8,0x00,0x03,
/*0001c0*/ 0x00,0x01,0x04,0x09,0x00,0x03,0x00,0x4e,0x01,0x11,0x00,0x03,0x00,0x01,0x04,0x09,
/*0001d0*/ 0x00,0x04,0x00,0x14,0x00,0xd6,0x00,0x03,0x00,0x01,0x04,0x09,0x00,0x05,0x00,0x48,
/*0001e0*/ 0x01,0x83,0x00,0x03,0x00,0x01,0x04,0x09,0x00,0x06,0x00,0x10,0x01,0xd3,0x43,0x6f,
/*0001f0*/ 0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x28,0x43,0x29,0x20,0x32,0x30,0x30,0x31,
/*000200*/ 0x20,0x42,0x69,0x74,0x6d,0x61,0x70,0x4d,0x61,0x6e,0x69,0x61,0x20,0x2f,0x20,0x43,
/*000210*/ 0x4f,0x4f,0x5a,0x2e,0x20,0x41,0x6c,0x6c,0x20,0x72,0x69,0x67,0x68,0x74,0x73,0x20,
/*000220*/ 0x72,0x65,0x73,0x65,0x72,0x76,0x65,0x64,0x2e,0x00,0x43,0x00,0x6f,0x00,0x70,0x00,
/*000230*/ 0x79,0x00,0x72,0x00,0x69,0x00,0x67,0x00,0x68,0x00,0x74,0x00,0x20,0x00,0x28,0x00,
/*000240*/ 0x43,0x00,0x29,0x00,0x20,0x00,0x32,0x00,0x30,0x00,0x30,0x00,0x31,0x00,0x20,0x00,
/*000250*/ 0x42,0x00,0x69,0x00,0x74,0x00,0x6d,0x00,0x61,0x00,0x70,0x00,0x4d,0x00,0x61,0x00,
/*000260*/ 0x6e,0x00,0x69,0x00,0x61,0x00,0x20,0x00,0x2f,0x00,0x20,0x00,0x43,0x00,0x4f,0x00,
/*000270*/ 0x4f,0x00,0x5a,0x00,0x2e,0x00,0x20,0x00,0x41,0x00,0x6c,0x00,0x6c,0x00,0x20,0x00,
/*000280*/ 0x72,0x00,0x69,0x00,0x67,0x00,0x68,0x00,0x74,0x00,0x73,0x00,0x20,0x00,0x72,0x00,
/*000290*/ 0x65,0x00,0x73,0x00,0x65,0x00,0x72,0x00,0x76,0x00,0x65,0x00,0x64,0x00,0x2e,0x42,
/*0002a0*/ 0x4d,0x20,0x6d,0x69,0x6e,0x69,0x00,0x42,0x00,0x4d,0x00,0x20,0x00,0x6d,0x00,0x69,
/*0002b0*/ 0x00,0x6e,0x00,0x69,0x41,0x38,0x00,0x41,0x00,0x38,0x42,0x4d,0x20,0x6d,0x69,0x6e,
/*0002c0*/ 0x69,0x20,0x41,0x38,0x00,0x42,0x00,0x4d,0x00,0x20,0x00,0x6d,0x00,0x69,0x00,0x6e,
/*0002d0*/ 0x00,0x69,0x00,0x20,0x00,0x41,0x00,0x38,0x4d,0x61,0x63,0x72,0x6f,0x6d,0x65,0x64,
/*0002e0*/ 0x69,0x61,0x20,0x46,0x6f,0x6e,0x74,0x6f,0x67,0x72,0x61,0x70,0x68,0x65,0x72,0x20,
/*0002f0*/ 0x34,0x2e,0x31,0x4a,0x20,0x42,0x4d,0x20,0x6d,0x69,0x6e,0x69,0x20,0x41,0x38,0x00,
/*000300*/ 0x4d,0x00,0x61,0x00,0x63,0x00,0x72,0x00,0x6f,0x00,0x6d,0x00,0x65,0x00,0x64,0x00,
/*000310*/ 0x69,0x00,0x61,0x00,0x20,0x00,0x46,0x00,0x6f,0x00,0x6e,0x00,0x74,0x00,0x6f,0x00,
/*000320*/ 0x67,0x00,0x72,0x00,0x61,0x00,0x70,0x00,0x68,0x00,0x65,0x00,0x72,0x00,0x20,0x00,
/*000330*/ 0x34,0x00,0x2e,0x00,0x31,0x00,0x4a,0x00,0x20,0x00,0x42,0x00,0x4d,0x00,0x20,0x00,
/*000340*/ 0x6d,0x00,0x69,0x00,0x6e,0x00,0x69,0x00,0x20,0x00,0x41,0x00,0x38,0x4d,0x61,0x63,
/*000350*/ 0x72,0x6f,0x6d,0x65,0x64,0x69,0x61,0x20,0x46,0x6f,0x6e,0x74,0x6f,0x67,0x72,0x61,
/*000360*/ 0x70,0x68,0x65,0x72,0x20,0x34,0x2e,0x31,0x4a,0x20,0x30,0x31,0x2e,0x31,0x2e,0x32,
/*000370*/ 0x37,0x00,0x4d,0x00,0x61,0x00,0x63,0x00,0x72,0x00,0x6f,0x00,0x6d,0x00,0x65,0x00,
/*000380*/ 0x64,0x00,0x69,0x00,0x61,0x00,0x20,0x00,0x46,0x00,0x6f,0x00,0x6e,0x00,0x74,0x00,
/*000390*/ 0x6f,0x00,0x67,0x00,0x72,0x00,0x61,0x00,0x70,0x00,0x68,0x00,0x65,0x00,0x72,0x00,
/*0003a0*/ 0x20,0x00,0x34,0x00,0x2e,0x00,0x31,0x00,0x4a,0x00,0x20,0x00,0x30,0x00,0x31,0x00,
/*0003b0*/ 0x2e,0x00,0x31,0x00,0x2e,0x00,0x32,0x00,0x37,0x42,0x4d,0x6d,0x69,0x6e,0x69,0x41,
/*0003c0*/ 0x38,0x00,0x42,0x00,0x4d,0x00,0x6d,0x00,0x69,0x00,0x6e,0x00,0x69,0x00,0x41,0x00,
/*0003d0*/ 0x38,0x00,0x00,0x00,0x40,0x01,0x00,0x2c,0x76,0x45,0x20,0xb0,0x03,0x25,0x45,0x23,
/*0003e0*/ 0x61,0x68,0x18,0x23,0x68,0x60,0x44,0x2d,0xff,0x39,0x00,0x01,0x01,0x8f,0x02,0x57,
/*0003f0*/ 0x00,0x62,0x00,0xc5,0x00,0x62,0x00,0xc5,0x01,0x29,0x01,0x8d,0x01,0x8e,0x01,0x2a,
/*000400*/ 0x5a,0x67,0x12,0x06,0xd2,0xb8,0x6a,0x18,0xf8,0x2a,0x61,0xa3,0x0e,0x40,0xee,0xd2,
/*000410*/ 0x80,0x3a,0x27,0x55,0xa2,0x87,0x00,0x01,0x00,0x0d,0x00,0x00,0x40,0x11,0x0b,0x0b,
/*000420*/ 0x0a,0x0a,0x09,0x09,0x08,0x08,0x03,0x03,0x02,0x02,0x01,0x01,0x00,0x00,0x01,0x8d,
/*000430*/ 0xb8,0x01,0xff,0x85,0x45,0x68,0x44,0x45,0x68,0x44,0x45,0x68,0x44,0x45,0x68,0x44,
/*000440*/ 0x45,0x68,0x44,0x45,0x68,0x44,0x45,0x68,0x44,0x45,0x68,0x44,0xb3,0x05,0x04,0x46,
/*000450*/ 0x00,0x2b,0xb3,0x07,0x06,0x46,0x00,0x2b,0xb1,0x04,0x04,0x45,0x68,0x44,0xb1,0x06,
/*000460*/ 0x06,0x45,0x68,0x44,0x00,0x02,0x00,0x32,0x00,0x00,0x01,0x5e,0x02,0x58,0x00,0x03,
/*000470*/ 0x00,0x07,0x00,0x55,0x40,0x1f,0x01,0x08,0x08,0x40,0x09,0x02,0x07,0x04,0x04,0x01,
/*000480*/ 0x00,0x06,0x05,0x04,0x03,0x02,0x05,0x04,0x06,0x00,0x07,0x06,0x06,0x01,0x02,0x01,
/*000490*/ 0x03,0x00,0x01,0x00,0x46,0x76,0x2f,0x37,0x18,0x00,0x2f,0x3c,0x2f,0x3c,0x10,0xfd,
/*0004a0*/ 0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x00,0x31,0x30,
/*0004b0*/ 0x01,0x49,0x68,0xb9,0x00,0x00,0x00,0x08,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,
/*0004c0*/ 0x11,0x37,0xb9,0x00,0x08,0xff,0xc0,0x38,0x59,0x33,0x11,0x21,0x11,0x27,0x33,0x11,
/*0004d0*/ 0x23,0x32,0x01,0x2c,0xfa,0xc8,0xc8,0x02,0x58,0xfd,0xa8,0x32,0x01,0xf4,0x00,0x02,
/*0004e0*/ 0x00,0x01,0x00,0x01,0x00,0x63,0x02,0x57,0x00,0x03,0x00,0x07,0x00,0x53,0x40,0x20,
/*0004f0*/ 0x01,0x08,0x08,0x40,0x09,0x00,0x01,0x00,0x06,0x05,0x02,0x03,0x01,0x04,0x07,0x04,
/*000500*/ 0x03,0x03,0x00,0x07,0x06,0x06,0x04,0x05,0x04,0x01,0x03,0x02,0x03,0x01,0x01,0x46,
/*000510*/ 0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x17,0x3c,
/*000520*/ 0xfd,0x17,0x3c,0x00,0x2e,0x2e,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x01,0x00,0x08,
/*000530*/ 0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x08,0xff,0xc0,0x38,
/*000540*/ 0x59,0x37,0x23,0x11,0x33,0x11,0x23,0x35,0x33,0x63,0x62,0x62,0x62,0x62,0xc9,0x01,
/*000550*/ 0x8e,0xfd,0xaa,0x62,0x00,0x02,0x00,0x01,0x01,0x91,0x01,0x2b,0x02,0x57,0x00,0x03,
/*000560*/ 0x00,0x07,0x00,0x50,0x40,0x1e,0x01,0x08,0x08,0x40,0x09,0x00,0x02,0x01,0x04,0x03,
/*000570*/ 0x00,0x07,0x04,0x04,0x06,0x05,0x05,0x04,0x01,0x03,0x00,0x07,0x06,0x03,0x03,0x02,
/*000580*/ 0x03,0x01,0x05,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x17,0x3c,0x2f,0x17,0x3c,0x01,
/*000590*/ 0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,
/*0005a0*/ 0x05,0x00,0x08,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x08,
/*0005b0*/ 0xff,0xc0,0x38,0x59,0x01,0x23,0x35,0x33,0x07,0x23,0x35,0x33,0x01,0x2b,0x62,0x62,
/*0005c0*/ 0xc8,0x62,0x62,0x01,0x91,0xc6,0xc6,0xc6,0x00,0x02,0x00,0x01,0x00,0x65,0x01,0xf3,
/*0005d0*/ 0x02,0x57,0x00,0x1b,0x00,0x1f,0x00,0xb3,0x40,0x5e,0x01,0x20,0x20,0x40,0x21,0x00,
/*0005e0*/ 0x1b,0x18,0x17,0x14,0x13,0x0e,0x0d,0x0a,0x09,0x00,0x06,0x05,0x04,0x07,0x1f,0x12,
/*0005f0*/ 0x11,0x03,0x1e,0x04,0x10,0x0f,0x0c,0x0b,0x08,0x05,0x07,0x1d,0x04,0x03,0x03,0x1c,
/*000600*/ 0x04,0x1a,0x19,0x16,0x02,0x01,0x05,0x15,0x1b,0x1a,0x06,0x00,0x1f,0x0b,0x0a,0x03,
/*000610*/ 0x1c,0x06,0x09,0x08,0x05,0x04,0x01,0x05,0x00,0x0d,0x0c,0x06,0x0e,0x1e,0x19,0x18,
/*000620*/ 0x03,0x1d,0x06,0x17,0x16,0x13,0x12,0x0f,0x05,0x0e,0x07,0x06,0x03,0x03,0x02,0x15,
/*000630*/ 0x14,0x11,0x03,0x10,0x03,0x01,0x09,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x17,0x3c,
/*000640*/ 0x2f,0x17,0x3c,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x2f,0x17,0x3c,0xfd,
/*000650*/ 0x17,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x2f,0x17,0x3c,0xfd,
/*000660*/ 0x17,0x3c,0x10,0xfd,0x3c,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x00,
/*000670*/ 0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x09,0x00,0x20,0x49,0x68,0x61,0xb0,0x40,0x52,
/*000680*/ 0x58,0x38,0x11,0x37,0xb9,0x00,0x20,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,0x23,0x35,
/*000690*/ 0x23,0x15,0x23,0x35,0x23,0x35,0x33,0x35,0x23,0x35,0x33,0x35,0x33,0x15,0x33,0x35,
/*0006a0*/ 0x33,0x15,0x33,0x15,0x23,0x15,0x33,0x27,0x35,0x23,0x15,0x01,0xf3,0x65,0x61,0x67,
/*0006b0*/ 0x61,0x64,0x64,0x64,0x64,0x62,0x67,0x61,0x64,0x64,0x64,0xc8,0x62,0xc9,0x64,0x64,
/*0006c0*/ 0x64,0x65,0x61,0x67,0x61,0x64,0x64,0x64,0x65,0x61,0x67,0x03,0x62,0x62,0x00,0x01,
/*0006d0*/ 0x00,0x01,0x00,0x01,0x01,0xf3,0x01,0xf3,0x00,0x13,0x00,0x88,0x40,0x42,0x01,0x14,
/*0006e0*/ 0x14,0x40,0x15,0x00,0x13,0x0a,0x09,0x00,0x04,0x03,0x04,0x05,0x0e,0x06,0x05,0x03,
/*0006f0*/ 0x0d,0x04,0x0c,0x0b,0x08,0x03,0x07,0x10,0x0f,0x04,0x12,0x02,0x01,0x03,0x11,0x07,
/*000700*/ 0x06,0x03,0x03,0x02,0x0b,0x00,0x09,0x08,0x01,0x03,0x00,0x07,0x12,0x11,0x10,0x0d,
/*000710*/ 0x03,0x0c,0x13,0x12,0x0f,0x0e,0x0b,0x05,0x0a,0x02,0x05,0x04,0x01,0x01,0x09,0x46,
/*000720*/ 0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x17,0x3c,0x2f,0x17,0x3c,0x10,0xfd,0x17,
/*000730*/ 0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,0x17,0x3c,0xfd,0x3c,0x2f,0x17,0x3c,0xfd,0x17,
/*000740*/ 0x3c,0x10,0xfd,0x3c,0x2e,0x2e,0x2e,0x2e,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,
/*000750*/ 0x09,0x00,0x14,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x14,
/*000760*/ 0xff,0xc0,0x38,0x59,0x25,0x23,0x15,0x23,0x15,0x23,0x35,0x23,0x35,0x23,0x35,0x33,
/*000770*/ 0x35,0x33,0x15,0x33,0x35,0x33,0x15,0x33,0x01,0xf3,0x65,0x64,0x61,0x64,0x64,0x64,
/*000780*/ 0x62,0x67,0x61,0x64,0xc9,0x64,0x64,0x65,0x64,0xc5,0x64,0x64,0x64,0x65,0x00,0x03,
/*000790*/ 0x00,0x01,0x00,0x01,0x02,0xbb,0x02,0x57,0x00,0x23,0x00,0x27,0x00,0x2b,0x00,0xde,
/*0007a0*/ 0x40,0x80,0x01,0x2c,0x2c,0x40,0x2d,0x00,0x20,0x1f,0x08,0x07,0x04,0x0a,0x09,0x1e,
/*0007b0*/ 0x1d,0x04,0x18,0x17,0x0c,0x03,0x0b,0x29,0x28,0x16,0x15,0x0e,0x05,0x0d,0x04,0x0f,
/*0007c0*/ 0x2b,0x2a,0x14,0x13,0x10,0x05,0x0f,0x04,0x12,0x11,0x1a,0x06,0x05,0x03,0x19,0x04,
/*0007d0*/ 0x27,0x26,0x1c,0x04,0x03,0x05,0x1b,0x25,0x24,0x22,0x02,0x01,0x05,0x21,0x04,0x23,
/*0007e0*/ 0x00,0x27,0x24,0x05,0x04,0x01,0x05,0x00,0x06,0x02,0x1d,0x1c,0x11,0x10,0x0d,0x05,
/*0007f0*/ 0x0c,0x06,0x0e,0x21,0x0f,0x0e,0x03,0x20,0x06,0x26,0x25,0x23,0x22,0x1f,0x1e,0x0b,
/*000800*/ 0x07,0x06,0x09,0x0a,0x2a,0x29,0x17,0x16,0x13,0x05,0x12,0x06,0x14,0x2b,0x28,0x19,
/*000810*/ 0x03,0x18,0x02,0x1b,0x1a,0x15,0x03,0x14,0x03,0x09,0x08,0x03,0x03,0x02,0x01,0x01,
/*000820*/ 0x11,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x17,0x3c,0x3f,0x17,0x3c,0x3f,0x17,0x3c,
/*000830*/ 0x10,0xfd,0x17,0x3c,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,
/*000840*/ 0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,
/*000850*/ 0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x2f,0x17,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,
/*000860*/ 0x2e,0x2e,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x11,0x00,0x2c,0x49,0x68,0x61,
/*000870*/ 0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x2c,0xff,0xc0,0x38,0x59,0x25,0x23,
/*000880*/ 0x15,0x23,0x35,0x23,0x35,0x23,0x15,0x23,0x35,0x33,0x35,0x23,0x15,0x23,0x35,0x23,
/*000890*/ 0x35,0x33,0x35,0x33,0x15,0x33,0x15,0x33,0x35,0x33,0x15,0x23,0x15,0x33,0x35,0x33,
/*0008a0*/ 0x15,0x33,0x07,0x35,0x23,0x15,0x01,0x35,0x23,0x15,0x02,0xbb,0x65,0x61,0x64,0x67,
/*0008b0*/ 0x61,0x64,0x67,0x61,0x64,0x64,0x62,0x64,0x67,0x61,0x64,0x67,0x61,0x64,0x64,0x62,
/*0008c0*/ 0xfe,0xd2,0x62,0x65,0x64,0x65,0x63,0xc8,0xc6,0xca,0x64,0x65,0x61,0x64,0x65,0x63,
/*0008d0*/ 0xc8,0xc6,0xca,0x64,0x65,0x61,0x62,0x62,0x01,0x2c,0x62,0x62,0x00,0x03,0x00,0x01,
/*0008e0*/ 0x00,0x01,0x01,0xf3,0x02,0x57,0x00,0x1b,0x00,0x1f,0x00,0x25,0x00,0xce,0x40,0x76,
/*0008f0*/ 0x01,0x26,0x26,0x40,0x27,0x00,0x1a,0x19,0x1b,0x04,0x03,0x03,0x00,0x04,0x05,0x10,
/*000900*/ 0x0f,0x0c,0x03,0x0b,0x04,0x09,0x25,0x24,0x1f,0x1e,0x12,0x11,0x0e,0x0d,0x0a,0x09,
/*000910*/ 0x09,0x04,0x13,0x23,0x22,0x1d,0x1c,0x18,0x17,0x14,0x07,0x13,0x04,0x15,0x21,0x20,
/*000920*/ 0x16,0x08,0x07,0x05,0x15,0x04,0x06,0x05,0x02,0x03,0x01,0x03,0x02,0x06,0x04,0x25,
/*000930*/ 0x20,0x0b,0x0a,0x07,0x05,0x06,0x06,0x04,0x1e,0x1d,0x15,0x14,0x11,0x05,0x10,0x06,
/*000940*/ 0x12,0x22,0x21,0x06,0x0c,0x24,0x23,0x19,0x18,0x0d,0x01,0x00,0x07,0x0c,0x06,0x17,
/*000950*/ 0x0f,0x0e,0x03,0x16,0x1f,0x1c,0x1b,0x03,0x1a,0x02,0x13,0x12,0x03,0x09,0x08,0x05,
/*000960*/ 0x03,0x04,0x01,0x01,0x0b,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x17,0x3c,0x3f,0x3c,
/*000970*/ 0x3f,0x17,0x3c,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x10,0xfd,0x17,0x3c,
/*000980*/ 0x10,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x10,0xfd,
/*000990*/ 0x17,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x2e,0x2e,
/*0009a0*/ 0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x0b,0x00,0x26,0x49,0x68,0x61,0xb0,0x40,
/*0009b0*/ 0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x26,0xff,0xc0,0x38,0x59,0x01,0x23,0x15,0x33,
/*0009c0*/ 0x15,0x23,0x35,0x23,0x15,0x23,0x35,0x23,0x35,0x33,0x35,0x23,0x35,0x33,0x35,0x33,
/*0009d0*/ 0x15,0x33,0x15,0x23,0x15,0x33,0x35,0x33,0x25,0x35,0x23,0x15,0x13,0x35,0x23,0x35,
/*0009e0*/ 0x23,0x15,0x01,0xf3,0x64,0x64,0x62,0x67,0xc5,0x64,0x64,0x64,0x64,0x62,0x64,0x64,
/*0009f0*/ 0xcb,0x61,0xfe,0xd4,0x62,0xc6,0x64,0x62,0x01,0x2d,0xcb,0x61,0x64,0x64,0x65,0xc5,
/*000a00*/ 0x67,0x61,0x64,0x65,0x61,0x66,0x64,0x02,0x62,0x62,0xfe,0xd4,0x61,0x65,0xc6,0x00,
/*000a10*/ 0x00,0x01,0x00,0x01,0x01,0x91,0x00,0x63,0x02,0x57,0x00,0x03,0x00,0x3f,0x40,0x13,
/*000a20*/ 0x01,0x04,0x04,0x40,0x05,0x00,0x02,0x01,0x04,0x03,0x00,0x01,0x00,0x03,0x02,0x03,
/*000a30*/ 0x01,0x01,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x2f,0x3c,0x01,0x2f,0x3c,0xfd,
/*000a40*/ 0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x01,0x00,0x04,0x49,0x68,0x61,0xb0,
/*000a50*/ 0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x04,0xff,0xc0,0x38,0x59,0x13,0x23,0x35,
/*000a60*/ 0x33,0x63,0x62,0x62,0x01,0x91,0xc6,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0xc7,
/*000a70*/ 0x02,0x57,0x00,0x0b,0x00,0x67,0x40,0x2d,0x01,0x0c,0x0c,0x40,0x0d,0x00,0x07,0x06,
/*000a80*/ 0x0b,0x04,0x03,0x03,0x00,0x04,0x05,0x0a,0x09,0x06,0x05,0x02,0x05,0x01,0x04,0x08,
/*000a90*/ 0x07,0x09,0x08,0x01,0x03,0x00,0x06,0x0a,0x03,0x02,0x06,0x04,0x0b,0x0a,0x03,0x05,
/*000aa0*/ 0x04,0x01,0x01,0x07,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,
/*000ab0*/ 0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x00,
/*000ac0*/ 0x2e,0x2e,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x07,0x00,0x0c,0x49,0x68,0x61,0xb0,
/*000ad0*/ 0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x0c,0xff,0xc0,0x38,0x59,0x13,0x23,0x11,
/*000ae0*/ 0x33,0x15,0x23,0x35,0x23,0x11,0x33,0x35,0x33,0xc7,0x64,0x64,0x62,0x64,0x64,0x62,
/*000af0*/ 0x01,0xf5,0xfe,0x6d,0x61,0x65,0x01,0x8d,0x64,0x00,0x00,0x01,0x00,0x01,0x00,0x01,
/*000b00*/ 0x00,0xc7,0x02,0x57,0x00,0x0b,0x00,0x67,0x40,0x2c,0x01,0x0c,0x0c,0x40,0x0d,0x00,
/*000b10*/ 0x0b,0x0a,0x02,0x01,0x04,0x08,0x07,0x04,0x03,0x03,0x0a,0x09,0x06,0x03,0x05,0x04,
/*000b20*/ 0x0b,0x00,0x05,0x01,0x00,0x03,0x04,0x06,0x02,0x07,0x06,0x06,0x08,0x09,0x08,0x03,
/*000b30*/ 0x03,0x02,0x01,0x01,0x03,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x10,
/*000b40*/ 0xfd,0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x17,0x3c,0xfd,
/*000b50*/ 0x3c,0x00,0x2e,0x2e,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x03,0x00,0x0c,0x49,0x68,
/*000b60*/ 0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x0c,0xff,0xc0,0x38,0x59,0x37,
/*000b70*/ 0x23,0x15,0x23,0x35,0x33,0x11,0x23,0x35,0x33,0x15,0x33,0xc7,0x65,0x61,0x64,0x64,
/*000b80*/ 0x62,0x64,0x65,0x64,0x62,0x01,0x92,0x62,0x65,0x00,0x00,0x01,0x00,0x01,0x01,0x2d,
/*000b90*/ 0x01,0x2b,0x02,0x57,0x00,0x13,0x00,0x83,0x40,0x40,0x01,0x14,0x14,0x40,0x15,0x00,
/*000ba0*/ 0x12,0x11,0x13,0x04,0x03,0x03,0x00,0x04,0x05,0x08,0x07,0x04,0x0e,0x0d,0x0a,0x03,
/*000bb0*/ 0x09,0x10,0x0f,0x0c,0x03,0x0b,0x04,0x06,0x05,0x02,0x03,0x01,0x07,0x06,0x06,0x11,
/*000bc0*/ 0x0d,0x0c,0x01,0x00,0x05,0x10,0x09,0x08,0x05,0x03,0x04,0x13,0x12,0x0f,0x03,0x0e,
/*000bd0*/ 0x03,0x0b,0x03,0x02,0x03,0x0a,0x02,0x01,0x09,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,
/*000be0*/ 0x17,0x3c,0x3f,0x17,0x3c,0x2f,0x17,0x3c,0x2f,0x17,0x3c,0xfd,0x3c,0x01,0x2f,0x17,
/*000bf0*/ 0x3c,0xfd,0x17,0x3c,0x2f,0x17,0x3c,0xfd,0x3c,0x10,0xfd,0x17,0x3c,0x2e,0x2e,0x00,
/*000c00*/ 0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x09,0x00,0x14,0x49,0x68,0x61,0xb0,0x40,0x52,
/*000c10*/ 0x58,0x38,0x11,0x37,0xb9,0x00,0x14,0xff,0xc0,0x38,0x59,0x01,0x23,0x15,0x33,0x15,
/*000c20*/ 0x23,0x35,0x23,0x15,0x23,0x35,0x33,0x35,0x23,0x35,0x33,0x15,0x33,0x35,0x33,0x01,
/*000c30*/ 0x2b,0x64,0x64,0x62,0x67,0x61,0x64,0x64,0x62,0x67,0x61,0x01,0xf5,0x67,0x61,0x64,
/*000c40*/ 0x64,0x62,0x66,0x62,0x64,0x64,0x00,0x01,0x00,0x01,0x00,0x65,0x01,0x2b,0x01,0x8f,
/*000c50*/ 0x00,0x0b,0x00,0x66,0x40,0x2c,0x01,0x0c,0x0c,0x40,0x0d,0x00,0x0b,0x00,0x08,0x01,
/*000c60*/ 0x06,0x05,0x08,0x03,0x0a,0x09,0x02,0x03,0x01,0x04,0x08,0x07,0x04,0x03,0x03,0x0b,
/*000c70*/ 0x07,0x06,0x03,0x0a,0x06,0x05,0x04,0x01,0x03,0x00,0x03,0x02,0x09,0x08,0x02,0x01,
/*000c80*/ 0x05,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x2f,0x3c,0x2f,0x17,0x3c,0xfd,0x17,
/*000c90*/ 0x3c,0x01,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x10,0xfd,0x3c,0x00,0x31,
/*000ca0*/ 0x30,0x01,0x49,0x68,0xb9,0x00,0x05,0x00,0x0c,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,
/*000cb0*/ 0x38,0x11,0x37,0xb9,0x00,0x0c,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,0x23,0x35,0x23,
/*000cc0*/ 0x35,0x33,0x35,0x33,0x15,0x33,0x01,0x2b,0x65,0x61,0x64,0x64,0x62,0x64,0xc9,0x64,
/*000cd0*/ 0x65,0x61,0x64,0x65,0x00,0x01,0x00,0x01,0xff,0x9d,0x00,0xc7,0x00,0x63,0x00,0x07,
/*000ce0*/ 0x00,0x53,0x40,0x1e,0x01,0x08,0x08,0x40,0x09,0x00,0x02,0x01,0x04,0x04,0x03,0x06,
/*000cf0*/ 0x05,0x04,0x07,0x00,0x05,0x04,0x06,0x02,0x07,0x06,0x03,0x02,0x01,0x00,0x01,0x01,
/*000d00*/ 0x03,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x2f,0x3c,0x2f,0x3c,0x10,0xfd,0x3c,
/*000d10*/ 0x01,0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,
/*000d20*/ 0x00,0x03,0x00,0x08,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,
/*000d30*/ 0x08,0xff,0xc0,0x38,0x59,0x37,0x23,0x15,0x23,0x35,0x33,0x35,0x33,0xc7,0x65,0x61,
/*000d40*/ 0x64,0x62,0x01,0x64,0x62,0x64,0x00,0x01,0x00,0x01,0x00,0xc9,0x01,0x2b,0x01,0x2b,
/*000d50*/ 0x00,0x03,0x00,0x3d,0x40,0x11,0x01,0x04,0x04,0x40,0x05,0x00,0x03,0x02,0x01,0x00,
/*000d60*/ 0x03,0x02,0x01,0x00,0x01,0x01,0x46,0x76,0x2f,0x37,0x18,0x00,0x2f,0x3c,0x2f,0x3c,
/*000d70*/ 0x01,0x2e,0x2e,0x2e,0x2e,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x01,0x00,0x04,
/*000d80*/ 0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x04,0xff,0xc0,0x38,
/*000d90*/ 0x59,0x25,0x21,0x35,0x21,0x01,0x2b,0xfe,0xd6,0x01,0x2a,0xc9,0x62,0x00,0x00,0x01,
/*000da0*/ 0x00,0x01,0x00,0x01,0x00,0x63,0x00,0x63,0x00,0x03,0x00,0x3f,0x40,0x13,0x01,0x04,
/*000db0*/ 0x04,0x40,0x05,0x00,0x03,0x00,0x04,0x02,0x01,0x03,0x02,0x01,0x00,0x01,0x01,0x01,
/*000dc0*/ 0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x2f,0x3c,0x01,0x2f,0x3c,0xfd,0x3c,0x00,
/*000dd0*/ 0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x01,0x00,0x04,0x49,0x68,0x61,0xb0,0x40,0x52,
/*000de0*/ 0x58,0x38,0x11,0x37,0xb9,0x00,0x04,0xff,0xc0,0x38,0x59,0x37,0x23,0x35,0x33,0x63,
/*000df0*/ 0x62,0x62,0x01,0x62,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x2b,0x02,0x57,0x00,0x0b,
/*000e00*/ 0x00,0x69,0x40,0x2c,0x01,0x0c,0x0c,0x40,0x0d,0x00,0x04,0x03,0x04,0x06,0x05,0x08,
/*000e10*/ 0x07,0x04,0x02,0x01,0x0a,0x09,0x04,0x0b,0x00,0x01,0x00,0x07,0x0a,0x07,0x03,0x02,
/*000e20*/ 0x03,0x06,0x07,0x04,0x0b,0x0a,0x03,0x09,0x08,0x02,0x05,0x04,0x01,0x01,0x05,0x46,
/*000e30*/ 0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x10,
/*000e40*/ 0xfd,0x3c,0x01,0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x00,
/*000e50*/ 0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x05,0x00,0x0c,0x49,0x68,0x61,0xb0,0x40,0x52,
/*000e60*/ 0x58,0x38,0x11,0x37,0xb9,0x00,0x0c,0xff,0xc0,0x38,0x59,0x01,0x23,0x15,0x23,0x15,
/*000e70*/ 0x23,0x35,0x33,0x35,0x33,0x35,0x33,0x01,0x2b,0x65,0x64,0x61,0x65,0x63,0x62,0x01,
/*000e80*/ 0x91,0xc8,0xc8,0xc6,0xc8,0xc8,0x00,0x03,0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,
/*000e90*/ 0x00,0x0b,0x00,0x11,0x00,0x17,0x00,0x95,0x40,0x4d,0x01,0x18,0x18,0x40,0x19,0x00,
/*000ea0*/ 0x15,0x14,0x05,0x05,0x11,0x10,0x05,0x00,0x17,0x16,0x0f,0x0e,0x08,0x07,0x04,0x07,
/*000eb0*/ 0x03,0x04,0x06,0x05,0x13,0x12,0x0d,0x0c,0x0a,0x02,0x01,0x07,0x09,0x04,0x0b,0x00,
/*000ec0*/ 0x17,0x12,0x05,0x04,0x01,0x05,0x00,0x06,0x02,0x0e,0x0d,0x0b,0x0a,0x07,0x05,0x06,
/*000ed0*/ 0x06,0x08,0x11,0x0c,0x06,0x13,0x14,0x13,0x10,0x03,0x0f,0x06,0x16,0x15,0x09,0x08,
/*000ee0*/ 0x03,0x03,0x02,0x01,0x01,0x05,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,
/*000ef0*/ 0x2f,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,
/*000f00*/ 0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x10,0xfd,
/*000f10*/ 0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x05,0x00,0x18,0x49,0x68,0x61,0xb0,
/*000f20*/ 0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x18,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,
/*000f30*/ 0x23,0x35,0x23,0x11,0x33,0x35,0x33,0x15,0x33,0x07,0x35,0x23,0x15,0x33,0x35,0x13,
/*000f40*/ 0x35,0x23,0x15,0x23,0x15,0x01,0x8f,0x65,0xc5,0x64,0x64,0xc6,0x64,0x64,0xc6,0x61,
/*000f50*/ 0x65,0x62,0x64,0x65,0x64,0x65,0x01,0x8d,0x64,0x65,0x61,0x62,0xc6,0x64,0xfe,0xd4,
/*000f60*/ 0xc6,0x64,0x62,0x00,0x00,0x01,0x00,0x65,0x00,0x01,0x01,0x2b,0x02,0x57,0x00,0x07,
/*000f70*/ 0x00,0x54,0x40,0x20,0x01,0x08,0x08,0x40,0x09,0x00,0x04,0x03,0x05,0x00,0x06,0x05,
/*000f80*/ 0x02,0x03,0x01,0x04,0x07,0x00,0x03,0x02,0x06,0x05,0x04,0x07,0x06,0x03,0x01,0x00,
/*000f90*/ 0x01,0x01,0x03,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x2f,0x3c,0xfd,
/*000fa0*/ 0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,
/*000fb0*/ 0xb9,0x00,0x03,0x00,0x08,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,
/*000fc0*/ 0x00,0x08,0xff,0xc0,0x38,0x59,0x25,0x23,0x11,0x23,0x35,0x33,0x35,0x33,0x01,0x2b,
/*000fd0*/ 0x62,0x64,0x64,0x62,0x01,0x01,0x91,0x61,0x64,0x00,0x00,0x01,0x00,0x01,0x00,0x01,
/*000fe0*/ 0x01,0x8f,0x02,0x57,0x00,0x19,0x00,0xa4,0x40,0x53,0x01,0x1a,0x1a,0x40,0x1b,0x00,
/*000ff0*/ 0x12,0x11,0x06,0x03,0x05,0x04,0x14,0x13,0x0a,0x03,0x09,0x04,0x03,0x04,0x16,0x15,
/*001000*/ 0x0c,0x03,0x0b,0x0e,0x0d,0x04,0x02,0x01,0x18,0x17,0x10,0x03,0x0f,0x04,0x19,0x08,
/*001010*/ 0x07,0x03,0x00,0x13,0x12,0x01,0x03,0x00,0x06,0x19,0x18,0x0d,0x03,0x02,0x03,0x0c,
/*001020*/ 0x06,0x0b,0x0a,0x05,0x03,0x04,0x07,0x06,0x06,0x08,0x15,0x14,0x11,0x03,0x10,0x06,
/*001030*/ 0x16,0x17,0x16,0x03,0x0f,0x0e,0x02,0x09,0x08,0x01,0x01,0x09,0x46,0x76,0x2f,0x37,
/*001040*/ 0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x2f,
/*001050*/ 0x17,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x01,0x2f,0x17,0x3c,0xfd,0x17,
/*001060*/ 0x3c,0x2f,0x3c,0xfd,0x3c,0x2f,0x17,0x3c,0xfd,0x3c,0x2f,0x17,0x3c,0xfd,0x17,0x3c,
/*001070*/ 0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x09,0x00,0x1a,0x49,0x68,0x61,0xb0,0x40,
/*001080*/ 0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x1a,0xff,0xc0,0x38,0x59,0x01,0x23,0x15,0x23,
/*001090*/ 0x15,0x23,0x15,0x21,0x15,0x21,0x35,0x33,0x35,0x33,0x35,0x33,0x35,0x23,0x15,0x23,
/*0010a0*/ 0x35,0x33,0x35,0x33,0x15,0x33,0x01,0x8f,0x65,0x64,0x63,0x01,0x2c,0xfe,0x72,0x65,
/*0010b0*/ 0x64,0x63,0xcb,0x61,0x64,0xc6,0x64,0x01,0x91,0x64,0x64,0x67,0x61,0xc6,0x64,0x64,
/*0010c0*/ 0x66,0x64,0x62,0x64,0x65,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,
/*0010d0*/ 0x00,0x1b,0x00,0xa4,0x40,0x57,0x01,0x1c,0x1c,0x40,0x1d,0x00,0x14,0x13,0x10,0x0f,
/*0010e0*/ 0x08,0x04,0x03,0x07,0x07,0x04,0x12,0x11,0x06,0x03,0x05,0x1b,0x18,0x17,0x03,0x00,
/*0010f0*/ 0x04,0x09,0x0c,0x0b,0x04,0x1a,0x19,0x16,0x0e,0x0d,0x0a,0x09,0x02,0x01,0x09,0x15,
/*001100*/ 0x1b,0x1a,0x07,0x00,0x09,0x08,0x06,0x02,0x07,0x06,0x06,0x05,0x01,0x00,0x03,0x04,
/*001110*/ 0x0b,0x0a,0x06,0x0c,0x19,0x18,0x11,0x03,0x10,0x06,0x17,0x16,0x13,0x0f,0x0e,0x05,
/*001120*/ 0x12,0x15,0x14,0x03,0x0d,0x0c,0x02,0x03,0x02,0x01,0x01,0x05,0x46,0x76,0x2f,0x37,
/*001130*/ 0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x3f,0x3c,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x10,0xfd,
/*001140*/ 0x3c,0x2f,0x17,0x3c,0xfd,0x3c,0x10,0xfd,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x17,0x3c,
/*001150*/ 0xfd,0x3c,0x10,0xfd,0x17,0x3c,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,
/*001160*/ 0x49,0x68,0xb9,0x00,0x05,0x00,0x1c,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,
/*001170*/ 0x37,0xb9,0x00,0x1c,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,0x23,0x35,0x23,0x35,0x33,
/*001180*/ 0x15,0x33,0x35,0x23,0x35,0x33,0x35,0x23,0x15,0x23,0x35,0x33,0x35,0x33,0x15,0x33,
/*001190*/ 0x15,0x23,0x15,0x33,0x01,0x8f,0x65,0xc5,0x64,0x62,0xca,0x64,0x64,0xcb,0x61,0x64,
/*0011a0*/ 0xc6,0x64,0x64,0x64,0x65,0x64,0x65,0x61,0x64,0xcb,0x61,0x66,0x64,0x62,0x64,0x65,
/*0011b0*/ 0x61,0x67,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,0x00,0x0d,0x00,0x6a,
/*0011c0*/ 0x40,0x2f,0x01,0x0e,0x0e,0x40,0x0f,0x00,0x0d,0x00,0x0a,0x09,0x04,0x03,0x03,0x04,
/*0011d0*/ 0x0c,0x0b,0x02,0x03,0x01,0x08,0x07,0x04,0x06,0x05,0x0d,0x09,0x08,0x03,0x0c,0x06,
/*0011e0*/ 0x05,0x04,0x01,0x03,0x00,0x0b,0x0a,0x07,0x03,0x06,0x03,0x03,0x02,0x01,0x01,0x05,
/*0011f0*/ 0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x17,0x3c,0x2f,0x17,0x3c,0xfd,0x17,
/*001200*/ 0x3c,0x01,0x2f,0x3c,0xfd,0x3c,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x2e,0x2e,0x00,0x31,
/*001210*/ 0x30,0x01,0x49,0x68,0xb9,0x00,0x05,0x00,0x0e,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,
/*001220*/ 0x38,0x11,0x37,0xb9,0x00,0x0e,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,0x23,0x35,0x23,
/*001230*/ 0x11,0x33,0x11,0x33,0x11,0x33,0x11,0x33,0x01,0x8f,0x65,0x61,0xc8,0x62,0x67,0x61,
/*001240*/ 0x64,0xc9,0xc8,0xc8,0x01,0x8e,0xfe,0xd4,0x01,0x2c,0xfe,0xd3,0x00,0x01,0x00,0x01,
/*001250*/ 0x00,0x01,0x01,0x8f,0x02,0x57,0x00,0x0f,0x00,0x7b,0x40,0x38,0x01,0x10,0x10,0x40,
/*001260*/ 0x11,0x00,0x02,0x01,0x0e,0x0d,0x06,0x03,0x05,0x04,0x0f,0x0a,0x09,0x03,0x00,0x0c,
/*001270*/ 0x0b,0x04,0x08,0x07,0x04,0x03,0x03,0x0f,0x0e,0x07,0x00,0x05,0x01,0x00,0x03,0x04,
/*001280*/ 0x06,0x02,0x07,0x06,0x06,0x0d,0x0c,0x0b,0x0a,0x06,0x08,0x09,0x08,0x03,0x03,0x02,
/*001290*/ 0x01,0x01,0x03,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x3c,
/*0012a0*/ 0x2f,0x3c,0xfd,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x17,0x3c,0xfd,
/*0012b0*/ 0x3c,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x2e,0x2e,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,
/*0012c0*/ 0x00,0x03,0x00,0x10,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,
/*0012d0*/ 0x10,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,0x21,0x35,0x21,0x35,0x21,0x11,0x21,0x15,
/*0012e0*/ 0x21,0x15,0x33,0x15,0x33,0x01,0x8f,0x65,0xfe,0xd7,0x01,0x2c,0xfe,0xd4,0x01,0x8e,
/*0012f0*/ 0xfe,0xd4,0xc8,0x64,0x65,0x64,0x62,0xca,0x01,0x2a,0x62,0x67,0x64,0x00,0x00,0x03,
/*001300*/ 0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,0x00,0x0f,0x00,0x13,0x00,0x17,0x00,0x8a,
/*001310*/ 0x40,0x49,0x01,0x18,0x18,0x40,0x19,0x00,0x17,0x16,0x13,0x12,0x08,0x07,0x04,0x07,
/*001320*/ 0x03,0x04,0x06,0x05,0x15,0x14,0x11,0x10,0x0e,0x0d,0x0a,0x02,0x01,0x09,0x09,0x04,
/*001330*/ 0x0f,0x0c,0x0b,0x03,0x00,0x17,0x14,0x05,0x04,0x01,0x05,0x00,0x06,0x02,0x12,0x11,
/*001340*/ 0x0b,0x0a,0x07,0x05,0x06,0x06,0x08,0x16,0x0f,0x0e,0x03,0x15,0x06,0x13,0x10,0x0d,
/*001350*/ 0x03,0x0c,0x09,0x08,0x03,0x03,0x02,0x01,0x01,0x05,0x46,0x76,0x2f,0x37,0x18,0x00,
/*001360*/ 0x3f,0x3c,0x3f,0x3c,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,
/*001370*/ 0x17,0x3c,0x01,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x00,0x31,
/*001380*/ 0x30,0x01,0x49,0x68,0xb9,0x00,0x05,0x00,0x18,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,
/*001390*/ 0x38,0x11,0x37,0xb9,0x00,0x18,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,0x23,0x35,0x23,
/*0013a0*/ 0x11,0x33,0x35,0x33,0x15,0x33,0x15,0x23,0x15,0x33,0x27,0x35,0x23,0x15,0x13,0x35,
/*0013b0*/ 0x23,0x15,0x01,0x8f,0x65,0xc5,0x64,0x64,0xc6,0x64,0x64,0x64,0x64,0xc6,0xc6,0xc6,
/*0013c0*/ 0x65,0x64,0x65,0x01,0x8d,0x64,0x65,0x61,0x67,0x67,0x62,0x62,0xfe,0xd4,0xc6,0xc6,
/*0013d0*/ 0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,0x00,0x0f,0x00,0x7d,0x40,0x38,
/*0013e0*/ 0x01,0x10,0x10,0x40,0x11,0x00,0x04,0x03,0x04,0x06,0x05,0x08,0x07,0x04,0x02,0x01,
/*0013f0*/ 0x0a,0x09,0x04,0x0f,0x00,0x0c,0x0b,0x04,0x0e,0x0d,0x0d,0x0c,0x01,0x03,0x00,0x07,
/*001400*/ 0x0e,0x07,0x03,0x02,0x03,0x06,0x07,0x04,0x0b,0x0a,0x06,0x0e,0x0f,0x0e,0x03,0x09,
/*001410*/ 0x08,0x02,0x05,0x04,0x01,0x01,0x0d,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,
/*001420*/ 0x3c,0x3f,0x3c,0x10,0xfd,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,
/*001430*/ 0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x00,
/*001440*/ 0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x0d,0x00,0x10,0x49,0x68,0x61,0xb0,0x40,0x52,
/*001450*/ 0x58,0x38,0x11,0x37,0xb9,0x00,0x10,0xff,0xc0,0x38,0x59,0x01,0x23,0x15,0x23,0x15,
/*001460*/ 0x23,0x35,0x33,0x35,0x33,0x35,0x23,0x15,0x23,0x35,0x21,0x01,0x8f,0x65,0x64,0x61,
/*001470*/ 0x65,0x63,0xcb,0x61,0x01,0x8e,0x01,0x91,0xc8,0xc8,0xc6,0xc8,0x66,0x64,0xc6,0x00,
/*001480*/ 0x00,0x03,0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,0x00,0x13,0x00,0x17,0x00,0x1b,
/*001490*/ 0x00,0x94,0x40,0x52,0x01,0x1c,0x1c,0x40,0x1d,0x00,0x1b,0x1a,0x17,0x16,0x0c,0x0b,
/*0014a0*/ 0x08,0x07,0x04,0x09,0x03,0x04,0x0a,0x09,0x06,0x03,0x05,0x19,0x18,0x15,0x14,0x12,
/*0014b0*/ 0x11,0x0e,0x02,0x01,0x09,0x0d,0x04,0x13,0x10,0x0f,0x03,0x00,0x1b,0x18,0x05,0x04,
/*0014c0*/ 0x01,0x05,0x00,0x06,0x02,0x16,0x15,0x0f,0x0e,0x0b,0x05,0x0a,0x06,0x0c,0x1a,0x19,
/*0014d0*/ 0x13,0x12,0x07,0x05,0x06,0x06,0x17,0x14,0x11,0x09,0x08,0x05,0x10,0x0d,0x0c,0x03,
/*0014e0*/ 0x03,0x02,0x01,0x01,0x05,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x2f,
/*0014f0*/ 0x17,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,0x17,
/*001500*/ 0x3c,0xfd,0x17,0x3c,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,
/*001510*/ 0xb9,0x00,0x05,0x00,0x1c,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,
/*001520*/ 0x00,0x1c,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,0x23,0x35,0x23,0x35,0x33,0x35,0x23,
/*001530*/ 0x35,0x33,0x35,0x33,0x15,0x33,0x15,0x23,0x15,0x33,0x27,0x35,0x23,0x15,0x13,0x35,
/*001540*/ 0x23,0x15,0x01,0x8f,0x65,0xc5,0x64,0x64,0x64,0x64,0xc6,0x64,0x64,0x64,0x64,0xc6,
/*001550*/ 0xc6,0xc6,0x65,0x64,0x65,0xc5,0x67,0x61,0x64,0x65,0x61,0x67,0x67,0x62,0x62,0xfe,
/*001560*/ 0xd4,0xc6,0xc6,0x00,0x00,0x03,0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,0x00,0x0f,
/*001570*/ 0x00,0x13,0x00,0x17,0x00,0x8a,0x40,0x49,0x01,0x18,0x18,0x40,0x19,0x00,0x17,0x16,
/*001580*/ 0x13,0x12,0x0c,0x0b,0x08,0x07,0x04,0x09,0x03,0x04,0x0a,0x09,0x06,0x03,0x05,0x15,
/*001590*/ 0x14,0x11,0x10,0x0e,0x02,0x01,0x07,0x0d,0x04,0x0f,0x00,0x17,0x14,0x05,0x01,0x00,
/*0015a0*/ 0x05,0x04,0x06,0x06,0x12,0x11,0x0f,0x0e,0x0b,0x05,0x0a,0x06,0x0c,0x13,0x09,0x08,
/*0015b0*/ 0x03,0x10,0x06,0x16,0x15,0x07,0x03,0x06,0x0d,0x0c,0x03,0x03,0x02,0x01,0x01,0x05,
/*0015c0*/ 0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x2f,0x17,0x3c,0xfd,0x17,0x3c,
/*0015d0*/ 0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x17,
/*0015e0*/ 0x3c,0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x05,0x00,0x18,0x49,
/*0015f0*/ 0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x18,0xff,0xc0,0x38,0x59,
/*001600*/ 0x25,0x23,0x15,0x23,0x35,0x23,0x35,0x33,0x35,0x23,0x35,0x33,0x35,0x33,0x15,0x33,
/*001610*/ 0x07,0x35,0x23,0x15,0x17,0x35,0x23,0x15,0x01,0x8f,0x65,0xc5,0x64,0x64,0x64,0x64,
/*001620*/ 0xc6,0x64,0x64,0xc6,0xc6,0xc6,0x65,0x64,0x65,0x61,0x67,0xc5,0x64,0x65,0xc5,0xc6,
/*001630*/ 0xc6,0xc8,0x62,0x62,0x00,0x02,0x00,0x01,0x00,0x65,0x00,0x63,0x01,0xf3,0x00,0x03,
/*001640*/ 0x00,0x07,0x00,0x54,0x40,0x20,0x01,0x08,0x08,0x40,0x09,0x00,0x07,0x04,0x03,0x03,
/*001650*/ 0x00,0x04,0x06,0x05,0x02,0x03,0x01,0x01,0x00,0x06,0x02,0x07,0x06,0x06,0x04,0x03,
/*001660*/ 0x02,0x05,0x04,0x01,0x01,0x46,0x76,0x2f,0x37,0x18,0x00,0x2f,0x3c,0x2f,0x3c,0x10,
/*001670*/ 0xfd,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,
/*001680*/ 0x49,0x68,0xb9,0x00,0x01,0x00,0x08,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,
/*001690*/ 0x37,0xb9,0x00,0x08,0xff,0xc0,0x38,0x59,0x13,0x23,0x35,0x33,0x11,0x23,0x35,0x33,
/*0016a0*/ 0x63,0x62,0x62,0x62,0x62,0x01,0x91,0x62,0xfe,0x72,0x62,0x00,0x00,0x02,0x00,0x01,
/*0016b0*/ 0x00,0x01,0x00,0x63,0x01,0xf3,0x00,0x03,0x00,0x07,0x00,0x55,0x40,0x21,0x01,0x08,
/*0016c0*/ 0x08,0x40,0x09,0x00,0x07,0x04,0x03,0x03,0x00,0x04,0x06,0x05,0x02,0x03,0x01,0x07,
/*0016d0*/ 0x06,0x07,0x04,0x01,0x00,0x06,0x02,0x03,0x02,0x05,0x04,0x01,0x01,0x01,0x46,0x76,
/*0016e0*/ 0x2f,0x37,0x18,0x00,0x3f,0x3c,0x2f,0x3c,0x10,0xfd,0x3c,0x10,0xfd,0x3c,0x01,0x2f,
/*0016f0*/ 0x17,0x3c,0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x01,0x00,0x08,
/*001700*/ 0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x08,0xff,0xc0,0x38,
/*001710*/ 0x59,0x13,0x23,0x35,0x33,0x11,0x23,0x35,0x33,0x63,0x62,0x62,0x62,0x62,0x01,0x91,
/*001720*/ 0x62,0xfe,0x0e,0xc6,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x2b,0x01,0xf3,0x00,0x13,
/*001730*/ 0x00,0x8a,0x40,0x42,0x01,0x14,0x14,0x40,0x15,0x00,0x10,0x0f,0x13,0x08,0x07,0x03,
/*001740*/ 0x00,0x04,0x09,0x12,0x11,0x0a,0x09,0x06,0x02,0x01,0x07,0x05,0x04,0x0b,0x0c,0x0b,
/*001750*/ 0x04,0x03,0x03,0x04,0x0e,0x0d,0x01,0x00,0x06,0x12,0x07,0x06,0x06,0x08,0x05,0x04,
/*001760*/ 0x06,0x0b,0x0a,0x0d,0x0c,0x06,0x0f,0x03,0x02,0x03,0x0e,0x13,0x12,0x11,0x10,0x02,
/*001770*/ 0x09,0x08,0x01,0x01,0x0d,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x2f,
/*001780*/ 0x3c,0x2f,0x17,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x10,0xfd,0x3c,0x10,0xfd,0x3c,
/*001790*/ 0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x2e,0x2e,
/*0017a0*/ 0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x0d,0x00,0x14,0x49,0x68,0x61,0xb0,0x40,
/*0017b0*/ 0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x14,0xff,0xc0,0x38,0x59,0x01,0x23,0x15,0x23,
/*0017c0*/ 0x15,0x33,0x15,0x33,0x15,0x23,0x35,0x23,0x35,0x23,0x35,0x33,0x35,0x33,0x35,0x33,
/*0017d0*/ 0x01,0x2b,0x65,0x63,0x64,0x64,0x62,0x64,0x64,0x65,0x63,0x62,0x01,0x91,0x64,0x67,
/*0017e0*/ 0x64,0x61,0x65,0x64,0x61,0x64,0x64,0x00,0x00,0x02,0x00,0x01,0x00,0x65,0x01,0x2b,
/*0017f0*/ 0x01,0x8f,0x00,0x03,0x00,0x07,0x00,0x54,0x40,0x1e,0x01,0x08,0x08,0x40,0x09,0x00,
/*001800*/ 0x07,0x06,0x05,0x04,0x03,0x02,0x01,0x00,0x01,0x00,0x06,0x02,0x07,0x06,0x06,0x04,
/*001810*/ 0x05,0x04,0x03,0x02,0x02,0x01,0x01,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x2f,
/*001820*/ 0x3c,0x10,0xfd,0x3c,0x10,0xfd,0x3c,0x01,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,
/*001830*/ 0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x01,0x00,0x08,0x49,0x68,0x61,0xb0,0x40,
/*001840*/ 0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x08,0xff,0xc0,0x38,0x59,0x01,0x21,0x35,0x21,
/*001850*/ 0x11,0x21,0x35,0x21,0x01,0x2b,0xfe,0xd6,0x01,0x2a,0xfe,0xd6,0x01,0x2a,0x01,0x2d,
/*001860*/ 0x62,0xfe,0xd6,0x62,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x2b,0x01,0xf3,0x00,0x13,
/*001870*/ 0x00,0x8e,0x40,0x44,0x01,0x14,0x14,0x40,0x15,0x00,0x10,0x0f,0x04,0x03,0x03,0x04,
/*001880*/ 0x0e,0x0d,0x06,0x03,0x05,0x0c,0x0b,0x08,0x03,0x07,0x04,0x02,0x01,0x12,0x11,0x0a,
/*001890*/ 0x03,0x09,0x04,0x13,0x00,0x13,0x12,0x06,0x00,0x09,0x01,0x00,0x03,0x08,0x06,0x02,
/*0018a0*/ 0x07,0x03,0x02,0x03,0x06,0x06,0x04,0x0b,0x0a,0x06,0x11,0x10,0x0d,0x0c,0x06,0x0e,
/*0018b0*/ 0x0f,0x0e,0x05,0x04,0x01,0x01,0x05,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x2f,
/*0018c0*/ 0x3c,0x10,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,
/*0018d0*/ 0x10,0xfd,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x17,
/*0018e0*/ 0x3c,0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x05,0x00,0x14,0x49,
/*0018f0*/ 0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x14,0xff,0xc0,0x38,0x59,
/*001900*/ 0x25,0x23,0x15,0x23,0x15,0x23,0x35,0x33,0x35,0x33,0x35,0x23,0x35,0x23,0x35,0x33,
/*001910*/ 0x15,0x33,0x15,0x33,0x01,0x2b,0x65,0x64,0x61,0x65,0x63,0x64,0x64,0x62,0x64,0x64,
/*001920*/ 0xc9,0x64,0x64,0x62,0x64,0x67,0x63,0x62,0x65,0x64,0x00,0x02,0x00,0x01,0x00,0x01,
/*001930*/ 0x01,0x8f,0x02,0x57,0x00,0x0f,0x00,0x13,0x00,0x86,0x40,0x40,0x01,0x14,0x14,0x40,
/*001940*/ 0x15,0x00,0x02,0x01,0x05,0x03,0x0e,0x0d,0x06,0x03,0x05,0x04,0x0f,0x00,0x08,0x07,
/*001950*/ 0x04,0x0a,0x09,0x13,0x10,0x04,0x12,0x11,0x0c,0x0b,0x04,0x05,0x03,0x05,0x01,0x00,
/*001960*/ 0x03,0x04,0x06,0x03,0x02,0x09,0x08,0x06,0x0f,0x0e,0x0b,0x07,0x06,0x05,0x0a,0x13,
/*001970*/ 0x12,0x06,0x10,0x11,0x10,0x01,0x0d,0x0c,0x03,0x01,0x09,0x46,0x76,0x2f,0x37,0x18,
/*001980*/ 0x00,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x3c,0x2f,0x17,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,
/*001990*/ 0x17,0x3c,0x01,0x2f,0x17,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x17,
/*0019a0*/ 0x3c,0x10,0xfd,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x09,0x00,0x14,0x49,
/*0019b0*/ 0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x14,0xff,0xc0,0x38,0x59,
/*0019c0*/ 0x01,0x23,0x15,0x23,0x35,0x33,0x35,0x23,0x15,0x23,0x35,0x33,0x35,0x33,0x15,0x33,
/*0019d0*/ 0x03,0x23,0x35,0x33,0x01,0x8f,0x65,0xc5,0xc8,0xcb,0x61,0x64,0xc6,0x64,0xc8,0x62,
/*0019e0*/ 0x62,0x01,0x2d,0x64,0x62,0xca,0x64,0x62,0x64,0x65,0xfe,0x0f,0x62,0x00,0x00,0x03,
/*0019f0*/ 0x00,0x01,0xff,0x9d,0x01,0xf3,0x01,0xf3,0x00,0x0b,0x00,0x0f,0x00,0x15,0x00,0x8a,
/*001a00*/ 0x40,0x42,0x01,0x16,0x16,0x40,0x17,0x00,0x14,0x13,0x12,0x11,0x13,0x12,0x08,0x07,
/*001a10*/ 0x04,0x05,0x03,0x04,0x06,0x05,0x11,0x10,0x0d,0x0c,0x0a,0x05,0x09,0x04,0x0b,0x00,
/*001a20*/ 0x0f,0x0e,0x04,0x15,0x02,0x01,0x03,0x14,0x0f,0x0c,0x06,0x00,0x0e,0x0d,0x06,0x15,
/*001a30*/ 0x10,0x09,0x08,0x03,0x02,0x0b,0x0a,0x07,0x03,0x06,0x02,0x05,0x04,0x01,0x03,0x00,
/*001a40*/ 0x01,0x01,0x05,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x17,0x3c,0x3f,0x17,0x3c,0x2f,
/*001a50*/ 0x3c,0x2f,0x3c,0x2f,0x3c,0xfd,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x17,0x3c,0xfd,0x3c,
/*001a60*/ 0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x00,0x2e,0x2e,0x2e,0x2e,0x31,
/*001a70*/ 0x30,0x01,0x49,0x68,0xb9,0x00,0x05,0x00,0x16,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,
/*001a80*/ 0x38,0x11,0x37,0xb9,0x00,0x16,0xff,0xc0,0x38,0x59,0x25,0x21,0x15,0x23,0x35,0x23,
/*001a90*/ 0x11,0x33,0x35,0x21,0x15,0x33,0x03,0x35,0x23,0x15,0x37,0x35,0x21,0x11,0x33,0x11,
/*001aa0*/ 0x01,0xf3,0xfe,0xd3,0x61,0x64,0x64,0x01,0x2a,0x64,0x64,0x62,0x62,0xfe,0xd6,0x62,
/*001ab0*/ 0x01,0x64,0x65,0x01,0x8d,0x64,0x65,0xfe,0xd7,0x62,0x62,0xc8,0x62,0xfe,0x72,0x01,
/*001ac0*/ 0x2c,0x00,0x00,0x02,0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,0x00,0x09,0x00,0x0d,
/*001ad0*/ 0x00,0x6b,0x40,0x30,0x01,0x0e,0x0e,0x40,0x0f,0x00,0x0d,0x0c,0x08,0x04,0x03,0x05,
/*001ae0*/ 0x07,0x04,0x06,0x05,0x0b,0x02,0x01,0x03,0x0a,0x04,0x09,0x00,0x0d,0x0a,0x06,0x03,
/*001af0*/ 0x02,0x0c,0x0b,0x07,0x03,0x06,0x06,0x08,0x09,0x08,0x03,0x05,0x04,0x01,0x03,0x00,
/*001b00*/ 0x01,0x01,0x05,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x17,0x3c,0x3f,0x3c,0x10,0xfd,
/*001b10*/ 0x17,0x3c,0x2f,0x3c,0xfd,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,
/*001b20*/ 0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x05,0x00,0x0e,0x49,0x68,0x61,0xb0,
/*001b30*/ 0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x0e,0xff,0xc0,0x38,0x59,0x25,0x23,0x35,
/*001b40*/ 0x23,0x15,0x23,0x11,0x33,0x35,0x21,0x03,0x35,0x23,0x15,0x01,0x8f,0x62,0xca,0x62,
/*001b50*/ 0x64,0x01,0x2a,0x64,0xc6,0x01,0xc8,0xc8,0x01,0xf2,0x64,0xfe,0xd6,0xc6,0xc6,0x00,
/*001b60*/ 0x00,0x03,0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,0x00,0x0b,0x00,0x0f,0x00,0x13,
/*001b70*/ 0x00,0x82,0x40,0x41,0x01,0x14,0x14,0x40,0x15,0x00,0x13,0x12,0x0f,0x03,0x0e,0x04,
/*001b80*/ 0x04,0x03,0x11,0x10,0x0d,0x0c,0x0a,0x09,0x06,0x02,0x01,0x09,0x05,0x04,0x0b,0x08,
/*001b90*/ 0x07,0x03,0x00,0x13,0x10,0x01,0x03,0x00,0x06,0x02,0x0e,0x07,0x06,0x03,0x0d,0x06,
/*001ba0*/ 0x04,0x12,0x0b,0x0a,0x03,0x11,0x06,0x0f,0x0c,0x09,0x03,0x08,0x05,0x04,0x03,0x03,
/*001bb0*/ 0x02,0x01,0x01,0x03,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x2f,0x17,
/*001bc0*/ 0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,0x17,0x3c,
/*001bd0*/ 0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,
/*001be0*/ 0x03,0x00,0x14,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x14,
/*001bf0*/ 0xff,0xc0,0x38,0x59,0x25,0x23,0x15,0x21,0x11,0x21,0x15,0x33,0x15,0x23,0x15,0x33,
/*001c00*/ 0x27,0x35,0x23,0x15,0x13,0x35,0x23,0x15,0x01,0x8f,0x65,0xfe,0xd7,0x01,0x2a,0x64,
/*001c10*/ 0x64,0x64,0x64,0xc6,0xc6,0xc6,0x65,0x64,0x02,0x56,0x65,0x61,0x67,0x67,0x62,0x62,
/*001c20*/ 0xfe,0xd4,0xc6,0xc6,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,0x00,0x0b,
/*001c30*/ 0x00,0x64,0x40,0x2a,0x01,0x0c,0x0c,0x40,0x0d,0x00,0x07,0x06,0x0b,0x04,0x03,0x00,
/*001c40*/ 0x0a,0x09,0x06,0x05,0x02,0x05,0x01,0x04,0x08,0x07,0x09,0x08,0x01,0x03,0x00,0x06,
/*001c50*/ 0x0a,0x03,0x02,0x06,0x04,0x0b,0x0a,0x03,0x05,0x04,0x01,0x01,0x07,0x46,0x76,0x2f,
/*001c60*/ 0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,
/*001c70*/ 0x3c,0xfd,0x17,0x3c,0x2e,0x2e,0x2e,0x2e,0x00,0x2e,0x2e,0x31,0x30,0x01,0x49,0x68,
/*001c80*/ 0xb9,0x00,0x07,0x00,0x0c,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,
/*001c90*/ 0x00,0x0c,0xff,0xc0,0x38,0x59,0x01,0x21,0x11,0x21,0x15,0x21,0x35,0x23,0x11,0x33,
/*001ca0*/ 0x35,0x21,0x01,0x8f,0xfe,0xd4,0x01,0x2c,0xfe,0xd6,0x64,0x64,0x01,0x2a,0x01,0xf5,
/*001cb0*/ 0xfe,0x6e,0x62,0x65,0x01,0x8d,0x64,0x00,0x00,0x02,0x00,0x01,0x00,0x01,0x01,0x8f,
/*001cc0*/ 0x02,0x57,0x00,0x07,0x00,0x0b,0x00,0x65,0x40,0x2c,0x01,0x0c,0x0c,0x40,0x0d,0x00,
/*001cd0*/ 0x0b,0x0a,0x04,0x04,0x03,0x09,0x08,0x06,0x02,0x01,0x05,0x05,0x04,0x07,0x00,0x0b,
/*001ce0*/ 0x08,0x01,0x03,0x00,0x06,0x02,0x0a,0x07,0x06,0x03,0x09,0x06,0x04,0x05,0x04,0x03,
/*001cf0*/ 0x03,0x02,0x01,0x01,0x03,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x10,
/*001d00*/ 0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,
/*001d10*/ 0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x03,0x00,0x0c,0x49,0x68,0x61,0xb0,
/*001d20*/ 0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x0c,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,
/*001d30*/ 0x21,0x11,0x21,0x15,0x33,0x03,0x11,0x23,0x11,0x01,0x8f,0x65,0xfe,0xd7,0x01,0x2a,
/*001d40*/ 0x64,0x64,0xc6,0x65,0x64,0x02,0x56,0x65,0xfe,0x73,0x01,0x8e,0xfe,0x72,0x00,0x01,
/*001d50*/ 0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,0x00,0x0d,0x00,0x6d,0x40,0x2f,0x01,0x0e,
/*001d60*/ 0x0e,0x40,0x0f,0x00,0x0d,0x08,0x07,0x04,0x03,0x00,0x0c,0x0b,0x06,0x05,0x02,0x05,
/*001d70*/ 0x01,0x04,0x0a,0x09,0x0b,0x0a,0x01,0x03,0x00,0x06,0x0c,0x05,0x04,0x06,0x03,0x02,
/*001d80*/ 0x07,0x06,0x06,0x08,0x0d,0x0c,0x03,0x09,0x08,0x01,0x01,0x09,0x46,0x76,0x2f,0x37,
/*001d90*/ 0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x10,0xfd,0x17,
/*001da0*/ 0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x00,0x31,0x30,
/*001db0*/ 0x01,0x49,0x68,0xb9,0x00,0x09,0x00,0x0e,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,
/*001dc0*/ 0x11,0x37,0xb9,0x00,0x0e,0xff,0xc0,0x38,0x59,0x01,0x21,0x15,0x33,0x15,0x23,0x15,
/*001dd0*/ 0x21,0x15,0x21,0x11,0x33,0x35,0x21,0x01,0x8f,0xfe,0xd4,0xc8,0xc8,0x01,0x2c,0xfe,
/*001de0*/ 0x72,0x64,0x01,0x2a,0x01,0xf5,0x67,0x61,0xca,0x62,0x01,0xf2,0x64,0x00,0x00,0x01,
/*001df0*/ 0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,0x00,0x0b,0x00,0x62,0x40,0x29,0x01,0x0c,
/*001e00*/ 0x0c,0x40,0x0d,0x00,0x0b,0x04,0x03,0x00,0x0a,0x09,0x06,0x05,0x02,0x05,0x01,0x04,
/*001e10*/ 0x08,0x07,0x09,0x08,0x01,0x03,0x00,0x06,0x0a,0x05,0x04,0x06,0x03,0x02,0x0b,0x0a,
/*001e20*/ 0x03,0x07,0x06,0x01,0x01,0x07,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,
/*001e30*/ 0x2f,0x3c,0xfd,0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2e,0x2e,
/*001e40*/ 0x2e,0x2e,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x07,0x00,0x0c,0x49,0x68,0x61,
/*001e50*/ 0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x0c,0xff,0xc0,0x38,0x59,0x01,0x21,
/*001e60*/ 0x15,0x33,0x15,0x23,0x11,0x23,0x11,0x33,0x35,0x21,0x01,0x8f,0xfe,0xd4,0xc8,0xc8,
/*001e70*/ 0x62,0x64,0x01,0x2a,0x01,0xf5,0x67,0x61,0xfe,0xd4,0x01,0xf2,0x64,0x00,0x00,0x01,
/*001e80*/ 0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,0x00,0x0f,0x00,0x7c,0x40,0x39,0x01,0x10,
/*001e90*/ 0x10,0x40,0x11,0x00,0x03,0x02,0x0e,0x0d,0x05,0x00,0x0a,0x06,0x05,0x02,0x01,0x05,
/*001ea0*/ 0x09,0x04,0x04,0x03,0x0c,0x0b,0x04,0x0f,0x08,0x07,0x03,0x00,0x0b,0x0a,0x06,0x00,
/*001eb0*/ 0x09,0x05,0x04,0x03,0x08,0x06,0x06,0x0d,0x0c,0x06,0x0e,0x0f,0x0e,0x02,0x07,0x06,
/*001ec0*/ 0x03,0x01,0x00,0x01,0x01,0x03,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,
/*001ed0*/ 0x3f,0x3c,0x10,0xfd,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x17,0x3c,
/*001ee0*/ 0xfd,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x00,0x2e,0x2e,0x31,0x30,0x01,
/*001ef0*/ 0x49,0x68,0xb9,0x00,0x03,0x00,0x10,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,
/*001f00*/ 0x37,0xb9,0x00,0x10,0xff,0xc0,0x38,0x59,0x25,0x21,0x35,0x23,0x11,0x33,0x35,0x21,
/*001f10*/ 0x15,0x21,0x11,0x33,0x35,0x23,0x35,0x33,0x01,0x8f,0xfe,0xd6,0x64,0x64,0x01,0x2a,
/*001f20*/ 0xfe,0xd4,0xca,0x64,0xc6,0x01,0x65,0x01,0x8d,0x64,0x62,0xfe,0x6e,0xcb,0x61,0x00,
/*001f30*/ 0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,0x00,0x0b,0x00,0x65,0x40,0x2c,
/*001f40*/ 0x01,0x0c,0x0c,0x40,0x0d,0x00,0x0a,0x09,0x02,0x03,0x01,0x04,0x0b,0x00,0x08,0x07,
/*001f50*/ 0x04,0x03,0x03,0x04,0x06,0x05,0x03,0x02,0x06,0x08,0x09,0x08,0x02,0x0b,0x0a,0x07,
/*001f60*/ 0x03,0x06,0x03,0x05,0x04,0x01,0x03,0x00,0x01,0x01,0x05,0x46,0x76,0x2f,0x37,0x18,
/*001f70*/ 0x00,0x3f,0x17,0x3c,0x3f,0x17,0x3c,0x3f,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x3c,0xfd,
/*001f80*/ 0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x05,
/*001f90*/ 0x00,0x0c,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x0c,0xff,
/*001fa0*/ 0xc0,0x38,0x59,0x25,0x23,0x11,0x23,0x11,0x23,0x11,0x33,0x15,0x33,0x35,0x33,0x01,
/*001fb0*/ 0x8f,0x62,0xca,0x62,0x62,0xca,0x62,0x01,0x01,0x2c,0xfe,0xd4,0x02,0x56,0xc8,0xc8,
/*001fc0*/ 0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x2b,0x02,0x57,0x00,0x0b,0x00,0x6c,0x40,0x30,
/*001fd0*/ 0x01,0x0c,0x0c,0x40,0x0d,0x00,0x0b,0x08,0x07,0x03,0x00,0x08,0x09,0x06,0x05,0x02,
/*001fe0*/ 0x03,0x01,0x08,0x03,0x04,0x03,0x04,0x0a,0x09,0x0b,0x0a,0x03,0x03,0x02,0x06,0x00,
/*001ff0*/ 0x09,0x08,0x05,0x03,0x04,0x06,0x06,0x07,0x06,0x03,0x01,0x00,0x01,0x01,0x01,0x46,
/*002000*/ 0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,
/*002010*/ 0x3c,0x01,0x2f,0x3c,0xfd,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x00,0x31,
/*002020*/ 0x30,0x01,0x49,0x68,0xb9,0x00,0x01,0x00,0x0c,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,
/*002030*/ 0x38,0x11,0x37,0xb9,0x00,0x0c,0xff,0xc0,0x38,0x59,0x25,0x21,0x35,0x33,0x11,0x23,
/*002040*/ 0x35,0x21,0x15,0x23,0x11,0x33,0x01,0x2b,0xfe,0xd6,0x64,0x64,0x01,0x2a,0x64,0x64,
/*002050*/ 0x01,0x62,0x01,0x92,0x62,0x62,0xfe,0x6d,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x8f,
/*002060*/ 0x02,0x57,0x00,0x09,0x00,0x61,0x40,0x27,0x01,0x0a,0x0a,0x40,0x0b,0x00,0x04,0x03,
/*002070*/ 0x02,0x01,0x08,0x07,0x05,0x00,0x06,0x05,0x04,0x09,0x00,0x05,0x01,0x00,0x03,0x04,
/*002080*/ 0x06,0x02,0x07,0x06,0x06,0x08,0x09,0x08,0x03,0x03,0x02,0x01,0x01,0x03,0x46,0x76,
/*002090*/ 0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x3c,0x10,0xfd,0x17,0x3c,0x01,
/*0020a0*/ 0x2f,0x3c,0xfd,0x3c,0x10,0xfd,0x3c,0x2e,0x2e,0x2e,0x2e,0x00,0x31,0x30,0x01,0x49,
/*0020b0*/ 0x68,0xb9,0x00,0x03,0x00,0x0a,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,
/*0020c0*/ 0xb9,0x00,0x0a,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,0x21,0x35,0x21,0x11,0x23,0x35,
/*0020d0*/ 0x33,0x01,0x8f,0x65,0xfe,0xd7,0x01,0x2c,0x64,0xc6,0x65,0x64,0x62,0x01,0x92,0x62,
/*0020e0*/ 0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,0x00,0x0f,0x00,0x76,0x40,0x37,
/*0020f0*/ 0x01,0x10,0x10,0x40,0x11,0x00,0x0f,0x0e,0x0e,0x0d,0x0a,0x09,0x02,0x05,0x01,0x04,
/*002100*/ 0x0f,0x0c,0x0b,0x03,0x00,0x08,0x07,0x04,0x03,0x03,0x04,0x06,0x05,0x0d,0x0c,0x07,
/*002110*/ 0x06,0x03,0x02,0x06,0x08,0x09,0x08,0x02,0x0b,0x0a,0x07,0x03,0x06,0x03,0x05,0x04,
/*002120*/ 0x01,0x03,0x00,0x01,0x01,0x05,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x17,0x3c,0x3f,
/*002130*/ 0x17,0x3c,0x3f,0x3c,0x10,0xfd,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,
/*002140*/ 0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x00,0x2e,0x2e,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,
/*002150*/ 0x05,0x00,0x10,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x10,
/*002160*/ 0xff,0xc0,0x38,0x59,0x25,0x23,0x11,0x23,0x11,0x23,0x11,0x33,0x15,0x33,0x35,0x33,
/*002170*/ 0x15,0x23,0x15,0x33,0x01,0x8f,0x62,0xca,0x62,0x62,0xca,0x62,0x64,0x64,0x01,0x01,
/*002180*/ 0x2c,0xfe,0xd4,0x02,0x56,0xc8,0xc8,0xc6,0x67,0x00,0x00,0x01,0x00,0x01,0x00,0x01,
/*002190*/ 0x01,0x8f,0x02,0x57,0x00,0x07,0x00,0x53,0x40,0x1f,0x01,0x08,0x08,0x40,0x09,0x00,
/*0021a0*/ 0x03,0x02,0x07,0x00,0x06,0x02,0x01,0x03,0x05,0x04,0x04,0x03,0x07,0x06,0x06,0x00,
/*0021b0*/ 0x05,0x04,0x03,0x01,0x00,0x01,0x01,0x03,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,
/*0021c0*/ 0x3f,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2e,0x2e,0x00,0x2e,0x2e,
/*0021d0*/ 0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x03,0x00,0x08,0x49,0x68,0x61,0xb0,0x40,0x52,
/*0021e0*/ 0x58,0x38,0x11,0x37,0xb9,0x00,0x08,0xff,0xc0,0x38,0x59,0x25,0x21,0x35,0x23,0x11,
/*0021f0*/ 0x33,0x11,0x21,0x01,0x8f,0xfe,0xd6,0x64,0x62,0x01,0x2c,0x01,0x65,0x01,0xf1,0xfe,
/*002200*/ 0x0c,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0xf3,0x02,0x57,0x00,0x13,0x00,0x8a,
/*002210*/ 0x40,0x43,0x01,0x14,0x14,0x40,0x15,0x00,0x10,0x0f,0x05,0x00,0x12,0x11,0x02,0x03,
/*002220*/ 0x01,0x04,0x13,0x00,0x04,0x03,0x04,0x0e,0x0d,0x06,0x03,0x05,0x0c,0x0b,0x08,0x03,
/*002230*/ 0x07,0x04,0x0a,0x09,0x07,0x06,0x03,0x03,0x02,0x06,0x11,0x0d,0x0c,0x03,0x10,0x05,
/*002240*/ 0x04,0x06,0x0e,0x0f,0x0e,0x02,0x13,0x12,0x0b,0x03,0x0a,0x03,0x09,0x08,0x01,0x03,
/*002250*/ 0x00,0x01,0x01,0x09,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x17,0x3c,0x3f,0x17,0x3c,
/*002260*/ 0x3f,0x3c,0x10,0xfd,0x3c,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x17,
/*002270*/ 0x3c,0x2f,0x17,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x00,0x31,
/*002280*/ 0x30,0x01,0x49,0x68,0xb9,0x00,0x09,0x00,0x14,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,
/*002290*/ 0x38,0x11,0x37,0xb9,0x00,0x14,0xff,0xc0,0x38,0x59,0x25,0x23,0x11,0x23,0x15,0x23,
/*0022a0*/ 0x35,0x23,0x11,0x23,0x11,0x33,0x15,0x33,0x15,0x33,0x35,0x33,0x35,0x33,0x01,0xf3,
/*0022b0*/ 0x62,0x67,0x61,0x67,0x61,0x62,0x64,0x67,0x64,0x61,0x01,0x01,0x90,0x64,0x64,0xfe,
/*0022c0*/ 0x70,0x02,0x56,0x65,0x63,0x64,0x64,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x8f,
/*0022d0*/ 0x02,0x57,0x00,0x0f,0x00,0x79,0x40,0x38,0x01,0x10,0x10,0x40,0x11,0x00,0x0c,0x04,
/*0022e0*/ 0x03,0x03,0x0b,0x05,0x07,0x0e,0x0d,0x02,0x03,0x01,0x04,0x0f,0x00,0x0a,0x09,0x06,
/*0022f0*/ 0x03,0x05,0x04,0x08,0x07,0x03,0x02,0x06,0x0c,0x05,0x04,0x06,0x0b,0x0a,0x0d,0x0c,
/*002300*/ 0x02,0x0f,0x0e,0x09,0x03,0x08,0x03,0x07,0x06,0x01,0x03,0x00,0x01,0x01,0x07,0x46,
/*002310*/ 0x76,0x2f,0x37,0x18,0x00,0x3f,0x17,0x3c,0x3f,0x17,0x3c,0x3f,0x3c,0x2f,0x3c,0xfd,
/*002320*/ 0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x10,
/*002330*/ 0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x07,0x00,0x10,0x49,0x68,
/*002340*/ 0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x10,0xff,0xc0,0x38,0x59,0x25,
/*002350*/ 0x23,0x11,0x23,0x35,0x23,0x11,0x23,0x11,0x33,0x15,0x33,0x15,0x33,0x35,0x33,0x01,
/*002360*/ 0x8f,0x62,0x64,0x67,0x61,0x62,0x64,0x67,0x61,0x01,0x01,0x2d,0x63,0xfe,0x70,0x02,
/*002370*/ 0x56,0x65,0x63,0xc8,0x00,0x02,0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,0x00,0x0b,
/*002380*/ 0x00,0x0f,0x00,0x6f,0x40,0x35,0x01,0x10,0x10,0x40,0x11,0x00,0x0f,0x0e,0x08,0x07,
/*002390*/ 0x04,0x05,0x03,0x04,0x06,0x05,0x0d,0x0c,0x0a,0x02,0x01,0x05,0x09,0x04,0x0b,0x00,
/*0023a0*/ 0x0f,0x0c,0x05,0x04,0x01,0x05,0x00,0x06,0x02,0x0e,0x0d,0x0b,0x0a,0x07,0x05,0x06,
/*0023b0*/ 0x06,0x08,0x09,0x08,0x03,0x03,0x02,0x01,0x01,0x05,0x46,0x76,0x2f,0x37,0x18,0x00,
/*0023c0*/ 0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,0x3c,0xfd,
/*0023d0*/ 0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x05,
/*0023e0*/ 0x00,0x10,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x10,0xff,
/*0023f0*/ 0xc0,0x38,0x59,0x25,0x23,0x15,0x23,0x35,0x23,0x11,0x33,0x35,0x33,0x15,0x33,0x03,
/*002400*/ 0x11,0x23,0x11,0x01,0x8f,0x65,0xc5,0x64,0x64,0xc6,0x64,0x64,0xc6,0x65,0x64,0x65,
/*002410*/ 0x01,0x8d,0x64,0x65,0xfe,0x73,0x01,0x8e,0xfe,0x72,0x00,0x02,0x00,0x01,0x00,0x01,
/*002420*/ 0x01,0x8f,0x02,0x57,0x00,0x09,0x00,0x0d,0x00,0x6b,0x40,0x30,0x01,0x0e,0x0e,0x40,
/*002430*/ 0x0f,0x00,0x0d,0x04,0x03,0x03,0x0c,0x04,0x06,0x05,0x0b,0x0a,0x08,0x02,0x01,0x05,
/*002440*/ 0x07,0x04,0x09,0x00,0x0d,0x0a,0x01,0x03,0x00,0x06,0x03,0x02,0x0c,0x09,0x08,0x03,
/*002450*/ 0x0b,0x06,0x06,0x07,0x06,0x03,0x05,0x04,0x01,0x01,0x05,0x46,0x76,0x2f,0x37,0x18,
/*002460*/ 0x00,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x01,0x2f,
/*002470*/ 0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,
/*002480*/ 0x00,0x05,0x00,0x0e,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,
/*002490*/ 0x0e,0xff,0xc0,0x38,0x59,0x01,0x23,0x15,0x23,0x15,0x23,0x11,0x21,0x15,0x33,0x07,
/*0024a0*/ 0x35,0x23,0x15,0x01,0x8f,0x65,0xc7,0x62,0x01,0x2a,0x64,0x64,0xc6,0x01,0x2d,0x64,
/*0024b0*/ 0xc8,0x02,0x56,0x65,0xc5,0xc6,0xc6,0x00,0x00,0x03,0x00,0x01,0x00,0x01,0x01,0x8f,
/*0024c0*/ 0x02,0x57,0x00,0x09,0x00,0x0f,0x00,0x13,0x00,0x85,0x40,0x42,0x01,0x14,0x14,0x40,
/*0024d0*/ 0x15,0x00,0x11,0x0f,0x0e,0x03,0x10,0x05,0x00,0x13,0x12,0x0d,0x0c,0x06,0x05,0x02,
/*0024e0*/ 0x07,0x01,0x04,0x04,0x03,0x0b,0x0a,0x08,0x03,0x07,0x04,0x09,0x00,0x13,0x03,0x02,
/*0024f0*/ 0x03,0x10,0x06,0x00,0x0c,0x0b,0x09,0x08,0x05,0x05,0x04,0x06,0x06,0x0e,0x0d,0x06,
/*002500*/ 0x12,0x0f,0x0a,0x03,0x11,0x07,0x06,0x03,0x01,0x00,0x01,0x01,0x03,0x46,0x76,0x2f,
/*002510*/ 0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x2f,0x17,0x3c,0xfd,0x3c,0x10,0xfd,0x17,0x3c,
/*002520*/ 0x10,0xfd,0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x10,
/*002530*/ 0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x03,0x00,0x14,0x49,0x68,
/*002540*/ 0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x14,0xff,0xc0,0x38,0x59,0x25,
/*002550*/ 0x21,0x35,0x23,0x11,0x33,0x35,0x33,0x15,0x33,0x03,0x11,0x23,0x15,0x33,0x15,0x07,
/*002560*/ 0x35,0x23,0x15,0x01,0x8f,0xfe,0xd6,0x64,0x64,0xc6,0x64,0x64,0xc6,0x64,0x02,0x62,
/*002570*/ 0x01,0x65,0x01,0x8d,0x64,0x65,0xfe,0xd7,0x01,0x2a,0xc5,0x65,0x64,0x62,0x62,0x00,
/*002580*/ 0x00,0x02,0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,0x00,0x0d,0x00,0x11,0x00,0x7c,
/*002590*/ 0x40,0x3c,0x01,0x12,0x12,0x40,0x13,0x00,0x11,0x04,0x03,0x03,0x10,0x04,0x06,0x05,
/*0025a0*/ 0x0f,0x0e,0x0c,0x0b,0x08,0x02,0x01,0x07,0x07,0x04,0x0d,0x0a,0x09,0x03,0x00,0x0d,
/*0025b0*/ 0x0c,0x07,0x00,0x11,0x0e,0x0b,0x03,0x0a,0x06,0x03,0x02,0x10,0x09,0x08,0x03,0x0f,
/*0025c0*/ 0x06,0x06,0x07,0x06,0x03,0x05,0x04,0x01,0x03,0x00,0x01,0x01,0x05,0x46,0x76,0x2f,
/*0025d0*/ 0x37,0x18,0x00,0x3f,0x17,0x3c,0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,
/*0025e0*/ 0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,
/*0025f0*/ 0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x05,0x00,0x12,0x49,0x68,0x61,0xb0,0x40,
/*002600*/ 0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x12,0xff,0xc0,0x38,0x59,0x25,0x23,0x35,0x23,
/*002610*/ 0x15,0x23,0x11,0x21,0x15,0x33,0x15,0x23,0x15,0x33,0x27,0x35,0x23,0x15,0x01,0x8f,
/*002620*/ 0x62,0xca,0x62,0x01,0x2a,0x64,0x64,0x64,0x64,0xc6,0x01,0xc8,0xc8,0x02,0x56,0x65,
/*002630*/ 0xc5,0x67,0x67,0xc6,0xc6,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,
/*002640*/ 0x00,0x17,0x00,0xa3,0x40,0x53,0x01,0x18,0x18,0x40,0x19,0x00,0x17,0x10,0x0f,0x03,
/*002650*/ 0x00,0x04,0x05,0x16,0x06,0x05,0x02,0x01,0x05,0x15,0x04,0x07,0x14,0x08,0x07,0x03,
/*002660*/ 0x13,0x04,0x09,0x12,0x0e,0x0d,0x0a,0x09,0x05,0x11,0x04,0x0c,0x0b,0x04,0x03,0x03,
/*002670*/ 0x17,0x16,0x06,0x00,0x05,0x01,0x00,0x03,0x04,0x06,0x02,0x15,0x14,0x06,0x07,0x06,
/*002680*/ 0x09,0x08,0x06,0x13,0x12,0x0b,0x0a,0x06,0x0c,0x11,0x0d,0x0c,0x03,0x10,0x06,0x0e,
/*002690*/ 0x0f,0x0e,0x03,0x03,0x02,0x01,0x01,0x03,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,
/*0026a0*/ 0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,
/*0026b0*/ 0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x10,
/*0026c0*/ 0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,0x49,
/*0026d0*/ 0x68,0xb9,0x00,0x03,0x00,0x18,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,
/*0026e0*/ 0xb9,0x00,0x18,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,0x21,0x35,0x21,0x35,0x23,0x35,
/*0026f0*/ 0x23,0x35,0x23,0x35,0x33,0x35,0x21,0x15,0x21,0x15,0x33,0x15,0x33,0x15,0x33,0x01,
/*002700*/ 0x8f,0x65,0xfe,0xd7,0x01,0x2c,0x64,0x64,0x64,0x64,0x01,0x2a,0xfe,0xd4,0x64,0x64,
/*002710*/ 0x64,0x65,0x64,0x62,0x67,0x64,0x64,0x61,0x64,0x62,0x67,0x64,0x64,0x00,0x00,0x01,
/*002720*/ 0x00,0x01,0x00,0x01,0x01,0xf3,0x02,0x57,0x00,0x07,0x00,0x53,0x40,0x1f,0x01,0x08,
/*002730*/ 0x08,0x40,0x09,0x00,0x07,0x06,0x05,0x00,0x02,0x01,0x04,0x04,0x03,0x05,0x04,0x01,
/*002740*/ 0x03,0x00,0x06,0x06,0x07,0x06,0x03,0x03,0x02,0x01,0x01,0x05,0x46,0x76,0x2f,0x37,
/*002750*/ 0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x3c,0x2e,
/*002760*/ 0x2e,0x2e,0x2e,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x05,0x00,0x08,0x49,0x68,
/*002770*/ 0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x08,0xff,0xc0,0x38,0x59,0x01,
/*002780*/ 0x23,0x11,0x23,0x11,0x23,0x35,0x21,0x01,0xf3,0xc8,0x62,0xc8,0x01,0xf2,0x01,0xf5,
/*002790*/ 0xfe,0x0c,0x01,0xf4,0x62,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,
/*0027a0*/ 0x00,0x0b,0x00,0x67,0x40,0x2c,0x01,0x0c,0x0c,0x40,0x0d,0x00,0x05,0x04,0x02,0x01,
/*0027b0*/ 0x05,0x03,0x08,0x04,0x03,0x03,0x07,0x04,0x06,0x05,0x0a,0x09,0x04,0x0b,0x00,0x09,
/*0027c0*/ 0x01,0x00,0x03,0x08,0x06,0x02,0x0b,0x0a,0x07,0x03,0x06,0x03,0x03,0x02,0x01,0x01,
/*0027d0*/ 0x05,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x17,0x3c,0x10,0xfd,0x17,0x3c,
/*0027e0*/ 0x01,0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x00,0x2e,0x2e,
/*0027f0*/ 0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x05,0x00,0x0c,0x49,0x68,0x61,0xb0,0x40,0x52,
/*002800*/ 0x58,0x38,0x11,0x37,0xb9,0x00,0x0c,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,0x23,0x35,
/*002810*/ 0x23,0x11,0x33,0x11,0x33,0x11,0x33,0x01,0x8f,0x65,0xc5,0x64,0x62,0xca,0x62,0x65,
/*002820*/ 0x64,0x65,0x01,0xf1,0xfe,0x0c,0x01,0xf4,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x8f,
/*002830*/ 0x02,0x57,0x00,0x0f,0x00,0x79,0x40,0x37,0x01,0x10,0x10,0x40,0x11,0x00,0x04,0x03,
/*002840*/ 0x05,0x07,0x0a,0x09,0x06,0x03,0x05,0x04,0x08,0x07,0x0c,0x0b,0x04,0x02,0x01,0x0e,
/*002850*/ 0x0d,0x04,0x0f,0x00,0x0d,0x01,0x00,0x03,0x0c,0x06,0x02,0x0b,0x03,0x02,0x03,0x0a,
/*002860*/ 0x06,0x05,0x04,0x0f,0x0e,0x09,0x03,0x08,0x03,0x07,0x06,0x01,0x01,0x07,0x46,0x76,
/*002870*/ 0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x10,0xfd,
/*002880*/ 0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,
/*002890*/ 0x10,0xfd,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x07,0x00,0x10,0x49,0x68,
/*0028a0*/ 0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x10,0xff,0xc0,0x38,0x59,0x01,
/*0028b0*/ 0x23,0x15,0x23,0x15,0x23,0x15,0x23,0x11,0x33,0x11,0x33,0x35,0x33,0x11,0x33,0x01,
/*0028c0*/ 0x8f,0x65,0x64,0x64,0x61,0x62,0x67,0x64,0x61,0x01,0x2d,0x64,0x64,0x64,0x02,0x56,
/*0028d0*/ 0xfe,0x70,0x64,0x01,0x2c,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0xf3,0x02,0x57,
/*0028e0*/ 0x00,0x13,0x00,0x87,0x40,0x41,0x01,0x14,0x14,0x40,0x15,0x00,0x0f,0x0e,0x09,0x08,
/*0028f0*/ 0x02,0x01,0x04,0x03,0x06,0x05,0x04,0x07,0x0c,0x08,0x07,0x03,0x0b,0x04,0x0a,0x09,
/*002900*/ 0x0e,0x0d,0x04,0x10,0x04,0x03,0x03,0x0f,0x12,0x11,0x04,0x13,0x00,0x11,0x10,0x0d,
/*002910*/ 0x05,0x04,0x01,0x00,0x07,0x0c,0x06,0x02,0x13,0x12,0x0b,0x03,0x0a,0x03,0x07,0x06,
/*002920*/ 0x03,0x03,0x02,0x01,0x01,0x09,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x17,0x3c,0x3f,
/*002930*/ 0x17,0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x3c,0x2f,0x17,0x3c,0xfd,0x3c,
/*002940*/ 0x2f,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x10,0xfd,0x3c,0x00,0x2e,0x2e,0x2e,0x2e,
/*002950*/ 0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x09,0x00,0x14,0x49,0x68,0x61,0xb0,0x40,0x52,
/*002960*/ 0x58,0x38,0x11,0x37,0xb9,0x00,0x14,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,0x23,0x35,
/*002970*/ 0x23,0x15,0x23,0x35,0x23,0x11,0x33,0x11,0x33,0x11,0x33,0x11,0x33,0x11,0x33,0x01,
/*002980*/ 0xf3,0x65,0x61,0x67,0x61,0x64,0x62,0x67,0x61,0x66,0x62,0x65,0x64,0x64,0x64,0x65,
/*002990*/ 0x01,0xf1,0xfe,0x0c,0x01,0x90,0xfe,0x70,0x01,0xf4,0x00,0x01,0x00,0x01,0x00,0x01,
/*0029a0*/ 0x01,0x8f,0x02,0x57,0x00,0x13,0x00,0x87,0x40,0x44,0x01,0x14,0x14,0x40,0x15,0x00,
/*0029b0*/ 0x12,0x11,0x06,0x02,0x01,0x05,0x05,0x04,0x13,0x04,0x03,0x03,0x00,0x10,0x0f,0x0c,
/*0029c0*/ 0x0b,0x08,0x05,0x07,0x04,0x0e,0x0d,0x0a,0x03,0x09,0x0d,0x0c,0x01,0x03,0x00,0x07,
/*0029d0*/ 0x0e,0x03,0x02,0x07,0x04,0x0b,0x0a,0x07,0x03,0x06,0x07,0x10,0x11,0x10,0x02,0x13,
/*0029e0*/ 0x12,0x0f,0x03,0x0e,0x03,0x09,0x08,0x05,0x03,0x04,0x01,0x01,0x09,0x46,0x76,0x2f,
/*0029f0*/ 0x37,0x18,0x00,0x3f,0x17,0x3c,0x3f,0x17,0x3c,0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x10,
/*002a00*/ 0xfd,0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x2f,0x17,0x3c,
/*002a10*/ 0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x09,0x00,0x14,0x49,0x68,
/*002a20*/ 0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x14,0xff,0xc0,0x38,0x59,0x01,
/*002a30*/ 0x23,0x15,0x33,0x15,0x23,0x35,0x23,0x15,0x23,0x35,0x33,0x35,0x23,0x35,0x33,0x15,
/*002a40*/ 0x33,0x35,0x33,0x01,0x8f,0x64,0x64,0x62,0xca,0x62,0x64,0x64,0x62,0xca,0x62,0x01,
/*002a50*/ 0x91,0xcb,0xc5,0xc8,0xc8,0xc6,0xca,0xc6,0xc8,0xc8,0x00,0x01,0x00,0x01,0x00,0x01,
/*002a60*/ 0x01,0x8f,0x02,0x57,0x00,0x0f,0x00,0x76,0x40,0x36,0x01,0x10,0x10,0x40,0x11,0x00,
/*002a70*/ 0x09,0x08,0x02,0x01,0x05,0x03,0x0e,0x0d,0x06,0x03,0x05,0x04,0x0f,0x00,0x0c,0x08,
/*002a80*/ 0x07,0x04,0x03,0x05,0x0b,0x04,0x0a,0x09,0x05,0x01,0x00,0x03,0x04,0x06,0x02,0x0d,
/*002a90*/ 0x0c,0x06,0x07,0x06,0x0f,0x0e,0x0b,0x03,0x0a,0x03,0x03,0x02,0x01,0x01,0x09,0x46,
/*002aa0*/ 0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x17,0x3c,0x2f,0x3c,0xfd,0x3c,0x10,0xfd,
/*002ab0*/ 0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x3c,
/*002ac0*/ 0x00,0x2e,0x2e,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x09,0x00,0x10,0x49,0x68,0x61,
/*002ad0*/ 0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x10,0xff,0xc0,0x38,0x59,0x25,0x23,
/*002ae0*/ 0x15,0x23,0x35,0x33,0x35,0x23,0x35,0x23,0x11,0x33,0x11,0x33,0x11,0x33,0x01,0x8f,
/*002af0*/ 0x65,0xc5,0xc8,0xc8,0x64,0x62,0xca,0x62,0x65,0x64,0x62,0x67,0x64,0x01,0x29,0xfe,
/*002b00*/ 0xd4,0x01,0x2c,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,0x00,0x13,
/*002b10*/ 0x00,0x8e,0x40,0x43,0x01,0x14,0x14,0x40,0x15,0x00,0x06,0x05,0x04,0x12,0x11,0x0a,
/*002b20*/ 0x03,0x09,0x04,0x03,0x04,0x0c,0x0b,0x0e,0x0d,0x04,0x02,0x01,0x10,0x0f,0x04,0x13,
/*002b30*/ 0x08,0x07,0x03,0x00,0x01,0x00,0x07,0x12,0x0d,0x03,0x02,0x03,0x0c,0x06,0x0b,0x0a,
/*002b40*/ 0x05,0x03,0x04,0x07,0x06,0x06,0x08,0x11,0x10,0x06,0x12,0x13,0x12,0x03,0x0f,0x0e,
/*002b50*/ 0x02,0x09,0x08,0x01,0x01,0x09,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,
/*002b60*/ 0x3f,0x3c,0x10,0xfd,0x3c,0x10,0xfd,0x3c,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x10,0xfd,
/*002b70*/ 0x3c,0x01,0x2f,0x17,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x2f,
/*002b80*/ 0x17,0x3c,0xfd,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x09,0x00,0x14,0x49,
/*002b90*/ 0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x14,0xff,0xc0,0x38,0x59,
/*002ba0*/ 0x01,0x23,0x15,0x23,0x15,0x23,0x15,0x21,0x15,0x21,0x35,0x33,0x35,0x33,0x35,0x33,
/*002bb0*/ 0x35,0x21,0x35,0x21,0x01,0x8f,0x65,0x64,0x63,0x01,0x2c,0xfe,0x72,0x65,0x64,0x63,
/*002bc0*/ 0xfe,0xd4,0x01,0x8e,0x01,0x91,0x64,0x64,0x67,0x61,0xc6,0x64,0x64,0x66,0x62,0x00,
/*002bd0*/ 0x00,0x01,0x00,0x01,0x00,0x01,0x00,0xc7,0x02,0x57,0x00,0x07,0x00,0x59,0x40,0x23,
/*002be0*/ 0x01,0x08,0x08,0x40,0x09,0x00,0x07,0x04,0x03,0x03,0x00,0x05,0x01,0x06,0x05,0x04,
/*002bf0*/ 0x02,0x01,0x07,0x06,0x06,0x00,0x05,0x04,0x06,0x02,0x03,0x02,0x03,0x01,0x00,0x01,
/*002c00*/ 0x01,0x01,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x3c,0x10,
/*002c10*/ 0xfd,0x3c,0x01,0x2f,0x3c,0xfd,0x3c,0x10,0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,0x49,
/*002c20*/ 0x68,0xb9,0x00,0x01,0x00,0x08,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,
/*002c30*/ 0xb9,0x00,0x08,0xff,0xc0,0x38,0x59,0x37,0x23,0x11,0x33,0x15,0x23,0x11,0x33,0xc7,
/*002c40*/ 0xc6,0xc6,0x64,0x64,0x01,0x02,0x56,0x62,0xfe,0x6d,0x00,0x01,0x00,0x01,0x00,0x01,
/*002c50*/ 0x01,0x2b,0x02,0x57,0x00,0x0b,0x00,0x6d,0x40,0x2f,0x01,0x0c,0x0c,0x40,0x0d,0x00,
/*002c60*/ 0x0b,0x00,0x04,0x01,0x0a,0x02,0x01,0x03,0x09,0x04,0x03,0x08,0x04,0x03,0x03,0x07,
/*002c70*/ 0x04,0x06,0x05,0x0b,0x0a,0x07,0x00,0x03,0x02,0x07,0x09,0x08,0x05,0x04,0x07,0x06,
/*002c80*/ 0x07,0x06,0x03,0x01,0x00,0x01,0x01,0x05,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,
/*002c90*/ 0x3f,0x3c,0x10,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x3c,0xfd,
/*002ca0*/ 0x17,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,
/*002cb0*/ 0x00,0x05,0x00,0x0c,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,
/*002cc0*/ 0x0c,0xff,0xc0,0x38,0x59,0x25,0x23,0x35,0x23,0x35,0x23,0x35,0x33,0x15,0x33,0x15,
/*002cd0*/ 0x33,0x01,0x2b,0x62,0x64,0x64,0x62,0x64,0x64,0x01,0xc9,0xc7,0xc6,0xc9,0xc8,0x00,
/*002ce0*/ 0x00,0x01,0x00,0x01,0x00,0x01,0x00,0xc7,0x02,0x57,0x00,0x07,0x00,0x59,0x40,0x23,
/*002cf0*/ 0x01,0x08,0x08,0x40,0x09,0x00,0x06,0x05,0x02,0x03,0x01,0x05,0x00,0x04,0x03,0x04,
/*002d00*/ 0x07,0x00,0x03,0x02,0x06,0x00,0x05,0x04,0x06,0x06,0x07,0x06,0x03,0x01,0x00,0x01,
/*002d10*/ 0x01,0x01,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x3c,0x10,
/*002d20*/ 0xfd,0x3c,0x01,0x2f,0x3c,0xfd,0x3c,0x10,0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,0x49,
/*002d30*/ 0x68,0xb9,0x00,0x01,0x00,0x08,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,
/*002d40*/ 0xb9,0x00,0x08,0xff,0xc0,0x38,0x59,0x37,0x23,0x35,0x33,0x11,0x23,0x35,0x33,0xc7,
/*002d50*/ 0xc6,0x64,0x64,0xc6,0x01,0x62,0x01,0x92,0x62,0x00,0x00,0x01,0x00,0x01,0x01,0x91,
/*002d60*/ 0x01,0x2b,0x02,0x57,0x00,0x0b,0x00,0x69,0x40,0x2d,0x01,0x0c,0x0c,0x40,0x0d,0x00,
/*002d70*/ 0x0b,0x00,0x04,0x01,0x04,0x03,0x04,0x06,0x05,0x08,0x07,0x04,0x0a,0x02,0x01,0x03,
/*002d80*/ 0x09,0x0b,0x0a,0x06,0x00,0x07,0x06,0x03,0x03,0x02,0x06,0x08,0x05,0x04,0x01,0x03,
/*002d90*/ 0x00,0x09,0x08,0x03,0x01,0x05,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x2f,0x17,
/*002da0*/ 0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x17,0x3c,0xfd,0x3c,0x2f,0x3c,
/*002db0*/ 0xfd,0x3c,0x10,0xfd,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x05,0x00,0x0c,
/*002dc0*/ 0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x0c,0xff,0xc0,0x38,
/*002dd0*/ 0x59,0x01,0x23,0x35,0x23,0x15,0x23,0x35,0x33,0x35,0x33,0x15,0x33,0x01,0x2b,0x62,
/*002de0*/ 0x67,0x61,0x64,0x62,0x64,0x01,0x91,0x64,0x64,0x62,0x64,0x65,0x00,0x01,0x00,0x01,
/*002df0*/ 0x00,0x01,0x01,0x8f,0x00,0x63,0x00,0x03,0x00,0x3e,0x40,0x12,0x01,0x04,0x04,0x40,
/*002e00*/ 0x05,0x00,0x03,0x02,0x01,0x00,0x03,0x02,0x01,0x00,0x01,0x01,0x01,0x46,0x76,0x2f,
/*002e10*/ 0x37,0x18,0x00,0x3f,0x3c,0x2f,0x3c,0x01,0x2e,0x2e,0x2e,0x2e,0x00,0x31,0x30,0x01,
/*002e20*/ 0x49,0x68,0xb9,0x00,0x01,0x00,0x04,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,
/*002e30*/ 0x37,0xb9,0x00,0x04,0xff,0xc0,0x38,0x59,0x25,0x21,0x35,0x21,0x01,0x8f,0xfe,0x72,
/*002e40*/ 0x01,0x8e,0x01,0x62,0x00,0x02,0x00,0x01,0x00,0x01,0x01,0x8f,0x01,0x8f,0x00,0x07,
/*002e50*/ 0x00,0x0b,0x00,0x65,0x40,0x2c,0x01,0x0c,0x0c,0x40,0x0d,0x00,0x0b,0x0a,0x06,0x05,
/*002e60*/ 0x02,0x05,0x01,0x04,0x04,0x03,0x09,0x08,0x04,0x07,0x00,0x0b,0x03,0x02,0x03,0x08,
/*002e70*/ 0x06,0x00,0x0a,0x09,0x05,0x03,0x04,0x06,0x06,0x07,0x06,0x02,0x01,0x00,0x01,0x01,
/*002e80*/ 0x03,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x10,
/*002e90*/ 0xfd,0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x00,0x31,0x30,
/*002ea0*/ 0x01,0x49,0x68,0xb9,0x00,0x03,0x00,0x0c,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,
/*002eb0*/ 0x11,0x37,0xb9,0x00,0x0c,0xff,0xc0,0x38,0x59,0x25,0x21,0x35,0x23,0x35,0x33,0x35,
/*002ec0*/ 0x21,0x03,0x35,0x23,0x15,0x01,0x8f,0xfe,0xd6,0x64,0x64,0x01,0x2a,0x64,0xc6,0x01,
/*002ed0*/ 0x65,0xc5,0x64,0xfe,0xd6,0xc6,0xc6,0x00,0x00,0x02,0x00,0x01,0x00,0x01,0x01,0x8f,
/*002ee0*/ 0x02,0x57,0x00,0x09,0x00,0x0d,0x00,0x6e,0x40,0x32,0x01,0x0e,0x0e,0x40,0x0f,0x00,
/*002ef0*/ 0x0d,0x06,0x05,0x03,0x0c,0x04,0x04,0x03,0x0b,0x0a,0x08,0x02,0x01,0x05,0x07,0x04,
/*002f00*/ 0x09,0x00,0x0d,0x0a,0x01,0x03,0x00,0x06,0x02,0x0c,0x09,0x08,0x03,0x0b,0x06,0x06,
/*002f10*/ 0x07,0x06,0x02,0x05,0x04,0x03,0x03,0x02,0x01,0x01,0x03,0x46,0x76,0x2f,0x37,0x18,
/*002f20*/ 0x00,0x3f,0x3c,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x01,
/*002f30*/ 0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,
/*002f40*/ 0xb9,0x00,0x03,0x00,0x0e,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,
/*002f50*/ 0x00,0x0e,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,0x21,0x11,0x33,0x15,0x33,0x15,0x33,
/*002f60*/ 0x07,0x35,0x23,0x15,0x01,0x8f,0x65,0xfe,0xd7,0x62,0xc8,0x64,0x64,0xc6,0x65,0x64,
/*002f70*/ 0x02,0x56,0xc8,0x65,0xc5,0xc6,0xc6,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x8f,
/*002f80*/ 0x01,0x8f,0x00,0x0b,0x00,0x67,0x40,0x2c,0x01,0x0c,0x0c,0x40,0x0d,0x00,0x0b,0x04,
/*002f90*/ 0x03,0x00,0x0a,0x09,0x06,0x05,0x02,0x05,0x01,0x04,0x08,0x07,0x07,0x06,0x07,0x08,
/*002fa0*/ 0x09,0x08,0x01,0x03,0x00,0x06,0x0a,0x03,0x02,0x06,0x04,0x0b,0x0a,0x02,0x05,0x04,
/*002fb0*/ 0x01,0x01,0x07,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x3c,
/*002fc0*/ 0x10,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2e,0x2e,0x2e,
/*002fd0*/ 0x2e,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x07,0x00,0x0c,0x49,0x68,0x61,0xb0,
/*002fe0*/ 0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x0c,0xff,0xc0,0x38,0x59,0x01,0x21,0x15,
/*002ff0*/ 0x21,0x15,0x21,0x35,0x23,0x35,0x33,0x35,0x21,0x01,0x8f,0xfe,0xd4,0x01,0x2c,0xfe,
/*003000*/ 0xd6,0x64,0x64,0x01,0x2a,0x01,0x2d,0xca,0x62,0x65,0xc5,0x64,0x00,0x02,0x00,0x01,
/*003010*/ 0x00,0x01,0x01,0x8f,0x02,0x57,0x00,0x09,0x00,0x0d,0x00,0x6e,0x40,0x32,0x01,0x0e,
/*003020*/ 0x0e,0x40,0x0f,0x00,0x0d,0x0c,0x06,0x05,0x02,0x05,0x01,0x04,0x04,0x03,0x0b,0x08,
/*003030*/ 0x07,0x03,0x0a,0x04,0x09,0x00,0x0d,0x03,0x02,0x03,0x0a,0x06,0x00,0x0c,0x0b,0x05,
/*003040*/ 0x03,0x04,0x06,0x06,0x09,0x08,0x03,0x07,0x06,0x02,0x01,0x00,0x01,0x01,0x03,0x46,
/*003050*/ 0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x10,
/*003060*/ 0xfd,0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x00,0x31,
/*003070*/ 0x30,0x01,0x49,0x68,0xb9,0x00,0x03,0x00,0x0e,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,
/*003080*/ 0x38,0x11,0x37,0xb9,0x00,0x0e,0xff,0xc0,0x38,0x59,0x25,0x21,0x35,0x23,0x35,0x33,
/*003090*/ 0x35,0x33,0x35,0x33,0x03,0x35,0x23,0x15,0x01,0x8f,0xfe,0xd6,0x64,0x65,0xc7,0x62,
/*0030a0*/ 0x64,0xc6,0x01,0x65,0xc5,0x64,0xc8,0xfe,0x0e,0xc6,0xc6,0x00,0x00,0x02,0x00,0x01,
/*0030b0*/ 0x00,0x01,0x01,0x8f,0x01,0x8f,0x00,0x0d,0x00,0x11,0x00,0x89,0x40,0x43,0x01,0x12,
/*0030c0*/ 0x12,0x40,0x13,0x00,0x0c,0x0b,0x05,0x05,0x0d,0x04,0x03,0x03,0x00,0x05,0x01,0x0f,
/*0030d0*/ 0x0e,0x02,0x03,0x01,0x04,0x05,0x11,0x10,0x0a,0x09,0x06,0x05,0x05,0x04,0x08,0x07,
/*0030e0*/ 0x07,0x06,0x07,0x08,0x11,0x0e,0x01,0x03,0x00,0x06,0x0c,0x03,0x02,0x06,0x04,0x10,
/*0030f0*/ 0x0f,0x0d,0x0c,0x09,0x05,0x08,0x06,0x0a,0x0b,0x0a,0x02,0x05,0x04,0x01,0x01,0x07,
/*003100*/ 0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,
/*003110*/ 0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x10,0xfd,
/*003120*/ 0x17,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,
/*003130*/ 0x00,0x07,0x00,0x12,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,
/*003140*/ 0x12,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,0x33,0x15,0x21,0x35,0x23,0x35,0x33,0x35,
/*003150*/ 0x33,0x15,0x33,0x07,0x35,0x23,0x15,0x01,0x8f,0xc8,0xc8,0xfe,0xd6,0x64,0x64,0xc6,
/*003160*/ 0x64,0xc8,0x62,0xc9,0x67,0x61,0x65,0xc5,0x64,0x65,0x61,0x62,0x62,0x00,0x00,0x01,
/*003170*/ 0x00,0x01,0x00,0x01,0x01,0x2b,0x02,0x57,0x00,0x0b,0x00,0x68,0x40,0x2d,0x01,0x0c,
/*003180*/ 0x0c,0x40,0x0d,0x00,0x04,0x03,0x05,0x07,0x0b,0x00,0x05,0x09,0x0a,0x09,0x06,0x05,
/*003190*/ 0x02,0x05,0x01,0x04,0x08,0x07,0x09,0x08,0x01,0x03,0x00,0x06,0x0a,0x05,0x04,0x06,
/*0031a0*/ 0x03,0x02,0x0b,0x0a,0x03,0x07,0x06,0x01,0x01,0x07,0x46,0x76,0x2f,0x37,0x18,0x00,
/*0031b0*/ 0x3f,0x3c,0x3f,0x3c,0x2f,0x3c,0xfd,0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,0x3c,0xfd,
/*0031c0*/ 0x17,0x3c,0x10,0xfd,0x3c,0x10,0xfd,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,
/*0031d0*/ 0x07,0x00,0x0c,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x0c,
/*0031e0*/ 0xff,0xc0,0x38,0x59,0x01,0x23,0x15,0x33,0x15,0x23,0x11,0x23,0x11,0x33,0x35,0x33,
/*0031f0*/ 0x01,0x2b,0xc8,0x64,0x65,0x61,0x64,0xc6,0x01,0xf5,0x67,0x61,0xfe,0xd4,0x01,0xf2,
/*003200*/ 0x64,0x00,0x00,0x02,0x00,0x01,0xff,0x39,0x01,0x8f,0x01,0x8f,0x00,0x0d,0x00,0x11,
/*003210*/ 0x00,0x7a,0x40,0x3b,0x01,0x12,0x12,0x40,0x13,0x00,0x11,0x10,0x0c,0x0b,0x08,0x07,
/*003220*/ 0x04,0x07,0x03,0x04,0x0a,0x09,0x0f,0x06,0x05,0x02,0x01,0x05,0x0e,0x04,0x0d,0x00,
/*003230*/ 0x05,0x01,0x00,0x03,0x04,0x06,0x02,0x11,0x09,0x08,0x03,0x0e,0x06,0x07,0x06,0x10,
/*003240*/ 0x0f,0x0b,0x03,0x0a,0x06,0x0c,0x0d,0x0c,0x02,0x03,0x02,0x00,0x01,0x09,0x46,0x76,
/*003250*/ 0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,
/*003260*/ 0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,
/*003270*/ 0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x09,0x00,0x12,0x49,0x68,0x61,0xb0,0x40,
/*003280*/ 0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x12,0xff,0xc0,0x38,0x59,0x05,0x23,0x15,0x23,
/*003290*/ 0x35,0x33,0x35,0x23,0x35,0x23,0x35,0x33,0x35,0x21,0x03,0x35,0x23,0x15,0x01,0x8f,
/*0032a0*/ 0x65,0xc5,0xc8,0xc8,0x64,0x64,0x01,0x2a,0x64,0xc6,0x63,0x64,0x62,0x67,0x64,0xc5,
/*0032b0*/ 0x64,0xfe,0xd6,0xc6,0xc6,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,
/*0032c0*/ 0x00,0x0b,0x00,0x65,0x40,0x2b,0x01,0x0c,0x0c,0x40,0x0d,0x00,0x0b,0x0a,0x0a,0x09,
/*0032d0*/ 0x02,0x03,0x01,0x04,0x0b,0x00,0x08,0x07,0x04,0x03,0x03,0x04,0x06,0x05,0x03,0x02,
/*0032e0*/ 0x06,0x08,0x09,0x08,0x02,0x07,0x06,0x03,0x05,0x04,0x01,0x03,0x00,0x01,0x01,0x05,
/*0032f0*/ 0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x17,0x3c,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x3c,
/*003300*/ 0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x00,0x2e,0x2e,0x31,0x30,
/*003310*/ 0x01,0x49,0x68,0xb9,0x00,0x05,0x00,0x0c,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,
/*003320*/ 0x11,0x37,0xb9,0x00,0x0c,0xff,0xc0,0x38,0x59,0x25,0x23,0x11,0x23,0x11,0x23,0x11,
/*003330*/ 0x33,0x15,0x33,0x15,0x33,0x01,0x8f,0x62,0xca,0x62,0x62,0xc8,0x64,0x01,0x01,0x2c,
/*003340*/ 0xfe,0xd4,0x02,0x56,0xc8,0x65,0x00,0x02,0x00,0x01,0x00,0x01,0x00,0x63,0x02,0x57,
/*003350*/ 0x00,0x03,0x00,0x07,0x00,0x54,0x40,0x21,0x01,0x08,0x08,0x40,0x09,0x00,0x07,0x04,
/*003360*/ 0x03,0x03,0x00,0x04,0x06,0x05,0x02,0x03,0x01,0x01,0x00,0x06,0x02,0x07,0x06,0x02,
/*003370*/ 0x05,0x04,0x01,0x03,0x02,0x03,0x01,0x01,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,
/*003380*/ 0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x00,0x31,
/*003390*/ 0x30,0x01,0x49,0x68,0xb9,0x00,0x01,0x00,0x08,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,
/*0033a0*/ 0x38,0x11,0x37,0xb9,0x00,0x08,0xff,0xc0,0x38,0x59,0x13,0x23,0x35,0x33,0x11,0x23,
/*0033b0*/ 0x11,0x33,0x63,0x62,0x62,0x62,0x62,0x01,0xf5,0x62,0xfd,0xaa,0x01,0x8e,0x00,0x02,
/*0033c0*/ 0x00,0x01,0xff,0x39,0x00,0xc7,0x02,0x57,0x00,0x03,0x00,0x0b,0x00,0x68,0x40,0x2d,
/*0033d0*/ 0x01,0x0c,0x0c,0x40,0x0d,0x00,0x0a,0x09,0x02,0x03,0x01,0x04,0x0b,0x04,0x03,0x03,
/*0033e0*/ 0x00,0x06,0x05,0x04,0x08,0x07,0x01,0x00,0x06,0x02,0x09,0x05,0x04,0x03,0x08,0x06,
/*0033f0*/ 0x06,0x0b,0x0a,0x02,0x07,0x06,0x00,0x03,0x02,0x03,0x01,0x07,0x46,0x76,0x2f,0x37,
/*003400*/ 0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x01,
/*003410*/ 0x2f,0x3c,0xfd,0x3c,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,0x49,0x68,
/*003420*/ 0xb9,0x00,0x07,0x00,0x0c,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,
/*003430*/ 0x00,0x0c,0xff,0xc0,0x38,0x59,0x13,0x23,0x35,0x33,0x11,0x23,0x15,0x23,0x35,0x33,
/*003440*/ 0x11,0x33,0xc7,0x62,0x62,0x65,0x61,0x64,0x62,0x01,0xf5,0x62,0xfd,0x46,0x64,0x62,
/*003450*/ 0x01,0xf4,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x8f,0x02,0x57,0x00,0x0f,0x00,0x79,
/*003460*/ 0x40,0x39,0x01,0x10,0x10,0x40,0x11,0x00,0x0e,0x0d,0x0a,0x09,0x02,0x05,0x01,0x04,
/*003470*/ 0x0f,0x0c,0x0b,0x03,0x00,0x08,0x07,0x04,0x03,0x03,0x04,0x06,0x05,0x0f,0x0e,0x07,
/*003480*/ 0x00,0x03,0x02,0x06,0x08,0x0d,0x09,0x08,0x03,0x0c,0x06,0x0a,0x0b,0x0a,0x02,0x07,
/*003490*/ 0x06,0x03,0x05,0x04,0x01,0x03,0x00,0x01,0x01,0x05,0x46,0x76,0x2f,0x37,0x18,0x00,
/*0034a0*/ 0x3f,0x17,0x3c,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x10,0xfd,
/*0034b0*/ 0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x00,0x31,0x30,
/*0034c0*/ 0x01,0x49,0x68,0xb9,0x00,0x05,0x00,0x10,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,
/*0034d0*/ 0x11,0x37,0xb9,0x00,0x10,0xff,0xc0,0x38,0x59,0x25,0x23,0x35,0x23,0x15,0x23,0x11,
/*0034e0*/ 0x33,0x11,0x33,0x35,0x33,0x15,0x23,0x15,0x33,0x01,0x8f,0x62,0xca,0x62,0x62,0xcb,
/*0034f0*/ 0x61,0x64,0x64,0x01,0xc8,0xc8,0x02,0x56,0xfe,0xd4,0x64,0x62,0x67,0x00,0x00,0x01,
/*003500*/ 0x00,0x01,0x00,0x01,0x00,0x63,0x02,0x57,0x00,0x03,0x00,0x40,0x40,0x14,0x01,0x04,
/*003510*/ 0x04,0x40,0x05,0x00,0x02,0x01,0x04,0x03,0x00,0x03,0x02,0x03,0x01,0x00,0x01,0x01,
/*003520*/ 0x01,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x01,0x2f,0x3c,0xfd,0x3c,
/*003530*/ 0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x01,0x00,0x04,0x49,0x68,0x61,0xb0,0x40,
/*003540*/ 0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x04,0xff,0xc0,0x38,0x59,0x37,0x23,0x11,0x33,
/*003550*/ 0x63,0x62,0x62,0x01,0x02,0x56,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0xf3,0x01,0x8f,
/*003560*/ 0x00,0x0d,0x00,0x6b,0x40,0x2f,0x01,0x0e,0x0e,0x40,0x0f,0x00,0x0d,0x0c,0x0c,0x0b,
/*003570*/ 0x02,0x03,0x01,0x04,0x0d,0x00,0x04,0x03,0x04,0x06,0x05,0x08,0x07,0x04,0x0a,0x09,
/*003580*/ 0x07,0x06,0x03,0x03,0x02,0x06,0x0a,0x0b,0x0a,0x02,0x09,0x08,0x05,0x04,0x01,0x05,
/*003590*/ 0x00,0x01,0x01,0x09,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x17,0x3c,0x3f,0x3c,0x10,
/*0035a0*/ 0xfd,0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x17,
/*0035b0*/ 0x3c,0x00,0x2e,0x2e,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x09,0x00,0x0e,0x49,0x68,
/*0035c0*/ 0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x0e,0xff,0xc0,0x38,0x59,0x25,
/*0035d0*/ 0x23,0x11,0x23,0x11,0x23,0x11,0x23,0x11,0x23,0x11,0x21,0x15,0x33,0x01,0xf3,0x62,
/*0035e0*/ 0x67,0x61,0x67,0x61,0x01,0x8e,0x64,0x01,0x01,0x2c,0xfe,0xd4,0x01,0x2c,0xfe,0xd4,
/*0035f0*/ 0x01,0x8e,0x65,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x8f,0x01,0x8f,0x00,0x09,
/*003600*/ 0x00,0x5c,0x40,0x25,0x01,0x0a,0x0a,0x40,0x0b,0x00,0x09,0x08,0x08,0x07,0x02,0x03,
/*003610*/ 0x01,0x04,0x09,0x00,0x04,0x03,0x04,0x06,0x05,0x03,0x02,0x06,0x06,0x07,0x06,0x02,
/*003620*/ 0x05,0x04,0x01,0x03,0x00,0x01,0x01,0x05,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x17,
/*003630*/ 0x3c,0x3f,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,
/*003640*/ 0x00,0x2e,0x2e,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x05,0x00,0x0a,0x49,0x68,0x61,
/*003650*/ 0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x0a,0xff,0xc0,0x38,0x59,0x25,0x23,
/*003660*/ 0x11,0x23,0x11,0x23,0x11,0x21,0x15,0x33,0x01,0x8f,0x62,0xca,0x62,0x01,0x2a,0x64,
/*003670*/ 0x01,0x01,0x2c,0xfe,0xd4,0x01,0x8e,0x65,0x00,0x02,0x00,0x01,0x00,0x01,0x01,0x8f,
/*003680*/ 0x01,0x8f,0x00,0x0b,0x00,0x0f,0x00,0x6f,0x40,0x35,0x01,0x10,0x10,0x40,0x11,0x00,
/*003690*/ 0x0f,0x0e,0x08,0x07,0x04,0x05,0x03,0x04,0x06,0x05,0x0d,0x0c,0x0a,0x02,0x01,0x05,
/*0036a0*/ 0x09,0x04,0x0b,0x00,0x0f,0x0c,0x05,0x04,0x01,0x05,0x00,0x06,0x02,0x0e,0x0d,0x0b,
/*0036b0*/ 0x0a,0x07,0x05,0x06,0x06,0x08,0x09,0x08,0x02,0x03,0x02,0x01,0x01,0x05,0x46,0x76,
/*0036c0*/ 0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,
/*0036d0*/ 0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,0x49,
/*0036e0*/ 0x68,0xb9,0x00,0x05,0x00,0x10,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,
/*0036f0*/ 0xb9,0x00,0x10,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,0x23,0x35,0x23,0x35,0x33,0x35,
/*003700*/ 0x33,0x15,0x33,0x07,0x35,0x23,0x15,0x01,0x8f,0x65,0xc5,0x64,0x64,0xc6,0x64,0x64,
/*003710*/ 0xc6,0x65,0x64,0x65,0xc5,0x64,0x65,0xc5,0xc6,0xc6,0x00,0x02,0x00,0x01,0xff,0x39,
/*003720*/ 0x01,0x8f,0x01,0x8f,0x00,0x09,0x00,0x0d,0x00,0x6e,0x40,0x32,0x01,0x0e,0x0e,0x40,
/*003730*/ 0x0f,0x00,0x0d,0x04,0x03,0x03,0x0c,0x04,0x06,0x05,0x0b,0x0a,0x08,0x02,0x01,0x05,
/*003740*/ 0x07,0x04,0x09,0x00,0x0d,0x0a,0x01,0x03,0x00,0x06,0x02,0x0c,0x09,0x08,0x03,0x0b,
/*003750*/ 0x06,0x06,0x07,0x06,0x02,0x05,0x04,0x00,0x03,0x02,0x01,0x01,0x05,0x46,0x76,0x2f,
/*003760*/ 0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,
/*003770*/ 0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,
/*003780*/ 0x49,0x68,0xb9,0x00,0x05,0x00,0x0e,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,
/*003790*/ 0x37,0xb9,0x00,0x0e,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,0x23,0x15,0x23,0x11,0x21,
/*0037a0*/ 0x15,0x33,0x07,0x35,0x23,0x15,0x01,0x8f,0x65,0xc7,0x62,0x01,0x2a,0x64,0x64,0xc6,
/*0037b0*/ 0x65,0x64,0xc8,0x02,0x56,0x65,0xc5,0xc6,0xc6,0x00,0x00,0x02,0x00,0x01,0xff,0x39,
/*0037c0*/ 0x01,0x8f,0x01,0x8f,0x00,0x09,0x00,0x0d,0x00,0x6e,0x40,0x32,0x01,0x0e,0x0e,0x40,
/*0037d0*/ 0x0f,0x00,0x0d,0x0c,0x08,0x07,0x04,0x05,0x03,0x04,0x06,0x05,0x0b,0x02,0x01,0x03,
/*0037e0*/ 0x0a,0x04,0x09,0x00,0x0d,0x05,0x04,0x03,0x0a,0x06,0x02,0x0c,0x0b,0x07,0x03,0x06,
/*0037f0*/ 0x06,0x08,0x09,0x08,0x02,0x03,0x02,0x01,0x01,0x00,0x00,0x01,0x05,0x46,0x76,0x2f,
/*003800*/ 0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,
/*003810*/ 0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x00,0x31,0x30,0x01,
/*003820*/ 0x49,0x68,0xb9,0x00,0x05,0x00,0x0e,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,
/*003830*/ 0x37,0xb9,0x00,0x0e,0xff,0xc0,0x38,0x59,0x05,0x23,0x35,0x23,0x35,0x23,0x35,0x33,
/*003840*/ 0x35,0x21,0x03,0x35,0x23,0x15,0x01,0x8f,0x62,0xc8,0x64,0x64,0x01,0x2a,0x64,0xc6,
/*003850*/ 0xc7,0xc8,0x65,0xc5,0x64,0xfe,0xd6,0xc6,0xc6,0x00,0x00,0x01,0x00,0x01,0x00,0x01,
/*003860*/ 0x01,0x8f,0x01,0x8f,0x00,0x0b,0x00,0x65,0x40,0x2b,0x01,0x0c,0x0c,0x40,0x0d,0x00,
/*003870*/ 0x02,0x01,0x05,0x05,0x0a,0x09,0x05,0x0b,0x00,0x08,0x07,0x04,0x03,0x03,0x04,0x06,
/*003880*/ 0x05,0x03,0x02,0x06,0x09,0x01,0x00,0x03,0x08,0x0b,0x0a,0x07,0x03,0x06,0x02,0x05,
/*003890*/ 0x04,0x01,0x01,0x05,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x17,0x3c,0x2f,
/*0038a0*/ 0x17,0x3c,0xfd,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x3c,0x10,0xfd,
/*0038b0*/ 0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x05,0x00,0x0c,0x49,0x68,0x61,0xb0,
/*0038c0*/ 0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x0c,0xff,0xc0,0x38,0x59,0x01,0x23,0x15,
/*0038d0*/ 0x23,0x15,0x23,0x11,0x33,0x15,0x33,0x35,0x33,0x01,0x8f,0xc9,0x64,0x61,0x62,0x67,
/*0038e0*/ 0xc5,0x01,0x2d,0x64,0xc8,0x01,0x8e,0x64,0x64,0x00,0x00,0x01,0x00,0x01,0x00,0x01,
/*0038f0*/ 0x01,0x8f,0x01,0x8f,0x00,0x0f,0x00,0x85,0x40,0x3f,0x01,0x10,0x10,0x40,0x11,0x00,
/*003900*/ 0x0a,0x09,0x08,0x05,0x0f,0x0c,0x0b,0x03,0x00,0x05,0x05,0x08,0x07,0x04,0x03,0x03,
/*003910*/ 0x05,0x0d,0x0e,0x0d,0x06,0x03,0x05,0x04,0x02,0x01,0x0f,0x0e,0x06,0x00,0x05,0x01,
/*003920*/ 0x00,0x03,0x04,0x06,0x02,0x07,0x06,0x06,0x08,0x0d,0x09,0x08,0x03,0x0c,0x06,0x0a,
/*003930*/ 0x0b,0x0a,0x02,0x03,0x02,0x01,0x01,0x03,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,
/*003940*/ 0x3f,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x3c,
/*003950*/ 0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,
/*003960*/ 0x3c,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x03,0x00,0x10,0x49,0x68,0x61,0xb0,
/*003970*/ 0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x10,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,
/*003980*/ 0x21,0x35,0x33,0x35,0x23,0x35,0x33,0x35,0x21,0x15,0x23,0x15,0x33,0x01,0x8f,0x65,
/*003990*/ 0xfe,0xd7,0xc8,0xc8,0x64,0x01,0x2a,0xc8,0xc8,0x65,0x64,0x62,0x67,0x61,0x64,0x62,
/*0039a0*/ 0x67,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x2b,0x01,0xf3,0x00,0x0b,0x00,0x64,
/*0039b0*/ 0x40,0x2a,0x01,0x0c,0x0c,0x40,0x0d,0x00,0x03,0x02,0x0b,0x08,0x07,0x03,0x00,0x05,
/*0039c0*/ 0x01,0x0a,0x09,0x06,0x02,0x01,0x05,0x05,0x04,0x04,0x03,0x0b,0x0a,0x06,0x00,0x09,
/*0039d0*/ 0x08,0x06,0x07,0x06,0x05,0x04,0x01,0x00,0x01,0x01,0x03,0x46,0x76,0x2f,0x37,0x18,
/*0039e0*/ 0x00,0x3f,0x3c,0x2f,0x3c,0x2f,0x3c,0xfd,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x3c,0xfd,
/*0039f0*/ 0x17,0x3c,0x10,0xfd,0x17,0x3c,0x00,0x2e,0x2e,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,
/*003a00*/ 0x03,0x00,0x0c,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x0c,
/*003a10*/ 0xff,0xc0,0x38,0x59,0x25,0x23,0x35,0x23,0x11,0x33,0x15,0x33,0x15,0x23,0x15,0x33,
/*003a20*/ 0x01,0x2b,0xc6,0x64,0x62,0xc8,0xc8,0xc8,0x01,0x65,0x01,0x8d,0x65,0x61,0xca,0x00,
/*003a30*/ 0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x8f,0x01,0x8f,0x00,0x09,0x00,0x5c,0x40,0x25,
/*003a40*/ 0x01,0x0a,0x0a,0x40,0x0b,0x00,0x03,0x02,0x06,0x02,0x01,0x03,0x05,0x04,0x04,0x03,
/*003a50*/ 0x08,0x07,0x04,0x09,0x00,0x07,0x06,0x06,0x00,0x09,0x08,0x05,0x03,0x04,0x02,0x01,
/*003a60*/ 0x00,0x01,0x01,0x03,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x17,0x3c,0x10,
/*003a70*/ 0xfd,0x3c,0x01,0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x00,0x2e,0x2e,0x31,
/*003a80*/ 0x30,0x01,0x49,0x68,0xb9,0x00,0x03,0x00,0x0a,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,
/*003a90*/ 0x38,0x11,0x37,0xb9,0x00,0x0a,0xff,0xc0,0x38,0x59,0x25,0x21,0x35,0x23,0x11,0x33,
/*003aa0*/ 0x11,0x33,0x11,0x33,0x01,0x8f,0xfe,0xd6,0x64,0x62,0xca,0x62,0x01,0x65,0x01,0x29,
/*003ab0*/ 0xfe,0xd4,0x01,0x2c,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x8f,0x01,0x8f,0x00,0x0b,
/*003ac0*/ 0x00,0x65,0x40,0x2c,0x01,0x0c,0x0c,0x40,0x0d,0x00,0x02,0x01,0x05,0x03,0x08,0x04,
/*003ad0*/ 0x03,0x03,0x07,0x04,0x06,0x05,0x0a,0x09,0x04,0x0b,0x00,0x09,0x05,0x04,0x01,0x00,
/*003ae0*/ 0x05,0x08,0x07,0x02,0x0b,0x0a,0x07,0x03,0x06,0x02,0x03,0x02,0x01,0x01,0x05,0x46,
/*003af0*/ 0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,
/*003b00*/ 0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x00,0x31,0x30,0x01,0x49,
/*003b10*/ 0x68,0xb9,0x00,0x05,0x00,0x0c,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,
/*003b20*/ 0xb9,0x00,0x0c,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,0x23,0x35,0x23,0x35,0x33,0x15,
/*003b30*/ 0x33,0x35,0x33,0x01,0x8f,0x65,0xc5,0x64,0x62,0xca,0x62,0xc9,0xc8,0xc8,0xc6,0xc8,
/*003b40*/ 0xc8,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x01,0xf3,0x01,0x8f,0x00,0x0d,0x00,0x6b,
/*003b50*/ 0x40,0x2f,0x01,0x0e,0x0e,0x40,0x0f,0x00,0x03,0x02,0x06,0x02,0x01,0x03,0x05,0x04,
/*003b60*/ 0x04,0x03,0x08,0x07,0x04,0x0a,0x09,0x0c,0x0b,0x04,0x0d,0x00,0x0b,0x0a,0x07,0x03,
/*003b70*/ 0x06,0x06,0x00,0x0d,0x0c,0x09,0x08,0x05,0x05,0x04,0x02,0x01,0x00,0x01,0x01,0x03,
/*003b80*/ 0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x01,
/*003b90*/ 0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x00,0x2e,0x2e,
/*003ba0*/ 0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x03,0x00,0x0e,0x49,0x68,0x61,0xb0,0x40,0x52,
/*003bb0*/ 0x58,0x38,0x11,0x37,0xb9,0x00,0x0e,0xff,0xc0,0x38,0x59,0x25,0x21,0x35,0x23,0x11,
/*003bc0*/ 0x33,0x11,0x33,0x11,0x33,0x11,0x33,0x11,0x33,0x01,0xf3,0xfe,0x72,0x64,0x62,0x67,
/*003bd0*/ 0x61,0x67,0x61,0x01,0x65,0x01,0x29,0xfe,0xd4,0x01,0x2c,0xfe,0xd4,0x01,0x2c,0x00,
/*003be0*/ 0x00,0x01,0x00,0x01,0x00,0x01,0x01,0x8f,0x01,0x8f,0x00,0x13,0x00,0x89,0x40,0x45,
/*003bf0*/ 0x01,0x14,0x14,0x40,0x15,0x00,0x0c,0x0b,0x05,0x01,0x12,0x11,0x06,0x02,0x01,0x05,
/*003c00*/ 0x05,0x04,0x13,0x04,0x03,0x03,0x00,0x10,0x0f,0x08,0x03,0x07,0x04,0x0e,0x0d,0x0a,
/*003c10*/ 0x03,0x09,0x07,0x06,0x07,0x10,0x11,0x10,0x0d,0x0c,0x01,0x05,0x00,0x06,0x0e,0x0b,
/*003c20*/ 0x0a,0x03,0x03,0x02,0x06,0x04,0x13,0x12,0x0f,0x03,0x0e,0x02,0x09,0x08,0x05,0x03,
/*003c30*/ 0x04,0x01,0x01,0x09,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x17,0x3c,0x3f,0x17,0x3c,
/*003c40*/ 0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x01,0x2f,0x17,0x3c,0xfd,
/*003c50*/ 0x17,0x3c,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x00,0x31,0x30,0x01,0x49,
/*003c60*/ 0x68,0xb9,0x00,0x09,0x00,0x14,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,
/*003c70*/ 0xb9,0x00,0x14,0xff,0xc0,0x38,0x59,0x01,0x23,0x15,0x33,0x15,0x23,0x35,0x23,0x15,
/*003c80*/ 0x23,0x35,0x33,0x35,0x23,0x35,0x33,0x15,0x33,0x35,0x33,0x01,0x8f,0x64,0x64,0x62,
/*003c90*/ 0xcb,0x61,0x64,0x64,0x62,0xcb,0x61,0x01,0x2d,0xcb,0x61,0x64,0x64,0x62,0xca,0x62,
/*003ca0*/ 0x64,0x64,0x00,0x01,0x00,0x01,0xff,0x39,0x01,0x8f,0x01,0x8f,0x00,0x0f,0x00,0x76,
/*003cb0*/ 0x40,0x36,0x01,0x10,0x10,0x40,0x11,0x00,0x09,0x08,0x02,0x01,0x05,0x03,0x0e,0x0d,
/*003cc0*/ 0x06,0x03,0x05,0x04,0x0f,0x00,0x0c,0x08,0x07,0x04,0x03,0x05,0x0b,0x04,0x0a,0x09,
/*003cd0*/ 0x05,0x01,0x00,0x03,0x04,0x06,0x02,0x0d,0x0c,0x06,0x07,0x06,0x0f,0x0e,0x0b,0x03,
/*003ce0*/ 0x0a,0x02,0x03,0x02,0x00,0x01,0x09,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,
/*003cf0*/ 0x17,0x3c,0x2f,0x3c,0xfd,0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,
/*003d00*/ 0x2f,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x3c,0x00,0x2e,0x2e,0x31,0x30,0x01,0x49,0x68,
/*003d10*/ 0xb9,0x00,0x09,0x00,0x10,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,
/*003d20*/ 0x00,0x10,0xff,0xc0,0x38,0x59,0x05,0x23,0x15,0x23,0x35,0x33,0x35,0x23,0x35,0x23,
/*003d30*/ 0x11,0x33,0x11,0x33,0x11,0x33,0x01,0x8f,0x65,0xc5,0xc8,0xc8,0x64,0x62,0xca,0x62,
/*003d40*/ 0x63,0x64,0x62,0x67,0x64,0x01,0x29,0xfe,0xd4,0x01,0x2c,0x00,0x00,0x01,0x00,0x01,
/*003d50*/ 0x00,0x01,0x01,0x8f,0x01,0x8f,0x00,0x0f,0x00,0x7c,0x40,0x38,0x01,0x10,0x10,0x40,
/*003d60*/ 0x11,0x00,0x0f,0x0a,0x09,0x08,0x07,0x02,0x01,0x00,0x04,0x03,0x04,0x0d,0x0e,0x0d,
/*003d70*/ 0x06,0x03,0x05,0x04,0x0c,0x0b,0x0d,0x05,0x04,0x03,0x0c,0x07,0x08,0x0f,0x0e,0x03,
/*003d80*/ 0x03,0x02,0x06,0x00,0x0b,0x0a,0x07,0x03,0x06,0x06,0x08,0x09,0x08,0x02,0x01,0x00,
/*003d90*/ 0x01,0x01,0x01,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x10,0xfd,0x17,
/*003da0*/ 0x3c,0x10,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x10,
/*003db0*/ 0xfd,0x3c,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x00,0x31,0x30,0x01,0x49,0x68,
/*003dc0*/ 0xb9,0x00,0x01,0x00,0x10,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,
/*003dd0*/ 0x00,0x10,0xff,0xc0,0x38,0x59,0x25,0x21,0x35,0x33,0x35,0x33,0x35,0x23,0x35,0x21,
/*003de0*/ 0x15,0x23,0x15,0x23,0x15,0x33,0x01,0x8f,0xfe,0x72,0x65,0x63,0xc8,0x01,0x8e,0x65,
/*003df0*/ 0x63,0xc8,0x01,0x62,0x64,0x66,0x62,0x62,0x64,0x67,0x00,0x01,0x00,0x01,0xff,0x9d,
/*003e00*/ 0x01,0x2b,0x02,0x57,0x00,0x13,0x00,0x89,0x40,0x42,0x01,0x14,0x14,0x40,0x15,0x00,
/*003e10*/ 0x10,0x0f,0x13,0x08,0x07,0x03,0x00,0x04,0x09,0x12,0x11,0x0a,0x09,0x06,0x02,0x01,
/*003e20*/ 0x07,0x05,0x04,0x0b,0x0c,0x0b,0x04,0x03,0x03,0x04,0x0e,0x0d,0x05,0x04,0x07,0x0b,
/*003e30*/ 0x0a,0x11,0x10,0x01,0x03,0x00,0x06,0x12,0x07,0x06,0x06,0x08,0x0d,0x0c,0x06,0x0f,
/*003e40*/ 0x03,0x02,0x03,0x0e,0x09,0x08,0x13,0x12,0x03,0x01,0x0d,0x46,0x76,0x2f,0x37,0x18,
/*003e50*/ 0x00,0x3f,0x3c,0x2f,0x3c,0x2f,0x17,0x3c,0xfd,0x3c,0x10,0xfd,0x3c,0x10,0xfd,0x17,
/*003e60*/ 0x3c,0x2f,0x3c,0xfd,0x3c,0x01,0x2f,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x10,
/*003e70*/ 0xfd,0x17,0x3c,0x2e,0x2e,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x0d,0x00,0x14,
/*003e80*/ 0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x14,0xff,0xc0,0x38,
/*003e90*/ 0x59,0x01,0x23,0x15,0x23,0x15,0x33,0x15,0x33,0x15,0x23,0x35,0x23,0x35,0x23,0x35,
/*003ea0*/ 0x33,0x35,0x33,0x35,0x33,0x01,0x2b,0x65,0x63,0x64,0x64,0x62,0x64,0x64,0x65,0x63,
/*003eb0*/ 0x62,0x01,0xf5,0xc8,0x67,0xc8,0x61,0x65,0xc8,0x61,0xc8,0x64,0x00,0x01,0x00,0x01,
/*003ec0*/ 0x00,0x01,0x00,0x63,0x02,0x57,0x00,0x03,0x00,0x40,0x40,0x14,0x01,0x04,0x04,0x40,
/*003ed0*/ 0x05,0x00,0x02,0x01,0x04,0x03,0x00,0x03,0x02,0x03,0x01,0x00,0x01,0x01,0x01,0x46,
/*003ee0*/ 0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x01,0x2f,0x3c,0xfd,0x3c,0x00,0x31,
/*003ef0*/ 0x30,0x01,0x49,0x68,0xb9,0x00,0x01,0x00,0x04,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,
/*003f00*/ 0x38,0x11,0x37,0xb9,0x00,0x04,0xff,0xc0,0x38,0x59,0x37,0x23,0x11,0x33,0x63,0x62,
/*003f10*/ 0x62,0x01,0x02,0x56,0x00,0x01,0x00,0x01,0xff,0x9d,0x01,0x2b,0x02,0x57,0x00,0x13,
/*003f20*/ 0x00,0x8a,0x40,0x41,0x01,0x14,0x14,0x40,0x15,0x00,0x10,0x0f,0x04,0x03,0x03,0x04,
/*003f30*/ 0x0e,0x0d,0x06,0x03,0x05,0x0c,0x0b,0x08,0x03,0x07,0x04,0x02,0x01,0x12,0x11,0x0a,
/*003f40*/ 0x03,0x09,0x04,0x13,0x00,0x0b,0x0a,0x07,0x11,0x10,0x13,0x12,0x06,0x09,0x08,0x01,
/*003f50*/ 0x03,0x00,0x07,0x06,0x06,0x04,0x0d,0x0c,0x06,0x0e,0x05,0x04,0x0f,0x0e,0x03,0x03,
/*003f60*/ 0x02,0x01,0x01,0x05,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x3c,0x2f,0x3c,
/*003f70*/ 0x10,0xfd,0x3c,0x10,0xfd,0x3c,0x2f,0x17,0x3c,0xfd,0x3c,0x2f,0x3c,0xfd,0x3c,0x01,
/*003f80*/ 0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x17,0x3c,0x2f,0x17,0x3c,0xfd,0x17,0x3c,
/*003f90*/ 0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x05,0x00,0x14,0x49,0x68,0x61,0xb0,0x40,
/*003fa0*/ 0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x14,0xff,0xc0,0x38,0x59,0x25,0x23,0x15,0x23,
/*003fb0*/ 0x15,0x23,0x35,0x33,0x35,0x33,0x35,0x23,0x35,0x23,0x35,0x33,0x15,0x33,0x15,0x33,
/*003fc0*/ 0x01,0x2b,0x65,0x64,0x61,0x65,0x63,0x64,0x64,0x62,0x64,0x64,0xc9,0xc8,0x64,0x62,
/*003fd0*/ 0xc8,0x67,0xc7,0x62,0x65,0xc8,0x00,0x01,0x00,0x01,0x01,0x91,0x01,0x8f,0x02,0x57,
/*003fe0*/ 0x00,0x0f,0x00,0x73,0x40,0x35,0x01,0x10,0x10,0x40,0x11,0x00,0x02,0x01,0x04,0x03,
/*003ff0*/ 0x06,0x05,0x04,0x08,0x07,0x0c,0x04,0x03,0x03,0x0b,0x04,0x0a,0x09,0x0e,0x0d,0x04,
/*004000*/ 0x0f,0x00,0x0d,0x0c,0x09,0x05,0x04,0x01,0x00,0x07,0x08,0x06,0x02,0x07,0x06,0x03,
/*004010*/ 0x03,0x02,0x0f,0x0e,0x0b,0x03,0x0a,0x03,0x01,0x07,0x46,0x76,0x2f,0x37,0x18,0x00,
/*004020*/ 0x3f,0x17,0x3c,0x2f,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x3c,0x2f,
/*004030*/ 0x3c,0xfd,0x17,0x3c,0x2f,0x3c,0xfd,0x3c,0x10,0xfd,0x3c,0x00,0x31,0x30,0x01,0x49,
/*004040*/ 0x68,0xb9,0x00,0x07,0x00,0x10,0x49,0x68,0x61,0xb0,0x40,0x52,0x58,0x38,0x11,0x37,
/*004050*/ 0xb9,0x00,0x10,0xff,0xc0,0x38,0x59,0x01,0x23,0x15,0x23,0x35,0x23,0x15,0x23,0x35,
/*004060*/ 0x33,0x35,0x33,0x15,0x33,0x35,0x33,0x01,0x8f,0x65,0x61,0x67,0x61,0x64,0x62,0x67,
/*004070*/ 0x61,0x01,0xf5,0x64,0x64,0x64,0x62,0x64,0x64,0x64,0x00,0x01,0x00,0x01,0x00,0x01,
/*004080*/ 0x01,0xf3,0x02,0x57,0x00,0x17,0x00,0x95,0x40,0x4a,0x01,0x18,0x18,0x40,0x19,0x00,
/*004090*/ 0x17,0x0e,0x0d,0x0a,0x09,0x04,0x03,0x00,0x06,0x02,0x01,0x03,0x05,0x04,0x07,0x12,
/*0040a0*/ 0x0c,0x0b,0x08,0x07,0x05,0x11,0x04,0x10,0x0f,0x14,0x13,0x04,0x16,0x15,0x0d,0x0c,
/*0040b0*/ 0x01,0x03,0x00,0x06,0x16,0x0b,0x0a,0x03,0x03,0x02,0x06,0x09,0x08,0x05,0x03,0x04,
/*0040c0*/ 0x15,0x14,0x11,0x03,0x10,0x03,0x17,0x16,0x13,0x12,0x0f,0x05,0x0e,0x02,0x07,0x06,
/*0040d0*/ 0x01,0x01,0x09,0x46,0x76,0x2f,0x37,0x18,0x00,0x3f,0x3c,0x3f,0x17,0x3c,0x3f,0x17,
/*0040e0*/ 0x3c,0x2f,0x17,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x01,0x2f,0x3c,0xfd,0x3c,
/*0040f0*/ 0x2f,0x3c,0xfd,0x17,0x3c,0x10,0xfd,0x17,0x3c,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,
/*004100*/ 0x2e,0x00,0x31,0x30,0x01,0x49,0x68,0xb9,0x00,0x09,0x00,0x18,0x49,0x68,0x61,0xb0,
/*004110*/ 0x40,0x52,0x58,0x38,0x11,0x37,0xb9,0x00,0x18,0xff,0xc0,0x38,0x59,0x01,0x23,0x15,
/*004120*/ 0x33,0x15,0x23,0x15,0x23,0x35,0x23,0x35,0x33,0x35,0x23,0x35,0x33,0x35,0x33,0x15,
/*004130*/ 0x33,0x35,0x33,0x15,0x33,0x01,0xf3,0xc8,0xc8,0xc9,0x61,0xc8,0xc8,0xc8,0x64,0x62,
/*004140*/ 0x67,0x61,0x64,0x01,0x2d,0x67,0x61,0x64,0x65,0x61,0x67,0x61,0xc8,0xc8,0xc8,0xc9,
/*004150*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7a,0x00,0x00,0x00,0x7a,0x00,0x00,0x00,0x7a,
/*004160*/ 0x00,0x00,0x00,0x7a,0x00,0x00,0x00,0xf0,0x00,0x00,0x01,0x64,0x00,0x00,0x02,0x6a,
/*004170*/ 0x00,0x00,0x03,0x2a,0x00,0x00,0x04,0x78,0x00,0x00,0x05,0xac,0x00,0x00,0x06,0x04,
/*004180*/ 0x00,0x00,0x06,0x96,0x00,0x00,0x07,0x26,0x00,0x00,0x07,0xe2,0x00,0x00,0x08,0x70,
/*004190*/ 0x00,0x00,0x08,0xe2,0x00,0x00,0x09,0x3a,0x00,0x00,0x09,0x90,0x00,0x00,0x0a,0x22,
/*0041a0*/ 0x00,0x00,0x0b,0x00,0x00,0x00,0x0b,0x76,0x00,0x00,0x0c,0x62,0x00,0x00,0x0d,0x4e,
/*0041b0*/ 0x00,0x00,0x0d,0xe8,0x00,0x00,0x0e,0x9a,0x00,0x00,0x0f,0x6c,0x00,0x00,0x10,0x1c,
/*0041c0*/ 0x00,0x00,0x11,0x00,0x00,0x00,0x11,0xd0,0x00,0x00,0x12,0x48,0x00,0x00,0x12,0xc0,
/*0041d0*/ 0x00,0x00,0x13,0x84,0x00,0x00,0x14,0x00,0x00,0x00,0x14,0xc6,0x00,0x00,0x15,0x8a,
/*0041e0*/ 0x00,0x00,0x16,0x5e,0x00,0x00,0x16,0xfc,0x00,0x00,0x17,0xc0,0x00,0x00,0x18,0x54,
/*0041f0*/ 0x00,0x00,0x18,0xea,0x00,0x00,0x19,0x8a,0x00,0x00,0x1a,0x1a,0x00,0x00,0x1a,0xcc,
/*004200*/ 0x00,0x00,0x1b,0x5c,0x00,0x00,0x1b,0xf4,0x00,0x00,0x1c,0x7c,0x00,0x00,0x1d,0x26,
/*004210*/ 0x00,0x00,0x1d,0x9e,0x00,0x00,0x1e,0x64,0x00,0x00,0x1f,0x10,0x00,0x00,0x1f,0xb6,
/*004220*/ 0x00,0x00,0x20,0x54,0x00,0x00,0x21,0x1c,0x00,0x00,0x21,0xd2,0x00,0x00,0x22,0xba,
/*004230*/ 0x00,0x00,0x23,0x32,0x00,0x00,0x23,0xc4,0x00,0x00,0x24,0x72,0x00,0x00,0x25,0x36,
/*004240*/ 0x00,0x00,0x25,0xf6,0x00,0x00,0x26,0xa0,0x00,0x00,0x27,0x6c,0x00,0x00,0x27,0xe6,
/*004250*/ 0x00,0x00,0x28,0x7c,0x00,0x00,0x28,0xf6,0x00,0x00,0x29,0x88,0x00,0x00,0x29,0xe0,
/*004260*/ 0x00,0x00,0x2a,0x74,0x00,0x00,0x2b,0x14,0x00,0x00,0x2b,0xa8,0x00,0x00,0x2c,0x48,
/*004270*/ 0x00,0x00,0x2d,0x0a,0x00,0x00,0x2d,0x9e,0x00,0x00,0x2e,0x52,0x00,0x00,0x2e,0xe2,
/*004280*/ 0x00,0x00,0x2f,0x5a,0x00,0x00,0x2f,0xee,0x00,0x00,0x30,0x9a,0x00,0x00,0x30,0xf2,
/*004290*/ 0x00,0x00,0x31,0x90,0x00,0x00,0x32,0x14,0x00,0x00,0x32,0xb6,0x00,0x00,0x33,0x56,
/*0042a0*/ 0x00,0x00,0x33,0xf6,0x00,0x00,0x34,0x86,0x00,0x00,0x35,0x3e,0x00,0x00,0x35,0xcc,
/*0042b0*/ 0x00,0x00,0x36,0x50,0x00,0x00,0x36,0xde,0x00,0x00,0x37,0x7c,0x00,0x00,0x38,0x3e,
/*0042c0*/ 0x00,0x00,0x38,0xe8,0x00,0x00,0x39,0x96,0x00,0x00,0x3a,0x58,0x00,0x00,0x3a,0xb0,
/*0042d0*/ 0x00,0x00,0x3b,0x72,0x00,0x00,0x3c,0x16,0x00,0x00,0x3c,0xec,0x00,0x00,0x3c,0xec,
/*0042e0*/ 0x01,0x90,0x00,0x32,0x00,0x00,0x00,0x00,0x01,0x2c,0x00,0x00,0x01,0x2c,0x00,0x00,
/*0042f0*/ 0x00,0xc8,0x00,0x01,0x01,0x90,0x00,0x01,0x02,0x58,0x00,0x01,0x02,0x58,0x00,0x01,
/*004300*/ 0x03,0x20,0x00,0x01,0x02,0x58,0x00,0x01,0x00,0xc8,0x00,0x01,0x01,0x2c,0x00,0x01,
/*004310*/ 0x01,0x2c,0x00,0x01,0x01,0x90,0x00,0x01,0x01,0x90,0x00,0x01,0x01,0x2c,0x00,0x01,
/*004320*/ 0x01,0x90,0x00,0x01,0x00,0xc8,0x00,0x01,0x01,0x90,0x00,0x01,0x01,0xf4,0x00,0x01,
/*004330*/ 0x01,0xf4,0x00,0x65,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,
/*004340*/ 0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,
/*004350*/ 0x01,0xf4,0x00,0x01,0x00,0xc8,0x00,0x01,0x00,0xc8,0x00,0x01,0x01,0x90,0x00,0x01,
/*004360*/ 0x01,0x90,0x00,0x01,0x01,0x90,0x00,0x01,0x01,0xf4,0x00,0x01,0x02,0x58,0x00,0x01,
/*004370*/ 0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,
/*004380*/ 0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,
/*004390*/ 0x01,0x90,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,
/*0043a0*/ 0x02,0x58,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,
/*0043b0*/ 0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,0x02,0x58,0x00,0x01,
/*0043c0*/ 0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,0x02,0x58,0x00,0x01,0x01,0xf4,0x00,0x01,
/*0043d0*/ 0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0x2c,0x00,0x01,0x01,0x90,0x00,0x01,
/*0043e0*/ 0x01,0x2c,0x00,0x01,0x01,0x90,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,
/*0043f0*/ 0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,
/*004400*/ 0x01,0x90,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,0x00,0xc8,0x00,0x01,
/*004410*/ 0x01,0x2c,0x00,0x01,0x01,0xf4,0x00,0x01,0x00,0xc8,0x00,0x01,0x02,0x58,0x00,0x01,
/*004420*/ 0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,
/*004430*/ 0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0x90,0x00,0x01,0x01,0xf4,0x00,0x01,
/*004440*/ 0x01,0xf4,0x00,0x01,0x02,0x58,0x00,0x01,0x01,0xf4,0x00,0x01,0x01,0xf4,0x00,0x01,
/*004450*/ 0x01,0xf4,0x00,0x01,0x01,0x90,0x00,0x01,0x00,0xc8,0x00,0x01,0x01,0x90,0x00,0x01,
/*004460*/ 0x01,0xf4,0x00,0x01,0x02,0x58,0x00,0x01,0x01,0x90,0x00,0x00,0x00,0x02,0x00,0x00,
/*004470*/ 0x00,0x00,0x00,0x00,0xff,0x7b,0x00,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
/*004480*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x63,0x00,0x00,
/*004490*/ 0x00,0x01,0x00,0x02,0x00,0x03,0x00,0x04,0x00,0x05,0x00,0x06,0x00,0x07,0x00,0x08,
/*0044a0*/ 0x00,0x09,0x00,0x0a,0x00,0x0b,0x00,0x0c,0x00,0x0d,0x00,0x0e,0x00,0x0f,0x00,0x10,
/*0044b0*/ 0x00,0x11,0x00,0x12,0x00,0x13,0x00,0x14,0x00,0x15,0x00,0x16,0x00,0x17,0x00,0x18,
/*0044c0*/ 0x00,0x19,0x00,0x1a,0x00,0x1b,0x00,0x1c,0x00,0x1d,0x00,0x1e,0x00,0x1f,0x00,0x20,
/*0044d0*/ 0x00,0x21,0x00,0x22,0x00,0x23,0x00,0x24,0x00,0x25,0x00,0x26,0x00,0x27,0x00,0x28,
/*0044e0*/ 0x00,0x29,0x00,0x2a,0x00,0x2b,0x00,0x2c,0x00,0x2d,0x00,0x2e,0x00,0x2f,0x00,0x30,
/*0044f0*/ 0x00,0x31,0x00,0x32,0x00,0x33,0x00,0x34,0x00,0x35,0x00,0x36,0x00,0x37,0x00,0x38,
/*004500*/ 0x00,0x39,0x00,0x3a,0x00,0x3b,0x00,0x3c,0x00,0x3d,0x00,0x3e,0x00,0x3f,0x00,0x40,
/*004510*/ 0x00,0x41,0x00,0x42,0x00,0x44,0x00,0x45,0x00,0x46,0x00,0x47,0x00,0x48,0x00,0x49,
/*004520*/ 0x00,0x4a,0x00,0x4b,0x00,0x4c,0x00,0x4d,0x00,0x4e,0x00,0x4f,0x00,0x50,0x00,0x51,
/*004530*/ 0x00,0x52,0x00,0x53,0x00,0x54,0x00,0x55,0x00,0x56,0x00,0x57,0x00,0x58,0x00,0x59,
/*004540*/ 0x00,0x5a,0x00,0x5b,0x00,0x5c,0x00,0x5d,0x00,0x5e,0x00,0x5f,0x00,0x60,0x00,0x61,
/*004550*/ 0x00,0x96,0x00,0xac,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x24,
/*004560*/ 0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x1c,0x00,0x03,0x00,0x01,0x00,0x00,0x01,0x24,
/*004570*/ 0x00,0x00,0x01,0x06,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,
/*004580*/ 0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
/*004590*/ 0x00,0x00,0x00,0x01,0x00,0x00,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,
/*0045a0*/ 0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,
/*0045b0*/ 0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,
/*0045c0*/ 0x2d,0x2e,0x2f,0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,
/*0045d0*/ 0x3d,0x3e,0x3f,0x40,0x41,0x42,0x00,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,
/*0045e0*/ 0x4c,0x4d,0x4e,0x4f,0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,
/*0045f0*/ 0x5c,0x5d,0x5e,0x5f,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
/*004600*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
/*004610*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x62,0x00,0x00,0x00,0x00,0x61,0x00,0x00,0x00,0x00,
/*004620*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
/*004630*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
/*004640*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,
/*004650*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
/*004660*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
/*004670*/ 0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x04,0x01,0x04,0x00,0x00,0x00,0x0c,
/*004680*/ 0x00,0x08,0x00,0x02,0x00,0x04,0x00,0x5f,0x00,0x7e,0x00,0xa0,0x00,0xa5,0x20,0x10,
/*004690*/ 0xff,0xff,0x00,0x00,0x00,0x20,0x00,0x61,0x00,0xa0,0x00,0xa5,0x20,0x10,0xff,0xff,
/*0046a0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x0c,0x00,0x8a,
/*0046b0*/ 0x00,0xc4,0x00,0xc4,0x00,0xc4,0xff,0xff,0x00,0x03,0x00,0x04,0x00,0x05,0x00,0x06,
/*0046c0*/ 0x00,0x07,0x00,0x08,0x00,0x09,0x00,0x0a,0x00,0x0b,0x00,0x0c,0x00,0x0d,0x00,0x0e,
/*0046d0*/ 0x00,0x0f,0x00,0x10,0x00,0x11,0x00,0x12,0x00,0x13,0x00,0x14,0x00,0x15,0x00,0x16,
/*0046e0*/ 0x00,0x17,0x00,0x18,0x00,0x19,0x00,0x1a,0x00,0x1b,0x00,0x1c,0x00,0x1d,0x00,0x1e,
/*0046f0*/ 0x00,0x1f,0x00,0x20,0x00,0x21,0x00,0x22,0x00,0x23,0x00,0x24,0x00,0x25,0x00,0x26,
/*004700*/ 0x00,0x27,0x00,0x28,0x00,0x29,0x00,0x2a,0x00,0x2b,0x00,0x2c,0x00,0x2d,0x00,0x2e,
/*004710*/ 0x00,0x2f,0x00,0x30,0x00,0x31,0x00,0x32,0x00,0x33,0x00,0x34,0x00,0x35,0x00,0x36,
/*004720*/ 0x00,0x37,0x00,0x38,0x00,0x39,0x00,0x3a,0x00,0x3b,0x00,0x3c,0x00,0x3d,0x00,0x3e,
/*004730*/ 0x00,0x3f,0x00,0x40,0x00,0x41,0x00,0x42,0x00,0x43,0x00,0x44,0x00,0x45,0x00,0x46,
/*004740*/ 0x00,0x47,0x00,0x48,0x00,0x49,0x00,0x4a,0x00,0x4b,0x00,0x4c,0x00,0x4d,0x00,0x4e,
/*004750*/ 0x00,0x4f,0x00,0x50,0x00,0x51,0x00,0x52,0x00,0x53,0x00,0x54,0x00,0x55,0x00,0x56,
/*004760*/ 0x00,0x57,0x00,0x58,0x00,0x59,0x00,0x5a,0x00,0x5b,0x00,0x5c,0x00,0x5d,0x00,0x5e,
/*004770*/ 0x00,0x5f,0x00,0x60,0x00,0x62,0x00,0x61,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x10,
/*004780*/ 0x00,0x00,0x00,0x68,0x09,0x09,0x05,0x00,0x03,0x03,0x02,0x05,0x07,0x07,0x09,0x07,
/*004790*/ 0x02,0x03,0x03,0x05,0x05,0x03,0x05,0x02,0x05,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
/*0047a0*/ 0x06,0x06,0x06,0x02,0x02,0x05,0x05,0x05,0x06,0x07,0x06,0x06,0x06,0x06,0x06,0x06,
/*0047b0*/ 0x06,0x06,0x05,0x06,0x06,0x06,0x07,0x06,0x06,0x06,0x06,0x06,0x06,0x07,0x06,0x06,
/*0047c0*/ 0x07,0x06,0x06,0x06,0x03,0x05,0x03,0x05,0x06,0x06,0x06,0x06,0x06,0x06,0x05,0x06,
/*0047d0*/ 0x06,0x02,0x03,0x06,0x02,0x07,0x06,0x06,0x06,0x06,0x06,0x06,0x05,0x06,0x06,0x07,
/*0047e0*/ 0x06,0x06,0x06,0x05,0x02,0x05,0x06,0x07,0x05,0x00,0x00,0x00,0x0a,0x0a,0x05,0x00,
/*0047f0*/ 0x04,0x04,0x03,0x05,0x08,0x08,0x0a,0x08,0x03,0x04,0x04,0x05,0x05,0x04,0x05,0x03,
/*004800*/ 0x05,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x03,0x03,0x05,0x05,0x05,
/*004810*/ 0x06,0x08,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x05,0x06,0x06,0x06,0x08,0x06,
/*004820*/ 0x06,0x06,0x06,0x06,0x06,0x08,0x06,0x06,0x08,0x06,0x06,0x06,0x04,0x05,0x04,0x05,
/*004830*/ 0x06,0x06,0x06,0x06,0x06,0x06,0x05,0x06,0x06,0x03,0x04,0x06,0x03,0x08,0x06,0x06,
/*004840*/ 0x06,0x06,0x06,0x06,0x05,0x06,0x06,0x08,0x06,0x06,0x06,0x05,0x03,0x05,0x06,0x08,
/*004850*/ 0x05,0x00,0x00,0x00,0x0b,0x0b,0x06,0x00,0x04,0x04,0x03,0x06,0x08,0x08,0x0b,0x08,
/*004860*/ 0x03,0x04,0x04,0x06,0x06,0x04,0x06,0x03,0x06,0x07,0x07,0x07,0x07,0x07,0x07,0x07,
/*004870*/ 0x07,0x07,0x07,0x03,0x03,0x06,0x06,0x06,0x07,0x08,0x07,0x07,0x07,0x07,0x07,0x07,
/*004880*/ 0x07,0x07,0x06,0x07,0x07,0x07,0x08,0x07,0x07,0x07,0x07,0x07,0x07,0x08,0x07,0x07,
/*004890*/ 0x08,0x07,0x07,0x07,0x04,0x06,0x04,0x06,0x07,0x07,0x07,0x07,0x07,0x07,0x06,0x07,
/*0048a0*/ 0x07,0x03,0x04,0x07,0x03,0x08,0x07,0x07,0x07,0x07,0x07,0x07,0x06,0x07,0x07,0x08,
/*0048b0*/ 0x07,0x07,0x07,0x06,0x03,0x06,0x07,0x08,0x06,0x00,0x00,0x00,0x0c,0x0c,0x06,0x00,
/*0048c0*/ 0x05,0x05,0x03,0x06,0x09,0x09,0x0c,0x09,0x03,0x05,0x05,0x06,0x06,0x05,0x06,0x03,
/*0048d0*/ 0x06,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x03,0x03,0x06,0x06,0x06,
/*0048e0*/ 0x08,0x09,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x06,0x08,0x08,0x08,0x09,0x08,
/*0048f0*/ 0x08,0x08,0x08,0x08,0x08,0x09,0x08,0x08,0x09,0x08,0x08,0x08,0x05,0x06,0x05,0x06,
/*004900*/ 0x08,0x08,0x08,0x08,0x08,0x08,0x06,0x08,0x08,0x03,0x05,0x08,0x03,0x09,0x08,0x08,
/*004910*/ 0x08,0x08,0x08,0x08,0x06,0x08,0x08,0x09,0x08,0x08,0x08,0x06,0x03,0x06,0x08,0x09,
/*004920*/ 0x06,0x00,0x00,0x00,0x0d,0x0d,0x07,0x00,0x05,0x05,0x03,0x07,0x0a,0x0a,0x0d,0x0a,
/*004930*/ 0x03,0x05,0x05,0x07,0x07,0x05,0x07,0x03,0x07,0x08,0x08,0x08,0x08,0x08,0x08,0x08,
/*004940*/ 0x08,0x08,0x08,0x03,0x03,0x07,0x07,0x07,0x08,0x0a,0x08,0x08,0x08,0x08,0x08,0x08,
/*004950*/ 0x08,0x08,0x07,0x08,0x08,0x08,0x0a,0x08,0x08,0x08,0x08,0x08,0x08,0x0a,0x08,0x08,
/*004960*/ 0x0a,0x08,0x08,0x08,0x05,0x07,0x05,0x07,0x08,0x08,0x08,0x08,0x08,0x08,0x07,0x08,
/*004970*/ 0x08,0x03,0x05,0x08,0x03,0x0a,0x08,0x08,0x08,0x08,0x08,0x08,0x07,0x08,0x08,0x0a,
/*004980*/ 0x08,0x08,0x08,0x07,0x03,0x07,0x08,0x0a,0x07,0x00,0x00,0x00,0x0e,0x0e,0x07,0x00,
/*004990*/ 0x05,0x05,0x04,0x07,0x0b,0x0b,0x0e,0x0b,0x04,0x05,0x05,0x07,0x07,0x05,0x07,0x04,
/*0049a0*/ 0x07,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x04,0x04,0x07,0x07,0x07,
/*0049b0*/ 0x09,0x0b,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x07,0x09,0x09,0x09,0x0b,0x09,
/*0049c0*/ 0x09,0x09,0x09,0x09,0x09,0x0b,0x09,0x09,0x0b,0x09,0x09,0x09,0x05,0x07,0x05,0x07,
/*0049d0*/ 0x09,0x09,0x09,0x09,0x09,0x09,0x07,0x09,0x09,0x04,0x05,0x09,0x04,0x0b,0x09,0x09,
/*0049e0*/ 0x09,0x09,0x09,0x09,0x07,0x09,0x09,0x0b,0x09,0x09,0x09,0x07,0x04,0x07,0x09,0x0b,
/*0049f0*/ 0x07,0x00,0x00,0x00,0x0f,0x0f,0x08,0x00,0x06,0x06,0x04,0x08,0x0b,0x0b,0x0f,0x0b,
/*004a00*/ 0x04,0x06,0x06,0x08,0x08,0x06,0x08,0x04,0x08,0x09,0x09,0x09,0x09,0x09,0x09,0x09,
/*004a10*/ 0x09,0x09,0x09,0x04,0x04,0x08,0x08,0x08,0x09,0x0b,0x09,0x09,0x09,0x09,0x09,0x09,
/*004a20*/ 0x09,0x09,0x08,0x09,0x09,0x09,0x0b,0x09,0x09,0x09,0x09,0x09,0x09,0x0b,0x09,0x09,
/*004a30*/ 0x0b,0x09,0x09,0x09,0x06,0x08,0x06,0x08,0x09,0x09,0x09,0x09,0x09,0x09,0x08,0x09,
/*004a40*/ 0x09,0x04,0x06,0x09,0x04,0x0b,0x09,0x09,0x09,0x09,0x09,0x09,0x08,0x09,0x09,0x0b,
/*004a50*/ 0x09,0x09,0x09,0x08,0x04,0x08,0x09,0x0b,0x08,0x00,0x00,0x00,0x10,0x10,0x08,0x00,
/*004a60*/ 0x06,0x06,0x04,0x08,0x0c,0x0c,0x10,0x0c,0x04,0x06,0x06,0x08,0x08,0x06,0x08,0x04,
/*004a70*/ 0x08,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x04,0x04,0x08,0x08,0x08,
/*004a80*/ 0x0a,0x0c,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x08,0x0a,0x0a,0x0a,0x0c,0x0a,
/*004a90*/ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0c,0x0a,0x0a,0x0c,0x0a,0x0a,0x0a,0x06,0x08,0x06,0x08,
/*004aa0*/ 0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x08,0x0a,0x0a,0x04,0x06,0x0a,0x04,0x0c,0x0a,0x0a,
/*004ab0*/ 0x0a,0x0a,0x0a,0x0a,0x08,0x0a,0x0a,0x0c,0x0a,0x0a,0x0a,0x08,0x04,0x08,0x0a,0x0c,
/*004ac0*/ 0x08,0x00,0x00,0x00,0x11,0x11,0x09,0x00,0x06,0x06,0x04,0x09,0x0d,0x0d,0x11,0x0d,
/*004ad0*/ 0x04,0x06,0x06,0x09,0x09,0x06,0x09,0x04,0x09,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
/*004ae0*/ 0x0b,0x0b,0x0b,0x04,0x04,0x09,0x09,0x09,0x0b,0x0d,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
/*004af0*/ 0x0b,0x0b,0x09,0x0b,0x0b,0x0b,0x0d,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0d,0x0b,0x0b,
/*004b00*/ 0x0d,0x0b,0x0b,0x0b,0x06,0x09,0x06,0x09,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x09,0x0b,
/*004b10*/ 0x0b,0x04,0x06,0x0b,0x04,0x0d,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x09,0x0b,0x0b,0x0d,
/*004b20*/ 0x0b,0x0b,0x0b,0x09,0x04,0x09,0x0b,0x0d,0x09,0x00,0x00,0x00,0x12,0x12,0x09,0x00,
/*004b30*/ 0x07,0x07,0x05,0x09,0x0e,0x0e,0x12,0x0e,0x05,0x07,0x07,0x09,0x09,0x07,0x09,0x05,
/*004b40*/ 0x09,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x05,0x05,0x09,0x09,0x09,
/*004b50*/ 0x0b,0x0e,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x09,0x0b,0x0b,0x0b,0x0e,0x0b,
/*004b60*/ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0e,0x0b,0x0b,0x0e,0x0b,0x0b,0x0b,0x07,0x09,0x07,0x09,
/*004b70*/ 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x09,0x0b,0x0b,0x05,0x07,0x0b,0x05,0x0e,0x0b,0x0b,
/*004b80*/ 0x0b,0x0b,0x0b,0x0b,0x09,0x0b,0x0b,0x0e,0x0b,0x0b,0x0b,0x09,0x05,0x09,0x0b,0x0e,
/*004b90*/ 0x09,0x00,0x00,0x00,0x13,0x13,0x0a,0x00,0x07,0x07,0x05,0x0a,0x0e,0x0e,0x13,0x0e,
/*004ba0*/ 0x05,0x07,0x07,0x0a,0x0a,0x07,0x0a,0x05,0x0a,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
/*004bb0*/ 0x0c,0x0c,0x0c,0x05,0x05,0x0a,0x0a,0x0a,0x0c,0x0e,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,
/*004bc0*/ 0x0c,0x0c,0x0a,0x0c,0x0c,0x0c,0x0e,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0e,0x0c,0x0c,
/*004bd0*/ 0x0e,0x0c,0x0c,0x0c,0x07,0x0a,0x07,0x0a,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0a,0x0c,
/*004be0*/ 0x0c,0x05,0x07,0x0c,0x05,0x0e,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0a,0x0c,0x0c,0x0e,
/*004bf0*/ 0x0c,0x0c,0x0c,0x0a,0x05,0x0a,0x0c,0x0e,0x0a,0x00,0x00,0x00,0x14,0x14,0x0a,0x00,
/*004c00*/ 0x08,0x08,0x05,0x0a,0x0f,0x0f,0x14,0x0f,0x05,0x08,0x08,0x0a,0x0a,0x08,0x0a,0x05,
/*004c10*/ 0x0a,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x05,0x05,0x0a,0x0a,0x0a,
/*004c20*/ 0x0d,0x0f,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0a,0x0d,0x0d,0x0d,0x0f,0x0d,
/*004c30*/ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0f,0x0d,0x0d,0x0f,0x0d,0x0d,0x0d,0x08,0x0a,0x08,0x0a,
/*004c40*/ 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0a,0x0d,0x0d,0x05,0x08,0x0d,0x05,0x0f,0x0d,0x0d,
/*004c50*/ 0x0d,0x0d,0x0d,0x0d,0x0a,0x0d,0x0d,0x0f,0x0d,0x0d,0x0d,0x0a,0x05,0x0a,0x0d,0x0f,
/*004c60*/ 0x0a,0x00,0x00,0x00,0x15,0x15,0x0b,0x00,0x08,0x08,0x05,0x0b,0x10,0x10,0x15,0x10,
/*004c70*/ 0x05,0x08,0x08,0x0b,0x0b,0x08,0x0b,0x05,0x0b,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
/*004c80*/ 0x0d,0x0d,0x0d,0x05,0x05,0x0b,0x0b,0x0b,0x0d,0x10,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
/*004c90*/ 0x0d,0x0d,0x0b,0x0d,0x0d,0x0d,0x10,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x10,0x0d,0x0d,
/*004ca0*/ 0x10,0x0d,0x0d,0x0d,0x08,0x0b,0x08,0x0b,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0b,0x0d,
/*004cb0*/ 0x0d,0x05,0x08,0x0d,0x05,0x10,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0b,0x0d,0x0d,0x10,
/*004cc0*/ 0x0d,0x0d,0x0d,0x0b,0x05,0x0b,0x0d,0x10,0x0b,0x00,0x00,0x00,0x16,0x16,0x0b,0x00,
/*004cd0*/ 0x08,0x08,0x06,0x0b,0x11,0x11,0x16,0x11,0x06,0x08,0x08,0x0b,0x0b,0x08,0x0b,0x06,
/*004ce0*/ 0x0b,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x06,0x06,0x0b,0x0b,0x0b,
/*004cf0*/ 0x0e,0x11,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0b,0x0e,0x0e,0x0e,0x11,0x0e,
/*004d00*/ 0x0e,0x0e,0x0e,0x0e,0x0e,0x11,0x0e,0x0e,0x11,0x0e,0x0e,0x0e,0x08,0x0b,0x08,0x0b,
/*004d10*/ 0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0b,0x0e,0x0e,0x06,0x08,0x0e,0x06,0x11,0x0e,0x0e,
/*004d20*/ 0x0e,0x0e,0x0e,0x0e,0x0b,0x0e,0x0e,0x11,0x0e,0x0e,0x0e,0x0b,0x06,0x0b,0x0e,0x11,
/*004d30*/ 0x0b,0x00,0x00,0x00,0x17,0x17,0x0c,0x00,0x09,0x09,0x06,0x0c,0x11,0x11,0x17,0x11,
/*004d40*/ 0x06,0x09,0x09,0x0c,0x0c,0x09,0x0c,0x06,0x0c,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
/*004d50*/ 0x0e,0x0e,0x0e,0x06,0x06,0x0c,0x0c,0x0c,0x0e,0x11,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,
/*004d60*/ 0x0e,0x0e,0x0c,0x0e,0x0e,0x0e,0x11,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x11,0x0e,0x0e,
/*004d70*/ 0x11,0x0e,0x0e,0x0e,0x09,0x0c,0x09,0x0c,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0c,0x0e,
/*004d80*/ 0x0e,0x06,0x09,0x0e,0x06,0x11,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0c,0x0e,0x0e,0x11,
/*004d90*/ 0x0e,0x0e,0x0e,0x0c,0x06,0x0c,0x0e,0x11,0x0c,0x00,0x00,0x00,0x18,0x18,0x0c,0x00,
/*004da0*/ 0x09,0x09,0x06,0x0c,0x12,0x12,0x18,0x12,0x06,0x09,0x09,0x0c,0x0c,0x09,0x0c,0x06,
/*004db0*/ 0x0c,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x06,0x06,0x0c,0x0c,0x0c,
/*004dc0*/ 0x0f,0x12,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0c,0x0f,0x0f,0x0f,0x12,0x0f,
/*004dd0*/ 0x0f,0x0f,0x0f,0x0f,0x0f,0x12,0x0f,0x0f,0x12,0x0f,0x0f,0x0f,0x09,0x0c,0x09,0x0c,
/*004de0*/ 0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0c,0x0f,0x0f,0x06,0x09,0x0f,0x06,0x12,0x0f,0x0f,
/*004df0*/ 0x0f,0x0f,0x0f,0x0f,0x0c,0x0f,0x0f,0x12,0x0f,0x0f,0x0f,0x0c,0x06,0x0c,0x0f,0x12,
/*004e00*/ 0x0c,0x00,0x00,0x00,0x00,0x00,0x01,0xaf,0x01,0x90,0x00,0x05,0x00,0x01,0x02,0x30,
/*004e10*/ 0x02,0x08,0x00,0x00,0x00,0x72,0x02,0x30,0x02,0x08,0x00,0x00,0x01,0x6b,0x00,0x28,
/*004e20*/ 0x00,0xcf,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
/*004e30*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x41,0x6c,
/*004e40*/ 0x74,0x73,0x00,0x40,0x00,0x20,0x20,0x10,0x02,0x58,0xff,0x38,0x00,0x00,0x02,0x58,
/*004e50*/ 0x00,0xc7,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0xe3,0xe5,0x12,0x31,
/*004e60*/ 0x5f,0x0f,0x3c,0xf5,0x00,0x00,0x03,0x20,0x00,0x00,0x00,0x00,0xb6,0x97,0xc2,0x82,
/*004e70*/ 0x00,0x00,0x00,0x00,0xb6,0x97,0xc2,0x82,0x00,0x00,0xff,0x39,0x02,0xbb,0x02,0x58,
/*004e80*/ 0x00,0x00,0x00,0x03,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,
/*004e90*/ 0x02,0x58,0xff,0x38,0x00,0x00,0x03,0x20,0x00,0x00,0x00,0x32,0x02,0xbb,0x00,0x01,
/*004ea0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x63,
/*004eb0*/ 0x00,0x01,0x00,0x00,0x00,0x63,0x00,0x2c,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x02,
/*004ec0*/ 0x00,0x08,0x00,0x40,0x00,0x0a,0x00,0x00,0x00,0x84,0x00,0xde,0x00,0x01,0x00,0x01
};
// -----------------------------------------------------------------------------
// The following data tables are coming from Dear Imgui.
// Re-licensed under permission as MIT-0.
//
// @todo: 0x3100, 0x312F, FONT_TW, // Bopomofo
static const unsigned table_common[] = {
0x0020, 0x00FF, // Basic Latin + Latin Supplement
0xFFFD, 0xFFFD, // Invalid
0,
};
static const unsigned table_cyrillic[] = {
0x0400, 0x052F, // Cyrillic + Cyrillic Supplement
0x2DE0, 0x2DFF, // Cyrillic Extended-A
0xA640, 0xA69F, // Cyrillic Extended-B
0,
};
static const unsigned table_vietnamese[] = {
0x0102, 0x0103,
0x0110, 0x0111,
0x0128, 0x0129,
0x0168, 0x0169,
0x01A0, 0x01A1,
0x01AF, 0x01B0,
0x1EA0, 0x1EF9,
0,
};
static const unsigned table_thai[] = {
0x0E00, 0x0E7F, // Thai
0x2010, 0x205E, // General Punctuation (smaller set than full CJK 2000-206F)
0,
};
static const unsigned table_korean[] = {
0x1100, 0x11FF, // Hangul Jamo
0x3131, 0x3163, // Korean alphabets (no 3130-318F?)
0xAC00, 0xD7A3, // Korean characters (no AC00-D7AF?)
0,
};
static const unsigned table_chinese_punctuation[] = {
0x2000, 0x206F, // General CJK Punctuation (Thai uses 2010-205E)
0,
};
static const unsigned table_chinese_japanese_common[] = {
0x3000, 0x30FF, // CJK Symbols and Punctuations, Hiragana, Katakana
0x31F0, 0x31FF, // Katakana Phonetic Extensions
0xFF00, 0xFFEF, // Half-width characters (Romaji)
0,
};
static const unsigned table_chinese_full[] = {
0x4e00, 0x9FAF, // CJK Ideograms
0,
};
static const unsigned table_eastern_europe[] = {
0x0100, 0x017F, // Latin_Extended-A
0x0180, 0x024F, // Latin_Extended-B
0x0250, 0x02AF, // International Phonetic Alphabet (IPA)
0x02B0, 0x02FF, // Spacing modifier letters (and quotes)
0x0300, 0x036F, // Fix IPA missing glyphs (?)
0x16A0, 0x16F0, // Old Norse / Runic
0x1E00, 0x1EFF, // Latin_Extended_Additional
0x2000, 0x206F, // General punctuaction
// 0x2C60, 0x2C7F, // Latin Extended-C (Uighur New Script, the Uralic Phonetic Alphabet, Shona, and Claudian Latin)
0xFB00, 0xFB4F, // Ligatures for the Latin, Armenian, and Hebrew scripts
0
};
static const unsigned table_western_europe[] = {
0x0370, 0x03FF, // Greek and Coptic
0x10A0, 0x10FF, // Modern Georgian, Svan, and Mingrelian
0x1F00, 0x1FFF, // fix ancient greek glyphs (?)
0x0400, 0x052F, // Cyrillic + Cyrillic Supplement
0x2DE0, 0x2DFF, // Cyrillic Extended-A
0xA640, 0xA69F, // Cyrillic Extended-B
0xFB00, 0xFB4F, // Ligatures for the Latin, Armenian, and Hebrew scripts
0
};
static const unsigned table_middle_east[] = {
0x0590, 0x05FF, // Hebrew, Yiddish, Ladino, and other Jewish diaspora languages.
0x0600, 0x06FF, // Arabic script and Arabic-Indic digits
0xFB00, 0xFB4F, // Ligatures for the Latin, Armenian, and Hebrew scripts
0
};
static const unsigned table_emoji[] = {
// 0xE000, 0xEB4C, // Private use (emojis)
0xE000, 0xF68B, // Private use (emojis+webfonts). U+F68C excluded
0xF68D, 0xF8FF, // Private use (emojis+webfonts)
0xF0001,0xF1CC7,// Private use (icon mdi)
0
};
// Store 2500 regularly used characters for Simplified Chinese. Table below covers 97.97% of all characters used during the month in July, 1987.
// [ref] https://zh.wiktionary.org/wiki/%E9%99%84%E5%BD%95:%E7%8E%B0%E4%BB%A3%E6%B1%89%E8%AF%AD%E5%B8%B8%E7%94%A8%E5%AD%97%E8%A1%A8
static const unsigned short packed_table_chinese[] = { // starts with 0x4E00
0,1,2,4,1,1,1,1,2,1,3,2,1,2,2,1,1,1,1,1,5,2,1,2,3,3,3,2,2,4,1,1,1,2,1,5,2,3,1,2,1,2,1,1,2,1,1,2,2,1,4,1,1,1,1,5,10,1,2,19,2,1,2,1,2,1,2,1,2,
1,5,1,6,3,2,1,2,2,1,1,1,4,8,5,1,1,4,1,1,3,1,2,1,5,1,2,1,1,1,10,1,1,5,2,4,6,1,4,2,2,2,12,2,1,1,6,1,1,1,4,1,1,4,6,5,1,4,2,2,4,10,7,1,1,4,2,4,
2,1,4,3,6,10,12,5,7,2,14,2,9,1,1,6,7,10,4,7,13,1,5,4,8,4,1,1,2,28,5,6,1,1,5,2,5,20,2,2,9,8,11,2,9,17,1,8,6,8,27,4,6,9,20,11,27,6,68,2,2,1,1,
1,2,1,2,2,7,6,11,3,3,1,1,3,1,2,1,1,1,1,1,3,1,1,8,3,4,1,5,7,2,1,4,4,8,4,2,1,2,1,1,4,5,6,3,6,2,12,3,1,3,9,2,4,3,4,1,5,3,3,1,3,7,1,5,1,1,1,1,2,
3,4,5,2,3,2,6,1,1,2,1,7,1,7,3,4,5,15,2,2,1,5,3,22,19,2,1,1,1,1,2,5,1,1,1,6,1,1,12,8,2,9,18,22,4,1,1,5,1,16,1,2,7,10,15,1,1,6,2,4,1,2,4,1,6,
1,1,3,2,4,1,6,4,5,1,2,1,1,2,1,10,3,1,3,2,1,9,3,2,5,7,2,19,4,3,6,1,1,1,1,1,4,3,2,1,1,1,2,5,3,1,1,1,2,2,1,1,2,1,1,2,1,3,1,1,1,3,7,1,4,1,1,2,1,
1,2,1,2,4,4,3,8,1,1,1,2,1,3,5,1,3,1,3,4,6,2,2,14,4,6,6,11,9,1,15,3,1,28,5,2,5,5,3,1,3,4,5,4,6,14,3,2,3,5,21,2,7,20,10,1,2,19,2,4,28,28,2,3,
2,1,14,4,1,26,28,42,12,40,3,52,79,5,14,17,3,2,2,11,3,4,6,3,1,8,2,23,4,5,8,10,4,2,7,3,5,1,1,6,3,1,2,2,2,5,28,1,1,7,7,20,5,3,29,3,17,26,1,8,4,
27,3,6,11,23,5,3,4,6,13,24,16,6,5,10,25,35,7,3,2,3,3,14,3,6,2,6,1,4,2,3,8,2,1,1,3,3,3,4,1,1,13,2,2,4,5,2,1,14,14,1,2,2,1,4,5,2,3,1,14,3,12,
3,17,2,16,5,1,2,1,8,9,3,19,4,2,2,4,17,25,21,20,28,75,1,10,29,103,4,1,2,1,1,4,2,4,1,2,3,24,2,2,2,1,1,2,1,3,8,1,1,1,2,1,1,3,1,1,1,6,1,5,3,1,1,
1,3,4,1,1,5,2,1,5,6,13,9,16,1,1,1,1,3,2,3,2,4,5,2,5,2,2,3,7,13,7,2,2,1,1,1,1,2,3,3,2,1,6,4,9,2,1,14,2,14,2,1,18,3,4,14,4,11,41,15,23,15,23,
176,1,3,4,1,1,1,1,5,3,1,2,3,7,3,1,1,2,1,2,4,4,6,2,4,1,9,7,1,10,5,8,16,29,1,1,2,2,3,1,3,5,2,4,5,4,1,1,2,2,3,3,7,1,6,10,1,17,1,44,4,6,2,1,1,6,
5,4,2,10,1,6,9,2,8,1,24,1,2,13,7,8,8,2,1,4,1,3,1,3,3,5,2,5,10,9,4,9,12,2,1,6,1,10,1,1,7,7,4,10,8,3,1,13,4,3,1,6,1,3,5,2,1,2,17,16,5,2,16,6,
1,4,2,1,3,3,6,8,5,11,11,1,3,3,2,4,6,10,9,5,7,4,7,4,7,1,1,4,2,1,3,6,8,7,1,6,11,5,5,3,24,9,4,2,7,13,5,1,8,82,16,61,1,1,1,4,2,2,16,10,3,8,1,1,
6,4,2,1,3,1,1,1,4,3,8,4,2,2,1,1,1,1,1,6,3,5,1,1,4,6,9,2,1,1,1,2,1,7,2,1,6,1,5,4,4,3,1,8,1,3,3,1,3,2,2,2,2,3,1,6,1,2,1,2,1,3,7,1,8,2,1,2,1,5,
2,5,3,5,10,1,2,1,1,3,2,5,11,3,9,3,5,1,1,5,9,1,2,1,5,7,9,9,8,1,3,3,3,6,8,2,3,2,1,1,32,6,1,2,15,9,3,7,13,1,3,10,13,2,14,1,13,10,2,1,3,10,4,15,
2,15,15,10,1,3,9,6,9,32,25,26,47,7,3,2,3,1,6,3,4,3,2,8,5,4,1,9,4,2,2,19,10,6,2,3,8,1,2,2,4,2,1,9,4,4,4,6,4,8,9,2,3,1,1,1,1,3,5,5,1,3,8,4,6,
2,1,4,12,1,5,3,7,13,2,5,8,1,6,1,2,5,14,6,1,5,2,4,8,15,5,1,23,6,62,2,10,1,1,8,1,2,2,10,4,2,2,9,2,1,1,3,2,3,1,5,3,3,2,1,3,8,1,1,1,11,3,1,1,4,
3,7,1,14,1,2,3,12,5,2,5,1,6,7,5,7,14,11,1,3,1,8,9,12,2,1,11,8,4,4,2,6,10,9,13,1,1,3,1,5,1,3,2,4,4,1,18,2,3,14,11,4,29,4,2,7,1,3,13,9,2,2,5,
3,5,20,7,16,8,5,72,34,6,4,22,12,12,28,45,36,9,7,39,9,191,1,1,1,4,11,8,4,9,2,3,22,1,1,1,1,4,17,1,7,7,1,11,31,10,2,4,8,2,3,2,1,4,2,16,4,32,2,
3,19,13,4,9,1,5,2,14,8,1,1,3,6,19,6,5,1,16,6,2,10,8,5,1,2,3,1,5,5,1,11,6,6,1,3,3,2,6,3,8,1,1,4,10,7,5,7,7,5,8,9,2,1,3,4,1,1,3,1,3,3,2,6,16,
1,4,6,3,1,10,6,1,3,15,2,9,2,10,25,13,9,16,6,2,2,10,11,4,3,9,1,2,6,6,5,4,30,40,1,10,7,12,14,33,6,3,6,7,3,1,3,1,11,14,4,9,5,12,11,49,18,51,31,
140,31,2,2,1,5,1,8,1,10,1,4,4,3,24,1,10,1,3,6,6,16,3,4,5,2,1,4,2,57,10,6,22,2,22,3,7,22,6,10,11,36,18,16,33,36,2,5,5,1,1,1,4,10,1,4,13,2,7,
5,2,9,3,4,1,7,43,3,7,3,9,14,7,9,1,11,1,1,3,7,4,18,13,1,14,1,3,6,10,73,2,2,30,6,1,11,18,19,13,22,3,46,42,37,89,7,3,16,34,2,2,3,9,1,7,1,1,1,2,
2,4,10,7,3,10,3,9,5,28,9,2,6,13,7,3,1,3,10,2,7,2,11,3,6,21,54,85,2,1,4,2,2,1,39,3,21,2,2,5,1,1,1,4,1,1,3,4,15,1,3,2,4,4,2,3,8,2,20,1,8,7,13,
4,1,26,6,2,9,34,4,21,52,10,4,4,1,5,12,2,11,1,7,2,30,12,44,2,30,1,1,3,6,16,9,17,39,82,2,2,24,7,1,7,3,16,9,14,44,2,1,2,1,2,3,5,2,4,1,6,7,5,3,
2,6,1,11,5,11,2,1,18,19,8,1,3,24,29,2,1,3,5,2,2,1,13,6,5,1,46,11,3,5,1,1,5,8,2,10,6,12,6,3,7,11,2,4,16,13,2,5,1,1,2,2,5,2,28,5,2,23,10,8,4,
4,22,39,95,38,8,14,9,5,1,13,5,4,3,13,12,11,1,9,1,27,37,2,5,4,4,63,211,95,2,2,2,1,3,5,2,1,1,2,2,1,1,1,3,2,4,1,2,1,1,5,2,2,1,1,2,3,1,3,1,1,1,
3,1,4,2,1,3,6,1,1,3,7,15,5,3,2,5,3,9,11,4,2,22,1,6,3,8,7,1,4,28,4,16,3,3,25,4,4,27,27,1,4,1,2,2,7,1,3,5,2,28,8,2,14,1,8,6,16,25,3,3,3,14,3,
3,1,1,2,1,4,6,3,8,4,1,1,1,2,3,6,10,6,2,3,18,3,2,5,5,4,3,1,5,2,5,4,23,7,6,12,6,4,17,11,9,5,1,1,10,5,12,1,1,11,26,33,7,3,6,1,17,7,1,5,12,1,11,
2,4,1,8,14,17,23,1,2,1,7,8,16,11,9,6,5,2,6,4,16,2,8,14,1,11,8,9,1,1,1,9,25,4,11,19,7,2,15,2,12,8,52,7,5,19,2,16,4,36,8,1,16,8,24,26,4,6,2,9,
5,4,36,3,28,12,25,15,37,27,17,12,59,38,5,32,127,1,2,9,17,14,4,1,2,1,1,8,11,50,4,14,2,19,16,4,17,5,4,5,26,12,45,2,23,45,104,30,12,8,3,10,2,2,
3,3,1,4,20,7,2,9,6,15,2,20,1,3,16,4,11,15,6,134,2,5,59,1,2,2,2,1,9,17,3,26,137,10,211,59,1,2,4,1,4,1,1,1,2,6,2,3,1,1,2,3,2,3,1,3,4,4,2,3,3,
1,4,3,1,7,2,2,3,1,2,1,3,3,3,2,2,3,2,1,3,14,6,1,3,2,9,6,15,27,9,34,145,1,1,2,1,1,1,1,2,1,1,1,1,2,2,2,3,1,2,1,1,1,2,3,5,8,3,5,2,4,1,3,2,2,2,12,
4,1,1,1,10,4,5,1,20,4,16,1,15,9,5,12,2,9,2,5,4,2,26,19,7,1,26,4,30,12,15,42,1,6,8,172,1,1,4,2,1,1,11,2,2,4,2,1,2,1,10,8,1,2,1,4,5,1,2,5,1,8,
4,1,3,4,2,1,6,2,1,3,4,1,2,1,1,1,1,12,5,7,2,4,3,1,1,1,3,3,6,1,2,2,3,3,3,2,1,2,12,14,11,6,6,4,12,2,8,1,7,10,1,35,7,4,13,15,4,3,23,21,28,52,5,
26,5,6,1,7,10,2,7,53,3,2,1,1,1,2,163,532,1,10,11,1,3,3,4,8,2,8,6,2,2,23,22,4,2,2,4,2,1,3,1,3,3,5,9,8,2,1,2,8,1,10,2,12,21,20,15,105,2,3,1,1,
3,2,3,1,1,2,5,1,4,15,11,19,1,1,1,1,5,4,5,1,1,2,5,3,5,12,1,2,5,1,11,1,1,15,9,1,4,5,3,26,8,2,1,3,1,1,15,19,2,12,1,2,5,2,7,2,19,2,20,6,26,7,5,
2,2,7,34,21,13,70,2,128,1,1,2,1,1,2,1,1,3,2,2,2,15,1,4,1,3,4,42,10,6,1,49,85,8,1,2,1,1,4,4,2,3,6,1,5,7,4,3,211,4,1,2,1,2,5,1,2,4,2,2,6,5,6,
10,3,4,48,100,6,2,16,296,5,27,387,2,2,3,7,16,8,5,38,15,39,21,9,10,3,7,59,13,27,21,47,5,21,6
};
// 2999 japanese ideograms = 2136 Joyo (for common use) + 863 Jinmeiyo (for personal name) Kanji code points.
// [ref] https://github.com/ocornut/imgui/pull/3627 - Missing 1 Joyo Kanji: U+20B9F (Kun'yomi: Shikaru, On'yomi: Shitsu,shichi).
static const unsigned short packed_table_japanese[] = { // starts with 0x4E00
0,1,2,4,1,1,1,1,2,1,3,3,2,2,1,5,3,5,7,5,6,1,2,1,7,2,6,3,1,8,1,1,4,1,1,18,2,11,2,6,2,1,2,1,5,1,2,1,3,1,2,1,2,3,3,1,1,2,3,1,1,1,12,7,9,1,4,5,1,
1,2,1,10,1,1,9,2,2,4,5,6,9,3,1,1,1,1,9,3,18,5,2,2,2,2,1,6,3,7,1,1,1,1,2,2,4,2,1,23,2,10,4,3,5,2,4,10,2,4,13,1,6,1,9,3,1,1,6,6,7,6,3,1,2,11,3,
2,2,3,2,15,2,2,5,4,3,6,4,1,2,5,2,12,16,6,13,9,13,2,1,1,7,16,4,7,1,19,1,5,1,2,2,7,7,8,2,6,5,4,9,18,7,4,5,9,13,11,8,15,2,1,1,1,2,1,2,2,1,2,2,8,
2,9,3,3,1,1,4,4,1,1,1,4,9,1,4,3,5,5,2,7,5,3,4,8,2,1,13,2,3,3,1,14,1,1,4,5,1,3,6,1,5,2,1,1,3,3,3,3,1,1,2,7,6,6,7,1,4,7,6,1,1,1,1,1,12,3,3,9,5,
2,6,1,5,6,1,2,3,18,2,4,14,4,1,3,6,1,1,6,3,5,5,3,2,2,2,2,12,3,1,4,2,3,2,3,11,1,7,4,1,2,1,3,17,1,9,1,24,1,1,4,2,2,4,1,2,7,1,1,1,3,1,2,2,4,15,1,
1,2,1,1,2,1,5,2,5,20,2,5,9,1,10,8,7,6,1,1,1,1,1,1,6,2,1,2,8,1,1,1,1,5,1,1,3,1,1,1,1,3,1,1,12,4,1,3,1,1,1,1,1,10,3,1,7,5,13,1,2,3,4,6,1,1,30,
2,9,9,1,15,38,11,3,1,8,24,7,1,9,8,10,2,1,9,31,2,13,6,2,9,4,49,5,2,15,2,1,10,2,1,1,1,2,2,6,15,30,35,3,14,18,8,1,16,10,28,12,19,45,38,1,3,2,3,
13,2,1,7,3,6,5,3,4,3,1,5,7,8,1,5,3,18,5,3,6,1,21,4,24,9,24,40,3,14,3,21,3,2,1,2,4,2,3,1,15,15,6,5,1,1,3,1,5,6,1,9,7,3,3,2,1,4,3,8,21,5,16,4,
5,2,10,11,11,3,6,3,2,9,3,6,13,1,2,1,1,1,1,11,12,6,6,1,4,2,6,5,2,1,1,3,3,6,13,3,1,1,5,1,2,3,3,14,2,1,2,2,2,5,1,9,5,1,1,6,12,3,12,3,4,13,2,14,
2,8,1,17,5,1,16,4,2,2,21,8,9,6,23,20,12,25,19,9,38,8,3,21,40,25,33,13,4,3,1,4,1,2,4,1,2,5,26,2,1,1,2,1,3,6,2,1,1,1,1,1,1,2,3,1,1,1,9,2,3,1,1,
1,3,6,3,2,1,1,6,6,1,8,2,2,2,1,4,1,2,3,2,7,3,2,4,1,2,1,2,2,1,1,1,1,1,3,1,2,5,4,10,9,4,9,1,1,1,1,1,1,5,3,2,1,6,4,9,6,1,10,2,31,17,8,3,7,5,40,1,
7,7,1,6,5,2,10,7,8,4,15,39,25,6,28,47,18,10,7,1,3,1,1,2,1,1,1,3,3,3,1,1,1,3,4,2,1,4,1,3,6,10,7,8,6,2,2,1,3,3,2,5,8,7,9,12,2,15,1,1,4,1,2,1,1,
1,3,2,1,3,3,5,6,2,3,2,10,1,4,2,8,1,1,1,11,6,1,21,4,16,3,1,3,1,4,2,3,6,5,1,3,1,1,3,3,4,6,1,1,10,4,2,7,10,4,7,4,2,9,4,3,1,1,1,4,1,8,3,4,1,3,1,
6,1,4,2,1,4,7,2,1,8,1,4,5,1,1,2,2,4,6,2,7,1,10,1,1,3,4,11,10,8,21,4,6,1,3,5,2,1,2,28,5,5,2,3,13,1,2,3,1,4,2,1,5,20,3,8,11,1,3,3,3,1,8,10,9,2,
10,9,2,3,1,1,2,4,1,8,3,6,1,7,8,6,11,1,4,29,8,4,3,1,2,7,13,1,4,1,6,2,6,12,12,2,20,3,2,3,6,4,8,9,2,7,34,5,1,18,6,1,1,4,4,5,7,9,1,2,2,4,3,4,1,7,
2,2,2,6,2,3,25,5,3,6,1,4,6,7,4,2,1,4,2,13,6,4,4,3,1,5,3,4,4,3,2,1,1,4,1,2,1,1,3,1,11,1,6,3,1,7,3,6,2,8,8,6,9,3,4,11,3,2,10,12,2,5,11,1,6,4,5,
3,1,8,5,4,6,6,3,5,1,1,3,2,1,2,2,6,17,12,1,10,1,6,12,1,6,6,19,9,6,16,1,13,4,4,15,7,17,6,11,9,15,12,6,7,2,1,2,2,15,9,3,21,4,6,49,18,7,3,2,3,1,
6,8,2,2,6,2,9,1,3,6,4,4,1,2,16,2,5,2,1,6,2,3,5,3,1,2,5,1,2,1,9,3,1,8,6,4,8,11,3,1,1,1,1,3,1,13,8,4,1,3,2,2,1,4,1,11,1,5,2,1,5,2,5,8,6,1,1,7,
4,3,8,3,2,7,2,1,5,1,5,2,4,7,6,2,8,5,1,11,4,5,3,6,18,1,2,13,3,3,1,21,1,1,4,1,4,1,1,1,8,1,2,2,7,1,2,4,2,2,9,2,1,1,1,4,3,6,3,12,5,1,1,1,5,6,3,2,
4,8,2,2,4,2,7,1,8,9,5,2,3,2,1,3,2,13,7,14,6,5,1,1,2,1,4,2,23,2,1,1,6,3,1,4,1,15,3,1,7,3,9,14,1,3,1,4,1,1,5,8,1,3,8,3,8,15,11,4,14,4,4,2,5,5,
1,7,1,6,14,7,7,8,5,15,4,8,6,5,6,2,1,13,1,20,15,11,9,2,5,6,2,11,2,6,2,5,1,5,8,4,13,19,25,4,1,1,11,1,34,2,5,9,14,6,2,2,6,1,1,14,1,3,14,13,1,6,
12,21,14,14,6,32,17,8,32,9,28,1,2,4,11,8,3,1,14,2,5,15,1,1,1,1,3,6,4,1,3,4,11,3,1,1,11,30,1,5,1,4,1,5,8,1,1,3,2,4,3,17,35,2,6,12,17,3,1,6,2,
1,1,12,2,7,3,3,2,1,16,2,8,3,6,5,4,7,3,3,8,1,9,8,5,1,2,1,3,2,8,1,2,9,12,1,1,2,3,8,3,24,12,4,3,7,5,8,3,3,3,3,3,3,1,23,10,3,1,2,2,6,3,1,16,1,16,
22,3,10,4,11,6,9,7,7,3,6,2,2,2,4,10,2,1,1,2,8,7,1,6,4,1,3,3,3,5,10,12,12,2,3,12,8,15,1,1,16,6,6,1,5,9,11,4,11,4,2,6,12,1,17,5,13,1,4,9,5,1,11,
2,1,8,1,5,7,28,8,3,5,10,2,17,3,38,22,1,2,18,12,10,4,38,18,1,4,44,19,4,1,8,4,1,12,1,4,31,12,1,14,7,75,7,5,10,6,6,13,3,2,11,11,3,2,5,28,15,6,18,
18,5,6,4,3,16,1,7,18,7,36,3,5,3,1,7,1,9,1,10,7,2,4,2,6,2,9,7,4,3,32,12,3,7,10,2,23,16,3,1,12,3,31,4,11,1,3,8,9,5,1,30,15,6,12,3,2,2,11,19,9,
14,2,6,2,3,19,13,17,5,3,3,25,3,14,1,1,1,36,1,3,2,19,3,13,36,9,13,31,6,4,16,34,2,5,4,2,3,3,5,1,1,1,4,3,1,17,3,2,3,5,3,1,3,2,3,5,6,3,12,11,1,3,
1,2,26,7,12,7,2,14,3,3,7,7,11,25,25,28,16,4,36,1,2,1,6,2,1,9,3,27,17,4,3,4,13,4,1,3,2,2,1,10,4,2,4,6,3,8,2,1,18,1,1,24,2,2,4,33,2,3,63,7,1,6,
40,7,3,4,4,2,4,15,18,1,16,1,1,11,2,41,14,1,3,18,13,3,2,4,16,2,17,7,15,24,7,18,13,44,2,2,3,6,1,1,7,5,1,7,1,4,3,3,5,10,8,2,3,1,8,1,1,27,4,2,1,
12,1,2,1,10,6,1,6,7,5,2,3,7,11,5,11,3,6,6,2,3,15,4,9,1,1,2,1,2,11,2,8,12,8,5,4,2,3,1,5,2,2,1,14,1,12,11,4,1,11,17,17,4,3,2,5,5,7,3,1,5,9,9,8,
2,5,6,6,13,13,2,1,2,6,1,2,2,49,4,9,1,2,10,16,7,8,4,3,2,23,4,58,3,29,1,14,19,19,11,11,2,7,5,1,3,4,6,2,18,5,12,12,17,17,3,3,2,4,1,6,2,3,4,3,1,
1,1,1,5,1,1,9,1,3,1,3,6,1,8,1,1,2,6,4,14,3,1,4,11,4,1,3,32,1,2,4,13,4,1,2,4,2,1,3,1,11,1,4,2,1,4,4,6,3,5,1,6,5,7,6,3,23,3,5,3,5,3,3,13,3,9,10,
1,12,10,2,3,18,13,7,160,52,4,2,2,3,2,14,5,4,12,4,6,4,1,20,4,11,6,2,12,27,1,4,1,2,2,7,4,5,2,28,3,7,25,8,3,19,3,6,10,2,2,1,10,2,5,4,1,3,4,1,5,
3,2,6,9,3,6,2,16,3,3,16,4,5,5,3,2,1,2,16,15,8,2,6,21,2,4,1,22,5,8,1,1,21,11,2,1,11,11,19,13,12,4,2,3,2,3,6,1,8,11,1,4,2,9,5,2,1,11,2,9,1,1,2,
14,31,9,3,4,21,14,4,8,1,7,2,2,2,5,1,4,20,3,3,4,10,1,11,9,8,2,1,4,5,14,12,14,2,17,9,6,31,4,14,1,20,13,26,5,2,7,3,6,13,2,4,2,19,6,2,2,18,9,3,5,
12,12,14,4,6,2,3,6,9,5,22,4,5,25,6,4,8,5,2,6,27,2,35,2,16,3,7,8,8,6,6,5,9,17,2,20,6,19,2,13,3,1,1,1,4,17,12,2,14,7,1,4,18,12,38,33,2,10,1,1,
2,13,14,17,11,50,6,33,20,26,74,16,23,45,50,13,38,33,6,6,7,4,4,2,1,3,2,5,8,7,8,9,3,11,21,9,13,1,3,10,6,7,1,2,2,18,5,5,1,9,9,2,68,9,19,13,2,5,
1,4,4,7,4,13,3,9,10,21,17,3,26,2,1,5,2,4,5,4,1,7,4,7,3,4,2,1,6,1,1,20,4,1,9,2,2,1,3,3,2,3,2,1,1,1,20,2,3,1,6,2,3,6,2,4,8,1,3,2,10,3,5,3,4,4,
3,4,16,1,6,1,10,2,4,2,1,1,2,10,11,2,2,3,1,24,31,4,10,10,2,5,12,16,164,15,4,16,7,9,15,19,17,1,2,1,1,5,1,1,1,1,1,3,1,4,3,1,3,1,3,1,2,1,1,3,3,7,
2,8,1,2,2,2,1,3,4,3,7,8,12,92,2,10,3,1,3,14,5,25,16,42,4,7,7,4,2,21,5,27,26,27,21,25,30,31,2,1,5,13,3,22,5,6,6,11,9,12,1,5,9,7,5,5,22,60,3,5,
13,1,1,8,1,1,3,3,2,1,9,3,3,18,4,1,2,3,7,6,3,1,2,3,9,1,3,1,3,2,1,3,1,1,1,2,1,11,3,1,6,9,1,3,2,3,1,2,1,5,1,1,4,3,4,1,2,2,4,4,1,7,2,1,2,2,3,5,13,
18,3,4,14,9,9,4,16,3,7,5,8,2,6,48,28,3,1,1,4,2,14,8,2,9,2,1,15,2,4,3,2,10,16,12,8,7,1,1,3,1,1,1,2,7,4,1,6,4,38,39,16,23,7,15,15,3,2,12,7,21,
37,27,6,5,4,8,2,10,8,8,6,5,1,2,1,3,24,1,16,17,9,23,10,17,6,1,51,55,44,13,294,9,3,6,2,4,2,2,15,1,1,1,13,21,17,68,14,8,9,4,1,4,9,3,11,7,1,1,1,
5,6,3,2,1,1,1,2,3,8,1,2,2,4,1,5,5,2,1,4,3,7,13,4,1,4,1,3,1,1,1,5,5,10,1,6,1,5,2,1,5,2,4,1,4,5,7,3,18,2,9,11,32,4,3,3,2,4,7,11,16,9,11,8,13,38,
32,8,4,2,1,1,2,1,2,4,4,1,1,1,4,1,21,3,11,1,16,1,1,6,1,3,2,4,9,8,57,7,44,1,3,3,13,3,10,1,1,7,5,2,7,21,47,63,3,15,4,7,1,16,1,1,2,8,2,3,42,15,4,
1,29,7,22,10,3,78,16,12,20,18,4,67,11,5,1,3,15,6,21,31,32,27,18,13,71,35,5,142,4,10,1,2,50,19,33,16,35,37,16,19,27,7,1,133,19,1,4,8,7,20,1,4,
4,1,10,3,1,6,1,2,51,5,40,15,24,43,22928,11,1,13,154,70,3,1,1,7,4,10,1,2,1,1,2,1,2,1,2,2,1,1,2,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,
3,2,1,1,1,1,2,1,1,
};
// -----------------------------------------------------------------------------
enum { FONT_MAX_COLORS = 256};
enum { FONT_MAX_STRING_LEN = 40000 }; // more glyphs than any reasonable person would show on the screen at once. you can only fit 20736 10x10 rects in a 1920x1080 window
static unsigned font_palette[FONT_MAX_COLORS] = {
RGB4(248, 248, 242, 255), // foreground color
RGB4(249, 38, 114, 255), // operator
RGB4(174, 129, 255, 255), // numeric
RGB4(102, 217, 239, 255), // function
RGB4(249, 38, 114, 255), // keyword
RGB4(117, 113, 94, 255), // comment
RGB4(102, 217, 239, 255), // type
RGB4( 73, 72, 62, 255), // background color
RGB4( 39, 40, 34, 255), // clear color
};
typedef struct font_t {
bool initialized;
//char filename[256];
// character info
// filled up by stb_truetype.h
stbtt_packedchar *cdata;
unsigned num_glyphs;
unsigned *cp2iter;
unsigned *iter2cp;
unsigned begin; // first glyph. used in cp2iter table to clamp into a lesser range
// font info and data
int height; // bitmap height
int width; // bitmap width
float font_size; // font size in pixels (matches scale[0+1] size below)
float factor; // font factor (font_size / (ascent - descent))
float scale[7]; // user defined font scale (match H1..H6 tags)
// displacement info
float ascent; // max distance above baseline for all glyphs
float descent; // max distance below baseline for all glyphs
float linegap; // distance betwen ascent of next line and descent of current line
float linedist; // distance between the baseline of two lines (ascent - descent + linegap)
// opengl stuff
GLuint vao;
GLuint program;
// font bitmap texture
// generated using stb_truetype.h
GLuint texture_fontdata;
// metadata texture.
// first row contains information on which parts of the bitmap correspond to a glyph.
// the second row contain information about the relative displacement of the glyph relative to the cursor position
GLuint texture_offsets;
// color texture
// used to color each glyph individually, e.g. for syntax highlighting
GLuint texture_colors;
// vbos
GLuint vbo_quad; // vec2: simply just a regular [0,1]x[0,1] quad
GLuint vbo_instances; // vec4: (char_pos_x, char_pos_y, char_index, color_index)
// render state
renderstate_t rs;
} font_t;
enum { FONTS_MAX = 10 };
static font_t fonts[FONTS_MAX] = {0};
static
void font_init() {
do_once {
font_face_from_mem(FONT_FACE1, bm_mini_ttf,countof(bm_mini_ttf), 42.5f, 0);
font_face_from_mem(FONT_FACE2, bm_mini_ttf,countof(bm_mini_ttf), 42.5f, 0);
font_face_from_mem(FONT_FACE3, bm_mini_ttf,countof(bm_mini_ttf), 42.5f, 0);
font_face_from_mem(FONT_FACE4, bm_mini_ttf,countof(bm_mini_ttf), 42.5f, 0);
font_face_from_mem(FONT_FACE5, bm_mini_ttf,countof(bm_mini_ttf), 42.5f, 0);
font_face_from_mem(FONT_FACE6, bm_mini_ttf,countof(bm_mini_ttf), 42.5f, 0);
font_face_from_mem(FONT_FACE7, bm_mini_ttf,countof(bm_mini_ttf), 42.5f, 0);
font_face_from_mem(FONT_FACE8, bm_mini_ttf,countof(bm_mini_ttf), 42.5f, 0);
font_face_from_mem(FONT_FACE9, bm_mini_ttf,countof(bm_mini_ttf), 42.5f, 0);
font_face_from_mem(FONT_FACE10,bm_mini_ttf,countof(bm_mini_ttf), 42.5f, 0);
}
}
// Remap color within all existing color textures
void font_color(const char *tag, uint32_t color) {
font_init();
unsigned index = *tag - FONT_COLOR1[0];
if( index < FONT_MAX_COLORS ) {
font_palette[index] = color;
for( int i = 0; i < FONTS_MAX; ++i ) {
font_t *f = &fonts[i];
if( f->initialized ) {
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, f->texture_colors);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, FONT_MAX_COLORS, 1, GL_RGBA, GL_UNSIGNED_BYTE, font_palette);
}
}
}
}
void ui_font() {
for( int i = 0; i < countof(fonts); ++i ) {
if( ui_collapse(va("Font %d", i), va("%p%d", &fonts[i], i) ) ) {
font_t *f = &fonts[i];
ui_float("Ascent", &f->ascent);
ui_float("Descent", &f->descent);
ui_float("Line Gap", &f->linegap);
f->linedist = (f->ascent-f->descent+f->linegap);
ui_collapse_end();
}
}
}
void font_scale(const char *tag, int s, float v) {
font_init();
if (s < 0 || s >= 10) return;
unsigned index = *tag - FONT_FACE1[0];
if( index > FONTS_MAX ) return;
font_t *f = &fonts[index];
if (!f->initialized) return;
f->scale[s] = v / f->font_size;
}
void font_scales(const char *tag, float h1, float h2, float h3, float h4, float h5, float h6) {
font_init();
unsigned index = *tag - FONT_FACE1[0];
if( index > FONTS_MAX ) return;
font_t *f = &fonts[index];
if (!f->initialized) return;
f->scale[0] = h1 / f->font_size;
f->scale[1] = h1 / f->font_size;
f->scale[2] = h2 / f->font_size;
f->scale[3] = h3 / f->font_size;
f->scale[4] = h4 / f->font_size;
f->scale[5] = h5 / f->font_size;
f->scale[6] = h6 / f->font_size;
}
// 1. Compile the shaders.
// 1. Call stb_truetype.h routines to read and parse a .ttf file.
// 1. Create a bitmap that is uploaded to the gpu using opengl.
// 1. Calculate and save a bunch of useful variables and put them in the global font variable.
void font_face_from_mem(const char *tag, const void *ttf_data, unsigned ttf_len, float font_size, unsigned flags) {
unsigned index = *tag - FONT_FACE1[0];
if( index > FONTS_MAX ) return;
if( font_size <= 0 || font_size > 72 ) return;
if( !ttf_data || !ttf_len ) return;
if(!(flags & FONT_EM))
flags |= FONT_ASCII; // ensure this minimal range [0020-00FF] is almost always in
font_t *f = &fonts[index];
f->initialized = 1;
// load .ttf into a bitmap using stb_truetype.h
int dim = flags & FONT_4096 ? 4096 : flags & FONT_2048 ? 2048 : flags & FONT_1024 ? 1024 : 512;
f->width = dim;
f->height = dim;
// change size [h1(largest) to h3(regular) to h6(smallest)]
f->font_size = font_size;
f->scale[0] = 1.0000f; // H1
f->scale[1] = 1.0000f; // H1
f->scale[2] = 0.7500f; // H2
f->scale[3] = 0.6600f; // H3
f->scale[4] = 0.5000f; // H4
f->scale[5] = 0.3750f; // H5
f->scale[6] = 0.2500f; // H6
const char *vs = vfs_read("vs_font.glsl");
const char *fs = vfs_read("fs_font.glsl");
f->program = shader(vs, fs, "vertexPosition,instanceGlyph", "outColor", NULL);
// figure out what ranges we're about to bake
#define MERGE_TABLE(table) do { \
for( unsigned i = 0 ; table[i]; i += 2 ) { \
uint64_t begin = table[i+0], end = table[i+1]; \
for( unsigned j = begin; j <= end; ++j ) { \
array_push(sorted, j); \
} \
} } while(0)
#define MERGE_PACKED_TABLE(codepoint_begin, table) do { \
for( int i = 0, begin = codepoint_begin, end = countof(table); i < end; i++) { \
array_push(sorted, (unsigned)(begin + table[i])); \
begin += table[i]; \
} } while(0)
array(uint64_t) sorted = 0;
if(flags & FONT_ASCII) { MERGE_TABLE(table_common); }
if(flags & FONT_EM) { MERGE_TABLE(table_emoji); }
if(flags & FONT_EU) { MERGE_TABLE(table_eastern_europe); }
if(flags & FONT_RU) { MERGE_TABLE(table_western_europe); }
if(flags & FONT_EL) { MERGE_TABLE(table_western_europe); }
if(flags & FONT_AR) { MERGE_TABLE(table_middle_east); }
if(flags & FONT_HE) { MERGE_TABLE(table_middle_east); }
if(flags & FONT_TH) { MERGE_TABLE(table_thai); }
if(flags & FONT_VI) { MERGE_TABLE(table_vietnamese); }
if(flags & FONT_KR) { MERGE_TABLE(table_korean); }
if(flags & FONT_JP) { MERGE_TABLE(table_chinese_japanese_common); MERGE_PACKED_TABLE(0x4E00, packed_table_japanese); }
if(flags & FONT_ZH) { MERGE_TABLE(table_chinese_japanese_common); MERGE_PACKED_TABLE(0x4E00, packed_table_chinese); } // zh-simplified
if(flags & FONT_ZH) { MERGE_TABLE(table_chinese_punctuation); } // both zh-simplified and zh-full
// if(flags & FONT_ZH) { MERGE_TABLE(table_chinese_full); } // zh-full
array_sort(sorted, less_64_ptr);
array_unique(sorted, less_64_ptr); // sort + unique pass
// pack and create bitmap
unsigned char *bitmap = (unsigned char*)MALLOC(f->height*f->width);
int charCount = *array_back(sorted) - sorted[0] + 1; // 0xEFFFF;
f->cdata = (stbtt_packedchar*)CALLOC(1, sizeof(stbtt_packedchar) * charCount);
f->iter2cp = (unsigned*)MALLOC( sizeof(unsigned) * charCount );
f->cp2iter = (unsigned*)MALLOC( sizeof(unsigned) * charCount );
for( int i = 0; i < charCount; ++i )
f->iter2cp[i] = f->cp2iter[i] = 0xFFFD; // default invalid glyph
// find first char
{
stbtt_fontinfo info = {0};
stbtt_InitFont(&info, ttf_data, stbtt_GetFontOffsetForIndex(ttf_data,0));
for( int i = 0, end = array_count(sorted); i < end; ++i ) {
unsigned glyph = sorted[i];
if(!stbtt_FindGlyphIndex(&info, glyph)) continue;
f->begin = glyph;
break;
}
}
stbtt_pack_context pc;
if( !stbtt_PackBegin(&pc, bitmap, f->width, f->height, 0, 1, NULL) ) {
PANIC("Failed to initialize atlas font");
}
stbtt_PackSetOversampling(&pc, flags & FONT_OVERSAMPLE_X ? 2 : 1, flags & FONT_OVERSAMPLE_Y ? 2 : 1); /*useful on small fonts*/
int count = 0;
for( int i = 0, num = array_count(sorted); i < num; ++i ) {
uint64_t begin = sorted[i], end = sorted[i];
while( i < (num-1) && (sorted[i+1]-sorted[i]) == 1 ) end = sorted[++i];
//printf("(%d,%d)", (unsigned)begin, (unsigned)end);
if( begin < f->begin ) continue;
if( stbtt_PackFontRange(&pc, ttf_data, 0, f->font_size, begin, end - begin + 1, (stbtt_packedchar*)f->cdata + begin - f->begin) ) {
for( uint64_t cp = begin; cp <= end; ++cp ) {
// unicode->index runtime lookup
f->cp2iter[ cp - f->begin ] = count;
f->iter2cp[ count++ ] = cp;
}
} else {
PRINTF("!Failed to pack atlas font. Likely out of texture mem.");
}
}
stbtt_PackEnd(&pc);
f->num_glyphs = count;
assert( f->num_glyphs < charCount );
array_free(sorted);
// calculate vertical font metrics
stbtt_fontinfo info = {0};
stbtt_InitFont(&info, ttf_data, stbtt_GetFontOffsetForIndex(ttf_data,0));
int a, d, l;
if (!stbtt_GetFontVMetricsOS2(&info, &a, &d, &l))
stbtt_GetFontVMetrics(&info, &a, &d, &l);
f->ascent = a;
f->descent = d;
f->linegap = l;
f->linedist = (a - d + l);
f->factor = (f->font_size / (f->ascent - f->descent));
// save some gpu memory by truncating unused vertical space in atlas texture
{
int max_y1 = 0;
for (int i = 0; i < f->num_glyphs; i++) {
int cp = f->iter2cp[i];
if( cp == 0xFFFD ) continue;
stbtt_packedchar *cd = &f->cdata[ cp - f->begin ];
if (cd->y1 > max_y1) {
max_y1 = cd->y1;
}
}
// cut away the unused part of the bitmap
f->height = max_y1+1;
}
PRINTF("Font atlas size %dx%d (GL_R, %5.2fKiB) (%u glyphs)\n", f->width, f->height, f->width * f->height / 1024.f, f->num_glyphs);
// vao
glGenVertexArrays(1, &f->vao);
glBindVertexArray(f->vao);
// quad vbo setup, used for glyph vertex positions,
// just uv coordinates that will be stretched accordingly by the glyphs width and height
float v[] = {0,0, 1,0, 0,1, 0,1, 1,0, 1,1};
glGenBuffers(1, &f->vbo_quad);
glBindBuffer(GL_ARRAY_BUFFER, f->vbo_quad);
glBufferData(GL_ARRAY_BUFFER, sizeof(v), v, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,0,(void*)0);
glVertexAttribDivisor(0, 0);
// instance vbo setup: for glyph positions, glyph index and color index
glGenBuffers(1, &f->vbo_instances);
glBindBuffer(GL_ARRAY_BUFFER, f->vbo_instances);
glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*FONT_MAX_STRING_LEN, NULL, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1,4,GL_FLOAT,GL_FALSE,0,(void*)0);
glVertexAttribDivisor(1, 1);
// glEnable(GL_FRAMEBUFFER_SRGB);
// setup and upload font bitmap texture
glGenTextures(1, &f->texture_fontdata);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, f->texture_fontdata);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, f->width, f->height, 0, GL_RED, GL_UNSIGNED_BYTE, bitmap);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// last chance to inspect the font atlases
if( flag("--font-debug") )
stbi_write_png(va("font_debug%d.png", index), f->width, f->height, 1, bitmap, 0);
FREE(bitmap);
// setup and upload font metadata texture
// used for lookup in the bitmap texture
glGenTextures(1, &f->texture_offsets);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, f->texture_offsets);
float *texture_offsets = (float*)MALLOC(sizeof(float)*8*f->num_glyphs);
// remap larger 0xFFFF unicodes into smaller NUM_GLYPHS glyphs
for (int i = 0, count = 0; i < f->num_glyphs; i++) {
unsigned cp = f->iter2cp[ i ];
if(cp == 0xFFFD) continue;
stbtt_packedchar *cd = &f->cdata[ cp - f->begin ];
// if(cd->x1==cd->x0) { f->iter2cp[i] = f->cp2iter[cp - f->begin] = 0xFFFD; continue; }
int k1 = 0*f->num_glyphs + count;
int k2 = 1*f->num_glyphs + count; ++count;
texture_offsets[4*k1+0] = cd->x0/(double)f->width;
texture_offsets[4*k1+1] = cd->y0/(double)f->height;
texture_offsets[4*k1+2] = (cd->x1-cd->x0)/(double)f->width;
texture_offsets[4*k1+3] = (cd->y1-cd->y0)/(double)f->height;
texture_offsets[4*k2+0] = cd->xoff/(double)f->width;
texture_offsets[4*k2+1] = cd->yoff/(double)f->height;
texture_offsets[4*k2+2] = cd->xoff2/(double)f->width;
texture_offsets[4*k2+3] = cd->yoff2/(double)f->height;
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, f->num_glyphs, 2, 0, GL_RGBA, GL_FLOAT, texture_offsets);
FREE(texture_offsets);
// setup color texture
glGenTextures(1, &f->texture_colors);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, f->texture_colors);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, FONT_MAX_COLORS, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, font_palette);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
// upload constant uniforms
glUseProgram(f->program);
glUniform1i(glGetUniformLocation(f->program, "sampler_font"), 0);
glUniform1i(glGetUniformLocation(f->program, "sampler_meta"), 1);
glUniform1i(glGetUniformLocation(f->program, "sampler_colors"), 2);
glUniform2f(glGetUniformLocation(f->program, "res_bitmap"), f->width, f->height);
glUniform2f(glGetUniformLocation(f->program, "res_meta"), f->num_glyphs, 2);
glUniform1f(glGetUniformLocation(f->program, "num_colors"), FONT_MAX_COLORS);
(void)flags;
// set up pipeline
f->rs = renderstate();
f->rs.blend_enabled = 1;
f->rs.blend_func = GL_FUNC_ADD;
f->rs.blend_src = GL_SRC_ALPHA;
f->rs.blend_dst = GL_ONE_MINUS_SRC_ALPHA;
f->rs.scissor_test_enabled = 1;
f->rs.depth_test_enabled = 0;
f->rs.cull_face_enabled = 0;
}
void font_face(const char *tag, const char *filename_ttf, float font_size, unsigned flags) {
font_init();
int len;
const char *buffer = vfs_load(filename_ttf, &len);
if( !buffer ) buffer = file_load(filename_ttf, &len);
font_face_from_mem(tag, buffer,len, font_size, flags);
}
static
void font_draw_cmd(font_t *f, const float *glyph_data, int glyph_idx, float factor, vec2 offset, vec4 rect) {
// Backup GL state
GLint last_program, last_vertex_array;
GLint last_texture0, last_texture1, last_texture2;
GLint last_blend_src, last_blend_dst;
GLint last_blend_equation_rgb, last_blend_equation_alpha;
glGetIntegerv(GL_CURRENT_PROGRAM, &last_program);
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array);
glActiveTexture(GL_TEXTURE0);
glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture0);
glActiveTexture(GL_TEXTURE1);
glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture1);
glActiveTexture(GL_TEXTURE2);
glGetIntegerv(GL_TEXTURE_BINDING_1D, &last_texture2);
glScissor(rect.x, window_height() - (rect.y+rect.w), rect.z, rect.w);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, f->texture_fontdata);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, f->texture_offsets);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, f->texture_colors);
// update bindings
glBindVertexArray(f->vao);
// update uniforms
glUseProgram(f->program);
glUniform1f(glGetUniformLocation(f->program, "scale_factor"), factor);
glUniform2fv(glGetUniformLocation(f->program, "string_offset"), 1, &offset.x);
glUniform1f(glGetUniformLocation(f->program, "offset_firstline"), f->ascent*f->factor);
GLint dims[4] = {0};
glGetIntegerv(GL_VIEWPORT, dims);
glUniform2f(glGetUniformLocation(f->program, "resolution"), dims[2], dims[3]);
// actual uploading
glBindBuffer(GL_ARRAY_BUFFER, f->vbo_instances);
glBufferSubData(GL_ARRAY_BUFFER, 0, 4*4*glyph_idx, glyph_data);
// setup pipeline
renderstate_apply(&f->rs);
// actual drawing
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, glyph_idx);
// Restore modified GL state
glUseProgram(last_program);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, last_texture0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, last_texture1);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, last_texture2);
glBindVertexArray(last_vertex_array);
}
// 1. call font_face() if it's the first time it's called.
// 1. parse the string and update the instance vbo, then upload it
// 1. draw the string
static
vec2 font_draw_ex(const char *text, vec2 offset, vec4 rect, const char *col, void (*draw_cmd)(font_t *,const float *,int,float,vec2,vec4)) {
font_init();
// sanity checks
int len = strlen(text);
if (len >= FONT_MAX_STRING_LEN) {
return vec2(0, 0);
}
// pre-init
static __thread float *text_glyph_data;
do_once text_glyph_data = MALLOC(4 * FONT_MAX_STRING_LEN * sizeof(float));
// ready
font_t *f = &fonts[0];
int S = 3;
uint32_t color = 0;
float X = 0, Y = 0, W = 0, L = f->ascent*f->factor*f->scale[S], LL = 0; // LL=largest linedist
offset.y = -offset.y; // invert y polarity
// utf8 to utf32
array(uint32_t) unicode = string32(text);
// parse string
float *t = text_glyph_data;
for( int i = 0, end = array_count(unicode); i < end; ++i ) {
uint32_t ch = unicode[i];
if( ch == '\n' ) {
// change cursor, advance y, record largest x as width, increase height
if( X > W ) W = X;
X = 0.0;
Y -= f->linedist*f->factor*f->scale[S];
// if (i+1==end) { //@hack: ensures we terminate the height at the correct position
// Y -= (f->descent+f->linegap)*f->factor*f->scale[S];
// }
continue;
}
if( ch >= 1 && ch <= 6 ) {
// flush previous state
if(draw_cmd) draw_cmd(f, text_glyph_data, (t - text_glyph_data)/4, f->scale[S], offset, rect);
t = text_glyph_data;
// reposition offset to align new baseline
// @fixme:
// offset.y += (f->linedist - f->linegap) * ( f->scale[ch] - f->scale[S] );
// change size
S = ch;
//@hack: use descent when we use >H4
L = f->ascent*f->factor*f->scale[S];
if(L > LL) LL = L;
continue;
}
if( ch >= 0x1a && ch <= 0x1f ) {
color = ch - 0x1a;
continue;
}
if( ch >= 0x10 && ch <= 0x19 ) {
if( fonts[ ch - 0x10 ].initialized) {
// flush previous state
if(draw_cmd) draw_cmd(f, text_glyph_data, (t - text_glyph_data)/4, f->scale[S], offset, rect);
t = text_glyph_data;
// change face
f = &fonts[ ch - 0x10 ];
L = f->ascent*f->factor*f->scale[S];
if(L > LL) LL = L;
}
continue;
}
if (!LL)
LL = L;
// convert to vbo data
int cp = ch - f->begin; // f->cp2iter[ch - f->begin];
//if(cp == 0xFFFD) continue;
//if (cp > f->num_glyphs) continue;
*t++ = X;
*t++ = Y;
*t++ = f->cp2iter[cp];
*t++ = col ? col[i] : color;
X += f->cdata[cp].xadvance*f->scale[S];
}
if(draw_cmd) draw_cmd(f, text_glyph_data, (t - text_glyph_data)/4, f->scale[S], offset, rect);
//if(strstr(text, "fps")) printf("(%f,%f) (%f) L:%f LINEDIST:%f\n", X, Y, W, L, f->linedist);
return abs2(vec2(W*W > X*X ? W : X, Y*Y > LL*LL ? Y : LL));
}
static vec2 gotoxy = {0};
// Return cursor
vec2 font_xy() {
return gotoxy;
}
// Relocate cursor
void font_goto(float x, float y) {
gotoxy = vec2(x, y);
}
// Print and linefeed. Text may include markup code
vec2 font_clip(const char *text, vec4 rect) {
int l=0,c=0,r=0,j=0,t=0,b=0,m=0,B=0;
while ( text[0] == FONT_LEFT[0] ) {
int has_set=0;
if (text[1] == FONT_LEFT[1]) l = 1, has_set=1;
if (text[1] == FONT_CENTER[1]) c = 1, has_set=1;
if (text[1] == FONT_RIGHT[1]) r = 1, has_set=1;
if (text[1] == FONT_JUSTIFY[1]) j = 1, has_set=1;
if (text[1] == FONT_TOP[1]) t = 1, has_set=1;
if (text[1] == FONT_BOTTOM[1]) b = 1, has_set=1;
if (text[1] == FONT_MIDDLE[1]) m = 1, has_set=1;
if (text[1] == FONT_BASELINE[1]) B = 1, has_set=1;
if (!has_set) break;
else text += 2;
}
int num_newlines = 0;
for (int i = 0, end = strlen(text); i < end; ++i) {
if (text[i] == '\n') ++num_newlines;
}
if (num_newlines > 1) {
vec2 text_dims = font_rect(text);
char tags[4] = {0};
int t=0;
while (*text) {
char ch = *text;
if( (ch >= 1 && ch <= 6) ||
(ch >= 0x1a && ch <= 0x1f) ||
(ch >= 0x10 && ch <= 0x19)) {
if (t < sizeof(tags)) tags[t++] = ch;
}
else break;
++text;
}
array(char *) lines = strsplit(text, "\n");
if (b) {
gotoxy.y += (rect.w - text_dims.y);
}
if (m) {
gotoxy.y += (rect.w/2. - text_dims.y/2.);
}
if (B) {
gotoxy.y += (rect.w/2. - text_dims.y/1.);
}
for (int i = 0; i < array_count(lines); i++) {
char *line = va("%s%s\n", tags, lines[i]);
vec2 text_rect = font_rect(line);
if( l || c || r ) {
gotoxy.x = l ? rect.x : r ? ((rect.x+rect.z) - text_rect.x) : rect.x+rect.z/2. - text_rect.x/2.;
} else if (j) {
float words_space = 0.0f;
array(char *) words = strsplit(lines[i], " ");
for (int k = 0; k < array_count(words); ++k) {
words_space += font_rect(words[k]).x;
}
if (array_count(words) == 0) {
gotoxy.y += text_rect.y;
continue;
}
float extra_space = rect.z - words_space;
int gaps = array_count(words) - 1;
float space_offset = gaps > 0 ? extra_space / (float)gaps : 0;
for (int k = 0; k < array_count(words); ++k) {
vec2 dims = font_draw_ex(va("%s%s", tags, words[k]), gotoxy, rect, NULL, font_draw_cmd);
gotoxy.x += dims.x + space_offset;
}
gotoxy.x = rect.x;
}
if (!j) {
font_draw_ex(line, gotoxy, rect, NULL, font_draw_cmd);
}
gotoxy.y += text_rect.y;
}
return text_dims;
} else {
if( l || c || r ) {
vec2 text_rect = font_rect(text);
gotoxy.x = l ? rect.x : r ? ((rect.x+rect.z) - text_rect.x) : rect.x+rect.z/2. - text_rect.x/2.;
}
if( t || b || m || B ) {
vec2 text_rect = font_rect(text);
gotoxy.y = t ? rect.y : b ? ((rect.y+rect.w) - text_rect.y) : m ? rect.y+rect.w/2.-text_rect.y/2. : rect.y+rect.w/2.-text_rect.y/1;
}
vec2 dims = font_draw_ex(text, gotoxy, rect, NULL, font_draw_cmd);
gotoxy.y += strchr(text, '\n') ? dims.y : 0;
gotoxy.x += !strchr(text, '\n') ? dims.x : 0;
return dims;
}
}
vec2 font_print(const char *text) {
vec4 dims = {0, 0, window_width(), window_height()};
return font_clip(text, dims);
}
const char *font_wrap(const char *text, float max_width) {
// return early if the text fits the max_width already
if (font_rect(text).x <= max_width) {
return text;
}
// skip alignment flags and collect tags
while ( text[0] == FONT_LEFT[0] ) {
int has_set=0;
if (text[1] == FONT_LEFT[1]) has_set=1;
if (text[1] == FONT_CENTER[1]) has_set=1;
if (text[1] == FONT_RIGHT[1]) has_set=1;
if (text[1] == FONT_JUSTIFY[1]) has_set=1;
if (text[1] == FONT_TOP[1]) has_set=1;
if (text[1] == FONT_BOTTOM[1]) has_set=1;
if (text[1] == FONT_MIDDLE[1]) has_set=1;
if (text[1] == FONT_BASELINE[1]) has_set=1;
if (!has_set) break;
else text += 2;
}
char tags[4] = {0};
int t=0;
while (*text) {
char ch = *text;
if( (ch >= 1 && ch <= 6) ||
(ch >= 0x1a && ch <= 0x1f) ||
(ch >= 0x10 && ch <= 0x19)) {
if (t < sizeof(tags)) tags[t++] = ch;
}
else break;
++text;
}
array(char*) words = strsplit(text, " ");
static __thread int slot = 0;
static __thread char buf[16][FONT_MAX_STRING_LEN] = {0};
int len = strlen(text) + array_count(words);
slot = (slot+1) % 16;
memset(buf[slot], 0, len+1);
char *out = buf[slot];
float width = 0.0f;
for (int i = 0; i < array_count(words); ++i) {
char *word = words[i];
float word_width = font_rect(va("%s%s ", tags, word)).x;
if (strstr(word, "\n")) {
width = word_width;
strcat(out, va("%s ", word));
} else {
if (width+word_width > max_width) {
width = 0.0f;
strcat(out, "\n");
}
width += word_width;
strcat(out, va("%s ", word));
}
}
// get rid of the space added at the end
out[strlen(out)] = 0;
return out;
}
// Print a code snippet with syntax highlighting
vec2 font_highlight(const char *text, const void *colors) {
vec4 screen_dim = {0, 0, window_width(), window_height()};
vec2 dims = font_draw_ex(text, gotoxy, screen_dim, (const char *)colors, font_draw_cmd);
gotoxy.y += strchr(text, '\n') ? dims.y : 0;
gotoxy.x += !strchr(text, '\n') ? dims.x : 0;
return dims;
}
// Calculate the size of a string, in the pixel size specified. Count stray newlines too.
vec2 font_rect(const char *str) {
vec4 dims = {0, 0, window_width(), window_height()};
return font_draw_ex(str, gotoxy, dims, NULL, NULL);
}
font_metrics_t font_metrics(const char *text) {
font_metrics_t m={0};
int S = 3;
font_t *f = &fonts[0];
// utf8 to utf32
array(uint32_t) unicode = string32(text);
// parse string
for( int i = 0, end = array_count(unicode); i < end; ++i ) {
uint32_t ch = unicode[i];
if( ch >= 1 && ch <= 6 ) {
S = ch;
continue;
}
if( ch >= 0x1a && ch <= 0x1f ) {
if( fonts[ ch - 0x1a ].initialized) {
// change face
f = &fonts[ ch - 0x1a ];
}
continue;
}
}
m.ascent = f->ascent*f->factor*f->scale[S];
m.descent = f->descent*f->factor*f->scale[S];
m.linegap = f->linegap*f->factor*f->scale[S];
m.linedist = f->linedist*f->factor*f->scale[S];
return m;
}
void *font_colorize(const char *text, const char *comma_types, const char *comma_keywords) {
// reallocate memory
static __thread int slot = 0;
static __thread char *buf[16] = {0};
static __thread array(char*) list[16] = {0};
slot = (slot+1) % 16;
buf[slot] = REALLOC(buf[slot], strlen(text)+1);
memset(buf[slot], 0, strlen(text)+1);
// ready
char *col = buf[slot];
char *str = STRDUP(text);
// split inputs
array(char*) TYPES = strsplit(comma_types, ", ");
array(char*) KEYWORDS = strsplit(comma_keywords, ", ");
// ignored characters
char delims[] = " ,(){}[];\t\n";
int num_delims = strlen(delims);
char operators[] = "/+-*<>=&|";
int num_operators = strlen(operators);
struct token {
char *start, *stop;
enum {
TOKEN_OTHER,TOKEN_OPERATOR,TOKEN_NUMERIC,TOKEN_FUNCTION,
TOKEN_KEYWORD,TOKEN_COMMENT,TOKEN_VARIABLE,TOKEN_UNSET
} type;
} tokens[9999]; // hurr
int num_tokens = 0; // running counter
char *ptr = str;
while (*ptr) {
// skip delimiters
int is_delim = 0;
for (int i = 0; i < num_delims; i++) {
if (*ptr == delims[i]) {
is_delim = 1;
break;
}
}
if (is_delim == 1) {
ptr++;
continue;
}
// found a token!
char *start = ptr;
if (*ptr == '/' && *(ptr+1) == '/') {
// found a line comment, go to end of line or end of file
while (*ptr != '\n' && *ptr != '\0') {
ptr++;
}
tokens[num_tokens].start = start;
tokens[num_tokens].stop = ptr;
tokens[num_tokens].type = TOKEN_COMMENT;
num_tokens++;
ptr++;
continue;
}
if (*ptr == '/' && *(ptr+1) == '*') {
// found a block comment, go to end of line or end of file
while (!(*ptr == '*' && *(ptr+1) == '/') && *ptr != '\0') {
ptr++;
}
ptr++;
tokens[num_tokens].start = start;
tokens[num_tokens].stop = ptr+1;
tokens[num_tokens].type = TOKEN_COMMENT;
num_tokens++;
ptr++;
continue;
}
// check if it's an operator
int is_operator = 0;
for (int i = 0; i < num_operators; i++) {
if (*ptr == operators[i]) {
is_operator = 1;
break;
}
}
if (is_operator == 1) {
tokens[num_tokens].start = start;
tokens[num_tokens].stop = ptr+1;
tokens[num_tokens].type = TOKEN_OPERATOR;
num_tokens++;
ptr++;
continue;
}
// it's either a name, type, a keyword, a function, or an names separated by an operator without spaces
while (*ptr) {
// check whether it's an operator stuck between two names
int is_operator2 = 0;
for (int i = 0; i < num_operators; i++) {
if (*ptr == operators[i]) {
is_operator2 = 1;
break;
}
}
if (is_operator2 == 1) {
tokens[num_tokens].start = start;
tokens[num_tokens].stop = ptr;
tokens[num_tokens].type = TOKEN_UNSET;
num_tokens++;
break;
}
// otherwise go until we find the next delimiter
int is_delim2 = 0;
for (int i = 0; i < num_delims; i++) {
if (*ptr == delims[i]) {
is_delim2 = 1;
break;
}
}
if (is_delim2 == 1) {
tokens[num_tokens].start = start;
tokens[num_tokens].stop = ptr;
tokens[num_tokens].type = TOKEN_UNSET;
num_tokens++;
ptr++;
break;
}
// did not find delimiter, check next char
ptr++;
}
}
// determine the types of the unset tokens, i.e. either
// a name, a type, a keyword, or a function
int num_keywords = array_count(KEYWORDS);
int num_types = array_count(TYPES);
for (int i = 0; i < num_tokens; i++) {
// TOKEN_OPERATOR and TOKEN_COMMENT should already be set, so skip those
if (tokens[i].type != TOKEN_UNSET) {
continue;
}
char end_char = *tokens[i].stop;
// temporarily null terminate at end of token, restored after parsing
*tokens[i].stop = '\0';
// parse
// if it's a keyword
int is_keyword = 0;
for (int j = 0; j < num_keywords; j++) {
if (strcmp(tokens[i].start, KEYWORDS[j]) == 0) {
is_keyword = 1;
break;
}
}
if (is_keyword == 1) {
tokens[i].type = TOKEN_KEYWORD;
*tokens[i].stop = end_char;
continue;
}
// Check if it's a function
float f;
if (end_char == '(') {
tokens[i].type = TOKEN_FUNCTION;
*tokens[i].stop = end_char;
continue;
}
// or if it's a numeric value. catches both integers and floats
if (sscanf(tokens[i].start, "%f", &f) == 1) {
tokens[i].type = TOKEN_NUMERIC;
*tokens[i].stop = end_char;
continue;
}
// if it's a variable type
int is_type = 0;
for (int j = 0; j < num_types; j++) {
if (strcmp(tokens[i].start, TYPES[j]) == 0) {
is_type = 1;
break;
}
}
if (is_type == 1) {
tokens[i].type = TOKEN_VARIABLE;
*tokens[i].stop = end_char;
continue;
}
// otherwise it's a regular variable name
tokens[i].type = TOKEN_OTHER;
*tokens[i].stop = end_char;
}
// print all tokens and their types
for (int i = 0; i < num_tokens; i++) {
for (char *p = tokens[i].start; p != tokens[i].stop; p++) {
col[(p - str)] = tokens[i].type;
}
}
FREE(str);
return col;
}
#line 0
#line 1 "v4k_gui.c"
// ----------------------------------------------------------------------------
// game ui (utils)
API void gui_drawrect( texture_t spritesheet, vec2 tex_start, vec2 tex_end, int rgba, vec2 start, vec2 end );
#define v42v2(rect) vec2(rect.x,rect.y), vec2(rect.z,rect.w)
void gui_drawrect( texture_t texture, vec2 tex_start, vec2 tex_end, int rgba, vec2 start, vec2 end ) {
static renderstate_t rect_rs;
do_once {
rect_rs = renderstate();
rect_rs.depth_test_enabled = false;
rect_rs.blend_enabled = true;
rect_rs.blend_src = GL_SRC_ALPHA;
rect_rs.blend_dst = GL_ONE_MINUS_SRC_ALPHA;
rect_rs.front_face = GL_CW;
}
static int program = -1, vbo = -1, vao = -1, u_tint = -1, u_has_tex = -1, u_window_width = -1, u_window_height = -1;
float gamma = 1;
vec2 dpi = ifdef(osx, window_dpi(), vec2(1,1));
if( program < 0 ) {
const char* vs = vfs_read("shaders/rect_2d.vs");
const char* fs = vfs_read("shaders/rect_2d.fs");
program = shader(vs, fs, "", "fragcolor" , NULL);
ASSERT(program > 0);
u_tint = glGetUniformLocation(program, "u_tint");
u_has_tex = glGetUniformLocation(program, "u_has_tex");
u_window_width = glGetUniformLocation(program, "u_window_width");
u_window_height = glGetUniformLocation(program, "u_window_height");
glGenVertexArrays( 1, (GLuint*)&vao );
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
}
start = mul2(start, dpi);
end = mul2(end, dpi);
renderstate_apply(&rect_rs);
GLenum texture_type = texture.flags & TEXTURE_ARRAY ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D;
glUseProgram( program );
glBindVertexArray( vao );
glActiveTexture( GL_TEXTURE0 );
glBindTexture( texture_type, texture.id );
glUniform1i(u_has_tex, (texture.id != 0));
glUniform1f(u_window_width, (float)window_width());
glUniform1f(u_window_height, (float)window_height());
vec4 rgbaf = {((rgba>>24)&255)/255.f, ((rgba>>16)&255)/255.f,((rgba>>8)&255)/255.f,((rgba>>0)&255)/255.f};
glUniform4fv(u_tint, GL_TRUE, &rgbaf.x);
// normalize texture regions
if (texture.id != 0) {
tex_start.x /= texture.w;
tex_start.y /= texture.h;
tex_end.x /= texture.w;
tex_end.y /= texture.h;
}
GLfloat vertices[] = {
// Positions // UVs
start.x, start.y, tex_start.x, tex_start.y,
end.x, start.y, tex_end.x, tex_start.y,
end.x, end.y, tex_end.x, tex_end.y,
start.x, start.y, tex_start.x, tex_start.y,
end.x, end.y, tex_end.x, tex_end.y,
start.x, end.y, tex_start.x, tex_end.y
};
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*)(2 * sizeof(GLfloat)));
glDrawArrays( GL_TRIANGLES, 0, 6 );
profile_incstat("Render.num_drawcalls", +1);
profile_incstat("Render.num_triangles", +2);
glBindTexture( texture_type, 0 );
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glBindVertexArray( 0 );
glBindBuffer(GL_ARRAY_BUFFER, 0);
glUseProgram( 0 );
}
// ----------------------------------------------------------------------------
// game ui
typedef union gui_state_t {
struct {
bool held;
bool hover;
};
} gui_state_t;
static __thread array(guiskin_t) skins=0;
static __thread guiskin_t *last_skin=0;
static __thread map(int, gui_state_t) ctl_states=0; //@leak
static __thread array(vec4) scissor_rects=0;
static __thread bool any_widget_used=0;
void gui_pushskin(guiskin_t skin) {
array_push(skins, skin);
last_skin = array_back(skins);
}
void gui_popskin() {
if (!last_skin) return;
if (last_skin->free) last_skin->free(last_skin->userdata);
array_pop(skins);
last_skin = array_count(skins) ? array_back(skins) : NULL;
}
void *gui_userdata() {
return last_skin->userdata;
}
vec2 gui_getskinsize(const char *skin, const char *fallback) {
vec2 size={0};
if (last_skin->getskinsize) last_skin->getskinsize(last_skin->userdata, skin, fallback, &size);
return size;
}
unsigned gui_getskincolor(const char *skin, const char *fallback) {
unsigned color = 0xFFFFFFFF;
if (last_skin->getskincolor) last_skin->getskincolor(last_skin->userdata, skin, fallback, &color);
return color;
}
bool gui_ismouseinrect(const char *skin, const char *fallback, vec4 rect) {
if (last_skin->ismouseinrect) return last_skin->ismouseinrect(last_skin->userdata, skin, fallback, rect);
return false;
}
vec4 gui_getscissorrect(const char *skin, const char *fallback, vec4 rect) {
vec4 scissor = rect;
if (last_skin->getscissorrect) last_skin->getscissorrect(last_skin->userdata, skin, fallback, rect, &scissor);
return scissor;
}
static
gui_state_t *gui_getstate(int id) {
if (!ctl_states) map_init(ctl_states, less_int, hash_int);
return map_find_or_add(ctl_states, id, (gui_state_t){0});
}
void gui_panel_id(int id, vec4 rect, const char *skin) {
(void)id;
vec4 scissor={0, 0, window_width(), window_height()};
if (last_skin->drawrect) last_skin->drawrect(last_skin->userdata, skin, NULL, rect);
scissor = gui_getscissorrect(skin, NULL, rect);
if (!array_count(scissor_rects))
glEnable(GL_SCISSOR_TEST);
glScissor(scissor.x, window_height()-scissor.w-scissor.y, scissor.z, scissor.w);
array_push(scissor_rects, scissor);
}
void gui_panel_end() {
ASSERT(array_count(scissor_rects));
array_pop(scissor_rects);
if (array_count(scissor_rects)) {
vec4 scissor = *array_back(scissor_rects);
glScissor(scissor.x, scissor.y, scissor.z, scissor.w);
} else {
glDisable(GL_SCISSOR_TEST);
}
}
bool gui_button_id(int id, vec4 r, const char *skin) {
gui_state_t *entry = gui_getstate(id);
bool was_clicked=0;
skin=skin?skin:"button";
char *btn = va("%s%s", skin, entry->held?"_press":entry->hover?"_hover":"");
if (gui_ismouseinrect(btn, skin, r)) {
if (input_up(MOUSE_L) && entry->held) {
was_clicked=1;
}
if (!any_widget_used) {
any_widget_used = entry->held = input_held(MOUSE_L);
entry->hover = true;
}
}
else {
entry->hover = false;
}
if (input_up(MOUSE_L) && entry->held) {
entry->held = false;
any_widget_used = false;
}
if (last_skin->drawrect) last_skin->drawrect(last_skin->userdata, btn, skin, r);
return was_clicked;
}
bool gui_button_label_id(int id, const char *text, vec4 rect, const char *skin) {
gui_state_t *entry = gui_getstate(id);
bool state = gui_button_id(id, rect, skin);
vec2 buttonsize={0};
skin=skin?skin:"button";
char *btn = va("%s%s", skin, entry->held?"_press":entry->hover?"_hover":"");
buttonsize = gui_getskinsize(btn, skin);
vec2 textsize = font_rect(text);
vec4 pos;
pos.x = rect.x + max(buttonsize.x*.5f, rect.z*.5f) - textsize.x*.5f;
pos.y = rect.y + max(buttonsize.y*.5f, rect.w*.5f) - textsize.y*.5f;
gui_label(btn, text, pos);
return state;
}
static
float slider2posx(float min, float max, float value, float step, float w) {
float norm = value - min;
float range = max - min;
float rel = norm / range;
float res = w * rel;
return step==0.0f?res:(round(res/step)*step);
}
static
float posx2slider(vec4 rect, float min, float max, float xpos, float step) {
xpos = clampf(xpos, rect.x, rect.x+rect.z);
double rel = (xpos - rect.x) / rect.z;
float res = min + (rel * (max - min));
return step==0.0f?res:(round(res/step)*step);
}
bool gui_slider_id(int id, vec4 rect, const char *skin, float min, float max, float step, float *value) {
gui_state_t *entry = gui_getstate(id);
skin = skin?skin:"slider";
char *cursorskin = va("%s_cursor%s", skin, entry->held?"_press":entry->hover?"_hover":"");
char *fbcursor = va("%s_cursor", skin);
if (gui_ismouseinrect(skin, NULL, rect) && !any_widget_used) {
any_widget_used = entry->held = input_held(MOUSE_L);
entry->hover = true;
}
else if (input_up(MOUSE_L) && entry->held) {
entry->held = false;
any_widget_used = false;
}
else {
entry->hover = false;
}
float old_value = *value;
if (last_skin->drawrect) last_skin->drawrect(last_skin->userdata, skin, NULL, rect);
vec2 slidersize={0}, cursorsize={0};
vec4 usablerect = gui_getscissorrect(skin, NULL, rect);
slidersize = gui_getskinsize(skin, NULL);
cursorsize = gui_getskinsize(cursorskin, fbcursor);
if (entry->held) {
*value = posx2slider(usablerect, min, max, input(MOUSE_X), step);
}
float sliderx = slider2posx(min, max, *value, step, usablerect.z);
vec2 cursorpos = vec2(sliderx+(usablerect.x-rect.x)-cursorsize.x*.5f, (slidersize.y*.5f - cursorsize.y*.5f));
vec4 cursorrect = rect;
cursorrect.x += cursorpos.x;
cursorrect.y += cursorpos.y;
cursorrect.z = cursorsize.x;
cursorrect.w = max(cursorsize.y, rect.w);
if (last_skin->drawrect) last_skin->drawrect(last_skin->userdata, cursorskin, fbcursor, cursorrect);
return entry->held && (old_value!=*value);
}
bool gui_slider_label_id(int id, const char *text, vec4 rect, const char *skin, float min, float max, float step, float *value) {
bool state = gui_slider_id(id, rect, skin, min, max, step, value);
vec2 slidersize={0};
skin=skin?skin:"slider";
slidersize = gui_getskinsize(skin, NULL);
vec2 textsize = font_rect(text);
vec4 pos;
pos.x = rect.x + max(slidersize.x, rect.z) + 8 /*padding*/;
pos.y = rect.y + max(slidersize.y*.5f, rect.w*.5f) - textsize.y*.5f;
gui_label(skin, text, pos);
return state;
}
void gui_rect_id(int id, vec4 r, const char *skin) {
(void)id;
if (last_skin->drawrect) last_skin->drawrect(last_skin->userdata, skin, NULL, r);
}
void gui_label_id(int id, const char *skin, const char *text, vec4 rect) {
(void)id;
font_color(FONT_COLOR6, gui_getskincolor(skin, NULL));
font_goto(rect.x, rect.y);
font_print(va(FONT_COLOR6 "%s", text));
}
/* skinned */
static
void skinned_free(void* userdata) {
skinned_t *a = C_CAST(skinned_t*, userdata);
atlas_destroy(&a->atlas);
FREE(a);
}
static
atlas_slice_frame_t *skinned_getsliceframe(atlas_t *a, const char *name) {
if (!name) return NULL;
for (int i = 0; i < array_count(a->slices); i++)
if (!strcmp(quark_string(&a->db, a->slices[i].name), name))
return &a->slice_frames[a->slices[i].frames[0]];
// PRINTF("slice name: '%s' is missing in atlas!\n", name);
return NULL;
}
static
void skinned_getskincolor(void *userdata, const char *skin, const char *fallback, unsigned *color) {
skinned_t *a = C_CAST(skinned_t*, userdata);
atlas_slice_frame_t *f = skinned_getsliceframe(&a->atlas, skin);
if (!f && fallback) f = skinned_getsliceframe(&a->atlas, fallback);
if (!f) return;
if (f->text && f->text[0] == '#') *color = atorgba(f->text);
}
static
void skinned_draw_missing_rect(vec4 r) {
vec4 size = vec4(0, 0, texture_checker().w, texture_checker().h);
gui_drawrect(texture_checker(), v42v2(size), 0x800080FF, v42v2(r));
}
static
bool skinned_ismouseinrect(void *userdata, const char *skin, const char *fallback, vec4 r) {
skinned_t *a = C_CAST(skinned_t*, userdata);
atlas_slice_frame_t *f = skinned_getsliceframe(&a->atlas, skin);
if (!f && fallback) f = skinned_getsliceframe(&a->atlas, fallback);
if (!f) return false;
vec4 outer = f->bounds;
r.x -= f->pivot.x*a->scale;
r.y -= f->pivot.y*a->scale;
r.z += r.x;
r.w += r.y;
if ((r.z-r.x) < (outer.z-outer.x) * a->scale) {
r.z = r.x + (outer.z-outer.x) * a->scale;
}
if ((r.w-r.y) < (outer.w-outer.y) * a->scale) {
r.w = r.y + (outer.w-outer.y) * a->scale;
}
return (input(MOUSE_X) > r.x && input(MOUSE_X) < r.z && input(MOUSE_Y) > r.y && input(MOUSE_Y) < r.w);
}
static
void skinned_draw_sprite(float scale, atlas_t *a, atlas_slice_frame_t *f, vec4 r) {
vec4 outer = f->bounds;
r.x -= f->pivot.x*scale;
r.y -= f->pivot.y*scale;
r.z += r.x;
r.w += r.y;
// Ensure dest rectangle is large enough to render the whole element
if ((r.z-r.x) < (outer.z-outer.x) * scale) {
r.z = r.x + (outer.z-outer.x) * scale;
}
if ((r.w-r.y) < (outer.w-outer.y) * scale) {
r.w = r.y + (outer.w-outer.y) * scale;
}
if (!f->has_9slice) {
gui_drawrect(a->tex, v42v2(f->bounds), 0xFFFFFFFF, v42v2(r));
return;
}
vec4 core = f->core;
core.x += outer.x;
core.y += outer.y;
core.z += outer.x;
core.w += outer.y;
// Define the 9 slices
vec4 top_left_slice = {outer.x, outer.y, core.x, core.y};
vec4 top_middle_slice = {core.x, outer.y, core.z, core.y};
vec4 top_right_slice = {core.z, outer.y, outer.z, core.y};
vec4 middle_left_slice = {outer.x, core.y, core.x, core.w};
vec4 center_slice = core;
vec4 middle_right_slice = {core.z, core.y, outer.z, core.w};
vec4 bottom_left_slice = {outer.x, core.w, core.x, outer.w};
vec4 bottom_middle_slice = {core.x, core.w, core.z, outer.w};
vec4 bottom_right_slice = {core.z, core.w, outer.z, outer.w};
vec4 top_left = {r.x, r.y, r.x + (core.x - outer.x) * scale, r.y + (core.y - outer.y) * scale};
vec4 top_right = {r.z - (outer.z - core.z) * scale, r.y, r.z, r.y + (core.y - outer.y) * scale};
vec4 bottom_left = {r.x, r.w - (outer.w - core.w) * scale, r.x + (core.x - outer.x) * scale, r.w};
vec4 bottom_right = {r.z - (outer.z - core.z) * scale, r.w - (outer.w - core.w) * scale, r.z, r.w};
vec4 top = {top_left.z, r.y, top_right.x, top_left.w};
vec4 bottom = {bottom_left.z, bottom_left.y, bottom_right.x, r.w};
vec4 left = {r.x, top_left.w, top_left.z, bottom_left.y};
vec4 right = {top_right.x, top_right.w, r.z, bottom_right.y};
vec4 center = {top_left.z, top_left.w, top_right.x, bottom_right.y};
gui_drawrect(a->tex, v42v2(center_slice), 0xFFFFFFFF, v42v2(center));
gui_drawrect(a->tex, v42v2(top_left_slice), 0xFFFFFFFF, v42v2(top_left));
gui_drawrect(a->tex, v42v2(top_right_slice), 0xFFFFFFFF, v42v2(top_right));
gui_drawrect(a->tex, v42v2(bottom_left_slice), 0xFFFFFFFF, v42v2(bottom_left));
gui_drawrect(a->tex, v42v2(bottom_right_slice), 0xFFFFFFFF, v42v2(bottom_right));
gui_drawrect(a->tex, v42v2(top_middle_slice), 0xFFFFFFFF, v42v2(top));
gui_drawrect(a->tex, v42v2(bottom_middle_slice), 0xFFFFFFFF, v42v2(bottom));
gui_drawrect(a->tex, v42v2(middle_left_slice), 0xFFFFFFFF, v42v2(left));
gui_drawrect(a->tex, v42v2(middle_right_slice), 0xFFFFFFFF, v42v2(right));
}
static
void skinned_draw_rect(void* userdata, const char *skin, const char *fallback, vec4 r) {
skinned_t *a = C_CAST(skinned_t*, userdata);
atlas_slice_frame_t *f = skinned_getsliceframe(&a->atlas, skin);
if (!f && fallback) f = skinned_getsliceframe(&a->atlas, fallback);
if (!f) skinned_draw_missing_rect(r);
else skinned_draw_sprite(a->scale, &a->atlas, f, r);
}
void skinned_getskinsize(void *userdata, const char *skin, const char *fallback, vec2 *size) {
skinned_t *a = C_CAST(skinned_t*, userdata);
atlas_slice_frame_t *f = skinned_getsliceframe(&a->atlas, skin);
if (!f && fallback) f = skinned_getsliceframe(&a->atlas, fallback);
if (f) {
size->x = (f->bounds.z-f->bounds.x)*a->scale;
size->y = (f->bounds.w-f->bounds.y)*a->scale;
}
}
static
void skinned_getscissorrect(void* userdata, const char *skin, const char *fallback, vec4 rect, vec4 *dims) {
skinned_t *a = C_CAST(skinned_t*, userdata);
atlas_slice_frame_t *f = skinned_getsliceframe(&a->atlas, skin);
if (!f && fallback) f = skinned_getsliceframe(&a->atlas, fallback);
if (!f) return;
*dims = rect;
if (!f->has_9slice) return;
vec2 skinsize, coresize;
skinsize.x = (f->bounds.z-f->bounds.x)*a->scale;
skinsize.y = (f->bounds.w-f->bounds.y)*a->scale;
coresize.x = (f->core.z-f->core.x)*a->scale;
coresize.y = (f->core.w-f->core.y)*a->scale;
dims->x += f->core.x*a->scale;
dims->y += f->core.y*a->scale;
dims->z -= (skinsize.x - coresize.x);
dims->w -= (skinsize.y - coresize.y);
}
guiskin_t gui_skinned(const char *asefile, float scale) {
skinned_t *a = REALLOC(0, sizeof(skinned_t));
a->atlas = atlas_create(asefile, 0);
a->scale = scale?scale:1.0f;
guiskin_t skin={0};
skin.userdata = a;
skin.drawrect = skinned_draw_rect;
skin.getskinsize = skinned_getskinsize;
skin.getskincolor = skinned_getskincolor;
skin.ismouseinrect = skinned_ismouseinrect;
skin.getscissorrect = skinned_getscissorrect;
skin.free = skinned_free;
return skin;
}
#line 0
#line 1 "v4k_input.c"
// input framework
// - rlyeh, public domain
//
// multi-touch(emscripten) port based on code by @procedural (MIT-0 licensed)
// gotta love linux
#ifdef __linux
#undef KEY_ESC
#undef KEY_TICK
#undef KEY_1
#undef KEY_2
#undef KEY_3
#undef KEY_4
#undef KEY_5
#undef KEY_6
#undef KEY_7
#undef KEY_8
#undef KEY_9
#undef KEY_0
#undef KEY_BS
#undef KEY_TAB
#undef KEY_Q
#undef KEY_W
#undef KEY_E
#undef KEY_R
#undef KEY_T
#undef KEY_Y
#undef KEY_U
#undef KEY_I
#undef KEY_O
#undef KEY_P
#undef KEY_CAPS
#undef KEY_A
#undef KEY_S
#undef KEY_D
#undef KEY_F
#undef KEY_G
#undef KEY_H
#undef KEY_J
#undef KEY_K
#undef KEY_L
#undef KEY_ENTER
#undef KEY_LSHIFT
#undef KEY_Z
#undef KEY_X
#undef KEY_C
#undef KEY_V
#undef KEY_B
#undef KEY_N
#undef KEY_M
#undef KEY_RSHIFT
#undef KEY_UP
#undef KEY_LCTRL
#undef KEY_LALT
#undef KEY_SPACE
#undef KEY_RALT
#undef KEY_RCTRL
#undef KEY_LEFT
#undef KEY_DOWN
#undef KEY_RIGHT
#undef KEY_INS
#undef KEY_HOME
#undef KEY_PGUP
#undef KEY_DEL
#undef KEY_END
#undef KEY_PGDN
#undef KEY_LMETA
#undef KEY_RMETA
#undef KEY_MENU
#undef KEY_PRINT
#undef KEY_PAUSE
#undef KEY_SCROLL
#undef KEY_NUMLOCK
#undef KEY_MINUS
#undef KEY_EQUAL
#undef KEY_LSQUARE
#undef KEY_RSQUARE
#undef KEY_SEMICOLON
#undef KEY_QUOTE
#undef KEY_HASH
#undef KEY_BAR
#undef KEY_COMMA
#undef KEY_DOT
#undef KEY_SLASH
#undef KEY_F1
#undef KEY_F2
#undef KEY_F3
#undef KEY_F4
#undef KEY_F5
#undef KEY_F6
#undef KEY_F7
#undef KEY_F8
#undef KEY_F9
#undef KEY_F10
#undef KEY_F11
#undef KEY_F12
#undef KEY_PAD1
#undef KEY_PAD2
#undef KEY_PAD3
#undef KEY_PAD4
#undef KEY_PAD5
#undef KEY_PAD6
#undef KEY_PAD7
#undef KEY_PAD8
#undef KEY_PAD9
#undef KEY_PAD0
#undef KEY_PADADD
#undef KEY_PADSUB
#undef KEY_PADMUL
#undef KEY_PADDIV
#undef KEY_PADDOT
#undef KEY_PADENTER
#undef MOUSE_L
#undef MOUSE_M
#undef MOUSE_R
#undef GAMEPAD_CONNECTED
#undef GAMEPAD_A
#undef GAMEPAD_B
#undef GAMEPAD_X
#undef GAMEPAD_Y
#undef GAMEPAD_UP
#undef GAMEPAD_DOWN
#undef GAMEPAD_LEFT
#undef GAMEPAD_RIGHT
#undef GAMEPAD_MENU
#undef GAMEPAD_START
#undef GAMEPAD_LB
#undef GAMEPAD_RB
#undef GAMEPAD_LTHUMB
#undef GAMEPAD_RTHUMB
#undef WINDOW_BLUR
#undef WINDOW_FOCUS
#undef WINDOW_CLOSE
#undef WINDOW_MINIMIZE
#undef WINDOW_MAXIMIZE
#undef WINDOW_FULLSCREEN
#undef WINDOW_WINDOWED
#undef GAMEPAD_LPAD
#undef GAMEPAD_LPAD
#undef GAMEPAD_LPADY
#undef GAMEPAD_RPAD
#undef GAMEPAD_RPAD
#undef GAMEPAD_RPADY
#undef GAMEPAD_LT
#undef GAMEPAD_RT
#undef GAMEPAD_BATTERY
#undef MOUSE
#undef MOUSE
#undef MOUSE_Y
#undef MOUSE_W
#undef TOUCH_X1
#undef TOUCH_Y1
#undef TOUCH_X2
#undef TOUCH_Y2
#undef WINDOW_RESIZE
#undef WINDOW_RESIZE
#undef WINDOW_RESIZEY
#undef WINDOW_ORIENTATION
#undef WINDOW_BATTERY
#undef GAMEPAD_GUID
#undef GAMEPAD_NAME
#endif
static int controller_id = 0;
static int controller_cycle[4] = {0};
static struct controller_t {
const char* strings[2];
float floats[7+3+4+4];
char bits[104+3+15+7];
} controller[4] = {0}, frame[4][60] = {{0},{0},{0},{0}};
static struct controller_t *input_logger(int position, int advance) {
int *cycle = &controller_cycle[controller_id];
position += (*cycle += advance);
position = position >= 0 ? position % 60 : 60-1 + ((position+1) % 60);
return &frame[controller_id][position];
}
void input_mappings(const char *filename) {
#if !is(ems) // emscripten: no glfwUpdateGamepadMappings() available
char* mappings = vfs_read(filename);
if( mappings ) { glfwUpdateGamepadMappings(mappings); /*REALLOC(mappings, 0);*/ }
#endif
}
void input_init() {
do_once {
input_mappings("gamecontrollerdb.txt");
}
#if 0 // deprecated
void input_update();
window_hook(input_update, NULL);
#endif
}
static int any_key = 0;
int input_anykey() {
return any_key;
}
void input_update() {
struct controller_t *c = &controller[0]; // @fixme
char *bits = &c->bits[0];
float *floats = c->floats; floats -= GAMEPAD_LPADX;
const char **strings = c->strings; strings -= GAMEPAD_GUID;
float mouse_wheel_old = floats[MOUSE_W];
struct controller_t clear = {0};
*c = clear;
for( int i = 0; i < countof(c->strings); ++i ) c->strings[i] = "";
struct GLFWwindow *win = window_handle();
// glfwSetInputMode(win, GLFW_STICKY_MOUSE_BUTTONS, GLFW_TRUE);
double mx, my;
glfwGetCursorPos(win, &mx, &my);
floats[MOUSE_X] = mx;
floats[MOUSE_Y] = my;
struct nk_glfw* glfw = glfwGetWindowUserPointer(win); // from nuklear, because it is overriding glfwSetScrollCallback()
floats[MOUSE_W] = !glfw ? 0 : mouse_wheel_old + (float)glfw->scroll_bak.x + (float)glfw->scroll_bak.y;
glfw->scroll_bak.x = glfw->scroll_bak.y = 0;
// Dear Win32 users,
// - Touchpad cursor freezing when any key is being pressed?
// If using Alps/Elantech/Dell/Toshiba touchpad driver or similar, ensure to disable TouchGuard, TouchCheck, PalmTracking, etc.
// - Touchpad button not clicking when any key is being pressed?
// Change Touchpad settings on Windows10 from HighSentivity (default) to MostSensitivity.
// - Apparently, a sane solution is just to never bind FIRE/JUMP actions to LMB/RMB buttons, and bind actions to keys instead.
bits[MOUSE_L] = (glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS);
bits[MOUSE_M] = (glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS);
bits[MOUSE_R] = (glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS);
#define k2(VK,GLFW) [KEY_##VK] = GLFW_KEY_##GLFW
#define k(VK) k2(VK,VK)
int table[] = {
k2(ESC,ESCAPE),
k2(TICK,GRAVE_ACCENT), k(1),k(2),k(3),k(4),k(5),k(6),k(7),k(8),k(9),k(0), k2(BS,BACKSPACE),
k(TAB), k(Q),k(W),k(E),k(R),k(T),k(Y),k(U),k(I),k(O),k(P),
k2(CAPS,CAPS_LOCK), k(A),k(S),k(D),k(F),k(G),k(H),k(J),k(K),k(L), k(ENTER),
k2(LSHIFT,LEFT_SHIFT), k(Z),k(X),k(C),k(V),k(B),k(N),k(M), k2(RSHIFT,RIGHT_SHIFT), k(UP),
k2(LCTRL,LEFT_CONTROL),k2(LALT,LEFT_ALT), k(SPACE), k2(RALT,RIGHT_ALT),k2(RCTRL,RIGHT_CONTROL), k(LEFT),k(DOWN),k(RIGHT),
k(F1),k(F2),k(F3),k(F4),k(F5),k(F6),k(F7),k(F8),k(F9),k(F10),k(F11),k(F12), k2(PRINT,PRINT_SCREEN),k(PAUSE),
k2(INS,INSERT),k(HOME),k2(PGUP,PAGE_UP), k2(DEL,DELETE),k(END), k2(PGDN,PAGE_DOWN),
};
#undef k
#undef k2
any_key = 0;
for(int i = 0; i < countof(table); ++i) {
#if is(ems)
if( table[i] ) any_key |= (bits[i] = glfwGetKey(win, table[i] ) == GLFW_PRESS);
#else
any_key |= (bits[i] = glfwGetKeys(win)[ table[i] ]);
#endif
}
// special cases: plain shift/alt/ctrl enums will also check right counterparts
any_key |= (bits[KEY_ALT] |= glfwGetKey(win, table[KEY_RALT] ) == GLFW_PRESS);
any_key |= (bits[KEY_CTRL] |= glfwGetKey(win, table[KEY_RCTRL] ) == GLFW_PRESS);
any_key |= (bits[KEY_SHIFT] |= glfwGetKey(win, table[KEY_RSHIFT] ) == GLFW_PRESS);
#if is(ems)
{
int jid = 0; // @fixme
EmscriptenGamepadEvent state = {0};
if( emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS ) {
if( emscripten_get_gamepad_status(jid, &state) == EMSCRIPTEN_RESULT_SUCCESS ) {
// hardcoded for Xbox controller
if( state.numAxes >= 4 && state.numButtons >= 16 ) {
bits[GAMEPAD_CONNECTED] = 1; // !!state.connected
strings[GAMEPAD_GUID] = va("%s", state.id);
strings[GAMEPAD_NAME] = va("emscripten %s", state.mapping);
floats[GAMEPAD_BATTERY] = 100;
// e.digitalButton[i], e.analogButton[i]
bits[GAMEPAD_A] = state.analogButton[0]; // cross
bits[GAMEPAD_B] = state.analogButton[1]; // circle
bits[GAMEPAD_X] = state.analogButton[2]; // square
bits[GAMEPAD_Y] = state.analogButton[3]; // triangle
bits[GAMEPAD_UP] = state.analogButton[12];
bits[GAMEPAD_DOWN] = state.analogButton[13];
bits[GAMEPAD_LEFT] = state.analogButton[14];
bits[GAMEPAD_RIGHT] = state.analogButton[15];
bits[GAMEPAD_LB] = state.analogButton[4];
bits[GAMEPAD_RB] = state.analogButton[5];
bits[GAMEPAD_MENU] = state.analogButton[8];
bits[GAMEPAD_START] = state.analogButton[9];
bits[GAMEPAD_LTHUMB] = state.analogButton[10];
bits[GAMEPAD_RTHUMB] = state.analogButton[11];
floats[GAMEPAD_LT] = state.analogButton[6];
floats[GAMEPAD_RT] = state.analogButton[7];
floats[GAMEPAD_LPADX] = state.axis[0];
floats[GAMEPAD_LPADY] = -state.axis[1];
floats[GAMEPAD_RPADX] = state.axis[2];
floats[GAMEPAD_RPADY] = -state.axis[3];
}
}
}
if( 0 && ui_panel("emspad", 0)) {
for(int i = 0; i <= 5; ++i )
ui_label(va("axis #%d: %5.2f", i, (float)state.axis[i]));
for(int i = 0; i <= 15; ++i )
ui_label(va("button #%d: %d %5.2f", i, state.digitalButton[i], (float)state.analogButton[i]));
ui_panel_end();
}
}
#else
int jid = GLFW_JOYSTICK_1 + 0; // @fixme
if( glfwGetGamepadName(jid) ) { // glfwJoystickPresent(jid) && glfwJoystickIsGamepad(jid) ) {
bits[GAMEPAD_CONNECTED] = 1;
strings[GAMEPAD_GUID] = glfwGetJoystickGUID(jid);
strings[GAMEPAD_NAME] = glfwGetGamepadName(jid);
floats[GAMEPAD_BATTERY] = 100; //glfwJoystickCurrentPowerLevel(jid);
GLFWgamepadstate state;
if (glfwGetGamepadState(jid, &state)) {
bits[GAMEPAD_A] = state.buttons[GLFW_GAMEPAD_BUTTON_A]; // cross
bits[GAMEPAD_B] = state.buttons[GLFW_GAMEPAD_BUTTON_B]; // circle
bits[GAMEPAD_X] = state.buttons[GLFW_GAMEPAD_BUTTON_X]; // square
bits[GAMEPAD_Y] = state.buttons[GLFW_GAMEPAD_BUTTON_Y]; // triangle
bits[GAMEPAD_UP] = state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_UP];
bits[GAMEPAD_DOWN] = state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_DOWN];
bits[GAMEPAD_LEFT] = state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_LEFT];
bits[GAMEPAD_RIGHT] = state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_RIGHT];
bits[GAMEPAD_LB] = state.buttons[GLFW_GAMEPAD_BUTTON_LEFT_BUMPER];
bits[GAMEPAD_RB] = state.buttons[GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER];
bits[GAMEPAD_MENU] = state.buttons[GLFW_GAMEPAD_BUTTON_BACK];
bits[GAMEPAD_START] = state.buttons[GLFW_GAMEPAD_BUTTON_START]; // _GUIDE
bits[GAMEPAD_LTHUMB] = state.buttons[GLFW_GAMEPAD_BUTTON_LEFT_THUMB];
bits[GAMEPAD_RTHUMB] = state.buttons[GLFW_GAMEPAD_BUTTON_RIGHT_THUMB];
floats[GAMEPAD_LT] = input_filter_positive(state.axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER]); // [-1..+1] -> [0..1]
floats[GAMEPAD_RT] = input_filter_positive(state.axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER]); // [-1..+1] -> [0..1]
floats[GAMEPAD_LPADX] = state.axes[GLFW_GAMEPAD_AXIS_LEFT_X];
floats[GAMEPAD_LPADY] = -state.axes[GLFW_GAMEPAD_AXIS_LEFT_Y];
floats[GAMEPAD_RPADX] = state.axes[GLFW_GAMEPAD_AXIS_RIGHT_X];
floats[GAMEPAD_RPADY] = -state.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y];
}
}
#endif
*input_logger(0,+1) = controller[0];
}
int input_use(int id) {
return controller_id >= 0 && controller_id <= 3 ? controller_id = id, 1 : 0;
}
float input_frame( int vk, int frame ) {
if( controller_id > 0 ) return 0; // @fixme
struct controller_t *c = input_logger(frame, +0);
if(vk < GAMEPAD_LPADX) return c->bits[vk]; // if in bits...
if(vk < GAMEPAD_GUID) return c->floats[vk - GAMEPAD_LPADX]; // if in floats...
return 0.f; // NAN?
}
vec2 input_frame2( int vk, int frame ) {
return vec2( input_frame(vk, frame), input_frame(vk+1, frame) );
}
const char *input_string( int vk ) {
int frame = 0;
if( controller_id > 0 ) return ""; // @fixme
struct controller_t *c = input_logger(frame, +0);
return vk >= GAMEPAD_GUID ? c->strings[vk - GAMEPAD_GUID] : ""; // if in strings...
}
// --- sugars
float input_diff( int vk ) {
return input_frame(vk, 0) - input_frame(vk, -1);
}
vec2 input_diff2( int vk ) {
return vec2( input_diff(vk), input_diff(vk+1) );
}
float input( int vk ) {
return input_frame( vk, 0 );
}
vec2 input2( int vk ) {
return vec2( input_frame(vk, 0), input_frame(vk+1, 0) );
}
// --- events
const float MS2FRAME = 0.06f; // 60 hz/1000 ms
int event( int vk ) {
float v = input_frame(vk,0);
return (v * v) > 0;
}
int input_chord2( int vk1, int vk2 ) {
return event(vk1) && event(vk2);
}
int input_chord3( int vk1, int vk2, int vk3 ) {
return event(vk1) && input_chord2(vk2, vk3);
}
int input_chord4( int vk1, int vk2, int vk3, int vk4 ) {
return event(vk1) && input_chord3(vk2, vk3, vk4);
}
int input_down( int vk ) {
return input_diff(vk) > 0; // input_frame(vk,-1) <= 0 && input_frame(vk,0) > 0;
}
int input_held( int vk ) {
return input_diff(vk) == 0 && input_frame(vk,0) > 0; // input_frame(vk,-1) > 0 && input_frame(vk,0) > 0;
}
int input_up( int vk ) {
return input_diff(vk) < 0; // input_frame(vk,-1) > 0 && input_frame(vk,0) <= 0;
}
int input_idle( int vk ) {
return input_diff(vk) == 0 && input_frame(vk,0) <= 0; // input_frame(vk,-1) <= 0 && input_frame(vk,0) <= 0;
}
int input_repeat( int vk, int ms ) { // @fixme: broken
assert((unsigned)ms <= 1000);
return input_frame(vk,-ms * MS2FRAME ) > 0 && input_frame(vk,-ms * MS2FRAME /2) > 0 && input_frame(vk,0) > 0;
}
int input_click( int vk, int ms ) { // @fixme: broken
assert((unsigned)ms <= 1000);
return input_frame(vk,-ms * MS2FRAME ) <= 0 && input_frame(vk,-ms * MS2FRAME /2) > 0 && input_frame(vk,0) <= 0;
}
int input_click2( int vk, int ms ) { // @fixme: broken
assert((unsigned)ms <= 1000);
return input_frame(vk,-ms * MS2FRAME ) <= 0 && input_frame(vk,-ms * MS2FRAME *3/4) > 0
&& input_frame(vk,-ms * MS2FRAME *2/4) <= 0 && input_frame(vk,-ms * MS2FRAME *1/4) > 0 && input_frame(vk,0) <= 0;
}
#undef MS2FRAME
// --- filters
float input_filter_positive( float v ) { // [-1..1] -> [0..1]
return ( v + 1 ) * 0.5f;
}
vec2 input_filter_positive2( vec2 v ) { // [-1..1] -> [0..1]
return scale2(inc2(v,1), 0.5f);
}
vec2 input_filter_deadzone( vec2 v, float deadzone ) {
assert(deadzone > 0);
float mag = sqrt( v.x*v.x + v.y*v.y );
float nx = v.x / mag, ny = v.y / mag, k = (mag - deadzone) / (1 - deadzone);
if( k > 1 ) k = 1; // clamp
// k = k * k; // uncomment for a smoother curve
return mag < deadzone ? vec2(0, 0) : vec2(nx * k, ny * k);
}
vec2 input_filter_deadzone_4way( vec2 v, float deadzone ) {
assert(deadzone > 0);
float v0 = v.x*v.x < deadzone*deadzone ? 0 : v.x;
float v1 = v.y*v.y < deadzone*deadzone ? 0 : v.y;
return vec2(v0, v1);
}
int input_enum(const char *vk) {
static map(char*,int) m = 0;
do_once {
map_init_str(m);
#define k(VK) map_find_or_add(m, STRINGIZE(VK), KEY_##VK); map_find_or_add(m, STRINGIZE(KEY_##VK), KEY_##VK);
k(ESC)
k(TICK) k(1) k(2) k(3) k(4) k(5) k(6) k(7) k(8) k(9) k(0) k(BS)
k(TAB) k(Q) k(W) k(E) k(R) k(T) k(Y) k(U) k(I) k(O) k(P)
k(CAPS) k(A) k(S) k(D) k(F) k(G) k(H) k(J) k(K) k(L) k(ENTER)
k(LSHIFT) k(Z) k(X) k(C) k(V) k(B) k(N) k(M) k(RSHIFT) k(UP)
k(LCTRL) k(LALT) k(SPACE) k(RALT) k(RCTRL) k(LEFT) k(DOWN) k(RIGHT)
k(F1) k(F2) k(F3) k(F4) k(F5) k(F6) k(F7) k(F8) k(F9) k(F10) k(F11) k(F12) k(PRINT) k(PAUSE)
k(INS) k(HOME) k(PGUP) k(DEL) k(END) k(PGDN)
k(ALT) k(CTRL) k(SHIFT)
#undef k
};
int *found = map_find(m, (char*)vk);
return found ? *found : -1;
}
int input_eval(const char *expression) {
if( expression && expression[0] ) {
return eval(expression) > 0;
}
return 0;
}
// converts keyboard code to its latin char (if any)
char input_keychar(unsigned code) {
#define k2(VK,GLFW) [KEY_##VK] = GLFW_KEY_##GLFW
#define k(VK) k2(VK,VK)
int table[256] = {
k2(ESC,ESCAPE),
k2(TICK,GRAVE_ACCENT), k(1),k(2),k(3),k(4),k(5),k(6),k(7),k(8),k(9),k(0), k2(BS,BACKSPACE),
k(TAB), k(Q),k(W),k(E),k(R),k(T),k(Y),k(U),k(I),k(O),k(P),
k2(CAPS,CAPS_LOCK), k(A),k(S),k(D),k(F),k(G),k(H),k(J),k(K),k(L), k(ENTER),
k2(LSHIFT,LEFT_SHIFT), k(Z),k(X),k(C),k(V),k(B),k(N),k(M), k2(RSHIFT,RIGHT_SHIFT), k(UP),
k2(LCTRL,LEFT_CONTROL),k2(LALT,LEFT_ALT), k(SPACE), k2(RALT,RIGHT_ALT),k2(RCTRL,RIGHT_CONTROL), k(LEFT),k(DOWN),k(RIGHT),
k(F1),k(F2),k(F3),k(F4),k(F5),k(F6),k(F7),k(F8),k(F9),k(F10),k(F11),k(F12), k2(PRINT,PRINT_SCREEN),k(PAUSE),
k2(INS,INSERT),k(HOME),k2(PGUP,PAGE_UP), k2(DEL,DELETE),k(END), k2(PGDN,PAGE_DOWN),
};
#undef k
#undef k2
code = table[ code & 255 ];
const char* name = glfwGetKeyName(code, 0);
if( name && strlen(name) == 1 ) {
return *name >= 'A' && *name <= 'Z' ? name[0] - 'A' + 'a' : name[0];
}
if( code >= GLFW_KEY_0 && code <= GLFW_KEY_9 ) return code - GLFW_KEY_0 + '0';
if( code >= GLFW_KEY_A && code <= GLFW_KEY_Z ) return code - GLFW_KEY_A + 'a';
switch(code) {
default: break;
case GLFW_KEY_APOSTROPHE: return '\'';
case GLFW_KEY_BACKSLASH: return '\\';
case GLFW_KEY_COMMA: return ',';
case GLFW_KEY_EQUAL: return '=';
case GLFW_KEY_GRAVE_ACCENT: return '`';
case GLFW_KEY_LEFT_BRACKET: return '[';
case GLFW_KEY_MINUS: return '-';
case GLFW_KEY_PERIOD: return '.';
case GLFW_KEY_RIGHT_BRACKET: return ']';
case GLFW_KEY_SEMICOLON: return ';';
case GLFW_KEY_SLASH: return '/';
//case GLFW_KEY_WORLD_1: return non-US #1;
//case GLFW_KEY_WORLD_2: return non-US #2;
}
return '\0';
}
// -- multi-touch input
// multi-touch(emscripten) port based on code by @procedural (MIT-0 licensed)
#if !is(ems)
void touch_init() {}
void touch_flush() {}
void input_touch_area(unsigned button, vec2 from, vec2 to) {}
vec2 input_touch(unsigned button, float sensitivity) { return vec2(0,0); }
vec2 input_touch_delta_from_origin(unsigned button, float sensitivity) { return vec2(0,0); }
vec2 input_touch_delta(unsigned button, float sensitivity) { return vec2(0,0); }
bool input_touch_active() { return false; }
#else
static struct touch {
bool init;
vec2 move, cached, origin, prev;
vec4 area;
} touch[2] = {0};
static EM_BOOL touch_move(int eventType, const EmscriptenTouchEvent *e, void *userData) {
for( int i = 0; i < (int)e->numTouches; ++i) {
if( !e->touches[i].isChanged ) continue;
int j = e->touches[i].identifier;
if( j >= countof(touch) ) continue;
touch[j].cached = vec2(e->touches[i].clientX, e->touches[i].clientY);
if (!touch[j].init) touch[j].init = 1, touch[j].origin = touch[j].prev = touch[j].move = touch[j].cached;
}
return EM_TRUE;
}
static EM_BOOL touch_end(int eventType, const EmscriptenTouchEvent *e, void *userData) {
for( int i = 0; i < (int)e->numTouches; ++i) {
if( !e->touches[i].isChanged ) continue;
int j = e->touches[i].identifier;
if( j >= countof(touch) ) continue;
//memset(&touch[j], 0, sizeof(touch[j]));
touch[j].init = false;
touch[j].move = touch[j].cached = touch[j].origin = touch[j].prev = vec2(0,0);
}
return EM_TRUE;
}
void input_touch_area(unsigned button, vec2 from_ndc, vec2 to_ndc) {
if( button >= countof(touch) ) return;
touch[button].area = vec4( from_ndc.x, from_ndc.y, to_ndc.x, to_ndc.y );
}
void touch_init() {
memset(touch, 0, sizeof(touch));
// default areas: left screen (button #0) and right_screen (button #1)
input_touch_area(0, vec2(0.0,0.0), vec2(0.5,1.0));
input_touch_area(1, vec2(0.5,0.0), vec2(1.0,1.0));
emscripten_set_touchstart_callback("#canvas", 0, EM_FALSE, &touch_move);
emscripten_set_touchmove_callback("#canvas", 0, EM_FALSE, &touch_move);
emscripten_set_touchend_callback("#canvas", 0, EM_FALSE, &touch_end);
}
void touch_flush() {
for( int j = 0; j < countof(touch); ++j) {
touch[j].prev = touch[j].move;
touch[j].move = touch[j].cached;
}
}
static
unsigned input_locate_button(unsigned button) {
// locate button in user-defined areas
vec2 c = window_canvas();
for( int j = 0; j < countof(touch); ++j ) {
if( touch[j].init )
if( touch[j].origin.x >= (touch[button].area.x * c.x) )
if( touch[j].origin.y >= (touch[button].area.y * c.y) )
if( touch[j].origin.x <= (touch[button].area.z * c.x) )
if( touch[j].origin.y <= (touch[button].area.w * c.y) )
return j;
}
return ~0u;
}
vec2 input_touch(unsigned button, float sensitivity) {
button = input_locate_button(button);
if( button >= countof(touch) ) return vec2(0,0);
return touch[button].init ? touch[button].move : vec2(0,0);
}
vec2 input_touch_delta(unsigned button, float sensitivity) {
button = input_locate_button(button);
if( button >= countof(touch) ) return vec2(0,0);
return touch[button].init ? scale2( sub2(touch[button].move, touch[button].prev), sensitivity ) : vec2(0,0);
}
vec2 input_touch_delta_from_origin(unsigned button, float sensitivity) {
button = input_locate_button(button);
if( button >= countof(touch) ) return vec2(0,0);
return touch[button].init ? scale2( sub2(touch[button].move, touch[button].origin), sensitivity ) : vec2(0,0);
}
bool input_touch_active() {
for( int j = 0; j < countof(touch); ++j ) {
if( touch[j].init ) return true;
}
return false;
}
#endif // !is(ems)
int ui_mouse() {
ui_label2_float("X", input(MOUSE_X));
ui_label2_float("Y", input(MOUSE_Y));
ui_label2_float("Wheel", input(MOUSE_W));
ui_separator();
ui_label2_bool("Left", input(MOUSE_L));
ui_label2_bool("Middle", input(MOUSE_M));
ui_label2_bool("Right", input(MOUSE_R));
ui_separator();
for( int i = 0; i <= CURSOR_SW_AUTO; ++i ) if(ui_button(va("Cursor shape #%d", i))) window_cursor_shape(i);
return 0;
}
int ui_keyboard() {
char *keys[] = {
"F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12",
"ESC",
"TICK","1","2","3","4","5","6","7","8","9","0","BS",
"TAB","Q","W","E","R","T","Y","U","I","O","P",
"CAPS","A","S","D","F","G","H","J","K","L","ENTER",
"LSHIFT","Z","X","C","V","B","N","M","RSHIFT","^",
"LCTRL","LALT","SPACE","RALT","RCTRL","<","V",">",
};
float rows[] = {
12,
1,
12,
11,
11,
10,
8
};
for( int row = 0, k = 0; row < countof(rows); ++row ) {
static char *buf = 0; if(buf) *buf = 0;
for( int col = 0; col < rows[row]; ++col, ++k ) {
assert( input_enum(keys[k]) == input_enum(va("KEY_%s", keys[k])) );
strcatf(&buf, input(input_enum(keys[k])) ? "[%s]" : " %s ", keys[k]);
}
ui_label(buf);
}
return 0;
}
int ui_gamepad(int gamepad_id) {
input_use(gamepad_id);
bool connected = !!input(GAMEPAD_CONNECTED);
ui_label2("Name", connected ? input_string(GAMEPAD_NAME) : "(Not connected)");
if( !connected ) ui_disable();
ui_separator();
ui_label2_bool("A", input(GAMEPAD_A) );
ui_label2_bool("B", input(GAMEPAD_B) );
ui_label2_bool("X", input(GAMEPAD_X) );
ui_label2_bool("Y", input(GAMEPAD_Y) );
ui_label2_bool("Up", input(GAMEPAD_UP) );
ui_label2_bool("Down", input(GAMEPAD_DOWN) );
ui_label2_bool("Left", input(GAMEPAD_LEFT) );
ui_label2_bool("Right", input(GAMEPAD_RIGHT) );
ui_label2_bool("Menu", input(GAMEPAD_MENU) );
ui_label2_bool("Start", input(GAMEPAD_START) );
ui_separator();
ui_label2_float("Left pad x", input(GAMEPAD_LPADX) );
ui_label2_float("Left pad y", input(GAMEPAD_LPADY) );
ui_label2_float("Left trigger", input(GAMEPAD_LT) );
ui_label2_bool("Left bumper", input(GAMEPAD_LB) );
ui_label2_bool("Left thumb", input(GAMEPAD_LTHUMB) );
vec2 v = input_filter_deadzone( input2(GAMEPAD_LPADX), 0.1f );
ui_label2_float("Filtered pad x", v.x);
ui_label2_float("Filtered pad y", v.y);
ui_separator();
ui_label2_float("Right pad x", input(GAMEPAD_RPADX) );
ui_label2_float("Right pad y", input(GAMEPAD_RPADY) );
ui_label2_float("Right trigger", input(GAMEPAD_RT) );
ui_label2_bool("Right bumper", input(GAMEPAD_RB) );
ui_label2_bool("Right thumb", input(GAMEPAD_RTHUMB) );
vec2 w = input_filter_deadzone( input2(GAMEPAD_RPADX), 0.1f );
ui_label2_float("Filtered pad x", w.x);
ui_label2_float("Filtered pad y", w.y);
ui_enable();
input_use(0);
return 0;
}
int ui_gamepads() {
for( int i = 0; i < 4; ++i ) ui_gamepad(i);
return 0;
}
#line 0
#line 1 "v4k_math.c"
// -----------------------------------------------------------------------------
// math framework: rand, ease, vec2, vec3, vec4, quat, mat2, mat33, mat34, mat4
// - rlyeh, public domain
//
// Credits: @ands+@krig+@vurtun (PD), @datenwolf (WTFPL2), @evanw+@barerose (CC0), @sgorsten (Unlicense).
#include <math.h>
#include <float.h>
#include <stdint.h>
#include <stdbool.h>
static uint64_t rand_xoro256(uint64_t x256_s[4]) { // xoshiro256+ 1.0 by David Blackman and Sebastiano Vigna (PD)
const uint64_t result = x256_s[0] + x256_s[3];
const uint64_t t = x256_s[1] << 17;
x256_s[2] ^= x256_s[0];
x256_s[3] ^= x256_s[1];
x256_s[1] ^= x256_s[2];
x256_s[0] ^= x256_s[3];
x256_s[2] ^= t;
x256_s[3] = (x256_s[3] << 45) | (x256_s[3] >> (64 - 45)); //x256_rotl(x256_s[3], 45);
return result;
}
static __thread uint64_t rand_state[4] = {// = splitmix64(0),splitmix64(splitmix64(0)),... x4 times
UINT64_C(0x9e3779b8bb0b2c64),UINT64_C(0x3c6ef372178960e7),
UINT64_C(0xdaa66d2b71a12917),UINT64_C(0x78dde6e4d584aef9)
};
void randset(uint64_t x) {
x = hash_64(x);
for( int i = 0; i < 4; ++i) {
// http://xoroshiro.di.unimi.it/splitmix64.c
uint64_t z = (x += UINT64_C(0x9E3779B97F4A7C15));
z = (z ^ (z >> 30)) * UINT64_C(0xBF58476D1CE4E5B9);
z = (z ^ (z >> 27)) * UINT64_C(0x94D049BB133111EB);
x = x ^ (z >> 31);
rand_state[i] = x;
}
}
uint64_t rand64(void) {
return rand_xoro256(rand_state);
}
double randf(void) { // [0, 1) interval
uint64_t u64 = rand64();
// fastest way to convert in C99 a 64-bit unsigned integer to a 64-bit double
union { uint64_t i; double d; } u; u.i = UINT64_C(0x3FF) << 52 | u64 >> 12;
double dbl = u.d - 1.0;
return 1 - 2.0 * ((float)(dbl / 2));
}
int randi(int mini, int maxi) { // [mini, maxi) interval ; @todo: test randi(-4,4) and #define randi(m,x) (m + randf() * (x-m))
if( mini < maxi ) {
uint32_t x, r, range = maxi - mini;
do r = (x = rand64()) % range; while(range > (r-x));
return mini + r;
}
return mini > maxi ? randi(maxi, mini) : mini;
}
#if 0 // @todo: deprecate me
double rng(void) { // [0..1) Lehmer RNG "minimal standard"
static __thread unsigned int seed = 123;
seed *= 16807;
return seed / (double)0x100000000ULL;
}
#endif
// ----------------------------------------------------------------------------
// @todo: evaluate stb_perlin.h as well
float simplex1( float v ){ return snoise1(v); }
float simplex2( vec2 v ) { return snoise2(v.x,v.y); }
float simplex3( vec3 v ) { return snoise3(v.x,v.y,v.z); }
float simplex4( vec4 v ) { return snoise4(v.x,v.y,v.z,v.w); }
// ----------------------------------------------------------------------------
float deg (float radians) { return radians / C_PI * 180.0f; }
float rad (float degrees) { return degrees * C_PI / 180.0f; }
int mini (int a, int b) { return a < b ? a : b; }
int maxi (int a, int b) { return a > b ? a : b; }
int absi (int a ) { return a < 0 ? -a : a; }
int clampi (int v,int a,int b) { return maxi(mini(b,v),a); }
float minf (float a, float b) { return a < b ? a : b; }
float maxf (float a, float b) { return a > b ? a : b; }
float absf (float a ) { return a < 0.0f ? -a : a; }
float pmodf (float a, float b) { return (a < 0.0f ? 1.0f : 0.0f) + (float)fmod(a, b); } // positive mod
float signf (float a) { return (a < 0) ? -1.f : 1.f; }
float clampf(float v,float a,float b){return maxf(minf(b,v),a); }
float mixf(float a,float b,float t) { return a*(1-t)+b*t; }
float unmixf(float a,float b,float t) { return (t - a) / (b - a); }
float mapf(float x,float a,float b,float c,float d) { return (x - a) / (b - a) * (d - c) + c; }
float slerpf(float a,float b,float t) {
a = fmod(a, 360); if (a < 0) a += 360;
b = fmod(b, 360); if (b < 0) b += 360;
float diff = b - a;
if (diff < 0.0)
diff += 360.0;
float r = a + t*diff;
if (r >= 360.0)
r -= 360.0;
return r;
}
float fractf (float a) { return a - (int)a; }
// ----------------------------------------------------------------------------
vec2 ptr2 (const float *a ) { return vec2(a[0],a[1]); }
//
vec2 neg2 (vec2 a ) { return vec2(-a.x, -a.y); }
vec2 add2 (vec2 a, vec2 b) { return vec2(a.x + b.x, a.y + b.y); }
vec2 sub2 (vec2 a, vec2 b) { return vec2(a.x - b.x, a.y - b.y); }
vec2 mul2 (vec2 a, vec2 b) { return vec2(a.x * b.x, a.y * b.y); }
vec2 div2 (vec2 a, vec2 b) { return vec2(a.x / (b.x + !b.x), a.y / (b.y + !b.y)); }
vec2 inc2 (vec2 a, float b) { return vec2(a.x + b, a.y + b); }
vec2 dec2 (vec2 a, float b) { return vec2(a.x - b, a.y - b); }
vec2 scale2 (vec2 a, float b) { return vec2(a.x * b, a.y * b); }
vec2 pmod2 (vec2 a, float b) { return vec2(pmodf(a.x, b), pmodf(a.y, b)); }
vec2 min2 (vec2 a, vec2 b) { return vec2(minf(a.x, b.x), minf(a.y, b.y)); }
vec2 max2 (vec2 a, vec2 b) { return vec2(maxf(a.x, b.x), maxf(a.y, b.y)); }
vec2 abs2 (vec2 a ) { return vec2(absf(a.x), absf(a.y)); }
vec2 floor2 (vec2 a ) { return vec2(floorf(a.x), floorf(a.y)); }
vec2 fract2 (vec2 a ) { return sub2(a, floor2(a)); }
vec2 ceil2 (vec2 a ) { return vec2(ceilf (a.x), ceilf (a.y)); }
float dot2 (vec2 a, vec2 b) { return a.x * b.x + a.y * b.y; }
vec2 refl2 (vec2 a, vec2 b) { return sub2(a, scale2(b, 2*dot2(a,b))); }
float cross2 (vec2 a, vec2 b) { return a.x * b.y - a.y * b.x; } // pseudo cross product
float len2sq (vec2 a ) { return a.x * a.x + a.y * a.y; }
float len2 (vec2 a ) { return sqrtf(len2sq(a)); }
vec2 norm2 (vec2 a ) { return len2sq(a) == 0 ? a : scale2(a, 1 / len2(a)); }
vec2 norm2sq (vec2 a ) { return len2sq(a) == 0 ? a : scale2(a, 1 / len2sq(a)); }
int finite2 (vec2 a ) { return FINITE(a.x) && FINITE(a.y); }
vec2 mix2 (vec2 a,vec2 b,float t) { return add2(scale2((a),1-(t)), scale2((b), t)); }
vec2 clamp2(vec2 v, vec2 a, vec2 b){ return vec2(maxf(minf(b.x,v.x),a.x),maxf(minf(b.y,v.y),a.y)); }
vec2 clamp2f(vec2 v,float a,float b){ return vec2(maxf(minf(b,v.x),a),maxf(minf(b,v.y),a)); }
// ----------------------------------------------------------------------------
vec3 ptr3 (const float *a ) { return vec3(a[0],a[1],a[2]); }
vec3 vec23 (vec2 a, float z ) { return vec3(a.x,a.y,z); }
//
vec3 neg3 (vec3 a ) { return vec3(-a.x, -a.y, -a.z); }
vec3 add3 (vec3 a, vec3 b) { return vec3(a.x + b.x, a.y + b.y, a.z + b.z); }
vec3 sub3 (vec3 a, vec3 b) { return vec3(a.x - b.x, a.y - b.y, a.z - b.z); }
vec3 mul3 (vec3 a, vec3 b) { return vec3(a.x * b.x, a.y * b.y, a.z * b.z); }
vec3 div3 (vec3 a, vec3 b) { return vec3(a.x / (b.x + !b.x), a.y / (b.y + !b.y), a.z / (b.z + !b.z)); }
vec3 inc3 (vec3 a, float b) { return vec3(a.x + b, a.y + b, a.z + b); }
vec3 dec3 (vec3 a, float b) { return vec3(a.x - b, a.y - b, a.z - b); }
vec3 scale3 (vec3 a, float b) { return vec3(a.x * b, a.y * b, a.z * b); }
vec3 pmod3 (vec3 a, float b) { return vec3(pmodf(a.x, b), pmodf(a.y, b), pmodf(a.z, b)); }
vec3 min3 (vec3 a, vec3 b) { return vec3(minf(a.x, b.x), minf(a.y, b.y), minf(a.z, b.z)); }
vec3 max3 (vec3 a, vec3 b) { return vec3(maxf(a.x, b.x), maxf(a.y, b.y), maxf(a.z, b.z)); }
vec3 abs3 (vec3 a ) { return vec3(absf(a.x), absf(a.y), absf(a.z)); }
vec3 floor3 (vec3 a ) { return vec3(floorf(a.x), floorf(a.y), floorf(a.z)); }
vec3 fract3 (vec3 a ) { return sub3(a, floor3(a)); }
vec3 ceil3 (vec3 a ) { return vec3(ceilf (a.x), ceilf (a.y), ceilf (a.z)); }
vec3 cross3 (vec3 a, vec3 b) { return vec3(a.y * b.z - b.y * a.z, a.z * b.x - b.z * a.x, a.x * b.y - b.x * a.y); }
float dot3 (vec3 a, vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
vec3 refl3 (vec3 a, vec3 b) { return sub3(a, scale3(b, 2*dot3(a, b))); }
float len3sq (vec3 a ) { return dot3(a,a); }
float len3 (vec3 a ) { return sqrtf(len3sq(a)); }
vec3 norm3 (vec3 a ) { return len3sq(a) == 0 ? a : scale3(a, 1 / len3(a)); }
vec3 norm3sq (vec3 a ) { return len3sq(a) == 0 ? a : scale3(a, 1 / len3sq(a)); }
int finite3 (vec3 a ) { return finite2(vec2(a.x,a.y)) && FINITE(a.z); }
vec3 mix3 (vec3 a,vec3 b,float t) { return add3(scale3((a),1-(t)), scale3((b), t)); }
vec3 clamp3(vec3 v, vec3 a, vec3 b){ return vec3(maxf(minf(b.x,v.x),a.x),maxf(minf(b.y,v.y),a.y),maxf(minf(b.z,v.z),a.z)); }
vec3 clamp3f(vec3 v,float a,float b){ return vec3(maxf(minf(b,v.x),a),maxf(minf(b,v.y),a),maxf(minf(b,v.z),a)); }
//vec3 tricross3 (vec3 a, vec3 b, vec3 c) { return cross3(a,cross3(b,c)); } // useful?
void ortho3 (vec3 *left, vec3 *up, vec3 v) {
#if 0
if ((v.z * v.z) > (0.7f * 0.7f)) {
float sqrlen = v.y*v.y + v.z*v.z;
float invlen = 1.f / sqrtf(sqrlen);
*up = vec3(0, v.z*invlen, -v.y*invlen);
*left = vec3(sqrlen*invlen, -v.x*up->z, v.x*up->y);
} else {
float sqrlen = v.x*v.x + v.y*v.y;
float invlen = 1.f / sqrtf(sqrlen);
*left = vec3(-v.y*invlen, v.x*invlen, 0);
*up = vec3(-v.z*left->y, v.z*left->x, sqrlen*invlen);
}
#else
*left = (v.z*v.z) < (v.x*v.x) ? vec3(v.y,-v.x,0) : vec3(0,-v.z,v.y);
*up = cross3(*left, v);
#endif
}
#define rotateq3(v,q) transformq(q,v)
vec3 rotatex3(vec3 dir, float degrees) { return rotateq3(dir, rotationq(degrees,1,0,0)); }
vec3 rotatey3(vec3 dir, float degrees) { return rotateq3(dir, rotationq(degrees,0,1,0)); }
vec3 rotatez3(vec3 dir, float degrees) { return rotateq3(dir, rotationq(degrees,0,0,1)); }
// ----------------------------------------------------------------------------
vec4 ptr4 (const float *a ) { return vec4(a[0],a[1],a[2],a[3]); }
vec4 vec34 (vec3 a, float w ) { return vec4(a.x,a.y,a.z,w); }
//
vec4 neg4 (vec4 a ) { return vec4(-a.x, -a.y, -a.z, -a.w); }
vec4 add4 (vec4 a, vec4 b) { return vec4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); }
vec4 sub4 (vec4 a, vec4 b) { return vec4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); }
vec4 mul4 (vec4 a, vec4 b) { return vec4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); }
vec4 div4 (vec4 a, vec4 b) { return vec4(a.x / (b.x + !b.x), a.y / (b.y + !b.y), a.z / (b.z + !b.z), a.w / (b.w + !b.w)); }
vec4 inc4 (vec4 a, float b) { return vec4(a.x + b, a.y + b, a.z + b, a.w + b); }
vec4 dec4 (vec4 a, float b) { return vec4(a.x - b, a.y - b, a.z - b, a.w - b); }
vec4 scale4 (vec4 a, float b) { return vec4(a.x * b, a.y * b, a.z * b, a.w * b); }
vec4 pmod4 (vec4 a, float b) { return vec4(pmodf(a.x, b), pmodf(a.y, b), pmodf(a.z, b), pmodf(a.w, b)); }
vec4 min4 (vec4 a, vec4 b) { return vec4(minf(a.x, b.x), minf(a.y, b.y), minf(a.z, b.z), minf(a.w, b.w)); }
vec4 max4 (vec4 a, vec4 b) { return vec4(maxf(a.x, b.x), maxf(a.y, b.y), maxf(a.z, b.z), maxf(a.w, b.w)); }
vec4 abs4 (vec4 a ) { return vec4(absf(a.x), absf(a.y), absf(a.z), absf(a.w)); }
vec4 floor4 (vec4 a ) { return vec4(floorf(a.x), floorf(a.y), floorf(a.z), floorf(a.w)); }
vec4 fract4 (vec4 a ) { return sub4(a, floor4(a)); }
vec4 ceil4 (vec4 a ) { return vec4(ceilf (a.x), ceilf (a.y), ceilf (a.z), ceilf (a.w)); }
float dot4 (vec4 a, vec4 b) { return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; }
vec4 refl4 (vec4 a, vec4 b) { return sub4(a, scale4(b, 2*dot4(a, b))); }
float len4sq (vec4 a ) { return dot4(a,a); }
float len4 (vec4 a ) { return sqrtf(len4sq(a)); }
vec4 norm4 (vec4 a ) { return len4sq(a) == 0 ? a : scale4(a, 1 / len4(a)); }
vec4 norm4sq (vec4 a ) { return len4sq(a) == 0 ? a : scale4(a, 1 / len4sq(a)); }
int finite4 (vec4 a ) { return finite3(vec3(a.x,a.y,a.z)) && FINITE(a.w); }
vec4 mix4 (vec4 a,vec4 b,float t) { return add4(scale4((a),1-(t)), scale4((b), t)); }
vec4 clamp4(vec4 v, vec4 a, vec4 b){ return vec4(maxf(minf(b.x,v.x),a.x),maxf(minf(b.y,v.y),a.y),maxf(minf(b.z,v.z),a.z),maxf(minf(b.w,v.w),a.w)); }
vec4 clamp4f(vec4 v,float a,float b){ return vec4(maxf(minf(b,v.x),a),maxf(minf(b,v.y),a),maxf(minf(b,v.z),a),maxf(minf(b,v.w),a)); }
// vec4 cross4(vec4 v0, vec4 v1) { return vec34(cross3(v0.xyz, v1.xyz), (v0.w + v1.w) * 0.5f); } // may fail
// ----------------------------------------------------------------------------
quat idq ( ) { return quat(1,0,0,0); } // 0,0,0,1?
quat ptrq (const float *a ) { return quat(a[0],a[1],a[2],a[3]); }
quat vec3q (vec3 a, float w ) { return quat(a.x,a.y,a.z,w); }
quat vec4q (vec4 a ) { return quat(a.x,a.y,a.z,a.w); }
//
quat negq (quat a ) { return quat(-a.x,-a.y,-a.z,-a.w); }
quat conjq (quat a ) { return quat(-a.x,-a.y,-a.z,a.w); }
quat addq (quat a, quat b) { return quat(a.x+b.x,a.y+b.y,a.z+b.z,a.w+b.w); }
quat subq (quat a, quat b) { return quat(a.x-b.x,a.y-b.y,a.z-b.z,a.w-b.w); }
quat mulq (quat p, quat q) { vec3 w = scale3(p.xyz, q.w), r = add3(add3(cross3(p.xyz, q.xyz), w), scale3(q.xyz, p.w)); return quat(r.x,r.y,r.z,p.w*q.w - dot3(p.xyz, q.xyz)); }
quat scaleq (quat a, float s) { return quat(a.x*s,a.y*s,a.z*s,a.w*s); }
quat normq (quat a ) { vec4 v = norm4(a.xyzw); return quat(v.x,v.y,v.z,v.w); }
float dotq (quat a, quat b) { return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w; }
quat mixq(quat a, quat b, float t) { return normq(dotq(a,b) < 0 ? addq(negq(a),scaleq(addq(b,a),t)) : addq(a,scaleq(subq(b,a),t))); }
/* quat lerpq(quat a, quat b, float s) {
return norm(quat((1-s)*a.x + s*b.x, (1-s)*a.y + s*b.y, (1-s)*a.z + s*b.z, (1-s)*a.w + s*b.w));
}*/
quat slerpq(quat a, quat b, float s) { //ok ?
float t = acosf(dotq(a,b)), st = sinf(t), wa = sinf((1-s)*t)/st, wb = sinf(s*t)/st;
return normq(quat(wa*a.x + wb*b.x, wa*a.y + wb*b.y, wa*a.z + wb*b.z, wa*a.w + wb*b.w));
}
quat rotationq(float deg,float x,float y,float z){ deg=rad(deg)*0.5f; return vec3q(scale3(vec3(x,y,z),sinf(deg)),cosf(deg)); }
quat mat44q (mat44 M) {
float r=0.f;
int perm[] = { 0, 1, 2, 0, 1 }, *p = perm;
for(int i = 0; i<3; i++) {
float m = M[i*4+i];
if( m < r ) continue;
m = r;
p = &perm[i];
}
r = sqrtf(1.f + M[p[0]*4+p[0]] - M[p[1]*4+p[1]] - M[p[2]*4+p[2]] );
return r >= 1e-6 ? quat(1,0,0,0)
: quat(r/2.f, (M[p[0]*4+p[1]] - M[p[1]*4+p[0]])/(2.f*r), (M[p[2]*4+p[0]] - M[p[0]*4+p[2]])/(2.f*r), (M[p[2]*4+p[1]] - M[p[1]*4+p[2]])/(2.f*r) );
}
vec3 rotate3q_2(vec3 v, quat q) { // rotate vec3 by quat @testme
vec3 u = {q.x, q.y, q.z}; // extract the vector part of the quaternion
float s = q.w; // scalar part
vec3 cuv = cross3(u, v);
float duv2 = dot3(u, v) * 2;
float ss_duu = s*s - dot3(u, u);
return add3(add3(scale3(u,duv2), scale3(v,ss_duu)), scale3(cuv,2*s));
}
vec3 rotate3q(vec3 v, quat r) { // rotate vec3 by quat @testme
float num12 = r.x + r.x;
float num2 = r.y + r.y;
float num = r.z + r.z;
float num11 = r.w * num12;
float num10 = r.w * num2;
float num9 = r.w * num;
float num8 = r.x * num12;
float num7 = r.x * num2;
float num6 = r.x * num;
float num5 = r.y * num2;
float num4 = r.y * num;
float num3 = r.z * num;
float num15 = ((v.x * ((1 - num5) - num3)) + (v.y * (num7 - num9))) + (v.z * (num6 + num10));
float num14 = ((v.x * (num7 + num9)) + (v.y * ((1 - num8) - num3))) + (v.z * (num4 - num11));
float num13 = ((v.x * (num6 - num10)) + (v.y * (num4 + num11))) + (v.z * ((1 - num8) - num5));
return vec3(num15, num14, num13);
}
// euler <-> quat
vec3 euler (quat q) { // bugs? returns PitchYawRoll (PYR) in degrees. ref: https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles
float sr_cp = 2*(q.x*q.y + q.z*q.w), cr_cp = 1-2*(q.y*q.y + q.z*q.z);
float sy_cp = 2*(q.x*q.w + q.y*q.z), cy_cp = 1-2*(q.z*q.z + q.w*q.w), sp = 2*(q.x*q.z-q.w*q.y);
float p = fabs(sp) >= 1 ? copysignf(C_PI / 2, sp) : asinf(sp);
float y = atan2f(sy_cp, cy_cp);
float r = atan2f(sr_cp, cr_cp);
return scale3(vec3(p, y, r), TO_DEG);
}
quat eulerq (vec3 pyr_degrees) { // bugs?
#if 0
quat x = vec3q(vec3(1,0,0),rad(pyr_degrees.x)); // x, not pitch
quat y = vec3q(vec3(0,1,0),rad(pyr_degrees.y)); // y, not yaw
quat z = vec3q(vec3(0,0,1),rad(pyr_degrees.z)); // z, not row
return normq(mulq(mulq(x, y), z));
#else
float p = rad(pyr_degrees.x), y = rad(pyr_degrees.y), r = rad(pyr_degrees.z);
float ha = p * 0.5f, hb = r * 0.5f, hc = y * 0.5f;
float cp = cosf(ha), sp = sinf(ha), cr = cosf(hb), sr = sinf(hb), cy = cosf(hc), sy = sinf(hc);
return quat(cy*cr*cp + sy*sr*sp, cy*sr*cp - sy*cr*sp, cy*cr*sp + sy*sr*cp, sy*cr*cp - cy*sr*sp);
#endif
}
// ----------------------------------------------------------------------------
void scaling33(mat33 m, float x, float y, float z) { // !!! ok, i guess
m[0] = x; m[1] = 0; m[2] = 0;
m[3] = 0; m[4] = y; m[5] = 0;
m[6] = 0; m[7] = 0; m[8] = z;
}
void scale33(mat33 m, float x, float y, float z) {
#if 0 // original !!! ok, i guess
m[0] *= x; m[1] *= x; m[2] *= x;
m[3] *= y; m[4] *= y; m[5] *= y;
m[6] *= z; m[7] *= z; m[8] *= z;
#else
m[0] *= x; m[3] *= x; m[6] *= x;
m[1] *= y; m[4] *= y; m[7] *= y;
m[2] *= z; m[5] *= z; m[8] *= z;
#endif
}
void id33(mat33 m) {
scaling33(m, 1,1,1);
}
void extract33(mat33 m, const mat44 M) { // extract rot/sca from mat44
m[0] = M[0]; m[1] = M[1]; m[2] = M[ 2];
m[3] = M[4]; m[4] = M[5]; m[5] = M[ 6];
m[6] = M[8]; m[7] = M[9]; m[8] = M[10];
}
void copy33(mat33 m, const mat33 a) {
for(int i = 0; i < 9; ++i) m[i] = a[i];
}
//
vec3 mulv33(mat33 m, vec3 v) {
return vec3(m[0]*v.x+m[1]*v.y+m[2]*v.z,m[3]*v.x+m[4]*v.y+m[5]*v.z,m[6]*v.x+m[7]*v.y+m[8]*v.z);
}
void multiply33x2(mat33 m, const mat33 a, const mat33 b) {
m[0] = a[0]*b[0]+a[1]*b[3]+a[2]*b[6];
m[1] = a[0]*b[1]+a[1]*b[4]+a[2]*b[7];
m[2] = a[0]*b[2]+a[1]*b[5]+a[2]*b[8];
m[3] = a[3]*b[0]+a[4]*b[3]+a[5]*b[6];
m[4] = a[3]*b[1]+a[4]*b[4]+a[5]*b[7];
m[5] = a[3]*b[2]+a[4]*b[5]+a[5]*b[8];
m[6] = a[6]*b[0]+a[7]*b[3]+a[8]*b[6];
m[7] = a[6]*b[1]+a[7]*b[4]+a[8]*b[7];
m[8] = a[6]*b[2]+a[7]*b[5]+a[8]*b[8];
}
void rotation33(mat33 m, float degrees, float x,float y,float z) {
float radians = degrees * C_PI / 180.0f;
float s = sinf(radians), c = cosf(radians), c1 = 1.0f - c;
float xy = x*y, yz = y*z, zx = z*x, xs = x*s, ys = y*s, zs = z*s;
m[0] = c1*x*x+c; m[1] = c1*xy-zs; m[2] = c1*zx+ys;
m[3] = c1*xy+zs; m[4] = c1*y*y+c; m[5] = c1*yz-xs;
m[6] = c1*zx-ys; m[7] = c1*yz+xs; m[8] = c1*z*z+c;
}
void rotationq33(mat33 m, quat q) {
#if 0
float a = q.w, b = q.x, c = q.y, d = q.z;
float a2 = a*a, b2 = b*b, c2 = c*c, d2 = d*d;
m[ 0] = a2 + b2 - c2 - d2; m[ 1] = 2*(b*c + a*d); m[ 2] = 2*(b*d - a*c);
m[ 3] = 2*(b*c - a*d); m[ 4] = a2 - b2 + c2 - d2; m[ 5] = 2*(c*d + a*b);
m[ 6] = 2*(b*d + a*c); m[ 7] = 2*(c*d - a*b); m[ 8] = a2 - b2 - c2 + d2;
#else
float x2 = q.x*q.x, y2 = q.y*q.y, z2 = q.z*q.z, w2 = q.w*q.w;
float xz = q.x*q.z, xy = q.x*q.y, yz = q.y*q.z, wz = q.w*q.z, wy = q.w*q.y, wx = q.w*q.x;
m[0] = 1-2*(y2+z2); m[1] = 2*(xy+wz); m[2] = 2*(xz-wy);
m[3] = 2*(xy-wz); m[4] = 1-2*(x2+z2); m[5] = 2*(yz+wx);
m[6] = 2*(xz+wy); m[7] = 2*(yz-wx); m[8] = 1-2*(x2+y2);
#endif
}
void rotate33(mat33 r, float degrees, float x,float y,float z) {
if(len3sq(vec3(x,y,z)) < (1e-4 * 1e-4)) return;
float m[9]; rotate33(m, degrees, x,y,z);
multiply33x2(r, r, m);
}
void compose33(mat33 m, quat r, vec3 s) { // verify me
rotationq33(m, r);
scale33(m, s.x,s.y,s.z);
}
// ----------------------------------------------------------------------------
void id34(mat34 m) { // verify me
m[ 0] = 1; m[ 1] = 0; m[ 2] = 0; m[ 3] = 0;
m[ 4] = 0; m[ 5] = 1; m[ 6] = 0; m[ 7] = 0;
m[ 8] = 0; m[ 9] = 0; m[10] = 1; m[11] = 0;
}
void copy34(mat34 m, const mat34 a) {
for( int i = 0; i < 12; ++i ) m[i] = a[i];
}
void scale34(mat34 m, float s) {
for( int i = 0; i < 12; ++i ) m[i] *= s;
}
void add34(mat34 m, mat34 n) {
for( int i = 0; i < 12; ++i ) m[i] += n[i];
}
void muladd34(mat34 m, mat34 n, float s) {
for( int i = 0; i < 12; ++i ) m[i] += n[i] * s;
}
void add34x2(mat34 m, mat34 n, mat34 o) {
for( int i = 0; i < 12; ++i ) m[i] = n[i] + o[i];
}
void lerp34(mat34 m, mat34 n, mat34 o, float alpha) {
for( int i = 0; i < 12; ++i ) m[i] = n[i] * (1-alpha) + o[i] * alpha;
}
void multiply34x2(mat34 m, const mat34 m0, const mat34 m1) {
vec4 r0 = { m0[0*4+0], m0[0*4+1], m0[0*4+2], m0[0*4+3] }; // rows
vec4 r1 = { m0[1*4+0], m0[1*4+1], m0[1*4+2], m0[1*4+3] };
vec4 r2 = { m0[2*4+0], m0[2*4+1], m0[2*4+2], m0[2*4+3] };
vec4 c0 = { m1[0*4+0], m1[1*4+0], m1[2*4+0], 0.0f }; // cols
vec4 c1 = { m1[0*4+1], m1[1*4+1], m1[2*4+1], 0.0f };
vec4 c2 = { m1[0*4+2], m1[1*4+2], m1[2*4+2], 0.0f };
vec4 c3 = { m1[0*4+3], m1[1*4+3], m1[2*4+3], 1.0f };
m[ 0] = dot4(r0, c0); m[ 1] = dot4(r0, c1); m[ 2] = dot4(r0, c2); m[ 3] = dot4(r0, c3);
m[ 4] = dot4(r1, c0); m[ 5] = dot4(r1, c1); m[ 6] = dot4(r1, c2); m[ 7] = dot4(r1, c3);
m[ 8] = dot4(r2, c0); m[ 9] = dot4(r2, c1); m[10] = dot4(r2, c2); m[11] = dot4(r2, c3);
}
void multiply34(mat34 m, const mat34 a) {
mat34 x; copy34(x, m);
multiply34x2(m, x, a);
}
void multiply34x3(mat34 m, const mat34 a, const mat34 b, const mat34 c) {
mat34 x;
multiply34x2(x, a, b);
multiply34x2(m, x, c);
}
void compose34(mat34 m, vec3 t, quat q, vec3 s) {
m[ 0] = s.x * (1 - 2 * q.y * q.y - 2 * q.z * q.z);
m[ 1] = s.y * ( 2 * q.x * q.y - 2 * q.w * q.z);
m[ 2] = s.z * ( 2 * q.x * q.z + 2 * q.w * q.y);
m[ 3] = t.x;
m[ 4] = s.x * ( 2 * q.x * q.y + 2 * q.w * q.z);
m[ 5] = s.y * (1 - 2 * q.x * q.x - 2 * q.z * q.z);
m[ 6] = s.z * ( 2 * q.y * q.z - 2 * q.w * q.x);
m[ 7] = t.y;
m[ 8] = s.x * ( 2 * q.x * q.z - 2 * q.w * q.y);
m[ 9] = s.y * ( 2 * q.y * q.z + 2 * q.w * q.x);
m[10] = s.z * (1 - 2 * q.x * q.x - 2 * q.y * q.y);
m[11] = t.z;
}
void invert34(mat34 m, const mat34 o) {
vec3 a = norm3sq(vec3(o[0*4+0], o[1*4+0], o[2*4+0]));
vec3 b = norm3sq(vec3(o[0*4+1], o[1*4+1], o[2*4+1]));
vec3 c = norm3sq(vec3(o[0*4+2], o[1*4+2], o[2*4+2]));
vec3 trans = vec3(o[0*4+3], o[1*4+3], o[2*4+3]);
vec4 A = vec34(a, -dot3(a, trans));
vec4 B = vec34(b, -dot3(b, trans));
vec4 C = vec34(c, -dot3(c, trans));
m[ 0] = A.x; m[ 1] = A.y; m[ 2] = A.z; m[ 3] = A.w;
m[ 4] = B.x; m[ 5] = B.y; m[ 6] = B.z; m[ 7] = B.w;
m[ 8] = C.x; m[ 9] = C.y; m[10] = C.z; m[11] = C.w;
}
// ----------------------------------------------------------------------------
void scaling44(mat44 m, float x, float y, float z);
void id44(mat44 m) {
scaling44(m, 1,1,1);
}
void identity44(mat44 m) {
scaling44(m, 1,1,1);
}
void copy44(mat44 m, const mat44 a) {
for( int i = 0; i < 16; ++i ) m[i] = a[i];
}
void multiply44x2(mat44 m, const mat44 a, const mat44 b) {
for (int y = 0; y < 4; y++)
for (int x = 0; x < 4; x++)
m[y*4+x] = a[x] * b[y*4]+a[4+x] * b[y*4+1]+a[8+x] * b[y*4+2]+a[12+x] * b[y*4+3];
}
void multiply44x3(mat44 m, const mat44 a, const mat44 b, const mat44 c) {
mat44 x;
multiply44x2(x, a, b);
multiply44x2(m, x, c);
}
void multiply44(mat44 m, const mat44 a) {
mat44 b; copy44(b, m);
multiply44x2(m, b, a);
}
// ---
void ortho44(mat44 m, float l, float r, float b, float t, float n, float f) {
m[ 0] = 2/(r-l); m[ 1] = 0; m[ 2] = 0; m[ 3] = 0;
m[ 4] = 0; m[ 5] = 2/(t-b); m[ 6] = 0; m[ 7] = 0;
m[ 8] = 0; m[ 9] = 0; m[10] = -2/(f-n); m[11] = 0;
m[12] = -(r+l)/(r-l); m[13] = -(t+b)/(t-b); m[14] = -(f+n)/(f-n); m[15] = 1;
}
void frustum44(mat44 m, float l, float r, float b, float t, float n, float f) {
m[ 0] = 2*n/(r-l); m[ 1] = 0; m[ 2] = 0; m[ 3] = 0;
m[ 4] = 0; m[ 5] = 2*n/(t-b); m[ 6] = 0; m[ 7] = 0;
m[ 8] = (r+l)/(r-l); m[ 9] = (t+b)/(t-b); m[10] = -(f+n)/(f-n); m[11] = -1;
m[12] = 0; m[13] = 0; m[14] = -2*(f*n)/(f-n); m[15] = 0;
}
void perspective44(mat44 m, float fovy_degrees, float aspect, float nearp, float farp) {
float y = tanf(fovy_degrees * C_PI / 360) * nearp, x = y * aspect;
frustum44(m, -x, x, -y, y, nearp, farp);
}
void lookat44(mat44 m, vec3 eye, vec3 center, vec3 up) {
vec3 f = norm3(sub3(center, eye));
vec3 r = norm3(cross3(f, up));
vec3 u = cross3(r, f);
m[ 0] = r.x; m[ 1] = u.x; m[ 2] = -f.x; m[ 3] = 0;
m[ 4] = r.y; m[ 5] = u.y; m[ 6] = -f.y; m[ 7] = 0;
m[ 8] = r.z; m[ 9] = u.z; m[10] = -f.z; m[11] = 0;
m[12] = -dot3(r, eye); m[13] = -dot3(u, eye); m[14] = dot3(f, eye); m[15] = 1;
}
2024-08-23 19:28:40 +00:00
vec3 pos44(mat44 m) {
vec3 position;
// The camera position is the negation of the translation vector
// transformed by the inverse of the rotation matrix.
// Since the upper-left 3x3 part of the view matrix is orthogonal,
// its inverse is equal to its transpose.
position.x = -(m[0] * m[12] + m[1] * m[13] + m[2] * m[14]);
position.y = -(m[4] * m[12] + m[5] * m[13] + m[6] * m[14]);
position.z = -(m[8] * m[12] + m[9] * m[13] + m[10] * m[14]);
return position;
}
2024-08-19 07:54:01 +00:00
// ---
void translation44(mat44 m, float x, float y, float z) { // identity4 + translate4
m[ 0] = 1.0f; m[ 1] = 0.0f; m[ 2] = 0.0f; m[ 3] = 0.0f;
m[ 4] = 0.0f; m[ 5] = 1.0f; m[ 6] = 0.0f; m[ 7] = 0.0f;
m[ 8] = 0.0f; m[ 9] = 0.0f; m[10] = 1.0f; m[11] = 0.0f;
m[12] = x; m[13] = y; m[14] = z; m[15] = 1.0f;
}
void translate44(mat44 m, float x, float y, float z) { // translate in place
#if 0 // original
vec4 t = {x, y, z, 0};
m[12] += dot4(vec4(m[0],m[4],m[ 8],m[12]),t); // row4(M,0)
m[13] += dot4(vec4(m[1],m[5],m[ 9],m[13]),t); // row4(M,1)
m[14] += dot4(vec4(m[2],m[6],m[10],m[14]),t); // row4(M,2)
m[15] += dot4(vec4(m[3],m[7],m[11],m[15]),t); // row4(M,3)
#else
m[12] += m[ 0]*x + m[ 4]*y + m[ 8]*z;
m[13] += m[ 1]*x + m[ 5]*y + m[ 9]*z;
m[14] += m[ 2]*x + m[ 6]*y + m[10]*z;
m[15] += m[ 3]*x + m[ 7]*y + m[11]*z;
#endif
}
void relocate44(mat44 m, float x, float y, float z) {
m[12] = x; m[13] = y; m[14] = z;
}
void rotationq44(mat44 m, quat q) {
#if 0
float a = q.w, b = q.x, c = q.y, d = q.z;
float a2 = a*a, b2 = b*b, c2 = c*c, d2 = d*d;
m[ 0] = a2 + b2 - c2 - d2; m[ 1] = 2*(b*c + a*d); m[ 2] = 2*(b*d - a*c); m[ 3] = 0;
m[ 4] = 2*(b*c - a*d); m[ 5] = a2 - b2 + c2 - d2; m[ 6] = 2*(c*d + a*b); m[ 7] = 0;
m[ 8] = 2*(b*d + a*c); m[ 9] = 2*(c*d - a*b); m[10] = a2 - b2 - c2 + d2; m[11] = 0;
m[12] = 0; m[13] = 0; m[14] = 0; m[15] = 1;
#else
float x2 = q.x*q.x, y2 = q.y*q.y, z2 = q.z*q.z, w2 = q.w*q.w;
float xz = q.x*q.z, xy = q.x*q.y, yz = q.y*q.z, wz = q.w*q.z, wy = q.w*q.y, wx = q.w*q.x;
m[ 0] = 1-2*(y2+z2); m[ 1] = 2*(xy+wz); m[ 2] = 2*(xz-wy); m[ 3] = 0;
m[ 4] = 2*(xy-wz); m[ 5] = 1-2*(x2+z2); m[ 6] = 2*(yz+wx); m[ 7] = 0;
m[ 8] = 2*(xz+wy); m[ 9] = 2*(yz-wx); m[10] = 1-2*(x2+y2); m[11] = 0;
m[12] = 0; m[13] = 0; m[14] = 0; m[15] = 1;
#endif
}
void rotation44(mat44 m, float degrees, float x, float y, float z) {
//if(len3sq(vec3(x,y,z)) < (1e-4 * 1e-4)) return;
float radians = degrees * C_PI / 180.0f;
float c = cosf(radians), s = sinf(radians), c1 = 1.0f - c;
m[ 0] = x*x*c1 + c; m[ 1] = y*x*c1 + z*s; m[ 2] = x*z*c1 - y*s; m[ 3] = 0.0f;
m[ 4] = x*y*c1 - z*s; m[ 5] = y*y*c1 + c; m[ 6] = y*z*c1 + x*s; m[ 7] = 0.0f;
m[ 8] = x*z*c1 + y*s; m[ 9] = y*z*c1 - x*s; m[10] = z*z*c1 + c; m[11] = 0.0f;
m[12] = 0.0f; m[13] = 0.0f; m[14] = 0.0f; m[15] = 1.0f;
}
void rotate44(mat44 m, float degrees, float x, float y, float z) { // !!! ok, i guess
if(len3sq(vec3(x,y,z)) < (1e-4 * 1e-4)) return;
float radians = degrees * C_PI / 180.0f;
float c = cosf(radians), s = -sinf(radians), c1 = 1 - c;
float m00 = m[ 0], m01 = m[ 1], m02 = m[ 2], m03 = m[ 3],
m04 = m[ 4], m05 = m[ 5], m06 = m[ 6], m07 = m[ 7],
m08 = m[ 8], m09 = m[ 9], m10 = m[10], m11 = m[11];
// rotation matrix
float r00 = x*x*c1 + c, r01 = y*x*c1 + z*s, r02 = x*z*c1 - y*s;
float r04 = x*y*c1 - z*s, r05 = y*y*c1 + c, r06 = y*z*c1 + x*s;
float r08 = x*z*c1 + y*s, r09 = y*z*c1 - x*s, r10 = z*z*c1 + c;
// multiply
m[ 0] = r00 * m00 + r04 * m04 + r08 * m08;
m[ 1] = r00 * m01 + r04 * m05 + r08 * m09;
m[ 2] = r00 * m02 + r04 * m06 + r08 * m10;
m[ 3] = r00 * m03 + r04 * m07 + r08 * m11;
m[ 4] = r01 * m00 + r05 * m04 + r09 * m08;
m[ 5] = r01 * m01 + r05 * m05 + r09 * m09;
m[ 6] = r01 * m02 + r05 * m06 + r09 * m10;
m[ 7] = r01 * m03 + r05 * m07 + r09 * m11;
m[ 8] = r02 * m00 + r06 * m04 + r10 * m08;
m[ 9] = r02 * m01 + r06 * m05 + r10 * m09;
m[10] = r02 * m02 + r06 * m06 + r10 * m10;
m[11] = r02 * m03 + r06 * m07 + r10 * m11;
}
void scaling44(mat44 m, float x, float y, float z) { // !!! ok, i guess
m[ 0] = x; m[ 1] = 0; m[ 2] = 0; m[ 3] = 0;
m[ 4] = 0; m[ 5] = y; m[ 6] = 0; m[ 7] = 0;
m[ 8] = 0; m[ 9] = 0; m[10] = z; m[11] = 0;
m[12] = 0; m[13] = 0; m[14] = 0; m[15] = 1;
}
void scale44(mat44 m, float x, float y, float z) {
#if 0 // original !!! ok, i guess
m[ 0] *= x; m[ 1] *= x; m[ 2] *= x; m[ 3] *= x;
m[ 4] *= y; m[ 5] *= y; m[ 6] *= y; m[ 7] *= y;
m[ 8] *= z; m[ 9] *= z; m[10] *= z; m[11] *= z;
#else
m[0] *= x; m[4] *= x; m[8] *= x;
m[1] *= y; m[5] *= y; m[9] *= y;
m[2] *= z; m[6] *= z; m[10] *= z;
#endif
}
// ---
void transpose44(mat44 m, const mat44 a) { // M[i][j] = A[j][i];
m[ 0] = a[0]; m[ 1] = a[4]; m[ 2] = a[ 8]; m[ 3] = a[12];
m[ 4] = a[1]; m[ 5] = a[5]; m[ 6] = a[ 9]; m[ 7] = a[13];
m[ 8] = a[2]; m[ 9] = a[6]; m[10] = a[10]; m[11] = a[14];
m[12] = a[3]; m[13] = a[7]; m[14] = a[11]; m[15] = a[15];
}
// @todo: test me
// float det33 = M[0,0]*((M[1,1]*M[2,2])-(M[2,1]*M[1,2]))-M[0,1]*(M[1,0]*M[2,2]-M[2,0]*M[1,2])+M[0,2]*(M[1,0]*M[2,1]-M[2,0]*M[1,1]);
//
// float det33 =
// rgt.x * fwd.y * upv.z - rgt.z * fwd.y * upv.x +
// rgt.y * fwd.z * upv.x - rgt.y * fwd.x * upv.z +
// rgt.z * fwd.x * upv.y - rgt.x * fwd.z * upv.y;
//
// void transpose33(mat33 m, const mat33 a) { // M[i][j] = A[j][i];
// m[0] = a[0]; m[1] = a[3]; m[2] = a[6];
// m[3] = a[1]; m[4] = a[4]; m[5] = a[7];
// m[6] = a[2]; m[7] = a[5]; m[8] = a[8];
// }
float det44(const mat44 M) { // !!! ok, i guess
float s[6], c[6];
s[0] = M[0*4+0]*M[1*4+1] - M[1*4+0]*M[0*4+1];
s[1] = M[0*4+0]*M[1*4+2] - M[1*4+0]*M[0*4+2];
s[2] = M[0*4+0]*M[1*4+3] - M[1*4+0]*M[0*4+3];
s[3] = M[0*4+1]*M[1*4+2] - M[1*4+1]*M[0*4+2];
s[4] = M[0*4+1]*M[1*4+3] - M[1*4+1]*M[0*4+3];
s[5] = M[0*4+2]*M[1*4+3] - M[1*4+2]*M[0*4+3];
c[0] = M[2*4+0]*M[3*4+1] - M[3*4+0]*M[2*4+1];
c[1] = M[2*4+0]*M[3*4+2] - M[3*4+0]*M[2*4+2];
c[2] = M[2*4+0]*M[3*4+3] - M[3*4+0]*M[2*4+3];
c[3] = M[2*4+1]*M[3*4+2] - M[3*4+1]*M[2*4+2];
c[4] = M[2*4+1]*M[3*4+3] - M[3*4+1]*M[2*4+3];
c[5] = M[2*4+2]*M[3*4+3] - M[3*4+2]*M[2*4+3];
return ( s[0]*c[5]-s[1]*c[4]+s[2]*c[3]+s[3]*c[2]-s[4]*c[1]+s[5]*c[0] );
}
bool invert44(mat44 T, const mat44 M) { // !!! ok, i guess
float s[6], c[6];
s[0] = M[0*4+0]*M[1*4+1] - M[1*4+0]*M[0*4+1];
s[1] = M[0*4+0]*M[1*4+2] - M[1*4+0]*M[0*4+2];
s[2] = M[0*4+0]*M[1*4+3] - M[1*4+0]*M[0*4+3];
s[3] = M[0*4+1]*M[1*4+2] - M[1*4+1]*M[0*4+2];
s[4] = M[0*4+1]*M[1*4+3] - M[1*4+1]*M[0*4+3];
s[5] = M[0*4+2]*M[1*4+3] - M[1*4+2]*M[0*4+3];
c[0] = M[2*4+0]*M[3*4+1] - M[3*4+0]*M[2*4+1];
c[1] = M[2*4+0]*M[3*4+2] - M[3*4+0]*M[2*4+2];
c[2] = M[2*4+0]*M[3*4+3] - M[3*4+0]*M[2*4+3];
c[3] = M[2*4+1]*M[3*4+2] - M[3*4+1]*M[2*4+2];
c[4] = M[2*4+1]*M[3*4+3] - M[3*4+1]*M[2*4+3];
c[5] = M[2*4+2]*M[3*4+3] - M[3*4+2]*M[2*4+3];
float det = ( s[0]*c[5]-s[1]*c[4]+s[2]*c[3]+s[3]*c[2]-s[4]*c[1]+s[5]*c[0] );
if( !det ) return false;
float idet = 1.0f / det;
T[0*4+0] = ( M[1*4+1] * c[5] - M[1*4+2] * c[4] + M[1*4+3] * c[3]) * idet;
T[0*4+1] = (-M[0*4+1] * c[5] + M[0*4+2] * c[4] - M[0*4+3] * c[3]) * idet;
T[0*4+2] = ( M[3*4+1] * s[5] - M[3*4+2] * s[4] + M[3*4+3] * s[3]) * idet;
T[0*4+3] = (-M[2*4+1] * s[5] + M[2*4+2] * s[4] - M[2*4+3] * s[3]) * idet;
T[1*4+0] = (-M[1*4+0] * c[5] + M[1*4+2] * c[2] - M[1*4+3] * c[1]) * idet;
T[1*4+1] = ( M[0*4+0] * c[5] - M[0*4+2] * c[2] + M[0*4+3] * c[1]) * idet;
T[1*4+2] = (-M[3*4+0] * s[5] + M[3*4+2] * s[2] - M[3*4+3] * s[1]) * idet;
T[1*4+3] = ( M[2*4+0] * s[5] - M[2*4+2] * s[2] + M[2*4+3] * s[1]) * idet;
T[2*4+0] = ( M[1*4+0] * c[4] - M[1*4+1] * c[2] + M[1*4+3] * c[0]) * idet;
T[2*4+1] = (-M[0*4+0] * c[4] + M[0*4+1] * c[2] - M[0*4+3] * c[0]) * idet;
T[2*4+2] = ( M[3*4+0] * s[4] - M[3*4+1] * s[2] + M[3*4+3] * s[0]) * idet;
T[2*4+3] = (-M[2*4+0] * s[4] + M[2*4+1] * s[2] - M[2*4+3] * s[0]) * idet;
T[3*4+0] = (-M[1*4+0] * c[3] + M[1*4+1] * c[1] - M[1*4+2] * c[0]) * idet;
T[3*4+1] = ( M[0*4+0] * c[3] - M[0*4+1] * c[1] + M[0*4+2] * c[0]) * idet;
T[3*4+2] = (-M[3*4+0] * s[3] + M[3*4+1] * s[1] - M[3*4+2] * s[0]) * idet;
T[3*4+3] = ( M[2*4+0] * s[3] - M[2*4+1] * s[1] + M[2*4+2] * s[0]) * idet;
return true;
}
vec4 transform444(const mat44, const vec4);
bool unproject44(vec3 *out, vec3 xyd, vec4 viewport, mat44 mvp) { // @fixme: this function is broken (verified by @zpl-zak)
// xyd: usually x:mouse_x,y:window_height()-mouse_y,d:0=znear/1=zfar
// src: https://www.khronos.org/opengl/wiki/GluProject_and_gluUnProject_code
mat44 inv_mvp;
if( invert44(inv_mvp, mvp) ) {
vec4 in = vec4( (xyd.x-viewport.x)/viewport.z*2-1, (xyd.y-viewport.y)/viewport.w*2-1, 2*xyd.z-1, 1 );
vec4 p = transform444(inv_mvp, in);
if( p.w != 0 ) {
p.w = 1.f/p.w;
*out = vec3(p.x*p.w,p.y*p.w,p.z*p.w);
return true;
}
}
return false;
}
void compose44(mat44 m, vec3 t, quat q, vec3 s) {
#if 0
// quat to rotation matrix
m[0] = 1 - 2 * (q.y * q.y + q.z * q.z);
m[1] = 2 * (q.x * q.y + q.z * q.w);
m[2] = 2 * (q.x * q.z - q.y * q.w);
m[4] = 2 * (q.x * q.y - q.z * q.w);
m[5] = 1 - 2 * (q.x * q.x + q.z * q.z);
m[6] = 2 * (q.y * q.z + q.x * q.w);
m[8] = 2 * (q.x * q.z + q.y * q.w);
m[9] = 2 * (q.y * q.z - q.x * q.w);
m[10] = 1 - 2 * (q.x * q.x + q.y * q.y);
// scale matrix
m[0] *= s.x; m[4] *= s.x; m[8] *= s.x;
m[1] *= s.y; m[5] *= s.y; m[9] *= s.y;
m[2] *= s.z; m[6] *= s.z; m[10] *= s.z;
// set translation
m[12] = t.x; m[13] = t.y; m[14] = t.z;
m[3] = 0; m[7] = 0; m[11] = 0; m[15] = 1;
#else
rotationq44(m,q);
scale44(m,s.x,s.y,s.z);
relocate44(m,t.x,t.y,t.z);
// relocate44(m,t.x,t.y,t.z); // ok?
// scale44(m,s.x,s.y,s.z); // ok?
m[3] = 0; m[7] = 0; m[11] = 0; m[15] = 1;
#endif
}
// ----------------------------------------------------------------------------
vec3 transform33(const mat33 m, vec3 p) {
float x = (m[0] * p.x) + (m[4] * p.y) + (m[ 8] * p.z);
float y = (m[1] * p.x) + (m[5] * p.y) + (m[ 9] * p.z);
float z = (m[2] * p.x) + (m[6] * p.y) + (m[10] * p.z);
return vec3(x,y,z);
}
vec4 transform444(const mat44 m, const vec4 p) {
// remember w = 1 for move in space; w = 0 rotate in space;
float x = m[0]*p.x + m[4]*p.y + m[ 8]*p.z + m[12]*p.w;
float y = m[1]*p.x + m[5]*p.y + m[ 9]*p.z + m[13]*p.w;
float z = m[2]*p.x + m[6]*p.y + m[10]*p.z + m[14]*p.w;
float w = m[3]*p.x + m[7]*p.y + m[11]*p.z + m[15]*p.w;
return vec4(x,y,z,w);
}
vec3 transform344(const mat44 m, const vec3 p) {
vec4 v = transform444(m, vec34(p, 1));
return scale3(v.xyz, 1.f / v.w);
}
vec3 transformq(const quat q, const vec3 v) { // !!! ok, i guess
// [src] https://gamedev.stackexchange.com/questions/28395/rotating-vector3-by-a-quaternion (laurent couvidou)
// Extract the vector part of the quaternion
vec3 u = vec3(q.x, q.y, q.z);
// Extract the scalar part of the quaternion
float s = q.w;
// Do the math
vec3 a = scale3(u, 2 * dot3(u,v));
vec3 b = scale3(v, s*s - dot3(u,u));
vec3 c = scale3(cross3(u,v), 2*s);
return add3(a, add3(b,c));
}
#if 0
vec3 transform_axis(const coord_system, const AXIS_ENUMS);
void rebase44(mat44 m, const coord_system src_basis, const coord_system dst_basis) {
vec3 v1 = transform_axis(src_basis, dst_basis.x);
vec3 v2 = transform_axis(src_basis, dst_basis.y);
vec3 v3 = transform_axis(src_basis, dst_basis.z);
m[ 0] = v1.x; m[ 1] = v1.y; m[ 2] = v1.z; m[ 3] = 0;
m[ 4] = v2.x; m[ 5] = v2.y; m[ 6] = v2.z; m[ 7] = 0;
m[ 8] = v3.x; m[ 9] = v3.y; m[10] = v3.z; m[11] = 0;
m[12] = 0; m[13] = 0; m[14] = 0; m[15] = 1;
}
vec3 transform_axis(const coord_system basis, const AXIS_ENUMS to) {
const float dot_table[6][6] = {
{+1,-1,0,0,0,0},{-1,+1,0,0,0,0},{0,0,+1,-1,0,0},
{0,0,-1,+1,0,0},{0,0,0,0,+1,-1},{0,0,0,0,-1,+1},
};
return vec3( dot_table[basis.x][to], dot_table[basis.y][to], dot_table[basis.z][to] );
}
// A vector is the difference between two points in 3D space, possessing both direction and magnitude
vec3 transform_vector (const mat44 m, const vec3 vector) {
return transform344(m, vector);
}
// A point is a specific location within a 3D space
vec3 transform_point (const mat44 m, const vec3 p) { // return (m * vec4{point,1).xyz()/r.w;
float inv = 1.0f / (m[3+4*0]*p.x + m[3+4*1]*p.y + m[3+4*2]*p.z + m[3+4*3]);
return vec3(
(m[0+4*0]*p.x + m[0+4*1]*p.y + m[0+4*2]*p.z + m[0+4*3]) * inv,
(m[1+4*0]*p.x + m[1+4*1]*p.y + m[1+4*2]*p.z + m[1+4*3]) * inv,
(m[2+4*0]*p.x + m[2+4*1]*p.y + m[2+4*2]*p.z + m[2+4*3]) * inv
);
}
// A tangent is a unit-length vector which is parallel to a piece of geometry, such as a surface or a curve
vec3 transform_tangent (const mat44 m, const vec3 tangent) { return norm3(transform_vector(m, tangent)); }
// A normal is a unit-length bivector which is perpendicular to a piece of geometry, such as a surface or a curve
vec3 transform_normal (const mat44 m, const vec3 normal) {
return transform_tangent(m, normal); // ok?
mat44 t; transpose44(t,m); mat44 i; invert44(i,t);
return scale3(norm3(transform_vector(i, normal)), det44(m) < 0 ? -1.f : 1.f);
}
// A quaternion can describe both a rotation and a uniform scaling in 3D space
quat transform_quat (const mat44 m, const quat q) {
vec3 s = scale3(transform_vector(m, q.xyz), det44(m) < 0 ? -1.f : 1.f);
return quat(s.x,s.y,s.z,q.w);
}
// A matrix can describe a general transformation of homogeneous coordinates in projective space
float* transform_matrix(mat44 out, const mat44 m, const mat44 matrix) {
mat44 I; invert44(I, m);
multiply44x3(out, I, matrix, m); // m,matrix,I instead ?
return out;
}
// Scaling factors are not a vector, they are a compact representation of a scaling matrix
vec3 transform_scaling (const mat44 m, const vec3 scaling) {
mat44 s; scaling44(s, scaling.x, scaling.y, scaling.z);
mat44 out; transform_matrix(out, m, s);
return vec3( out[0], out[5], out[10] );
}
#endif
// ----------------------------------------------------------------------------
// !!! for debugging
void printi_( int *m, int ii, int jj ) {
for( int j = 0; j < jj; ++j ) {
for( int i = 0; i < ii; ++i ) printf("%10d ", *m++);
puts("");
}
// puts("---");
}
void print_( float *m, int ii, int jj ) {
for( int j = 0; j < jj; ++j ) {
for( int i = 0; i < ii; ++i ) printf("%8.3f", *m++);
puts("");
}
// puts("---");
}
void print2i( vec2i v ) { printi_(&v.x,2,1); }
void print3i( vec3i v ) { printi_(&v.x,3,1); }
void print2( vec2 v ) { print_(&v.x,2,1); }
void print3( vec3 v ) { print_(&v.x,3,1); }
void print4( vec4 v ) { print_(&v.x,4,1); }
void printq( quat q ) { print_(&q.x,4,1); }
void print33( float *m ) { print_(m,3,3); }
void print34( float *m ) { print_(m,3,4); }
void print44( float *m ) { print_(m,4,4); }
// -----------
AUTORUN {
STRUCT( vec3, float, x );
STRUCT( vec3, float, y );
STRUCT( vec3, float, z, "Up" );
}
#line 0
#line 1 "v4k_memory.c"
size_t dlmalloc_usable_size(void*); // __ANDROID_API__
#if is(bsd) || is(osx) // bsd or osx
# include <malloc/malloc.h>
#else
# include <malloc.h>
#endif
#ifndef SYS_MEM_INIT
#define SYS_MEM_INIT()
#define SYS_MEM_REALLOC realloc
#define SYS_MEM_SIZE /* bsd/osx, then win32, then ems/__GLIBC__, then __ANDROID_API__ */ \
ifdef(osx, malloc_size, ifdef(bsd, malloc_size, \
ifdef(win32, _msize, malloc_usable_size)))
#endif
// xrealloc --------------------------------------------------------------------
static __thread uint64_t xstats_current = 0, xstats_total = 0, xstats_allocs = 0;
void* xrealloc(void* oldptr, size_t size) {
static __thread int once = 0; for(;!once;once = 1) SYS_MEM_INIT();
// for stats
size_t oldsize = xsize(oldptr);
void *ptr = SYS_MEM_REALLOC(oldptr, size);
if( !ptr && size ) {
PANIC("Not memory enough (trying to allocate %u bytes)", (unsigned)size);
}
#if ENABLE_MEMORY_POISON
if( !oldptr && size ) {
memset(ptr, 0xCD, size);
}
#endif
// for stats
if( oldptr ) {
xstats_current += (int64_t)size - (int64_t)oldsize;
xstats_allocs -= !size;
} else {
xstats_current += size;
xstats_allocs += !!size;
}
if( xstats_current > xstats_total ) {
xstats_total = xstats_current;
}
return ptr;
}
size_t xsize(void* p) {
if( p ) return SYS_MEM_SIZE(p);
return 0;
}
char *xstats(void) {
uint64_t xtra = 0; // xstats_allocs * 65536; // assumes 64K pagesize for every alloc
return va("%03u/%03uMB", (unsigned)((xstats_current+xtra) / 1024 / 1024), (unsigned)((xstats_total+xtra) / 1024 / 1024));
}
// stack -----------------------------------------------------------------------
void* stack(int bytes) { // use negative bytes to rewind stack
static __thread uint8_t *stack_mem = 0;
static __thread uint64_t stack_ptr = 0;
static __thread uint64_t stack_max = 0; // watch this var, in case you want to fine tune 4 MiB value below
if( bytes < 0 ) {
if( stack_ptr > stack_max ) stack_max = stack_ptr;
return (stack_ptr = 0), NULL;
}
if( !stack_mem ) stack_mem = xrealloc(stack_mem, xsize(stack_mem) + 4 * 1024 * 1024);
return &stack_mem[ (stack_ptr += bytes) - bytes ];
}
// leaks ----------------------------------------------------------------------
void* watch( void *ptr, int sz ) {
static __thread int open = 1;
if( ptr && open ) {
open = 0;
char buf[256];
sprintf(buf, "%p.mem", ptr);
for( FILE *fp = fopen(buf, "a+"); fp; fclose(fp), fp = 0 ) {
fseek(fp, 0L, SEEK_END);
const char *cs = callstack( +16 ); // +48
fprintf(fp, "Built %s %s\n", __DATE__, __TIME__); // today() instead?
fprintf(fp, "Memleak address: [%p], size: %d\n%s\n", ptr, sz, cs ? cs : "No callstack.");
}
open = 1;
}
return ptr;
}
void* forget( void *ptr ) {
if( ptr ) {
char buf[256];
sprintf(buf, "%p.mem", ptr);
unlink(buf);
}
return ptr;
}
#line 0
#line 1 "v4k_network.c"
#if is(tcc) && is(win32) // @fixme: https lib is broken with tcc. replaced with InternetReadFile() api for now
# include <wininet.h>
# pragma comment(lib,"wininet")
int download_file( FILE *out, const char *url ) {
int ok = false;
char buffer[ 4096 ];
DWORD response_size = 0;
if( out )
for( HINTERNET session = InternetOpenA("v4k.download_file", PRE_CONFIG_INTERNET_ACCESS, NULL, INTERNET_INVALID_PORT_NUMBER, 0); session; InternetCloseHandle(session), session = 0 ) // @fixme: download_file
for( HINTERNET request = InternetOpenUrlA(session, url, NULL, 0, INTERNET_FLAG_RELOAD, 0); request; InternetCloseHandle(request), request = 0 )
for(; InternetReadFile(request, buffer, sizeof(buffer), &response_size) != FALSE && response_size > 0; ) {
ok = (fwrite(buffer, response_size, 1, out) == 1);
if(!ok) break;
}
return ok;
}
array(char) download( const char *url ) {
char buffer[ 4096 ];
DWORD response_size = 0, pos = 0;
array(char) out = 0;
for( HINTERNET session = InternetOpenA("v4k.download", PRE_CONFIG_INTERNET_ACCESS, NULL, INTERNET_INVALID_PORT_NUMBER, 0); session; InternetCloseHandle(session), session = 0 )
for( HINTERNET request = InternetOpenUrlA(session, url, NULL, 0, INTERNET_FLAG_RELOAD, 0); request; InternetCloseHandle(request), request = 0 )
for(; InternetReadFile(request, buffer, sizeof(buffer), &response_size) != FALSE && response_size > 0; ) {
array_resize(out, pos + response_size);
memcpy(out + (pos += response_size) - response_size, buffer, response_size);
}
return out;
}
#else
int download_file( FILE *out, const char *url ) {
int ok = false;
if( out ) for( https_t *h = https_get(url, NULL); h; https_release(h), h = NULL ) {
while (https_process(h) == HTTPS_STATUS_PENDING) sleep_ms(1);
//printf("fetch status%d, %d %s\n\n%.*s\n", https_process(h), h->status_code, h->content_type, (int)h->response_size, (char*)h->response_data);
if(https_process(h) == HTTPS_STATUS_COMPLETED)
ok = fwrite(h->response_data, h->response_size, 1, out) == 1;
}
return ok;
}
// @fixme: broken with tcc -m64 (our default tcc configuration)
array(char) download( const char *url ) {
array(char) out = 0;
for( https_t *h = https_get(url, NULL); h; https_release(h), h = NULL ) {
while (https_process(h) == HTTPS_STATUS_PENDING) sleep_ms(1);
//printf("fetch status:%d, %d %s\n\n%.*s\n", https_process(h), h->status_code, h->content_type, (int)h->response_size, (char*)h->response_data);
if( https_process(h) == HTTPS_STATUS_COMPLETED ) {
array_resize(out, h->response_size);
memcpy(out, h->response_data, h->response_size);
}
}
return out;
}
#endif
bool network_tests() {
// network test (https)
array(char) webfile = download("https://www.google.com/");
printf("Network test: %d bytes downloaded from google.com\n", array_count(webfile));
// array_push(webfile, '\0'); puts(webfile);
return true;
}
int portname( const char *service_name, unsigned retries ) {
// Determine port for a given service based on hash of its name.
// If port cant be reached, client should retry with next hash.
// Algorithm: fnv1a(name of service) -> splitmix64 num retries -> remap bucket as [min..max] ports.
// hash64
uint64_t hash = 14695981039346656037ULL;
while( *service_name ) {
hash = ( (unsigned char)*service_name++ ^ hash ) * 0x100000001b3ULL;
}
// splitmix64
for( unsigned i = 0; i < retries; ++i ) {
uint64_t h = (hash += UINT64_C(0x9E3779B97F4A7C15));
h = (h ^ (h >> 30)) * UINT64_C(0xBF58476D1CE4E5B9);
h = (h ^ (h >> 27)) * UINT64_C(0x94D049BB133111EB);
h = (h ^ (h >> 31));
hash = h;
}
// See dynamic ports: https://en.wikipedia.org/wiki/Ephemeral_port
// So, excluded ranges: 32768..60999 (linux), 49152..65535 (freebsd+vista+win7), 1024..5000 (winsrv2003+bsd)
// Output range: [5001..32724], in 4096 steps
return ((hash & 0xFFF) * 677 / 100 + 5001);
}
// -----------------------------------------------------------------------------
#define UDP_DEBUG 0
static int udp_init() {
do_once {
int rc = swrapInit(); // atexit(swrapTerminate);
if( rc ) PANIC("udp_init: swrapInit error");
}
return 1;
}
int udp_open(const char *address, const char *port) {
do_once udp_init();
int fd = swrapSocket(SWRAP_UDP, SWRAP_CONNECT, 0, address, port);
// if( fd == -1 ) PANIC("udp_open: swrapSocket error");
return fd;
}
int udp_bind(const char *address, const char *port) {
do_once udp_init();
int fd = swrapSocket(SWRAP_UDP, SWRAP_BIND, 0, address, port);
// if( fd == -1 ) PANIC("udp_bind: swrapSocket error");
return fd;
}
int udp_send( int fd, const void *buf, int len ) { // returns bytes sent, or -1 if error
int rc = -1;
if( fd >= 0 ) for( ;; ) {
rc = swrapSend(fd, (const char *)buf, len);
#if is(win32)
if( rc == -1 && WSAGetLastError() == WSAEINTR ) continue;
else break;
#else
if( rc == -1 && errno == EINTR ) continue;
else break;
#endif
}
#if UDP_DEBUG
if( rc > 0 ) {
char host[128], serv[128];
int rc2 = swrapAddressInfo(&sa, host, 128, serv, 128 );
if( rc2 != 0 ) PANIC("swrapAddressInfo error");
printf("udp_send: %d bytes to %s:%s : %.*s\n", rc, host, serv, rc, buf );
hexdump(buf, rc);
}
#endif
return rc;
}
int udp_close( int fd ) { // @todo: expose me? needed?
#if is(win32)
// closesocket(fd);
#else
// close(fd);
#endif
fd = -1; // noop
return 0;
}
#if 0
// use socket to send data to another address
int udp_sendto( int fd, const char *ip, const char *port, const void *buf, int len ) { // return number of bytes sent
#if 0
int rc = swrapSendTo(fd, struct swrap_addr*, (const char*)buf, len);
if( rc == -1 ) return -1; //PANIC("udp_send: swrapSend error");
return rc;
#else
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
// use inet_addr. tcc(win32) wont work otherwise.
addr.sin_addr.s_addr = inet_addr(ip); // inet_pton(AF_INET, ip, &addr.sin_addr);
addr.sin_port = htons(atoi(port));
int n = sendto(fd, buf, len, 0, (struct sockaddr *)&addr, sizeof(addr));
return n < 0 ? -1 : n;
#endif
}
#endif
int udp_peek( int fd ) { // <0 error, 0 timeout, >0 data
int rc = swrapSelect(fd, 0.00001);
if( rc < 0 ) return -1; // PANIC("udp_peek: swrapSelect error");
if( rc == 0 ) return 0; // timeout
return 1; //> 0: new data is available
}
int udp_recv( int fd, void *buf, int len ) { // <0 error, 0 orderly shutdown, >0 received bytes
struct swrap_addr sa = {0};
int rc = swrapReceiveFrom(fd, &sa, buf, len);
if( rc < 0 ) return -1; // PANIC("udp_recv: swrapReceiveFrom error");
if( rc == 0 ) return 0; // orderly shutdown
#if UDP_DEBUG
char host[128], serv[128];
int rc2 = swrapAddressInfo(&sa, host, 128, serv, 128 );
if( rc2 != 0 ) PANIC("swrapAddressInfo error");
printf("udp_recv: %d bytes from %s:%s : %.*s\n", rc, host, serv, rc, buf );
hexdump(buf, rc);
#endif
return rc;
}
// -----------------------------------------------------------------------------
#define TCP_DEBUG 1
#if TCP_DEBUG
static set(int) tcp_set;
#endif
void tcp_init(void) {
do_once {
udp_init();
#if TCP_DEBUG
set_init(tcp_set, less_int, hash_int);
#endif
}
}
int tcp_open(const char *address, const char *port) {
do_once tcp_init();
int fd = swrapSocket(SWRAP_TCP, SWRAP_CONNECT, 0/*|SWRAP_NODELAY*/, address, port);
return fd;
}
int tcp_bind(const char *interface_, const char *port, int backlog) {
do_once tcp_init();
int fd = swrapSocket(SWRAP_TCP, SWRAP_BIND, 0/*|SWRAP_NODELAY*//*|SWRAP_NOBLOCK*/, interface_, port);
if( fd >= 0 ) swrapListen(fd, backlog);
return fd;
}
int tcp_peek(int fd, int(*callback)(int)) {
struct swrap_addr sa;
int fd2 = swrapAccept(fd, &sa);
if( fd2 >= 0 ) return callback(fd2);
return -1;
}
int tcp_send(int fd, const void *buf, int len) {
int rc = swrapSend(fd, (const char *)buf, len);
#if TCP_DEBUG
if( set_find(tcp_set, fd) ) {
printf("send -> %11d (status: %d) %s:%s\n", len, rc, tcp_host(fd), tcp_port(fd));
if( rc > 0 ) hexdump(buf, rc);
}
#endif
return rc;
}
int tcp_recv(int fd, void *buf, int len) {
int rc = swrapReceive(fd, (char*)buf, len);
#if TCP_DEBUG
if( rc != 0 && set_find(tcp_set, fd) ) {
printf("recv <- %11d (status: %d) %s:%s\n", len, rc, tcp_host(fd), tcp_port(fd));
if( rc > 0 ) hexdump(buf, rc);
}
#endif
return rc;
}
char* tcp_host(int fd) {
char buf[1024];
struct swrap_addr sa;
swrapAddress(fd, &sa);
swrapAddressInfo(&sa, buf, 512, buf+512, 512);
return va("%s", buf);
}
char* tcp_port(int fd) {
char buf[1024];
struct swrap_addr sa;
swrapAddress(fd, &sa);
swrapAddressInfo(&sa, buf, 512, buf+512, 512);
return va("%s", buf+512);
}
int tcp_close(int fd) {
swrapClose(fd);
return 0;
}
int tcp_debug(int fd) {
#if TCP_DEBUG
if( set_find(tcp_set, fd) ) {
set_erase(tcp_set, fd);
return 0;
} else {
set_insert(tcp_set, fd);
return 1;
}
#else
return 0;
#endif
}
// -----------------------------------------------------------------------------
static void network_init() {
do_once {
udp_init();
tcp_init();
}
}
#line 0
#line 1 "v4k_track.c"
static __thread int track__sock = -1;
//~ Lifecycle methods
int track_init(char const *host, char const *port) {
if (track__sock > -1) {
swrapClose(track__sock);
track__sock = -1;
}
track__sock = swrapSocket(SWRAP_UDP, SWRAP_CONNECT, SWRAP_DEFAULT, host, port);
if (track__sock == -1) {
return -TRACK_ERROR_SOCKET_FAIL;
}
return 0;
}
int track_destroy(void) {
if (track__sock > -1) swrapClose(track__sock);
track__sock = -1;
return 0;
}
//~ Buffer utilities
static __thread char track__buffer[TRACK_SEND_BUFSIZE+1];
static __thread int track__buffer_len = 0;
static __thread int track__errno = 0;
static void track__buffer_flush(void) {
track__buffer_len = 0;
}
static int track__buffer_appendc(char *buf, int *len, char const *str) {
int size = (int)strlen(str);
if (*len+size > TRACK_SEND_BUFSIZE)
return -TRACK_ERROR_BUFFER_FULL;
memcpy(buf+*len, str, size);
*len += size;
return 0;
}
#define TRACK__APPEND_SAFE_EX(buf, len, xx)\
track__errno = track__buffer_appendc(buf, len, xx);\
if (track__errno) return track__errno;
#define TRACK__APPEND_SAFE(xx)\
TRACK__APPEND_SAFE_EX(track__buffer, &track__buffer_len, xx);
//~ Event tracking
int track_event(char const *event_id, char const *user_id, char const *json_payload) {
if (track__sock == -1)
return -TRACK_ERROR_SOCKET_INVALID;
if (!event_id || !user_id || !json_payload)
return -TRACK_ERROR_INPUT_INVALID;
track__buffer_flush();
TRACK__APPEND_SAFE("{\"userId\":\"");
TRACK__APPEND_SAFE(user_id);
TRACK__APPEND_SAFE("\",\"event\":\"");
TRACK__APPEND_SAFE(event_id);
TRACK__APPEND_SAFE("\",\"properties\":");
TRACK__APPEND_SAFE(json_payload);
TRACK__APPEND_SAFE("}");
if (!swrapSend(track__sock, track__buffer, track__buffer_len))
return -TRACK_ERROR_SEND_FAIL;
return 0;
}
int track_ident(char const *user_id, char const *traits) {
if (track__sock == -1)
return -TRACK_ERROR_SOCKET_INVALID;
if (!user_id || !traits)
return -TRACK_ERROR_INPUT_INVALID;
track__buffer_flush();
TRACK__APPEND_SAFE("{\"userId\":\"");
TRACK__APPEND_SAFE(user_id);
TRACK__APPEND_SAFE("\",\"traits\":");
TRACK__APPEND_SAFE(traits);
TRACK__APPEND_SAFE("}");
if (!swrapSend(track__sock, track__buffer, track__buffer_len))
return -TRACK_ERROR_SEND_FAIL;
return 0;
}
int track_group(char const *user_id, char const *group_id, char const *traits) {
if (track__sock == -1)
return -TRACK_ERROR_SOCKET_INVALID;
if (!user_id || !group_id || !traits)
return -TRACK_ERROR_INPUT_INVALID;
track__buffer_flush();
TRACK__APPEND_SAFE("{\"userId\":\"");
TRACK__APPEND_SAFE(user_id);
TRACK__APPEND_SAFE("\",\"groupId\":\"");
TRACK__APPEND_SAFE(group_id);
TRACK__APPEND_SAFE("\",\"traits\":");
TRACK__APPEND_SAFE(traits);
TRACK__APPEND_SAFE("}");
if (!swrapSend(track__sock, track__buffer, track__buffer_len))
return -TRACK_ERROR_SEND_FAIL;
return 0;
}
int track_event_props(char const *event_id, char const *user_id, const track_prop *props) {
static char buf[TRACK_SEND_BUFSIZE+1] = {0};
int len = 0;
if (!props)
return track_event(event_id, user_id, "");
TRACK__APPEND_SAFE_EX(buf, &len, "{");
while (props->key) {
TRACK__APPEND_SAFE_EX(buf, &len, "\"");
TRACK__APPEND_SAFE_EX(buf, &len, props->key);
TRACK__APPEND_SAFE_EX(buf, &len, "\":");
TRACK__APPEND_SAFE_EX(buf, &len, props->val);
++props;
if (props->key) {
TRACK__APPEND_SAFE_EX(buf, &len, ",");
}
}
TRACK__APPEND_SAFE_EX(buf, &len, "}");
return track_event(event_id, user_id, buf);
}
#undef TRACK__APPEND_SAFE
#undef TRACK__APPEND_SAFE_EX
#line 0
#line 1 "v4k_netsync.c"
typedef void* (*rpc_function)();
typedef struct rpc_call {
char *method;
rpc_function function;
uint64_t function_hash;
} rpc_call;
#define RPC_SIGNATURE_i_iii UINT64_C(0x78409099752fa48a)
#define RPC_SIGNATURE_i_ii UINT64_C(0x258290edf43985a5)
#define RPC_SIGNATURE_i_s UINT64_C(0xf7b73162829ed667)
#define RPC_SIGNATURE_s_s UINT64_C(0x97deedd17d9afb12)
#define RPC_SIGNATURE_s_v UINT64_C(0x09c16a1242049b80)
#define RPC_SIGNATURE_v_v UINT64_C(0xc210c270b6f06552)
#define RPC_SIGNATURE_v_s UINT64_C(0xc1746990ab73ed24)
static
rpc_call rpc_new_call(const char *signature, rpc_function function) {
if( signature && function ) {
array(char*)tokens = strsplit(signature, "(,)");
if( array_count(tokens) >= 1 ) {
char *method = strrchr(tokens[0], ' ')+1;
char *rettype = va("%.*s", (int)(method - tokens[0] - 1), tokens[0]);
int num_args = array_count(tokens) - 1;
char* hash_sig = va("%s(%s)", rettype, num_args ? (array_pop_front(tokens), strjoin(tokens, ",")) : "void");
uint64_t hash = hash_str(hash_sig);
method = va("%s%d", method, num_args );
#if RPC_DEBUG
printf("%p %p %s `%s` %s(", function, (void*)hash, rettype, hash_sig, method); for(int i = 0, end = array_count(tokens); i < end; ++i) printf("%s%s", tokens[i], i == (end-1)? "":", "); puts(");");
#endif
return (rpc_call) { STRDUP(method), function, hash }; // LEAK
}
}
return (rpc_call) {0};
}
static map(char*, rpc_call) rpc_calls = 0;
static
void rpc_insert(const char *signature, void *function ) {
rpc_call call = rpc_new_call(signature, function);
if( call.method ) {
if( !rpc_calls ) map_init(rpc_calls, less_str, hash_str);
if( map_find(rpc_calls, call.method)) {
map_erase(rpc_calls, call.method);
}
map_insert(rpc_calls, call.method, call);
}
}
static
char *rpc_full(unsigned id, const char* method, unsigned num_args, char *args[]) {
#if RPC_DEBUG
printf("id:%x method:%s args:", id, method );
for( int i = 0; i < num_args; ++i ) printf("%s,", args[i]); puts("");
#endif
method = va("%s%d", method, num_args);
rpc_call *found = map_find(rpc_calls, (char*)method);
if( found ) {
switch(found->function_hash) {
case RPC_SIGNATURE_i_iii: return va("%d %d", id, (int)(intptr_t)found->function(atoi(args[0]), atoi(args[1]), atoi(args[2])) );
case RPC_SIGNATURE_i_ii: return va("%d %d", id, (int)(intptr_t)found->function(atoi(args[0]), atoi(args[1])) );
case RPC_SIGNATURE_i_s: return va("%d %d", id, (int)(intptr_t)found->function(args[0]) );
case RPC_SIGNATURE_s_s: return va("%d %s", id, (char*)found->function(args[0]) );
case RPC_SIGNATURE_s_v: return va("%d %s", id, (char*)found->function() );
case RPC_SIGNATURE_v_v: return found->function(), va("%d", id);
case RPC_SIGNATURE_v_s: return found->function(args[0]), va("%d", id);
default: break;
}
}
return va("%d -1", id);
}
static
array(char*) rpc_parse_args( const char *cmdline, bool quote_whitespaces ) { // parse cmdline arguments. must array_free() after use
// - supports quotes: "abc" "abc def" "abc \"def\"" "abc \"def\"""ghi" etc.
// - #comments removed
array(char*) args = 0; // LEAK
for( int i = 0; cmdline[i]; ) {
char buf[256] = {0}, *ptr = buf;
while(cmdline[i] && isspace(cmdline[i])) ++i;
bool quoted = cmdline[i] == '\"';
if( quoted ) {
while(cmdline[++i]) {
char ch = cmdline[i];
/**/ if (ch == '\\' && cmdline[i + 1] == '\"') *ptr++ = '\"', ++i;
else if (ch == '\"' && cmdline[i + 1] == '\"') ++i;
else if (ch == '\"' && (!cmdline[i + 1] || isspace(cmdline[i + 1]))) {
++i; break;
}
else *ptr++ = ch;
}
} else {
while(cmdline[i] && !isspace(cmdline[i])) *ptr++ = cmdline[i++];
}
if (buf[0] && buf[0] != '#') { // exclude empty args + comments
if( quote_whitespaces && quoted )
array_push(args, va("\"%s\"",buf));
else
array_push(args, va("%s",buf));
}
}
return args;
}
static
char* rpc(unsigned id, const char* cmdline) {
array(char*) args = rpc_parse_args(cmdline, false);
int num_args = array_count(args);
char *ret = num_args ? rpc_full(id, args[0], num_args - 1, &args[1]) : rpc_full(id, "", 0, NULL);
array_free(args);
return ret;
}
static void enet_quit(void) {
do_once {
// enet_deinitialize();
}
}
static void enet_init() {
do_once {
if( enet_initialize() != 0 ) {
PANIC("cannot initialize enet");
}
atexit( enet_quit );
}
}
struct peer_node_t {
int64_t id;
struct peer_node_t *next;
};
static ENetHost *Server;
static map(ENetPeer *, int64_t) clients;
static map(int64_t, ENetPeer *) peers;
static int64_t next_client_id = 1; // assumes ID 0 is server
static struct peer_node_t *next_free_id = NULL;
enum { MSG_INIT, MSG_BUF, MSG_RPC, MSG_RPC_RESP };
bool server_bind(int max_clients, int port) {
map_init(clients, less_64, hash_64);
map_init(peers, less_64, hash_64);
assert(port == 0 || (port > 1024 && port < 65500));
ENetAddress address = {0};
address.host = ENET_HOST_ANY;
address.port = port;
Server = enet_host_create(&address, max_clients, 2 /*channels*/, 0 /*in bandwidth*/, 0 /*out bandwidth*/);
return Server != NULL;
}
static
void server_drop_client(int64_t handle) {
map_erase(clients, *(ENetPeer **)map_find(peers, handle));
map_erase(peers, *(int64_t *)handle);
}
static
void server_drop_client_peer(ENetPeer *peer) {
struct peer_node_t *node = C_CAST(struct peer_node_t *, CALLOC(sizeof(struct peer_node_t), 1));
node->id = *(int64_t *)map_find(clients, peer);
if (!next_free_id) {
next_free_id = node;
} else {
node->next = next_free_id;
next_free_id = node;
}
map_erase(peers, *(int64_t *)map_find(clients, peer));
map_erase(clients, peer);
}
void server_broadcast_bin_flags(const void *msg, int len, uint64_t flags) {
ENetPacket *packet = enet_packet_create(msg, len, flags&NETWORK_UNRELIABLE ? ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT : ENET_PACKET_FLAG_RELIABLE | flags&(NETWORK_UNRELIABLE|NETWORK_UNORDERED) ? ENET_PACKET_FLAG_UNSEQUENCED : 0);
enet_host_broadcast(Server, 0, packet);
}
void server_broadcast_bin(const void *msg, int len) {
ENetPacket *packet = enet_packet_create(msg, len, ENET_PACKET_FLAG_RELIABLE);
enet_host_broadcast(Server, 0, packet);
//enet_host_flush(Server); // flush if needed
}
void server_broadcast_flags(const char *msg, uint64_t flags) {
server_broadcast_bin_flags(msg, strlen(msg)+1, flags);
}
void server_broadcast(const char *msg) {
server_broadcast_bin(msg, strlen(msg)+1);
}
void server_terminate() {
enet_host_destroy(Server);
Server = 0;
}
volatile int client_join_connected = 0;
static int client_join_threaded(void *userdata) {
ENetHost *host = (ENetHost *)userdata;
ENetPacket *packet = enet_packet_create("", 1, ENET_PACKET_FLAG_RELIABLE);
enet_host_broadcast(Server, 0, packet);
/* Wait up to 5 seconds for the connection attempt to succeed. */
ENetEvent event;
client_join_connected = 0;
client_join_connected = enet_host_service(host, &event, 5000) > 0 && event.type == ENET_EVENT_TYPE_CONNECT;
return 0;
}
int64_t client_join(const char *ip, int port) {
assert(port > 1024 && port < 65500);
ENetAddress address = {0};
enet_address_set_host(&address, !strcmp(ip, "localhost") ? "127.0.0.1" : ip);
address.port = port;
ENetHost *host = enet_host_create(NULL, 1 /*outgoing connections*/, 2 /*channels*/, 0 /*in bandwidth*/, 0 /*out bandwidth*/);
if(!host) return -1;
ENetPeer *peer = enet_host_connect(host, &address, 2, 0);
if(!peer) return -1;
Server = host;
ENetEvent event;
bool client_join_connected = enet_host_service(host, &event, 5000) > 0 && event.type == ENET_EVENT_TYPE_CONNECT;
if(!client_join_connected) { enet_peer_reset(peer); return -1; }
// wait for the response
bool msg_received = enet_host_service(host, &event, 5000) > 0 && event.type == ENET_EVENT_TYPE_RECEIVE;
if (!msg_received) { enet_peer_reset(peer); return -1; }
char *ptr = (char *)event.packet->data;
int64_t cid = -1;
// decapsulate incoming packet.
uint32_t mid = *(uint32_t*)(ptr + 0);
ptr += 4;
switch (mid) {
case MSG_INIT:
cid = *(int64_t*)ptr;
break;
default:
enet_peer_reset(peer);
enet_packet_destroy( event.packet );
return -1;
}
/* Clean up the packet now that we're done using it. */
enet_packet_destroy( event.packet );
return cid;
}
void server_drop(int64_t handle) {
enet_peer_disconnect_now(*(ENetPeer **)map_find(peers, handle), 0);
server_drop_client(handle);
}
void server_send_bin(int64_t handle, const void *ptr, int len) {
ENetPacket *packet = enet_packet_create(ptr, len, ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(*(ENetPeer **)map_find(peers, handle), 0, packet);
}
void server_send(int64_t handle, const char *msg) {
server_send_bin(handle, msg, strlen(msg)+1);
}
// ---
typedef struct netbuffer_t {
int64_t owner;
void *ptr;
unsigned sz;
uint64_t flags;
} netbuffer_t;
static array(char*) events; // @todo: make event 128 bytes max?
static array(int64_t) values; // @todo: map<key,values> instead?
static map( int64_t, array(netbuffer_t) ) buffers; // map<client,array<netbuffer>>
static double msg_send_cooldown = 0.0;
static double network_dt = 0.0;
static double last_netsync = 0.0;
void network_create(unsigned max_clients, const char *ip, const char *port_, unsigned flags) {
if (buffers) map_clear(buffers);
do_once {
array_resize(values, 128);
map_init(buffers, less_64, hash_64);
enet_init();
}
ip = ip ? ip : "0.0.0.0";
int port = atoi(port_ ? port_ : "1234");
// network_put(NETWORK_IP, 0x7F000001); // 127.0.0.1
network_put(NETWORK_PORT, port);
network_put(NETWORK_LIVE, -1);
network_put(NETWORK_COUNT, 0);
network_put(NETWORK_CAPACITY, max_clients);
network_put(NETWORK_BUF_CLEAR_ON_JOIN, 1);
if( !(flags&NETWORK_CONNECT) || flags&NETWORK_BIND ) {
// server, else client
PRINTF("Trying to bind server, else we connect as a client...\n");
network_put(NETWORK_RANK, 0);
if( server_bind(max_clients, port) ) {
network_put(NETWORK_LIVE, 1);
PRINTF("Server bound\n");
} else {
network_put(NETWORK_RANK, -1); /* unassigned until we connect successfully */
int64_t socket = client_join(ip, port);
if( socket >= 0 ) {
PRINTF("Client connected, id %d\n", (int)socket);
network_put(NETWORK_LIVE, 1);
network_put(NETWORK_RANK, socket);
} else {
PRINTF("!Client conn failed\n");
network_put(NETWORK_LIVE, 0);
if (!(flags&NETWORK_NOFAIL))
PANIC("cannot neither connect to %s:%d, nor create a server", ip, port);
}
}
} else {
// client only
PRINTF("Connecting to server...\n");
network_put(NETWORK_RANK, -1); /* unassigned until we connect successfully */
int64_t socket = client_join(ip, port);
if( socket > 0 ) {
PRINTF("Client connected, id %d\n", (int)socket);
network_put(NETWORK_LIVE, 1);
network_put(NETWORK_RANK, socket);
} else {
PRINTF("!Client conn failed\n");
network_put(NETWORK_LIVE, 0);
if (!(flags&NETWORK_NOFAIL))
PANIC("cannot connect to server %s:%d", ip, port);
}
}
PRINTF("Network rank:%u ip:%s port:%d\n", (unsigned)network_get(NETWORK_RANK), ip, (int)network_get(NETWORK_PORT));
}
int64_t network_put(uint64_t key, int64_t value) {
int64_t *found = key < array_count(values) ? &values[key] : NULL;
if(found) *found = value;
return value;
}
int64_t network_get(uint64_t key) {
int64_t *found = key < array_count(values) ? &values[key] : NULL;
return found ? *found : 0;
}
void* network_buffer(void *ptr, unsigned sz, uint64_t flags, int64_t rank) {
assert(flags);
array(netbuffer_t) *found = map_find_or_add(buffers, rank, NULL);
netbuffer_t nb;
nb.owner = rank;
nb.ptr = ptr;
nb.sz = sz;
nb.flags = flags;
array_push(*found, nb);
return ptr;
}
static
int enet_event_to_netsync(int ev) {
switch (ev) {
case ENET_EVENT_TYPE_CONNECT: return NETWORK_EVENT_CONNECT;
case ENET_EVENT_TYPE_DISCONNECT: return NETWORK_EVENT_DISCONNECT;
case ENET_EVENT_TYPE_RECEIVE: return NETWORK_EVENT_RECEIVE;
case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: return NETWORK_EVENT_DISCONNECT_TIMEOUT;
}
/* passthrough for our own events */
return ev;
}
char** network_sync(unsigned timeout_ms) {
int64_t whoami = network_get(NETWORK_RANK);
bool is_server = whoami == 0;
bool is_client = !is_server;
if(timeout_ms < 2) timeout_ms = 2;
network_dt = time_ss() - last_netsync;
last_netsync = time_ss();
// Split buffers into clients @todo
// clients need to do this before network polling; servers should do this after polling.
if (msg_send_cooldown <= 0.0) {
map_foreach(buffers, int64_t, rank, array(netbuffer_t), list) {
for(int i = 0, end = array_count(list); i < end; ++i) {
netbuffer_t *nb = &list[i];
if (!is_server && !(nb->flags & NETWORK_SEND))
continue;
static array(char) encapsulate;
array_resize(encapsulate, nb->sz + 28);
uint32_t *mid = (uint32_t*)&encapsulate[0]; *mid = MSG_BUF;
uint64_t *st = (uint64_t*)&encapsulate[4]; *st = nb->flags;
uint32_t *idx = (uint32_t*)&encapsulate[12]; *idx = i;
uint32_t *len = (uint32_t*)&encapsulate[16]; *len = nb->sz;
uint64_t *who = (uint64_t*)&encapsulate[20]; *who = nb->owner;
// PRINTF("sending %llx %u %lld %u\n", *st, *idx, *who, *len);
memcpy(&encapsulate[28], nb->ptr, nb->sz);
server_broadcast_bin(&encapsulate[0], nb->sz + 28);
}
}
msg_send_cooldown = (double)network_get(NETWORK_SEND_MS)/1000.0;
} else {
msg_send_cooldown -= network_dt;
}
if (is_server) {
return server_poll(timeout_ms);
} else {
return client_poll(timeout_ms);
}
}
char** server_poll(unsigned timeout_ms) {
array_clear(events);
if(timeout_ms < 2) timeout_ms = 2;
// network poll
for( ENetEvent event; Server && enet_host_service(Server, &event, timeout_ms) > 0; ) {
char *msg = 0;
char ip[128]; enet_peer_get_ip(event.peer, ip, 128);
switch (event.type) {
default: // case ENET_EVENT_TYPE_NONE:
break;
case ENET_EVENT_TYPE_CONNECT:;
/* ensure we have free slot for client */
if (map_count(clients) >= network_get(NETWORK_CAPACITY)-1) {
msg = va("%d Server is at maximum capacity, disconnecting the peer (::%s:%u)...", 1, ip, event.peer->address.port);
enet_peer_disconnect_now(event.peer, 1);
break;
}
int64_t client_id = -1;
if (next_free_id) {
struct peer_node_t *node = next_free_id;
client_id = next_free_id->id;
next_free_id = next_free_id->next;
FREE(node);
}
else client_id = next_client_id++;
// if (network_get(NETWORK_BUF_CLEAR_ON_JOIN)) {
// array(netbuffer_t) *list = map_find(buffers, client_id);
// if (list)
// for(int i = 0, end = array_count(list); i < end; ++i) {
// netbuffer_t *nb = &list[i];
// memset(nb->ptr, 0, nb->sz);
// }
// }
map_find_or_add(clients, event.peer, client_id);
map_find_or_add(peers, client_id, event.peer);
network_put(NETWORK_COUNT, network_get(NETWORK_COUNT)+1);
// send server slot
char init_msg[12];
*(uint32_t*)&init_msg[0] = MSG_INIT;
*(int64_t*)&init_msg[4] = client_id;
server_send_bin(client_id, init_msg, 12);
PRINTF("Client rank %u for peer ::%s:%u\n", (unsigned)client_id, ip, event.peer->address.port);
msg = va( "%d new client rank:%u from ::%s:%u", 0, (unsigned)client_id, ip, event.peer->address.port );
event.peer->data = (void*)client_id;
break;
case ENET_EVENT_TYPE_RECEIVE: {
char *dbg = (char *)event.peer->data;
char *ptr = (char *)event.packet->data;
unsigned sz = (unsigned)event.packet->dataLength;
unsigned id = (unsigned)event.channelID;
// debug
// puts(dbg);
// hexdump(ptr, sz);
// decapsulate incoming packet.
uint32_t mid = *(uint32_t*)(ptr + 0);
ptr += 4;
switch (mid) {
case MSG_BUF: {
uint64_t *flags = (uint64_t*)(ptr + 0);
uint32_t *idx = (uint32_t*)(ptr + 8);
uint32_t *len = (uint32_t*)(ptr + 12);
uint64_t *who = (uint64_t*)(ptr + 16);
ptr += 24;
// validate if peer owns the buffer
int64_t *cid = map_find(clients, event.peer);
uint8_t client_valid = cid ? *cid == *who : 0;
// apply incoming packet.
if( client_valid ) {
array(netbuffer_t) *list = map_find(buffers, *who);
assert( list );
assert( *idx < array_count(*list) );
netbuffer_t *nb = &(*list)[*idx];
assert( *len == nb->sz );
memcpy(nb->ptr, ptr, *len);
}
} break;
case MSG_RPC: {
event.type = NETWORK_EVENT_RPC;
unsigned id = *(uint32_t*)ptr; ptr += 4;
char *cmdline = ptr;
char *resp = rpc(id, cmdline);
char *resp_msg = va("%*.s%s", 4, "", resp);
*(uint32_t*)&resp_msg[0] = MSG_RPC_RESP;
ENetPacket *packet = enet_packet_create(resp_msg, strlen(resp) + 5, ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(event.peer, 0, packet);
msg = va("%d req:%s res:%s", 0, cmdline, resp);
} break;
case MSG_RPC_RESP: {
event.type = NETWORK_EVENT_RPC_RESP;
msg = va("%d %s", 0, va("%s", ptr));
} break;
default:
msg = va("%d unk msg len:%u from rank:%u ::%s:%u", -1, sz, (unsigned)(uintptr_t)event.peer->data, ip, event.peer->address.port); /* @TODO: hexdump? */
break;
}
/* Clean up the packet now that we're done using it. */
enet_packet_destroy( event.packet );
} break;
case ENET_EVENT_TYPE_DISCONNECT:
msg = va( "%d disconnect rank:%u", 0, (unsigned)(uintptr_t)event.peer->data);
/* Reset the peer's client information. */
FREE(event.peer->data);
event.peer->data = NULL;
server_drop_client_peer(event.peer);
network_put(NETWORK_COUNT, network_get(NETWORK_COUNT)-1);
break;
case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
msg = va( "%d timeout rank:%u", 0, (unsigned)(uintptr_t)event.peer->data);
FREE(event.peer->data);
event.peer->data = NULL;
server_drop_client_peer(event.peer);
network_put(NETWORK_COUNT, network_get(NETWORK_COUNT)-1);
break;
}
if(msg) array_push(events, va("%d %s", enet_event_to_netsync(event.type), msg));
}
array_push(events, NULL);
return events;
}
char** client_poll(unsigned timeout_ms) {
array_clear(events);
int64_t whoami = network_get(NETWORK_RANK);
if(timeout_ms < 2) timeout_ms = 2;
// network poll
for( ENetEvent event; Server && enet_host_service(Server, &event, timeout_ms) > 0; ) {
char *msg = 0;
char ip[128]; enet_peer_get_ip(event.peer, ip, 128);
switch (event.type) {
default: // case ENET_EVENT_TYPE_NONE:
break;
case ENET_EVENT_TYPE_CONNECT:
break;
case ENET_EVENT_TYPE_RECEIVE: {
char *dbg = (char *)event.peer->data;
char *ptr = (char *)event.packet->data;
unsigned sz = (unsigned)event.packet->dataLength;
unsigned id = (unsigned)event.channelID;
// decapsulate incoming packet.
uint32_t mid = *(uint32_t*)(ptr + 0);
ptr += 4;
switch (mid) {
case MSG_INIT:
/* handled by client_join */
break;
case MSG_BUF: {
uint64_t *flags = (uint64_t*)(ptr + 0);
uint32_t *idx = (uint32_t*)(ptr + 8);
uint32_t *len = (uint32_t*)(ptr + 12);
uint64_t *who = (uint64_t*)(ptr + 16);
ptr += 24;
// apply incoming packet.
if( *who != whoami ) {
array(netbuffer_t) *list = map_find(buffers, *who);
assert( list );
assert( *idx < array_count(*list) );
netbuffer_t *nb = &(*list)[*idx];
assert( *len == nb->sz );
memcpy(nb->ptr, ptr, *len);
}
} break;
case MSG_RPC: {
event.type = NETWORK_EVENT_RPC;
unsigned id = *(uint32_t*)ptr; ptr += 4;
char *cmdline = ptr;
char *resp = rpc(id, cmdline);
char *resp_msg = va("%*.s%s", 4, "", resp);
*(uint32_t*)&resp_msg[0] = MSG_RPC_RESP;
ENetPacket *packet = enet_packet_create(resp_msg, strlen(resp) + 5, ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(event.peer, 0, packet);
msg = va("%d req:%s res:%s", 0, cmdline, resp);
} break;
case MSG_RPC_RESP: {
event.type = NETWORK_EVENT_RPC_RESP;
msg = va("%d %s", 0, ptr);
} break;
default:
msg = va("%d unk msg len:%u from server", -1, sz); /* @TODO: hexdump? */
break;
}
/* Clean up the packet now that we're done using it. */
enet_packet_destroy( event.packet );
} break;
case ENET_EVENT_TYPE_DISCONNECT:
msg = va( "%d disconnect", 0 );
/* Reset the peer's client information. */
FREE(event.peer->data);
event.peer->data = NULL;
network_put(NETWORK_RANK, -1);
network_put(NETWORK_LIVE, 0);
break;
case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
msg = va( "%d timeout", 0);
FREE(event.peer->data);
event.peer->data = NULL;
network_put(NETWORK_RANK, -1);
network_put(NETWORK_LIVE, 0);
break;
}
if(msg) array_push(events, va("%d %s", enet_event_to_netsync(event.type), msg));
}
array_push(events, NULL);
return events;
}
int network_event(const char *msg, int *errcode, char **errstr) {
int evid = -1;
int err = 0;
char errbuf[128] = {0};
sscanf(msg, "%d %d %127[^\r\n]", &evid, &err, errbuf);
if (errcode) *errcode = err;
if (errstr) *errstr = va("%s", errbuf);
return evid;
}
void network_rpc(const char *signature, void *function) {
rpc_insert(signature, function);
}
void network_rpc_send_to(int64_t rank, unsigned id, const char *cmdline) {
assert(network_get(NETWORK_RANK) == 0); /* must be a host */
char *msg = va("%*.s%s", 8, "", cmdline);
unsigned sz = strlen(cmdline) + 9;
*(uint32_t*)&msg[0] = MSG_RPC;
*(uint32_t*)&msg[4] = id;
server_send_bin(rank, msg, sz);
}
void network_rpc_send(unsigned id, const char *cmdline) {
char *msg = va("%*.s%s", 8, "", cmdline);
unsigned sz = strlen(cmdline) + 9;
*(uint32_t*)&msg[0] = MSG_RPC;
*(uint32_t*)&msg[4] = id;
server_broadcast_bin(msg, sz);
}
#line 0
#line 1 "v4k_pack.c"
// -----------------------------------------------------------------------------
// semantic versioning in a single byte (octal)
// - rlyeh, public domain.
//
// - single octal byte that represents semantic versioning (major.minor.patch).
// - allowed range [0000..0377] ( <-> [0..255] decimal )
// - comparison checks only major.minor tuple as per convention.
int semver( int major, int minor, int patch ) {
return SEMVER(major, minor, patch);
}
int semvercmp( int v1, int v2 ) {
return SEMVERCMP(v1, v2);
}
#if 0
AUTORUN {
for( int i= 0; i <= 255; ++i) printf(SEMVERFMT ",", i);
puts("");
printf(SEMVERFMT "\n", semver(3,7,7));
printf(SEMVERFMT "\n", semver(2,7,7));
printf(SEMVERFMT "\n", semver(1,7,7));
printf(SEMVERFMT "\n", semver(0,7,7));
printf(SEMVERFMT "\n", semver(3,7,1));
printf(SEMVERFMT "\n", semver(2,5,3));
printf(SEMVERFMT "\n", semver(1,3,5));
printf(SEMVERFMT "\n", semver(0,1,7));
assert( semvercmp( 0357, 0300 ) > 0 );
assert( semvercmp( 0277, 0300 ) < 0 );
assert( semvercmp( 0277, 0200 ) > 0 );
assert( semvercmp( 0277, 0100 ) < 0 );
assert( semvercmp( 0076, 0070 ) == 0 );
assert( semvercmp( 0076, 0077 ) == 0 );
assert( semvercmp( 0176, 0170 ) == 0 );
assert( semvercmp( 0176, 0177 ) == 0 );
assert( semvercmp( 0276, 0270 ) == 0 );
assert( semvercmp( 0276, 0277 ) == 0 );
assert( semvercmp( 0376, 0370 ) == 0 );
assert( semvercmp( 0376, 0377 ) == 0 );
}
#endif
// -----------------------------------------------------------------------------
// compile-time fourcc, eightcc
char *cc4str(unsigned x) {
static __thread char type[4+1] = {0};
type[3] = (x >> 24ULL) & 255;
type[2] = (x >> 16ULL) & 255;
type[1] = (x >> 8ULL) & 255;
type[0] = (x >> 0ULL) & 255;
return type;
}
char *cc8str(uint64_t x) {
static __thread char type[8+1] = {0};
type[7] = (x >> 56ULL) & 255;
type[6] = (x >> 48ULL) & 255;
type[5] = (x >> 40ULL) & 255;
type[4] = (x >> 32ULL) & 255;
type[3] = (x >> 24ULL) & 255;
type[2] = (x >> 16ULL) & 255;
type[1] = (x >> 8ULL) & 255;
type[0] = (x >> 0ULL) & 255;
return type;
}
// ----------------------------------------------------------------------------
// float conversion (text)
char* itoa1(int v) {
return va("%d", v);
}
char* itoa2(vec2i v) {
return va("%d,%d", v.x,v.y);
}
char* itoa3(vec3i v) {
return va("%d,%d,%d", v.x,v.y,v.z);
}
char* ftoa1(float v) {
return va("%f", v);
}
char* ftoa2(vec2 v) {
return va("%f,%f", v.x, v.y);
}
char* ftoa3(vec3 v) {
return va("%f,%f,%f", v.x, v.y, v.z);
}
char* ftoa4(vec4 v) {
return va("%f,%f,%f,%f", v.x, v.y, v.z, v.w);
}
float atof1(const char *s) {
char buf[64];
return sscanf(s, "%63[^]\r\n,}]", buf) == 1 ? (float)eval(buf) : (float)NAN;
}
vec2 atof2(const char *s) {
vec2 v = { 0 };
char buf1[64],buf2[64];
int num = sscanf(s, "%63[^]\r\n,}],%63[^]\r\n,}]", buf1, buf2);
if( num > 0 ) v.x = eval(buf1);
if( num > 1 ) v.y = eval(buf2);
return v;
}
vec3 atof3(const char *s) {
vec3 v = {0};
char buf1[64],buf2[64],buf3[64];
int num = sscanf(s, "%63[^]\r\n,}],%63[^]\r\n,}],%63[^]\r\n,}]", buf1, buf2, buf3);
if( num > 0 ) v.x = eval(buf1);
if( num > 1 ) v.y = eval(buf2);
if( num > 2 ) v.z = eval(buf3);
return v;
}
vec4 atof4(const char *s) {
vec4 v = {0};
char buf1[64],buf2[64],buf3[64],buf4[64];
int num = sscanf(s, "%63[^]\r\n,}],%63[^]\r\n,}],%63[^]\r\n,}],%63[^]\r\n,}]", buf1, buf2, buf3, buf4);
if( num > 0 ) v.x = eval(buf1);
if( num > 1 ) v.y = eval(buf2);
if( num > 2 ) v.z = eval(buf3);
if( num > 3 ) v.w = eval(buf4);
return v;
}
// @todo: expand this to proper int parsers
int atoi1(const char *s) {
return (int)atof1(s);
}
vec2i atoi2(const char *s) {
vec2 v = atof2(s);
return vec2i( v.x, v.y );
}
vec3i atoi3(const char *s) {
vec3 v = atof3(s);
return vec3i( v.x, v.y, v.z );
}
// endianness -----------------------------------------------------------------
// - rlyeh, public domain
int is_big() { return IS_BIG; }
int is_little() { return IS_LITTLE; }
uint16_t lil16(uint16_t n) { return IS_BIG ? swap16(n) : n; }
uint32_t lil32(uint32_t n) { return IS_BIG ? swap32(n) : n; }
uint64_t lil64(uint64_t n) { return IS_BIG ? swap64(n) : n; }
uint16_t big16(uint16_t n) { return IS_LITTLE ? swap16(n) : n; }
uint32_t big32(uint32_t n) { return IS_LITTLE ? swap32(n) : n; }
uint64_t big64(uint64_t n) { return IS_LITTLE ? swap64(n) : n; }
float lil32f(float n) { return IS_BIG ? swap32f(n) : n; }
double lil64f(double n) { return IS_BIG ? swap64f(n) : n; }
float big32f(float n) { return IS_LITTLE ? swap32f(n) : n; }
double big64f(double n) { return IS_LITTLE ? swap64f(n) : n; }
uint16_t* lil16p(void *p, int sz) { if(IS_BIG ) { uint16_t *n = (uint16_t *)p; for(int i = 0; i < sz; ++i) n[i] = swap16(n[i]); } return p; }
uint16_t* big16p(void *p, int sz) { if(IS_LITTLE ) { uint16_t *n = (uint16_t *)p; for(int i = 0; i < sz; ++i) n[i] = swap16(n[i]); } return p; }
uint32_t* lil32p(void *p, int sz) { if(IS_BIG ) { uint32_t *n = (uint32_t *)p; for(int i = 0; i < sz; ++i) n[i] = swap32(n[i]); } return p; }
uint32_t* big32p(void *p, int sz) { if(IS_LITTLE ) { uint32_t *n = (uint32_t *)p; for(int i = 0; i < sz; ++i) n[i] = swap32(n[i]); } return p; }
uint64_t* lil64p(void *p, int sz) { if(IS_BIG ) { uint64_t *n = (uint64_t *)p; for(int i = 0; i < sz; ++i) n[i] = swap64(n[i]); } return p; }
uint64_t* big64p(void *p, int sz) { if(IS_LITTLE ) { uint64_t *n = (uint64_t *)p; for(int i = 0; i < sz; ++i) n[i] = swap64(n[i]); } return p; }
float * lil32pf(void *p, int sz) { if(IS_BIG ) { float *n = (float *)p; for(int i = 0; i < sz; ++i) n[i] = swap32f(n[i]); } return p; }
float * big32pf(void *p, int sz) { if(IS_LITTLE ) { float *n = (float *)p; for(int i = 0; i < sz; ++i) n[i] = swap32f(n[i]); } return p; }
double * lil64pf(void *p, int sz) { if(IS_BIG ) { double *n = (double *)p; for(int i = 0; i < sz; ++i) n[i] = swap64f(n[i]); } return p; }
double * big64pf(void *p, int sz) { if(IS_LITTLE ) { double *n = (double *)p; for(int i = 0; i < sz; ++i) n[i] = swap64f(n[i]); } return p; }
#if !is(cl) && !is(gcc)
uint16_t (swap16)( uint16_t x ) { return (x << 8) | (x >> 8); }
uint32_t (swap32)( uint32_t x ) { x = ((x << 8) & 0xff00ff00) | ((x >> 8) & 0x00ff00ff); return (x << 16) | (x >> 16); }
uint64_t (swap64)( uint64_t x ) { x = ((x << 8) & 0xff00ff00ff00ff00ULL) | ((x >> 8) & 0x00ff00ff00ff00ffULL); x = ((x << 16) & 0xffff0000ffff0000ULL) | ((x >> 16) & 0x0000ffff0000ffffULL); return (x << 32) | (x >> 32); }
#endif
float swap32f(float n) { union { float t; uint32_t i; } conv; conv.t = n; conv.i = swap32(conv.i); return conv.t; }
double swap64f(double n) { union { double t; uint64_t i; } conv; conv.t = n; conv.i = swap64(conv.i); return conv.t; }
void swapf(float *a, float *b) {
float t = *a; *a = *b; *b = *a;
}
void swapf2(vec2 *a, vec2 *b) {
float x = a->x; a->x = b->x; b->x = a->x;
float y = a->y; a->y = b->y; b->y = a->y;
}
void swapf3(vec3 *a, vec3 *b) {
float x = a->x; a->x = b->x; b->x = a->x;
float y = a->y; a->y = b->y; b->y = a->y;
float z = a->z; a->z = b->z; b->z = a->z;
}
void swapf4(vec4 *a, vec4 *b) {
float x = a->x; a->x = b->x; b->x = a->x;
float y = a->y; a->y = b->y; b->y = a->y;
float z = a->z; a->z = b->z; b->z = a->z;
float w = a->w; a->w = b->w; b->w = a->w;
}
// half packing -----------------------------------------------------------------
// from GingerBill's gbmath.h (public domain)
float half_to_float(half value) {
union { unsigned int i; float f; } result;
int s = (value >> 15) & 0x001;
int e = (value >> 10) & 0x01f;
int m = value & 0x3ff;
if (e == 0) {
if (m == 0) {
/* Plus or minus zero */
result.i = (unsigned int)(s << 31);
return result.f;
} else {
/* Denormalized number */
while (!(m & 0x00000400)) {
m <<= 1;
e -= 1;
}
e += 1;
m &= ~0x00000400;
}
} else if (e == 31) {
if (m == 0) {
/* Positive or negative infinity */
result.i = (unsigned int)((s << 31) | 0x7f800000);
return result.f;
} else {
/* Nan */
result.i = (unsigned int)((s << 31) | 0x7f800000 | (m << 13));
return result.f;
}
}
e = e + (127 - 15);
m = m << 13;
result.i = (unsigned int)((s << 31) | (e << 23) | m);
return result.f;
}
half float_to_half(float value) {
union { unsigned int i; float f; } v;
int i, s, e, m;
v.f = value;
i = (int)v.i;
s = (i >> 16) & 0x00008000;
e = ((i >> 23) & 0x000000ff) - (127 - 15);
m = i & 0x007fffff;
if (e <= 0) {
if (e < -10) return (half)s;
m = (m | 0x00800000) >> (1 - e);
if (m & 0x00001000)
m += 0x00002000;
return (half)(s | (m >> 13));
} else if (e == 0xff - (127 - 15)) {
if (m == 0) {
return (half)(s | 0x7c00); /* NOTE(bill): infinity */
} else {
/* NOTE(bill): NAN */
m >>= 13;
return (half)(s | 0x7c00 | m | (m == 0));
}
} else {
if (m & 0x00001000) {
m += 0x00002000;
if (m & 0x00800000) {
m = 0;
e += 1;
}
}
if (e > 30) {
float volatile f = 1e12f;
int j;
for (j = 0; j < 10; j++)
f *= f; /* NOTE(bill): Cause overflow */
return (half)(s | 0x7c00);
}
return (half)(s | (e << 10) | (m >> 13));
}
}
// int packing -----------------------------------------------------------------
// - rlyeh, public domain
// pack16i() -- store a 16-bit int into a char buffer (like htons())
// pack32i() -- store a 32-bit int into a char buffer (like htonl())
// pack64i() -- store a 64-bit int into a char buffer (like htonl())
void pack16i(uint8_t *buf, uint16_t i, int swap) {
if( swap ) i = swap16(i);
memcpy( buf, &i, sizeof(i) );
}
void pack32i(uint8_t *buf, uint32_t i, int swap) {
if( swap ) i = swap32(i);
memcpy( buf, &i, sizeof(i) );
}
void pack64i(uint8_t *buf, uint64_t i, int swap) {
if( swap ) i = swap64(i);
memcpy( buf, &i, sizeof(i) );
}
// unpack16i() -- unpack a 16-bit int from a char buffer (like ntohs())
// unpack32i() -- unpack a 32-bit int from a char buffer (like ntohl())
// unpack64i() -- unpack a 64-bit int from a char buffer (like ntohl())
// changes unsigned numbers to signed if needed.
int16_t unpack16i(const uint8_t *buf, int swap) {
uint16_t i;
memcpy(&i, buf, sizeof(i));
if( swap ) i = swap16(i);
return i <= 0x7fffu ? (int16_t)i : -1 -(uint16_t)(0xffffu - i);
}
int32_t unpack32i(const uint8_t *buf, int swap) {
uint32_t i;
memcpy(&i, buf, sizeof(i));
if( swap ) i = swap32(i);
return i <= 0x7fffffffu ? (int32_t)i : -1 -(int32_t)(0xffffffffu - i);
}
int64_t unpack64i(const uint8_t *buf, int swap) {
uint64_t i;
memcpy(&i, buf, sizeof(i));
if( swap ) i = swap64(i);
return i <= 0x7fffffffffffffffull ? (int64_t)i : -1 -(int64_t)(0xffffffffffffffffull - i);
}
// ----------------------------------------------------------------------------
// float un/packing: 8 (micro), 16 (half), 32 (float), 64 (double) types
// - rlyeh, public domain. original code by Beej.us (PD).
//
// [src] http://beej.us/guide/bgnet/output/html/multipage/advanced.html#serialization
// Modified to encode NaN and Infinity as well.
//
// [1] http://www.mrob.com/pub/math/floatformats.html#minifloat
// [2] microfloat: [0.002 to 240] range.
// [3] half float: can approximate any 16-bit unsigned integer or its reciprocal to 3 decimal places.
uint64_t pack754(long double f, unsigned bits, unsigned expbits) {
long double fnorm;
int shift;
long long sign, exp, significand;
unsigned significandbits = bits - expbits - 1; // -1 for sign bit
if (f == 0.0) return 0; // get this special case out of the way
//< @r-lyeh beware! works for 32/64 only
else if (f == INFINITY) return 0x7f800000ULL << (bits - 32); // 0111 1111 1000
else if (f == -INFINITY) return 0xff800000ULL << (bits - 32);
else if (f != f) return 0x7fc00000ULL << (bits - 32); // 0111 1111 1100 NaN
//< @r-lyeh
// check sign and begin normalization
if (f < 0) { sign = 1; fnorm = -f; }
else { sign = 0; fnorm = f; }
// get the normalized form of f and track the exponent
shift = 0;
while(fnorm >= 2.0) { fnorm /= 2.0; shift++; }
while(fnorm < 1.0) { fnorm *= 2.0; shift--; }
fnorm = fnorm - 1.0;
// calculate the binary form (non-float) of the significand data
significand = fnorm * ((1LL<<significandbits) + 0.5f);
// get the biased exponent
exp = shift + ((1<<(expbits-1)) - 1); // shift + bias
// return the final answer
return (sign<<(bits-1)) | (exp<<(bits-expbits-1)) | significand;
}
long double unpack754(uint64_t i, unsigned bits, unsigned expbits) {
long double result;
long long shift;
unsigned bias;
unsigned significandbits = bits - expbits - 1; // -1 for sign bit
if (i == 0) return 0.0;
//< @r-lyeh beware! works for 32 only
else if (i == 0x7fc00000ULL) return NAN; // NaN
else if (i == 0x7f800000ULL) return INFINITY; // +Inf
else if (i == 0xff800000ULL) return -INFINITY; // -Inf
//< @r-lyeh
// pull the significand
result = (i&((1LL<<significandbits)-1)); // mask
result /= (1LL<<significandbits); // convert back to float
result += 1.0f; // add the one back on
// deal with the exponent
bias = (1<<(expbits-1)) - 1;
shift = ((i>>significandbits)&((1LL<<expbits)-1)) - bias;
while(shift > 0) { result *= 2.0; shift--; }
while(shift < 0) { result /= 2.0; shift++; }
// sign it
result *= (i>>(bits-1))&1? -1.0: 1.0;
return result;
}
typedef int static_assert_flt[ sizeof(float) == 4 ];
typedef int static_assert_dbl[ sizeof(double) == 8 ];
// ----------------------------------------------------------------------------
// variable-length integer packing
// - rlyeh, public domain.
//
// 7 [0 xxxx xxx] 7-bit value in 1 byte (0- 127)
// 7 [10 xxx xxx] [yyyyyyyy] 14-bit value in 2 bytes (0- 16,383)
// 6 [110 xx xxx] [yyyyyyyy] [zzzzzzzz] 21-bit value in 3 bytes (0- 2,097,151)
// 8 [111 00 xxx] [ 3 bytes] 27-bit value in 4 bytes (0- 134,217,727)
// 8 [111 01 xxx] [ 4 bytes] 35-bit value in 5 bytes (0- 34,359,738,367)
// 5 [111 10 xxx] [ 5 bytes] 43-bit value in 6 bytes (0- 8,796,093,022,207)
// 8 [111 11 000] [ 6 bytes] 48-bit value in 7 bytes (0-281,474,976,710,655)
// 8 [111 11 001] [ 7 bytes] 56-bit value in 8 bytes (...)
// 8 [111 11 010] [ 8 bytes] 64-bit value in 9 bytes
// 8 [111 11 011] [ 9 bytes] 72-bit value in 10 bytes
// 8 [111 11 100] [10 bytes] 80-bit value in 11 bytes
// A [111 11 101] [12 bytes] 96-bit value in 13 bytes
// A [111 11 110] [14 bytes] 112-bit value in 15 bytes
// A [111 11 111] [16 bytes] 128-bit value in 17 bytes
// 1 1 2 3 = 7
uint64_t pack64uv( uint8_t *buffer, uint64_t value ) {
#if 1 // LEB128
/* encode unsigned : 7-bit pack. MSB terminates stream */
const uint8_t *buffer0 = buffer;
while( value > 127 ) {
*buffer++ = value | 0x80; // (uint8_t)(( value & 0xFF ) | 0x80 );
value >>= 7;
}
*buffer++ = value;
return buffer - buffer0;
#else
#define ADD(bits) *buffer++ = (value >>= bits)
if( value < (1ull<< 7) ) return *buffer = value, 1;
if( value < (1ull<<14) ) return *buffer++ = 0x80|(value&0x3f), ADD(6), 2;
if( value < (1ull<<21) ) return *buffer++ = 0xc0|(value&0x1f), ADD(5), ADD(8), 3;
if( value < (1ull<<27) ) return *buffer++ = 0xe0|(value&0x07), ADD(3), ADD(8), ADD(8), 4;
if( value < (1ull<<35) ) return *buffer++ = 0xe8|(value&0x07), ADD(3), ADD(8), ADD(8), ADD(8), 5;
if( value < (1ull<<43) ) return *buffer++ = 0xf0|(value&0x07), ADD(3), ADD(8), ADD(8), ADD(8), ADD(8), 6;
if( value < (1ull<<48) ) return *buffer++ = 0xf8|(value&0x00), ADD(0), ADD(8), ADD(8), ADD(8), ADD(8), ADD(8), 7;
if( value < (1ull<<56) ) return *buffer++ = 0xf9|(value&0x00), ADD(0), ADD(8), ADD(8), ADD(8), ADD(8), ADD(8), ADD(8), 8;
/*if( value < (1ull<<64))*/return *buffer++ = 0xfa|(value&0x00), ADD(0), ADD(8), ADD(8), ADD(8), ADD(8), ADD(8), ADD(8), ADD(8), 9;
/*...*/
#undef ADD
#endif
}
uint64_t unpack64uv( const uint8_t *buffer, uint64_t *value ) {
#if 1 // LEB128
/* decode unsigned : 7-bit unpack. MSB terminates stream */
const uint8_t *buffer0 = buffer;
uint64_t out = 0, j = -7;
do {
out |= ( ((uint64_t)*buffer) & 0x7f) << (j += 7);
} while( (*buffer++) & 0x80 );
return buffer - buffer0;
#else
uint64_t bytes, out = 0, shift = 0;
const int table[] = { 6,7,8,9,10,12,14,16 };
/**/ if( *buffer >= 0xf8 ) bytes = table[*buffer - 0xf8];
else if( *buffer >= 0xe0 ) bytes = 3 + ((*buffer>>3) & 3);
else if( *buffer >= 0xc0 ) bytes = 2;
else bytes = *buffer >= 0x80;
#define POP(bits) out = out | (uint64_t)*buffer++ << (shift += bits);
switch( bytes ) {
default:
break; case 0: out = *buffer++;
break; case 1: out = *buffer++ & 0x3f; POP(6);
break; case 2: out = *buffer++ & 0x1f; POP(5); POP(8);
break; case 3: out = *buffer++ & 0x07; POP(3); POP(8); POP(8);
break; case 4: out = *buffer++ & 0x07; POP(3); POP(8); POP(8); POP(8);
break; case 5: out = *buffer++ & 0x07; POP(3); POP(8); POP(8); POP(8); POP(8);
break; case 6: ++buffer; shift = -8; POP(8); POP(8); POP(8); POP(8); POP(8); POP(8);
break; case 7: ++buffer; shift = -8; POP(8); POP(8); POP(8); POP(8); POP(8); POP(8); POP(8);
break; case 8: ++buffer; shift = -8; POP(8); POP(8); POP(8); POP(8); POP(8); POP(8); POP(8); POP(8);
}
#undef POP
return *value = out, bytes+1;
#endif
}
// vbyte, varint (signed)
uint64_t pack64iv( uint8_t *buffer, int64_t value_ ) {
uint64_t value = (uint64_t)((value_ >> 63) ^ (value_ << 1));
return pack64uv(buffer, value); /* convert sign|magnitude to magnitude|sign */
}
uint64_t unpack64iv( const uint8_t *buffer, int64_t *value ) {
uint64_t out = 0, ret = unpack64uv( buffer, &out );
*value = ((out >> 1) ^ -(out & 1)); /* convert magnitude|sign to sign|magnitude */
return ret;
}
#if 0
AUTORUN {
int tests = 0, passes = 0;
#define testi(v) do { \
int64_t val = v; \
char out[16]; \
int len = pack64iv(out, val); \
int64_t in = ~val; \
unpack64iv(out, &in); \
int ok = val == in; ++tests; passes += ok; \
printf("%c %02d/%02d (-) %#llx (%llu) <-> %d bytes <-> %#llx (%llu)\n", "NY"[ok], passes, tests, val, val, len, in, in); \
} while(0)
#define testu(v) do { \
uint64_t val = (v); \
char out[16]; \
int len = pack64uv(out, val); \
uint64_t in = ~val; \
unpack64uv(out, &in); \
int ok = val == in; ++tests; passes += ok; \
printf("%c %02d/%02d (+) %#llx (%llu) <-> %d bytes <-> %#llx (%llu)\n", "NY"[ok], passes, tests, val, val, len, in, in); \
} while(0)
#define TEST(v) do { testi(v); testu(v); } while(0)
TEST(0);
TEST((1ull<<7)-1);
TEST( 1ull<<7);
TEST((1ull<<14)-1);
TEST( 1ull<<14);
TEST((1ull<<21)-1);
TEST( 1ull<<21);
TEST((1ull<<27)-1);
TEST( 1ull<<27);
TEST((1ull<<35)-1);
TEST( 1ull<<35);
TEST((1ull<<48)-1);
TEST( 1ull<<48);
TEST(~0ull-1);
TEST(~0ull);
#undef TEST
printf("%d tests, %d errors\n", tests, tests - passes);
}
#endif
// ----------------------------------------------------------------------------
// msgpack v5, schema based struct/buffer bitpacking
// - rlyeh, public domain.
//
// [ref] https://github.com/msgpack/msgpack/blob/master/spec.md
//
// @todo: finish msgunpack()
// @todo: alt api v3
// int msgpack( uint8_t *buf, const char *fmt, ... );
// if !buf, bulk size; else pack.
// returns number of bytes written; 0 if not space enough.
// int msgunpack( const uint8_t *buf, const char *fmt, ... );
// if !buf, test message; else unpack.
// returns number of processed bytes; 0 if parse error.
// private alt unpack api v1 {
enum {
ERR,NIL,BOL,UNS,SIG,STR,BIN,FLT,EXT,ARR,MAP
};
typedef struct variant {
union {
uint8_t chr;
uint64_t uns;
int64_t sig;
char *str;
void *bin;
double flt;
uint32_t u32;
};
uint64_t sz;
uint16_t ext;
uint16_t type; //[0..10]={err,nil,bol,uns,sig,str,bin,flt,ext,arr,map}
} variant;
bool msgunpack_var(struct variant *var);
// } private alt unpack api v1
struct writer {
uint8_t *w; // Write pointer into buffer
size_t len; // Written bytes up to date
size_t cap; // Buffer capacity
};
struct reader {
FILE *fp;
const void *membuf;
size_t memsize, offset;
struct variant v; // tmp
};
static __thread struct writer out;
static __thread struct reader in;
static void wrbe(uint64_t n, uint8_t *b) {
#ifndef BIG
n = ntoh64(n);
#endif
memcpy(b, &n, sizeof(uint64_t));
}
static int wr(int len, uint8_t opcode, uint64_t value) {
uint8_t b[8];
assert((out.len + (len+1) < out.cap) && "buffer overflow!");
*out.w++ = (opcode);
/**/ if(len == 1) *out.w++ = (uint8_t)(value);
else if(len == 2) wrbe(value, b), memcpy(out.w, &b[6], 2), out.w += 2;
else if(len == 4) wrbe(value, b), memcpy(out.w, &b[4], 4), out.w += 4;
else if(len == 8) wrbe(value, b), memcpy(out.w, &b[0], 8), out.w += 8;
out.len += len+1;
return len+1;
}
static bool rd(void *buf, size_t len, size_t swap) { // return false any error and/or eof
bool ret;
if( in.fp ) {
assert( !ferror(in.fp) && "invalid file handle (reader)" );
ret = 1 == fread((char*)buf, len, 1, in.fp);
} else {
assert( in.membuf && "invalid memory buffer (reader)");
assert( (in.offset + len <= in.memsize) && "memory overflow! (reader)");
ret = !!memcpy(buf, (char*)in.membuf + in.offset, len);
}
#ifndef BIG
/**/ if( swap && len == 2 ) *((uint16_t*)buf) = ntoh16(*((uint16_t*)buf));
else if( swap && len == 4 ) *((uint32_t*)buf) = ntoh32(*((uint32_t*)buf));
else if( swap && len == 8 ) *((uint64_t*)buf) = ntoh64(*((uint64_t*)buf));
#endif
return in.offset += len, ret;
}
static bool rdbuf(char **buf, size_t len) { // return false on error or out of memory
char *ptr = REALLOC(*buf, len+1);
if( ptr && rd(ptr, len, 0) ) {
(*buf = ptr)[len] = 0;
} else {
FREE(ptr), ptr = 0;
}
return !!ptr;
}
int msgpack_new(uint8_t *w, size_t l) {
out.w = w;
out.len = 0;
out.cap = l;
return w != 0 && l != 0;
}
int msgpack_nil() {
return wr(0, 0xC0, 0);
}
int msgpack_chr(bool c) {
return wr(0, c ? 0xC3 : 0xC2, 0);
}
int msgpack_uns(uint64_t n) {
/**/ if (n < 0x80) return wr(0, n, 0);
else if (n < 0x100) return wr(1, 0xCC, n);
else if (n < 0x10000) return wr(2, 0xCD, n);
else if (n < 0x100000000) return wr(4, 0xCE, n);
else return wr(8, 0xCF, n);
}
int msgpack_int(int64_t n) {
/**/ if (n >= 0) return msgpack_uns(n);
else if (n >= -32) return wr(0, n, 0); //wr(0, 0xE0 | n, 0);
else if (n >= -128) return wr(1, 0xD0, n + 0xff + 1);
else if (n >= -32768) return wr(2, 0xD1, n + 0xffff + 1);
else if (n >= -2147483648LL) return wr(4, 0xD2, n + 0xffffffffull + 1);
else return wr(8, 0xD3, n + 0xffffffffffffffffull + 1);
}
int msgpack_flt(double g) {
float f = (float)g;
double h = f;
/**/ if(g == h) return wr(4, 0xCA, pack754_32(f));
else return wr(8, 0xCB, pack754_64(g));
}
int msgpack_str(const char *s) {
size_t n = strlen(s), c = n;
/**/ if (n < 0x20) c += wr(0, 0xA0 | n, 0);
else if (n < 0x100) c += wr(1, 0xD9, n);
else if (n < 0x10000) c += wr(2, 0xDA, n);
else c += wr(4, 0xDB, n);
memcpy(out.w, s, n);
out.w += n;
out.len += n;
return c;
}
int msgpack_bin(const char *s, size_t n) {
size_t c = n;
/**/ if (n < 0x100) c += wr(1, 0xC4, n);
else if (n < 0x10000) c += wr(2, 0xC5, n);
else c += wr(4, 0xC6, n);
memcpy(out.w, s, n);
out.w += n;
out.len += n;
return c;
}
int msgpack_arr(uint32_t numitems) {
uint32_t n = numitems;
/**/ if (n < 0x10) return wr(0, 0x90 | n, 0);
else if (n < 0x10000) return wr(2, 0xDC, n);
else return wr(4, 0xDD, n);
}
int msgpack_map(uint32_t numpairs) {
uint32_t n = numpairs;
/**/ if (n < 0x10) return wr(0, 0x80 | n, 0);
else if (n < 0x10000) return wr(2, 0xDE, n);
else return wr(4, 0xDF, n);
}
int msgpack_ext(uint8_t key, void *val, size_t n) {
uint32_t c = n;
/**/ if (n == 1) c += wr(1, 0xD4, key);
else if (n == 2) c += wr(1, 0xD5, key);
else if (n == 4) c += wr(1, 0xD6, key);
else if (n == 8) c += wr(1, 0xD7, key);
else if (n == 16) c += wr(1, 0xD8, key);
else if (n < 0x100) c += wr(1, 0xC7, n), c += wr(0, key, 0);
else if (n < 0x10000) c += wr(2, 0xC8, n), c += wr(0, key, 0);
else c += wr(4, 0xC9, n), c += wr(0, key, 0);
memcpy(out.w, val, n);
out.w += n;
out.len += n;
return c;
}
bool msgunpack_new( const void *opaque_or_FILE, size_t bytes ) {
return !!((memset(&in, 0, sizeof(in)), in.memsize = bytes) ? (in.membuf = opaque_or_FILE) : (in.fp = (FILE*)opaque_or_FILE));
}
bool msgunpack_eof() {
return in.fp ? !!feof(in.fp) : (in.offset > in.memsize);
}
bool msgunpack_err() {
return in.fp ? !!ferror(in.fp) : !in.memsize;
}
bool msgunpack_var(struct variant *w) {
uint8_t tag;
struct variant v = {0};
if( rd(&tag, 1, 0) )
switch(tag) {
default:
/**/ if((tag & 0x80) == 0x00) { v.type = UNS; v.sz = 1; v.uns = tag; }
else if((tag & 0xe0) == 0xe0) { v.type = SIG; v.sz = 1; v.sig = (int8_t)tag; }
else if((tag & 0xe0) == 0xa0) { v.type = rdbuf(&v.str, v.sz = tag & 0x1f) ? STR : ERR; }
else if((tag & 0xf0) == 0x90) { v.type = ARR; v.sz = tag & 0x0f; }
else if((tag & 0xf0) == 0x80) { v.type = MAP; v.sz = tag & 0x0f; }
break; case 0xc0: v.type = NIL; v.sz = 0;
break; case 0xc2: v.type = BOL; v.sz = 1; v.chr = 0;
break; case 0xc3: v.type = BOL; v.sz = 1; v.chr = 1;
break; case 0xcc: v.type = rd(&v.uns, v.sz = 1, 0) ? UNS : ERR;
break; case 0xcd: v.type = rd(&v.uns, v.sz = 2, 1) ? UNS : ERR;
break; case 0xce: v.type = rd(&v.uns, v.sz = 4, 1) ? UNS : ERR;
break; case 0xcf: v.type = rd(&v.uns, v.sz = 8, 1) ? UNS : ERR;
break; case 0xd0: v.type = rd(&v.uns, v.sz = 1, 0) ? (v.sig -= 0xff + 1, SIG) : ERR;
break; case 0xd1: v.type = rd(&v.uns, v.sz = 2, 1) ? (v.sig -= 0xffff + 1, SIG) : ERR;
break; case 0xd2: v.type = rd(&v.uns, v.sz = 4, 1) ? (v.sig -= 0xffffffffull + 1, SIG) : ERR;
break; case 0xd3: v.type = rd(&v.uns, v.sz = 8, 1) ? (v.sig -= 0xffffffffffffffffull + 1, SIG) : ERR;
break; case 0xca: v.type = rd(&v.u32, v.sz = 4, 1) ? (v.flt = unpack754_32(v.u32), FLT) : ERR;
break; case 0xcb: v.type = rd(&v.uns, v.sz = 8, 1) ? (v.flt = unpack754_64(v.uns), FLT) : ERR;
break; case 0xd9: v.type = rd(&v.sz, 1, 0) && rdbuf(&v.str, v.sz) ? STR : ERR;
break; case 0xda: v.type = rd(&v.sz, 2, 1) && rdbuf(&v.str, v.sz) ? STR : ERR;
break; case 0xdb: v.type = rd(&v.sz, 4, 1) && rdbuf(&v.str, v.sz) ? STR : ERR;
break; case 0xc4: v.type = rd(&v.sz, 1, 0) && rdbuf(&v.str, v.sz) ? BIN : ERR;
break; case 0xc5: v.type = rd(&v.sz, 2, 1) && rdbuf(&v.str, v.sz) ? BIN : ERR;
break; case 0xc6: v.type = rd(&v.sz, 4, 1) && rdbuf(&v.str, v.sz) ? BIN : ERR;
break; case 0xdc: v.type = rd(&v.sz, 2, 1) ? ARR : ERR;
break; case 0xdd: v.type = rd(&v.sz, 4, 1) ? ARR : ERR;
break; case 0xde: v.type = rd(&v.sz, 2, 1) ? MAP : ERR;
break; case 0xdf: v.type = rd(&v.sz, 4, 1) ? MAP : ERR;
break; case 0xd4: v.type = rd(&v.ext, 1, 0) && rd(&v.uns, 1, 0) && rdbuf(&v.str, v.sz = v.uns) ? EXT : ERR;
break; case 0xd5: v.type = rd(&v.ext, 1, 0) && rd(&v.uns, 2, 1) && rdbuf(&v.str, v.sz = v.uns) ? EXT : ERR;
break; case 0xd6: v.type = rd(&v.ext, 1, 0) && rd(&v.uns, 4, 1) && rdbuf(&v.str, v.sz = v.uns) ? EXT : ERR;
break; case 0xd7: v.type = rd(&v.ext, 1, 0) && rd(&v.uns, 8, 1) && rdbuf(&v.str, v.sz = v.uns) ? EXT : ERR;
break; case 0xd8: v.type = rd(&v.ext, 1, 0) && rd(&v.uns,16, 1) && rdbuf(&v.str, v.sz = v.uns) ? EXT : ERR;
break; case 0xc7: v.type = rd(&v.sz, 1, 0) && rd(&v.ext, 1, 0) && rdbuf(&v.str,v.sz) ? EXT : ERR;
break; case 0xc8: v.type = rd(&v.sz, 2, 1) && rd(&v.ext, 1, 1) && rdbuf(&v.str,v.sz) ? EXT : ERR;
break; case 0xc9: v.type = rd(&v.sz, 4, 1) && rd(&v.ext, 1, 1) && rdbuf(&v.str,v.sz) ? EXT : ERR;
}
return *w = v, v.type != ERR;
}
bool msgunpack_nil() {
return msgunpack_var(&in.v) && (in.v.type == NIL);
}
bool msgunpack_chr(bool *chr) {
return msgunpack_var(&in.v) && (*chr = in.v.chr, in.v.type == BOL);
}
bool msgunpack_uns(uint64_t *uns) {
return msgunpack_var(&in.v) && (*uns = in.v.uns, in.v.type == UNS);
}
bool msgunpack_int(int64_t *sig) {
return msgunpack_var(&in.v) && (*sig = in.v.sig, in.v.type == SIG);
}
bool msgunpack_flt(float *flt) {
return msgunpack_var(&in.v) && (*flt = in.v.flt, in.v.type == FLT);
}
bool msgunpack_dbl(double *dbl) {
return msgunpack_var(&in.v) && (*dbl = in.v.flt, in.v.type == FLT);
}
bool msgunpack_bin(void **bin, uint64_t *len) {
return msgunpack_var(&in.v) && (*bin = in.v.bin, *len = in.v.sz, in.v.type == BIN);
}
bool msgunpack_str(char **str) {
return msgunpack_var(&in.v) && (str ? *str = in.v.str, in.v.type == STR : in.v.type == STR);
}
bool msgunpack_ext(uint8_t *key, void **val, uint64_t *len) {
return msgunpack_var(&in.v) && (*key = in.v.ext, *val = in.v.bin, *len = in.v.sz, in.v.type == EXT);
}
bool msgunpack_arr(uint64_t *len) {
return msgunpack_var(&in.v) && (*len = in.v.sz, in.v.type == ARR);
}
bool msgunpack_map(uint64_t *len) {
return msgunpack_var(&in.v) && (*len = in.v.sz, in.v.type == MAP);
}
int msgpack(const char *fmt, ... ) {
int count = 0;
va_list vl;
va_start(vl, fmt);
while( *fmt ) {
char f = *fmt++;
switch( f ) {
break; case '{': { int i = va_arg(vl, int64_t); count += msgpack_map( i ); }
break; case '[': { int i = va_arg(vl, int64_t); count += msgpack_arr( i ); }
break; case 'b': { bool v = !!va_arg(vl, int64_t); count += msgpack_chr(v); }
break; case 'e': { uint8_t k = va_arg(vl, uint64_t); void *v = va_arg(vl, void*); size_t l = va_arg(vl, uint64_t); count += msgpack_ext( k, v, l ); }
break; case 'n': { count += msgpack_nil(); }
break; case 'p': { void *p = va_arg(vl, void*); size_t l = va_arg(vl, uint64_t); count += msgpack_bin( p, l ); }
break; case 's': { const char *v = va_arg(vl, const char *); count += msgpack_str(v); }
break; case 'u': { uint64_t v = va_arg(vl, uint64_t); count += msgpack_uns(v); }
break; case 'd': case 'i': { int64_t v = va_arg(vl, int64_t); count += msgpack_int(v); }
break; case 'f': case 'g': { double v = va_arg(vl, double); count += msgpack_flt(v); }
default: /*count = 0;*/ break;
}
}
va_end(vl);
return count;
}
int msgunpack(const char *fmt, ... ) {
int count = 0;
va_list vl;
va_start(vl, fmt);
while( *fmt ) {
char f = *fmt++;
switch( f ) {
break; case '{': { int64_t *i = va_arg(vl, int64_t*); count += msgunpack_map( i ); }
break; case '[': { int64_t *i = va_arg(vl, int64_t*); count += msgunpack_arr( i ); }
break; case 'f': { float *v = va_arg(vl, float*); count += msgunpack_flt(v); }
break; case 'g': { double *v = va_arg(vl, double*); count += msgunpack_dbl(v); }
break; case 's': { char **v = va_arg(vl, char **); count += msgunpack_str(v); }
// break; case 'b': { bool *v = !!va_arg(vl, bool*); count += msgunpack_chr(v); }
// break; case 'e': { uint8_t k = va_arg(vl, uint64_t); void *v = va_arg(vl, void*); size_t l = va_arg(vl, uint64_t); count += msgunpack_ext( k, v, l ); }
// break; case 'n': { count += msgunpack_nil(); }
break; case 'p': { void *p = va_arg(vl, void*); uint64_t l = va_arg(vl, uint64_t); count += msgunpack_bin( p, &l ); }
// break; case 'u': { uint64_t v = va_arg(vl, uint64_t); count += msgunpack_uns(v); }
// break; case 'd': case 'i': { int64_t v = va_arg(vl, int64_t); count += msgunpack_int(v); }
default: /*count = 0;*/ break;
}
}
va_end(vl);
return count;
}
#if 0
AUTORUN {
# define unit(title)
# define data(data) msgunpack_new(data, sizeof(data) -1 )
# define TEST(expr) test(msgunpack_var(&obj) && !!(expr))
int test_len;
const char *test_data = 0;
struct variant obj = {0};
/*
* Test vectors are derived from
* `https://github.com/ludocode/mpack/blob/v0.8.2/test/test-write.c`.
*/
unit("(minposfixint)");
data("\x00");
TEST(obj.type == UNS && obj.sz == 1 && obj.uns == 0);
unit("(maxposfixint)");
data("\x7f");
TEST(obj.type == UNS && obj.sz == 1 && obj.uns == 127);
unit("(maxnegfixint)");
data("\xe0");
TEST(obj.type == SIG && obj.sz == 1 && obj.uns == -32);
unit("(minnegfixint)");
data("\xff");
TEST(obj.type == SIG && obj.sz == 1 && obj.uns == -1);
unit("(uint8)");
data("\xcc\0");
TEST(obj.type == UNS && obj.sz == 1 && obj.uns == 0);
unit("(uint16)");
data("\xcd\0\0");
TEST(obj.type == UNS && obj.sz == 2 && obj.uns == 0);
unit("(uint32)");
data("\xce\0\0\0\0");
TEST(obj.type == UNS && obj.sz == 4 && obj.uns == 0);
unit("(uint64)");
data("\xcf\0\0\0\0\0\0\0\0");
TEST(obj.type == UNS && obj.sz == 8 && obj.uns == 0);
unit("(float32)");
data("\xca\0\0\0\0");
TEST(obj.type == FLT && obj.sz == 4 && obj.uns == 0);
unit("(float64)");
data("\xcb\0\0\0\0\0\0\0\0");
TEST(obj.type == FLT && obj.sz == 8 && obj.uns == 0);
unit("(string)");
data("\xa5Hello");
TEST(obj.type == STR && obj.sz == 5 && !strcmp(obj.str, "Hello"));
unit("(str8)");
data("\xd9\x05Hello");
TEST(obj.type == STR && obj.sz == 5 && !strcmp(obj.str, "Hello"));
unit("(str16)");
data("\xda\0\x05Hello");
TEST(obj.type == STR && obj.sz == 5 && !strcmp(obj.str, "Hello"));
unit("(str32)");
data("\xdb\0\0\0\x05Hello");
TEST(obj.type == STR && obj.sz == 5 && !strcmp(obj.str, "Hello"));
unit("(array)");
data("\x91\x01");
TEST(obj.type == ARR && obj.sz == 1);
unit("(array8)");
data("\x91\x01");
TEST(obj.type == ARR && obj.sz == 1);
unit("(array16)");
data("\xdc\0\x01\x01");
TEST(obj.type == ARR && obj.sz == 1);
unit("(map8)");
data("\x81\x01\x01");
TEST(obj.type == MAP && obj.sz == 1);
TEST(obj.type == UNS && obj.sz == 1 && obj.uns == 1);
TEST(obj.type == UNS && obj.sz == 1 && obj.uns == 1);
unit("(map32)");
data("\xdf\0\0\0\x01\xa5Hello\x01");
TEST(obj.type == MAP && obj.sz == 1);
TEST(obj.type == STR && obj.sz == 5 && !strcmp(obj.str, "Hello"));
TEST(obj.type == UNS && obj.sz == 1 && obj.uns == 1);
unit("(+fixnum)");
data("\x00"); TEST(obj.type == UNS && obj.uns == 0);
data("\x01"); TEST(obj.type == UNS && obj.uns == 1);
data("\x02"); TEST(obj.type == UNS && obj.uns == 2);
data("\x0f"); TEST(obj.type == UNS && obj.uns == 0x0f);
data("\x10"); TEST(obj.type == UNS && obj.uns == 0x10);
data("\x7f"); TEST(obj.type == UNS && obj.uns == 0x7f);
unit("(-fixnum)");
data("\xff"); TEST(obj.type == SIG && obj.sig == -1);
data("\xfe"); TEST(obj.type == SIG && obj.sig == -2);
data("\xf0"); TEST(obj.type == SIG && obj.sig == -16);
data("\xe0"); TEST(obj.type == SIG && obj.sig == -32);
unit("(+int)");
data("\xcc\x80"); TEST(obj.type == UNS && obj.uns == 0x80);
data("\xcc\xff"); TEST(obj.type == UNS && obj.uns == 0xff);
data("\xcd\x01\x00"); TEST(obj.type == UNS && obj.uns == 0x100);
data("\xcd\xff\xff"); TEST(obj.type == UNS && obj.uns == 0xffff);
data("\xce\x00\x01\x00\x00"); TEST(obj.type == UNS && obj.uns == 0x10000);
data("\xce\xff\xff\xff\xff"); TEST(obj.type == UNS && obj.uns == 0xffffffffull);
data("\xcf\x00\x00\x00\x01\x00\x00\x00\x00"); TEST(obj.type == UNS && obj.uns == 0x100000000ull);
data("\xcf\xff\xff\xff\xff\xff\xff\xff\xff"); TEST(obj.type == UNS && obj.uns == 0xffffffffffffffffull);
unit("(-int)");
data("\xd0\xdf"); TEST(obj.type == SIG && obj.sig == -33);
data("\xd0\x80"); TEST(obj.type == SIG && obj.sig == -128);
data("\xd1\xff\x7f"); TEST(obj.type == SIG && obj.sig == -129);
data("\xd1\x80\x00"); TEST(obj.type == SIG && obj.sig == -32768);
data("\xd2\xff\xff\x7f\xff"); TEST(obj.type == SIG && obj.sig == -32769);
data("\xd2\x80\x00\x00\x00"); TEST(obj.type == SIG && obj.sig == -2147483648ll);
data("\xd3\xff\xff\xff\xff\x7f\xff\xff\xff"); TEST(obj.type == SIG && obj.sig == -2147483649ll);
data("\xd3\x80\x00\x00\x00\x00\x00\x00\x00"); TEST(obj.type == SIG && obj.sig == INT64_MIN);
unit("(misc)");
data("\xc0"); TEST(obj.type == NIL && obj.chr == 0);
data("\xc2"); TEST(obj.type == BOL && obj.chr == 0);
data("\xc3"); TEST(obj.type == BOL && obj.chr == 1);
data("\x90"); TEST(obj.type == ARR && obj.sz == 0);
data("\x91\xc0");
TEST(obj.type==ARR && obj.sz==1);
TEST(obj.type==NIL);
data("\x9f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e");
TEST(obj.type==ARR && obj.sz==15);
for(int i = 0; i < 15; ++i) {
TEST(obj.type==UNS && obj.sig==i);
}
data("\xdc\x00\x10\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c"
"\x0d\x0e\x0f");
TEST(obj.type==ARR && obj.sz==16);
for(unsigned i = 0; i < 16; ++i) {
TEST(obj.type == UNS && obj.uns == i);
}
data("\x80");
TEST(obj.type == MAP && obj.sz == 0);
data("\x81\xc0\xc0");
TEST(obj.type == MAP && obj.sz == 1);
TEST(obj.type == NIL);
TEST(obj.type == NIL);
data("\x82\x00\x00\x01\x01");
TEST(obj.type == MAP && obj.sz == 2);
TEST(obj.type == UNS && obj.sig == 0);
TEST(obj.type == UNS && obj.sig == 0);
TEST(obj.type == UNS && obj.sig == 1);
TEST(obj.type == UNS && obj.sig == 1);
data("\x8f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e"
"\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d");
TEST(obj.type == MAP && obj.sz == 15);
for(unsigned i = 0; i < 15; ++i) {
TEST(obj.type == UNS && obj.uns == i*2+0);
TEST(obj.type == UNS && obj.uns == i*2+1);
}
data("\xde\x00\x10\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c"
"\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c"
"\x1d\x1e\x1f");
TEST(obj.type == MAP && obj.sz == 16);
for(unsigned i = 0; i < 16; ++i) {
TEST(obj.type == UNS && obj.uns == i*2+0);
TEST(obj.type == UNS && obj.uns == i*2+1);
}
data("\x91\x90");
test( obj.type == ARR && obj.sz == 1 );
test( obj.type == ARR && obj.sz == 0 );
data("\x93\x90\x91\x00\x92\x01\x02");
test( obj.type == ARR && obj.sz == 3 );
test( obj.type == ARR && obj.sz == 0 );
test( obj.type == ARR && obj.sz == 1 );
test( obj.type == UNS && obj.uns == 0 );
test( obj.type == ARR && obj.sz == 2 );
test( obj.type == UNS && obj.uns == 1 );
test( obj.type == UNS && obj.uns == 2 );
data("\x95\x90\x91\xc0\x92\x90\x91\xc0\x9f\x00\x01\x02\x03\x04\x05\x06"
"\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\xdc\x00\x10\x00\x01\x02\x03\x04"
"\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f");
test( obj.type == ARR && obj.sz == 5 );
test( obj.type == ARR && obj.sz == 0 );
test( obj.type == ARR && obj.sz == 1 );
test( obj.type == NIL );
test( obj.type == ARR && obj.sz == 2 );
test( obj.type == ARR && obj.sz == 0 );
test( obj.type == ARR && obj.sz == 1 );
test( obj.type == NIL );
test( obj.type == ARR && obj.sz == 15 );
for( unsigned i = 0; i < 15; ++i ) {
test( obj.type == UNS && obj.uns == i );
}
test( obj.type == ARR && obj.sz == 16 );
for( unsigned i = 0; i < 15; ++i ) {
test( obj.type == UNS && obj.uns == i );
}
data("\x85\x00\x80\x01\x81\x00\xc0\x02\x82\x00\x80\x01\x81\xc0\xc0\x03"
"\x8f\x00\x00\x01\x01\x02\x02\x03\x03\x04\x04\x05\x05\x06\x06\x07"
"\x07\x08\x08\x09\x09\x0a\x0a\x0b\x0b\x0c\x0c\x0d\x0d\x0e\x0e\x04"
"\xde\x00\x10\x00\x00\x01\x01\x02\x02\x03\x03\x04\x04\x05\x05\x06"
"\x06\x07\x07\x08\x08\x09\x09\x0a\x0a\x0b\x0b\x0c\x0c\x0d\x0d\x0e"
"\x0e\x0f\x0f");
TEST(obj.type == MAP && obj.sz == 5);
TEST(obj.type == UNS && obj.uns == 0);
TEST(obj.type == MAP && obj.sz == 0);
TEST(obj.type == UNS && obj.uns == 1);
TEST(obj.type == MAP && obj.sz == 1);
TEST(obj.type == UNS && obj.uns == 0);
TEST(obj.type == NIL);
TEST(obj.type == UNS && obj.uns == 2);
TEST(obj.type == MAP && obj.sz == 2);
TEST(obj.type == UNS && obj.uns == 0);
TEST(obj.type == MAP && obj.sz == 0);
TEST(obj.type == UNS && obj.uns == 1);
TEST(obj.type == MAP && obj.sz == 1);
TEST(obj.type == NIL);
TEST(obj.type == NIL);
TEST(obj.type == UNS && obj.uns == 3);
TEST(obj.type == MAP && obj.sz == 15);
for( unsigned i = 0; i < 15; ++i ) {
TEST(obj.type == UNS && obj.uns == i);
TEST(obj.type == UNS && obj.uns == i);
}
TEST(obj.type == UNS && obj.uns == 4);
TEST(obj.type == MAP && obj.sz == 16);
for( unsigned i = 0; i < 16; ++i ) {
TEST(obj.type == UNS && obj.uns == i);
TEST(obj.type == UNS && obj.uns == i);
}
data("\x85\xd0\xd1\x91\xc0\x90\x81\xc0\x00\xc0\x82\xc0\x90\x04\x05\xa5"
"\x68\x65\x6c\x6c\x6f\x93\xa7\x62\x6f\x6e\x6a\x6f\x75\x72\xc0\xff"
"\x91\x5c\xcd\x01\x5e");
TEST(obj.type == MAP && obj.sz == 5);
TEST(obj.type == SIG && obj.sig == -47);
TEST(obj.type == ARR && obj.sz == 1);
TEST(obj.type == NIL);
TEST(obj.type == ARR && obj.sz == 0);
TEST(obj.type == MAP && obj.sz == 1);
TEST(obj.type == NIL);
TEST(obj.type == UNS && obj.uns == 0);
TEST(obj.type == NIL);
TEST(obj.type == MAP && obj.sz == 2);
TEST(obj.type == NIL);
TEST(obj.type == ARR && obj.sz == 0);
TEST(obj.type == UNS && obj.uns == 4);
TEST(obj.type == UNS && obj.uns == 5);
TEST(obj.type == STR && !strcmp(obj.str, "hello"));
TEST(obj.type == ARR && obj.sz == 3);
TEST(obj.type == STR && !strcmp(obj.str, "bonjour"));
TEST(obj.type == NIL);
TEST(obj.type == SIG && obj.sig == -1);
TEST(obj.type == ARR && obj.sz == 1);
TEST(obj.type == UNS && obj.uns == 92);
TEST(obj.type == UNS && obj.uns == 350);
data("\x82\xa7" "compact" "\xc3\xa6" "schema" "\x00");
TEST(obj.type == MAP && obj.sz == 2);
TEST(obj.type == STR && obj.sz == 7 && !strcmp(obj.str, "compact"));
TEST(obj.type == BOL && obj.chr == 1);
TEST(obj.type == STR && obj.sz == 6 && !strcmp(obj.str, "schema"));
TEST(obj.type == UNS && obj.sz == 1 && obj.uns == 0);
# undef TEST
# undef data
# undef unit
}
bool vardump( struct variant *w ) {
static int tabs = 0;
struct variant v = *w;
printf("%.*s", tabs, "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t");
switch( v.type ) {
default: case ERR:
if( !msgunpack_eof() ) printf("ERROR: unknown tag type (%02X)\n", (int)v.type);
return false;
break; case NIL: printf("(%s)\n", "null");
break; case BOL: printf("bool: %d\n", v.chr);
break; case SIG: printf("int: %lld\n", v.sig);
break; case UNS: printf("uint: %llu\n", v.uns);
break; case FLT: printf("float: %g\n", v.flt);
break; case STR: printf("string: '%s'\n", v.str);
break; case BIN: { for( size_t n = 0; n < v.sz; n++ ) printf("%s%02x(%c)", n > 0 ? " ":"binary: ", v.str[n], v.str[n] >= 32 ? v.str[n] : '.'); puts(""); }
break; case EXT: { printf("ext: [%02X (%d)] ", v.ext, v.ext); for( size_t n = 0; n < v.sz; n++ ) printf("%s%02x(%c)", n > 0 ? " ":"", v.str[n], v.str[n] >= 32 ? v.str[n] : '.'); puts(""); }
break; case ARR: {
++tabs; puts("[");
for( size_t n = v.sz; n-- > 0; ) {
if( !msgunpack_var(&v) || !vardump(&v) ) return false;
}
--tabs; puts("]");
}
break; case MAP: {
++tabs; puts("{");
for( size_t n = v.sz; n-- > 0; ) {
if( !msgunpack_var(&v) || !vardump(&v) ) return false;
if( !msgunpack_var(&v) || !vardump(&v) ) return false;
}
--tabs; puts("}");
}}
return true;
}
void testdump( const char *fname ) {
FILE *fp = fopen(fname, "rb");
if( !fp ) {
fputs("Cannot read input stream", stderr);
} else {
if( msgunpack_new(fp, 0) ) {
struct variant v;
while( msgunpack_var(&v) ) {
vardump(&v);
}
if( msgunpack_err() ) {
fputs("Error while unpacking", stderr);
}
}
fclose(fp);
}
}
void testwrite(const char *outfile) {
char buf[256];
msgpack_new(buf, 256);
int len = msgpack("ddufs [dddddddd-dddddddd {sisi bne"/*bp0*/,
-123LL, 123LL, 123456ULL, 3.14159f, "hello world",
16ULL,
-31LL, -32LL, -127LL, -128LL, -255LL, -256LL, -511LL, -512LL, // ,121, 3, "hi",
+31LL, +32LL, +127LL, +128LL, +255LL, +256LL, +511LL, +512LL, // ,121, 3, "hi",
2ULL,
"hello", -123LL,
"world", -456LL,
1ULL,
0xeeULL, "this is an EXT type", sizeof("this is an EXT type")-1
);
hexdump(buf, len);
FILE *fp = fopen(outfile, "wb");
if( fp ) {
fwrite( buf, len, 1, fp );
fclose(fp);
}
}
AUTORUN {
testwrite("out.mp");
testdump("out.mp");
}
#endif
// ----------------------------------------------------------------------------
// STRUCT PACKING
// Based on code by Brian "Beej Jorgensen" Hall (public domain) [1].
// Based on code by Ginger Bill's half<->float (public domain) [2].
// - rlyeh, public domain.
//
// pack.c -- perl/python-ish pack/unpack functions
// like printf and scanf, but for binary data.
//
// format flags:
// (<) little endian (>) big endian (! also) (=) native endian
// (c) 8-bit char (b) 8-bit byte
// (h) 16-bit half (w) 16-bit word
// (i) 32-bit integer (u) 32-bit unsigned (f) 32-bit float
// (l) 64-bit long (q) 64-bit quad (d) 64-bit double
// (v) varint
// (s) string (64-bit varint length prepended)
// (S) string (32-bit fixed length prepended)
// (m) memblock (64-bit varint length prepended)
// (M) memblock (32-bit fixed length prepended)
// (z) memblock (zeroed)
// (#) number of arguments processed (only when unpacking)
//
// @todo:
// - (x) document & test flag
// @totest:
// - (s) string (64-bit variable length automatically prepended)
// - (S) string (32-bit fixed length automatically prepended)
// - (m) memblock (64-bit variable length automatically prepended)
// - (M) memblock (32-bit fixed length automatically prepended)
// - (z) memblock (zeroed)
// - (#) number of arguments processed (only when unpacking)
//
// @refs:
// [1] http://beej.us/guide/bgnet/output/html/multipage/advanced.html#serialization (Modified to encode NaN and Infinity as well.)
// [2] https://github.com/gingerBill/gb
// [3] http://www.mrob.com/pub/math/floatformats.html#minifloat
// [4] microfloat: [0.002 to 240] range.
// [5] half float: can approximate any 16-bit unsigned integer or its reciprocal to 3 decimal places.
// b/f packing -----------------------------------------------------------------
int loadb_(const uint8_t *buf, const char *fmt, va_list ap) {
uint64_t args = 0;
const uint8_t *buf0 = buf;
char tmp[16+1];
//uint64_t size = 0, len;
int32_t len, count, maxstrlen=0;
int le = 0;
if(!buf) // buffer estimation
for(; *fmt != '\0'; fmt++) {
switch(*fmt) {
default: if (!isdigit(*fmt)) return 0;
break; case '!': case '>': case '<': case '=': case ' ': // 0-bit endianness
break; case 'c': case 'b': { int8_t c = (int8_t)va_arg(ap, int); buf += 1; } // 8-bit promoted
break; case 'h': case 'w': { int16_t h = (int16_t)va_arg(ap, int); buf += 2; } // 16-bit promoted
break; case 'i': case 'u': { int32_t l = va_arg(ap, int32_t); buf += 4; } // 32-bit
break; case 'l': case 'q': { int64_t L = va_arg(ap, int64_t); buf += 8; } // 64-bit
break; case 'f': { float f = (float)va_arg(ap, double); buf += 4; } // 32-bit float promoted
break; case 'd': { double F = (double)va_arg(ap, double); buf += 8; } // 64-bit float (double)
break; case 'v': { int64_t L = va_arg(ap, int64_t); buf += pack64iv(tmp, L); } // varint (8,16,32,64 ...)
break; case 's': { char* s = va_arg(ap, char*); len = strlen(s); buf += pack64iv(tmp, len) + len; } // string, 64-bit variable length prepended
break; case 'S': { char* s = va_arg(ap, char*); len = strlen(s); buf += 4 + len; } // string, 32-bit fixed length prepended
break; case 'm': { int len = va_arg(ap, int); char *s = va_arg(ap, char*); buf += pack64iv(tmp, len) + len; } // memblock, 64-bit variable length prepended
break; case 'M': { int len = va_arg(ap, int); char *s = va_arg(ap, char*); buf += 4 + len; } // memblock, 32-bit fixed length prepended
break; case 'z': { int len = va_arg(ap, int); buf += len; } // memblock (zeroed)
}
}
if(buf) // buffer unpacking
for(; *fmt != '\0'; fmt++) {
switch(*fmt) {
default:
if (isdigit(*fmt)) { // track max str len
maxstrlen = maxstrlen * 10 + (*fmt-'0');
} else {
return 0;
}
break; case ' ':
break; case '!': le = 0;
break; case '>': le = 0;
break; case '<': le = 1;
break; case '=': le = is_little() ? 1 : 0;
break; case 'c': case 'b': ++args; { // 8-bit
int8_t *v = va_arg(ap, int8_t*);
*v = *buf <= 0x7f ? (int8_t)*buf : -1 -(uint8_t)(0xffu - *buf);
buf += 1;
}
break; case 'h': case 'w': ++args; { // 16-bit
int16_t *v = va_arg(ap, int16_t*);
*v = unpack16i(buf, le);
buf += 2;
}
break; case 'i': case 'u': ++args; { // 32-bit
int32_t *v = va_arg(ap, int32_t*);
*v = unpack32i(buf, le);
buf += 4;
}
break; case 'l': case 'q': ++args; { // 64-bit
int64_t *v = va_arg(ap, int64_t*);
*v = unpack64i(buf, le);
buf += 8;
}
break; case 'v': ++args; { // varint (8,16,32,64 ...)
int64_t *L = va_arg(ap, int64_t*);
buf += unpack64iv(buf, L);
}
break; case 'f': ++args; { // 32-bit float
float *v = va_arg(ap, float*);
int32_t i = unpack32i(buf, le);
*v = unpack754_32(i);
buf += 4;
}
break; case 'd': ++args; { // 64-bit float (double)
double *v = va_arg(ap, double*);
int64_t i = unpack64i(buf, le);
*v = unpack754_64(i);
buf += 8;
}
break; case 'S': ++args; { // string, 32-bit fixed length prepended
char *s = va_arg(ap, char*);
int64_t vlen = unpack32i(buf, le), read = 4;
count = (maxstrlen > 0 && vlen >= maxstrlen ? maxstrlen - 1 : vlen);
memcpy(s, buf + read, count);
s[count] = '\0';
buf += read + vlen;
}
break; case 's': ++args; { // string, 64-bit variable length prepended
char *s = va_arg(ap, char*);
int64_t vlen, read = unpack64iv(buf, &vlen);
count = (maxstrlen > 0 && vlen >= maxstrlen ? maxstrlen - 1 : vlen);
memcpy(s, buf + read, count);
s[count] = '\0';
buf += read + vlen;
}
break; case 'M': ++args; { // memblock, 32-bit fixed length prepended
char *s = va_arg(ap, char*);
int64_t vlen = unpack64iv(buf, &vlen), read = 4;
count = vlen; //(maxstrlen > 0 && vlen >= maxstrlen ? maxstrlen - 1 : vlen);
memcpy(s, buf + read, count);
//s[count] = '\0';
buf += read + vlen;
}
break; case 'm': ++args; { // memblock, 64-bit variable length prepended
char *s = va_arg(ap, char*);
int64_t vlen, read = unpack64iv(buf, &vlen);
count = vlen; //(maxstrlen > 0 && vlen >= maxstrlen ? maxstrlen - 1 : vlen);
memcpy(s, buf + read, count);
//s[count] = '\0';
buf += read + vlen;
}
break; case 'z': ++args; { // zero-init mem block
int *l = va_arg(ap, int*);
const uint8_t *prev = buf;
while( *buf == 0 ) ++buf;
*l = buf - prev;
}
break; case '#': {
int *l = va_arg(ap, int*);
*l = args;
}
}
if (!isdigit(*fmt)) {
maxstrlen = 0;
}
}
return (int)( buf - buf0 );
}
int saveb_(uint8_t *buf, const char *fmt, va_list ap) {
uint64_t size = 0, len;
int le = 0;
// buffer estimation
if( !buf ) {
return loadb_(buf, fmt, ap); // + strlen(buf) * 17; // worse (v)arint estimation for 128-bit ints (17 bytes each)
}
// buffer packing
for(; *fmt != '\0'; fmt++) {
switch(*fmt) {
default: size = 0; // error
break; case '!': le = 0;
break; case '>': le = 0;
break; case '<': le = 1;
break; case ' ': le = le;
break; case '=': le = is_little() ? 1 : 0;
break; case 'c': case 'b': { // 8-bit
int v = (int8_t)va_arg(ap, int /*promoted*/ );
*buf++ = (v>>0)&0xff;
size += 1;
}
break; case 'h': case 'w': { // 16-bit
int v = (int16_t)va_arg(ap, int /*promoted*/ );
pack16i(buf, v, le);
buf += 2;
size += 2;
}
break; case 'i': case 'u': { // 32-bit
int32_t v = va_arg(ap, int32_t);
pack32i(buf, v, le);
buf += 4;
size += 4;
}
break; case 'l': case 'q': { // 64-bit
int64_t v = va_arg(ap, int64_t);
pack64i(buf, v, le);
buf += 8;
size += 8;
}
break; case 'v': { // varint (8,16,32,64 ...)
int64_t v = va_arg(ap, int64_t);
int64_t L = pack64iv(buf, v);
buf += L;
size += L;
}
break; case 'f': { // 32-bit float
double v = (float)va_arg(ap, double /*promoted*/ );
int32_t i = pack754_32(v); // convert to IEEE 754
pack32i(buf, i, le);
buf += 4;
size += 4;
}
break; case 'd': { // 64-bit float (double)
double v = (double)va_arg(ap, double);
int64_t i = pack754_64(v); // convert to IEEE 754
pack64i(buf, i, le);
buf += 8;
size += 8;
}
break; case 'S': { // string, 32-bit fixed length prepended
char* s = va_arg(ap, char*);
int len = strlen(s);
pack32i(buf, len, le);
memcpy(buf + 4, s, len);
buf += 4 + len;
size += 4 + len;
}
break; case 's': { // string, 64-bit variable length prepended
char* s = va_arg(ap, char*);
int len = strlen(s);
int64_t L = pack64iv(buf, len);
memcpy(buf + L, s, len);
buf += L + len;
size += L + len;
}
break; case 'M': { // memblock, 32-bit fixed length prepended
int len = va_arg(ap, int);
char* s = va_arg(ap, char*);
pack32i(buf, len, le);
memcpy(buf + 4, s, len);
buf += 4 + len;
size += 4 + len;
}
break; case 'm': { // memblock, 64-bit variable length prepended
int len = va_arg(ap, int);
char* s = va_arg(ap, char*);
int64_t L = pack64iv(buf, len);
memcpy(buf + L, s, len);
buf += L + len;
size += L + len;
}
break; case 'z': { // memblock (zeroed)
int len = va_arg(ap, int);
memset(buf, 0, len);
buf += len;
size += len;
}
}
}
return (int)size;
}
int saveb(uint8_t *buf, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int rc = saveb_( buf, fmt, ap);
va_end(ap);
return rc;
}
int loadb(const uint8_t *buf, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int rc = loadb_( buf, fmt, ap);
va_end(ap);
return rc;
}
int savef(FILE *fp, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
// estimate bytes
int req = saveb_( 0, fmt, ap);
char stack[4096];
char *buf = req < 4096 ? stack : (char*)calloc(1, req + 1 );
int rc = saveb_(buf, fmt, ap);
fwrite(buf, req,1, fp);
if( !(req < 4096) ) free(buf);
va_end(ap);
return rc;
}
int loadf(FILE *fp, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
// estimate bytes
int req = loadb_( 0, fmt, ap) * 2; // *2 in case it is underestimated
char stack[4096];
char *buf = req < 4096 ? stack : (char*)calloc(1, req + 1 );
fread(buf, req,1, fp);
int rc = loadb_(buf, fmt, ap);
if( !(req < 4096) ) free(buf);
va_end(ap);
return rc;
}
#if 0
AUTORUN {
const char *dna = "3b8bhbhbbhhbhhhuu"; // "1c1h212122122233"; "i3c8chchchhchhhdd"
struct bootsector {
uint8_t jump_instruction[3];
uint8_t oem_name[8];
uint16_t bytes_per_sector;
uint8_t sectors_per_cluster;
uint16_t reserved_sectors;
uint8_t fat_copies;
uint16_t max_dirs;
uint16_t sector_count;
uint8_t media_descriptor;
uint16_t sectors_per_fat;
uint16_t sectors_per_head;
uint16_t heads;
uint32_t hidden_sectors;
uint32_t sector_countz;
} fat = { {0,1,2},{3,4,5,6,7,8,9,10},11,12,13,14,15,16,17,18,19,20,21,22 };
hexdump(&fat, sizeof(struct bootsector));
FILE *fp = fopen("test.mbr", "wb");
savef(fp, dna, &fat); //
fclose(fp);
memset(&fat, 0, sizeof(struct bootsector));
fp = fopen("test.mbr", "rb");
loadf(fp, dna, &fat);
fclose(fp);
hexdump(&fat, sizeof(struct bootsector));
}
#endif
// ----------------------------------------------------------------------------
// compression api
static struct zcompressor {
// id of compressor
unsigned enumerator;
// name of compressor
const char name1, *name4, *name;
// returns worst case compression estimation for selected flags
unsigned (*bounds)(unsigned bytes, unsigned flags);
// returns number of bytes written. 0 if error.
unsigned (*encode)(const void *in, unsigned inlen, void *out, unsigned outcap, unsigned flags);
// returns number of excess bytes that will be overwritten when decoding.
unsigned (*excess)(unsigned flags);
// returns number of bytes written. 0 if error.
unsigned (*decode)(const void *in, unsigned inlen, void *out, unsigned outcap);
} zlist[] = {
{ COMPRESS_RAW, '0', "raw", "raw", raw_bounds, raw_encode, raw_excess, raw_decode },
{ COMPRESS_PPP, 'p', "ppp", "ppp", ppp_bounds, ppp_encode, ppp_excess, ppp_decode },
{ COMPRESS_ULZ, 'u', "ulz", "ulz", ulz_bounds, ulz_encode, ulz_excess, ulz_decode },
{ COMPRESS_LZ4, '4', "lz4x", "lz4x", lz4x_bounds, lz4x_encode, lz4x_excess, lz4x_decode },
{ COMPRESS_CRUSH, 'c', "crsh", "crush", crush_bounds, crush_encode, crush_excess, crush_decode },
{ COMPRESS_DEFLATE, 'd', "defl", "deflate", deflate_bounds, deflate_encode, deflate_excess, deflate_decode },
{ COMPRESS_LZP1, '1', "lzp1", "lzp1", lzp1_bounds, lzp1_encode, lzp1_excess, lzp1_decode },
{ COMPRESS_LZMA, 'm', "lzma", "lzma", lzma_bounds, lzma_encode, lzma_excess, lzma_decode },
{ COMPRESS_BALZ, 'b', "balz", "balz", balz_bounds, balz_encode, balz_excess, balz_decode },
{ COMPRESS_LZW3, 'w', "lzw3", "lzrw3a", lzrw3a_bounds, lzrw3a_encode, lzrw3a_excess, lzrw3a_decode },
{ COMPRESS_LZSS, 's', "lzss", "lzss", lzss_bounds, lzss_encode, lzss_excess, lzss_decode },
{ COMPRESS_BCM, 'B', "bcm", "bcm", bcm_bounds, bcm_encode, bcm_excess, bcm_decode },
{ COMPRESS_ZLIB, 'z', "zlib", "zlib", deflate_bounds, deflatez_encode, deflate_excess, deflatez_decode },
};
enum { COMPRESS_NUM = 14 };
static char *znameof(unsigned flags) {
static __thread char buf[16];
snprintf(buf, 16, "%4s.%c", zlist[(flags>>4)&0x0F].name4, "0123456789ABCDEF"[flags&0xF]);
return buf;
}
unsigned zencode(void *out, unsigned outlen, const void *in, unsigned inlen, unsigned flags) {
return zlist[(flags >> 4) % COMPRESS_NUM].encode(in, inlen, (uint8_t*)out, outlen, flags & 0x0F);
}
unsigned zdecode(void *out, unsigned outlen, const void *in, unsigned inlen, unsigned flags) {
return zlist[(flags >> 4) % COMPRESS_NUM].decode((uint8_t*)in, inlen, out, outlen);
}
unsigned zbounds(unsigned inlen, unsigned flags) {
return zlist[(flags >> 4) % COMPRESS_NUM].bounds(inlen, flags & 0x0F);
}
unsigned zexcess(unsigned flags) {
return zlist[(flags >> 4) % COMPRESS_NUM].excess(flags & 0x0F);
}
// ----------------------------------------------------------------------------
// BASE92 en/decoder
// THE BEERWARE LICENSE (Revision 42):
// <thenoviceoof> wrote this file. As long as you retain this notice you
// can do whatever you want with this stuff. If we meet some day, and you
// think this stuff is worth it, you can buy me a beer in return
// - Nathan Hwang (thenoviceoof)
unsigned base92_bounds(unsigned inlen) {
unsigned size = (inlen * 8) % 13, extra_null = 1;
if(size == 0) return 2 * ((inlen * 8) / 13) + extra_null;
if(size < 7) return 2 * ((inlen * 8) / 13) + extra_null + 1;
return 2 * ((inlen * 8) / 13) + extra_null + 2;
}
unsigned base92_encode(const void* in, unsigned inlen, void *out, unsigned size) {
char *res = (char *)out;
const unsigned char *str = (const unsigned char *)in;
unsigned int j = 0; // j for encoded
unsigned long workspace = 0; // bits holding bin
unsigned short wssize = 0; // number of good bits in workspace
unsigned char c;
const unsigned char ENCODE_MAPPING[256] = {
33, 35, 36, 37, 38, 39, 40, 41, 42, 43,
44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
64, 65, 66, 67, 68, 69, 70, 71, 72, 73,
74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
84, 85, 86, 87, 88, 89, 90, 91, 92, 93,
94, 95, 97, 98, 99, 100, 101, 102, 103, 104,
105, 106, 107, 108, 109, 110, 111, 112, 113, 114,
115, 116, 117, 118, 119, 120, 121, 122, 123, 124,
125, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0
};
if (inlen) {
for (unsigned i = 0; i < inlen; i++) {
workspace = workspace << 8 | str[i];
wssize += 8;
if (wssize >= 13) {
int tmp = (workspace >> (wssize - 13)) & 8191;
c = ENCODE_MAPPING[(tmp / 91)];
if (c == 0) return 0; // illegal char
res[j++] = c;
c = ENCODE_MAPPING[(tmp % 91)];
if (c == 0) return 0; // illegal char
res[j++] = c;
wssize -= 13;
}
}
// encode a last byte
if (0 < wssize && wssize < 7) {
int tmp = (workspace << (6 - wssize)) & 63; // pad the right side
c = ENCODE_MAPPING[(tmp)];
if (c == 0) return 0; // illegal char
res[j++] = c;
} else if (7 <= wssize) {
int tmp = (workspace << (13 - wssize)) & 8191; // pad the right side
c = ENCODE_MAPPING[(tmp / 91)];
if (c == 0) return 0; // illegal char
res[j++] = c;
c = ENCODE_MAPPING[(tmp % 91)];
if (c == 0) return 0; // illegal char
res[j++] = c;
}
} else {
res[j++] = '~';
}
// add null byte
res[j] = 0;
return j;
}
// this guy expects a null-terminated string
// gives back a non-null terminated string, and properly filled len
unsigned base92_decode(const void* in, unsigned size, void *out, unsigned outlen_unused) {
const char* str = (const char*)in;
unsigned char *res = (unsigned char *)out;
int i, j = 0, b1, b2;
unsigned long workspace = 0;
unsigned short wssize = 0;
const unsigned char DECODE_MAPPING[256] = {
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 0, 255, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 255, 62, 63, 64,
65, 66, 67, 68, 69, 70, 71, 72, 73, 74,
75, 76, 77, 78, 79, 80, 81, 82, 83, 84,
85, 86, 87, 88, 89, 90, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255
};
// handle small cases first
if (size == 0 || (str[0] == '~' && str[1] == '\0')) {
res[0] = 0;
return 1;
}
// calculate size
int len = ((size/2 * 13) + (size%2 * 6)) / 8;
// handle pairs of chars
for (i = 0; i + 1 < size; i += 2) {
b1 = DECODE_MAPPING[(str[i])];
b2 = DECODE_MAPPING[(str[i+1])];
workspace = (workspace << 13) | (b1 * 91 + b2);
wssize += 13;
while (wssize >= 8) {
res[j++] = (workspace >> (wssize - 8)) & 255;
wssize -= 8;
}
}
// handle single char
if (size % 2 == 1) {
workspace = (workspace << 6) | DECODE_MAPPING[(str[size - 1])];
wssize += 6;
while (wssize >= 8) {
res[j++] = (workspace >> (wssize - 8)) & 255;
wssize -= 8;
}
}
//assert(j == len);
return j;
}
// ----------------------------------------------------------------------------
// COBS en/decoder
// Based on code by Jacques Fortier.
// "Redistribution and use in source and binary forms are permitted, with or without modification."
//
// Consistent Overhead Byte Stuffing is an encoding that removes all 0 bytes from arbitrary binary data.
// The encoded data consists only of bytes with values from 0x01 to 0xFF. This is useful for preparing data for
// transmission over a serial link (RS-232 or RS-485 for example), as the 0 byte can be used to unambiguously indicate
// packet boundaries. COBS also has the advantage of adding very little overhead (at least 1 byte, plus up to an
// additional byte per 254 bytes of data). For messages smaller than 254 bytes, the overhead is constant.
//
// This implementation is designed to be both efficient and robust.
// The decoder is designed to detect malformed input data and report an error upon detection.
//
unsigned cobs_bounds( unsigned len ) {
return len + ceil(len / 254.0) + 1;
}
unsigned cobs_encode(const void *in, unsigned inlen, void *out, unsigned outlen) {
const uint8_t *src = (const uint8_t *)in;
uint8_t *dst = (uint8_t*)out;
size_t srclen = inlen;
uint8_t code = 1;
size_t read_index = 0, write_index = 1, code_index = 0;
while( read_index < srclen ) {
if( src[ read_index ] == 0) {
dst[ code_index ] = code;
code = 1;
code_index = write_index++;
read_index++;
} else {
dst[ write_index++ ] = src[ read_index++ ];
code++;
if( code == 0xFF ) {
dst[ code_index ] = code;
code = 1;
code_index = write_index++;
}
}
}
dst[ code_index ] = code;
return write_index;
}
unsigned cobs_decode(const void *in, unsigned inlen, void *out, unsigned outlen) {
const uint8_t *src = (const uint8_t *)in;
uint8_t *dst = (uint8_t*)out;
size_t srclen = inlen;
uint8_t code, i;
size_t read_index = 0, write_index = 0;
while( read_index < srclen ) {
code = src[ read_index ];
if( ((read_index + code) > srclen) && (code != 1) ) {
return 0;
}
read_index++;
for( i = 1; i < code; i++ ) {
dst[ write_index++ ] = src[ read_index++ ];
}
if( (code != 0xFF) && (read_index != srclen) ) {
dst[ write_index++ ] = '\0';
}
}
return write_index;
}
#if 0
static
void cobs_test( const char *buffer, int buflen ) {
char enc[4096];
int enclen = cobs_encode( buffer, buflen, enc, 4096 );
char dec[4096];
int declen = cobs_decode( enc, enclen, dec, 4096 );
test( enclen >= buflen );
test( declen == buflen );
test( memcmp(dec, buffer, buflen) == 0 );
printf("%d->%d->%d (+%d extra bytes)\n", declen, enclen, declen, enclen - declen);
}
AUTORUN {
const char *null = 0;
cobs_test( null, 0 );
const char empty[] = "";
cobs_test( empty, sizeof(empty) );
const char text[] = "hello world\n";
cobs_test( text, sizeof(text) );
const char bintext[] = "hello\0\0\0world\n";
cobs_test( bintext, sizeof(bintext) );
const char blank[512] = {0};
cobs_test( blank, sizeof(blank) );
char longbintext[1024];
for( int i = 0; i < 1024; ++i ) longbintext[i] = (unsigned char)i;
cobs_test( longbintext, sizeof(longbintext) );
assert(~puts("Ok"));
}
#endif
// ----------------------------------------------------------------------------
// netstring en/decoder
// - rlyeh, public domain.
unsigned netstring_bounds(unsigned inlen) {
return 5 + inlen + 3; // 3 for ;,\0 + 5 if inlen < 100k ; else (unsigned)ceil(log10(inlen + 1))
}
unsigned netstring_encode(const char *in, unsigned inlen, char *out, unsigned outlen) {
// if(outlen < netstring_bounds(inlen)) return 0;
sprintf(out, "%u:%.*s,", inlen, inlen, in);
return strlen(out);
}
unsigned netstring_decode(const char *in, unsigned inlen, char *out, unsigned outlen) {
// if(outlen < inlen) return 0;
const char *bak = in;
sscanf(in, "%u", &outlen);
while( *++in != ':' );
memcpy(out, in+1, outlen), out[outlen-1] = 0;
// return outlen; // number of written bytes
return (outlen + (in+2 - bak)); // number of read bytes
}
#if 0
AUTORUN {
// encode
const char text1[] = "hello world!", text2[] = "abc123";
unsigned buflen = netstring_bounds(strlen(text1) + strlen(text2));
char *buf = malloc(buflen), *ptr = buf;
ptr += netstring_encode(text1, strlen(text1), ptr, buflen -= (ptr - buf));
ptr += netstring_encode(text2, strlen(text2), ptr, buflen -= (ptr - buf));
printf("%s -> ", buf);
// decode
char out[12];
unsigned plen = strlen(ptr = buf);
while(plen > 0) {
int written = netstring_decode(ptr, plen, out, 12);
ptr += written;
plen -= written;
printf("'%s'(%s)(%d), ", out, ptr, plen );
}
puts("");
}
#endif
// ----------------------------------------------------------------------------
// array de/interleaving
// - rlyeh, public domain.
//
// results:
// R0G0B0 R1G1B1 R2G2B2... -> R0R1R2... B0B1B2... G0G1G2...
// R0G0B0A0 R1G1B1A1 R2G2B2A2... -> R0R1R2... A0A1A2... B0B1B2... G0G1G2...
void *interleave( void *out, const void *list, int list_count, int sizeof_item, unsigned columns ) {
void *bak = out;
assert( columns < list_count ); // required
int row_count = list_count / columns;
for( int offset = 0; offset < columns; offset++ ) {
for( int row = 0; row < row_count; row++ ) {
memcpy( out, &((char*)list)[ (offset + row * columns) * sizeof_item ], sizeof_item );
out = ((char*)out) + sizeof_item;
}
}
return bak;
}
#if 0
static
void interleave_test( const char *name, int interleaving, int deinterleaving, const char *original ) {
char interleaved[128] = {0};
interleave( interleaved, original, strlen(original)/2, 2, interleaving );
char deinterleaved[128] = {0};
interleave( deinterleaved, interleaved, strlen(original)/2, 2, deinterleaving );
printf( "\n%s\n", name );
printf( "original:\t%s\n", original );
printf( "interleaved:\t%s\n", interleaved );
printf( "deinterleaved:\t%s\n", deinterleaved );
assert( 0 == strcmp(original, deinterleaved) );
}
AUTORUN {
interleave_test(
"audio 2ch", 2, 3,
"L0R0"
"L1R1"
"L2R2"
);
interleave_test(
"image 3ch", 3, 3,
"R0G0B0"
"R1G1B1"
"R2G2B2"
);
interleave_test(
"image 4ch", 4, 3,
"R0G0B0A0"
"R1G1B1A1"
"R2G2B2A2"
);
interleave_test(
"audio 5ch", 5, 3,
"A0B0C0L0R0"
"A1B1C1L1R1"
"A2B2C2L2R2"
);
interleave_test(
"audio 5.1ch", 6, 3,
"A0B0C0L0R0S0"
"A1B1C1L1R1S1"
"A2B2C2L2R2S2"
);
interleave_test(
"opengl material 9ch", 9, 3,
"X0Y0Z0q0w0e0r0u0v0"
"X1Y1Z1q1w1e1r1u1v1"
"X2Y2Z2q2w2e2r2u2v2"
);
interleave_test(
"opengl material 10ch", 10, 3,
"X0Y0Z0q0w0e0r0s0u0v0"
"X1Y1Z1q1w1e1r1s1u1v1"
"X2Y2Z2q2w2e2r2s2u2v2"
);
assert(~puts("Ok"));
}
#endif
// ----------------------------------------------------------------------------
// delta encoder
#define delta_expand_template(N) \
void delta##N##_encode(void *buffer_, unsigned count) { \
uint##N##_t current, last = 0, *buffer = (uint##N##_t*)buffer_; \
for( unsigned i = 0; i < count; i++ ) { \
current = buffer[i]; \
buffer[i] = current - last; \
last = current; \
} \
} \
void delta##N##_decode(void *buffer_, unsigned count) { \
uint##N##_t delta, last = 0, *buffer = (uint##N##_t*)buffer_; \
for( unsigned i = 0; i < count; i++ ) { \
delta = buffer[i]; \
buffer[i] = delta + last; \
last = buffer[i]; \
} \
}
delta_expand_template(8);
delta_expand_template(16);
delta_expand_template(32);
delta_expand_template(64);
#if 0
AUTORUN {
char buf[] = "1231112223345555";
int buflen = strlen(buf);
char *dt = strdup(buf);
printf(" delta8: ", dt);
for( int i = 0; i < buflen; ++i ) printf("%c", dt[i] );
printf("->");
delta8_encode(dt, buflen);
for( int i = 0; i < buflen; ++i ) printf("%02d,", dt[i] );
printf("->");
delta8_decode(dt, buflen);
for( int i = 0; i < buflen; ++i ) printf("%c", dt[i] );
printf("\r%c\n", 0 == strcmp(buf,dt) ? 'Y':'N');
}
#endif
// ----------------------------------------------------------------------------
// zigzag en/decoder
// - rlyeh, public domain
uint64_t zig64( int64_t value ) { // convert sign|magnitude to magnitude|sign
return (value >> 63) ^ (value << 1);
}
int64_t zag64( uint64_t value ) { // convert magnitude|sign to sign|magnitude
return (value >> 1) ^ -(value & 1);
}
// branchless zigzag encoding 32/64
// sign|magnitude to magnitude|sign and back
// [ref] https://developers.google.com/protocol-buffers/docs/encoding
uint32_t enczig32u( int32_t n) { return ((n << 1) ^ (n >> 31)); }
uint64_t enczig64u( int64_t n) { return ((n << 1) ^ (n >> 63)); }
int32_t deczig32i(uint32_t n) { return ((n >> 1) ^ -(n & 1)); }
int64_t deczig64i(uint64_t n) { return ((n >> 1) ^ -(n & 1)); }
#if 0
AUTORUN {
int16_t x = -1000;
printf("%d -> %llu %llx -> %lld\n", x, zig64(x), zig64(x), zag64(zig64(x)));
}
AUTORUN {
#define CMP32(signedN) do { \
int32_t reconverted = deczig32i( enczig32u(signedN) ); \
int equal = signedN == reconverted; \
printf("[%s] %d vs %d\n", equal ? " OK " : "FAIL", signedN, reconverted ); \
} while(0)
#define CMP64(signedN) do { \
int64_t reconverted = deczig64i( enczig64u(signedN) ); \
int equal = signedN == reconverted; \
printf("[%s] %lld vs %lld\n", equal ? " OK " : "FAIL", signedN, reconverted ); \
} while(0)
CMP32( 0);
CMP32(-1);
CMP32(+1);
CMP32(-2);
CMP32(+2);
CMP32(INT32_MAX - 1);
CMP32(INT32_MIN + 1);
CMP32(INT32_MAX);
CMP32(INT32_MIN);
CMP64( 0ll);
CMP64(-1ll);
CMP64(+1ll);
CMP64(-2ll);
CMP64(+2ll);
CMP64(INT64_MAX - 1);
CMP64(INT64_MIN + 1);
CMP64(INT64_MAX);
CMP64(INT64_MIN);
}
void TESTU( uint64_t N ) {
uint8_t buf[9] = {0};
enczig64i(buf, (N));
uint64_t reconstructed = deczig64i(buf, 0);
if( reconstructed != (N) ) printf("[FAIL] %llu vs %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", (N), buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8] );
else if( 0xffffff == ((N) & 0xffffff) ) printf("[ OK ] %llx\n", (N));
}
void TESTI( int64_t N ) {
TESTU( enczig64u(N) );
}
AUTORUN {
TESTU(0LLU);
TESTU(1LLU);
TESTU(2LLU);
TESTU(UINT64_MAX/8);
TESTU(UINT64_MAX/4);
TESTU(UINT64_MAX/2);
TESTU(UINT64_MAX-2);
TESTU(UINT64_MAX-1);
TESTU(UINT64_MAX);
#pragma omp parallel for // compile with /openmp
for( int64_t N = INT64_MIN; N < INT64_MAX; ++N ) {
TESTU(N);
TESTI((int64_t)N);
}
}
#endif
// ----------------------------------------------------------------------------
// ARC4 en/decryptor. Based on code by Mike Shaffer.
// - rlyeh, public domain.
void *arc4( void *buf_, unsigned buflen, const void *pass_, unsigned passlen ) {
// [ref] http://www.4guysfromrolla.com/webtech/code/rc4.inc.html
assert(passlen);
int sbox[256], key[256];
char *buf = (char*)buf_;
const char *pass = (const char*)pass_;
for( unsigned a = 0; a < 256; a++ ) {
key[a] = pass[a % passlen];
sbox[a] = a;
}
for( unsigned a = 0, b = 0; a < 256; a++ ) {
b = (b + sbox[a] + key[a]) % 256;
int swap = sbox[a]; sbox[a] = sbox[b]; sbox[b] = swap;
}
for( unsigned a = 0, b = 0, i = 0; i < buflen; ++i ) {
a = (a + 1) % 256;
b = (b + sbox[a]) % 256;
int swap = sbox[a]; sbox[a] = sbox[b]; sbox[b] = swap;
buf[i] ^= sbox[(sbox[a] + sbox[b]) % 256];
}
return buf_;
}
#if 0
AUTORUN {
char buffer[] = "Hello world."; int buflen = strlen(buffer);
char *password = "abc123"; int passlen = strlen(password);
printf("Original: %s\n", buffer);
printf("Password: %s\n", password);
char *encrypted = arc4( buffer, buflen, password, passlen );
printf("ARC4 Encrypted text: '%s'\n", encrypted);
char *decrypted = arc4( buffer, buflen, password, passlen );
printf("ARC4 Decrypted text: '%s'\n", decrypted);
}
#endif
// ----------------------------------------------------------------------------
// crc64
// - rlyeh, public domain
uint64_t crc64(uint64_t h, const void *ptr, uint64_t len) {
// based on public domain code by Lasse Collin
// also, use poly64 0xC96C5795D7870F42 for crc64-ecma
static uint64_t crc64_table[256];
static uint64_t poly64 = UINT64_C(0x95AC9329AC4BC9B5);
if( poly64 ) {
for( int b = 0; b < 256; ++b ) {
uint64_t r = b;
for( int i = 0; i < 8; ++i ) {
r = r & 1 ? (r >> 1) ^ poly64 : r >> 1;
}
crc64_table[ b ] = r;
//printf("%016llx\n", crc64_table[b]);
}
poly64 = 0;
}
const uint8_t *buf = (const uint8_t *)ptr;
uint64_t crc = ~h; // ~crc;
while( len != 0 ) {
crc = crc64_table[(uint8_t)crc ^ *buf++] ^ (crc >> 8);
--len;
}
return ~crc;
}
#if 0
unsigned crc32(unsigned h, const void *ptr_, unsigned len) {
// based on public domain code by Karl Malbrain
const uint8_t *ptr = (const uint8_t *)ptr_;
if (!ptr) return 0;
const unsigned tbl[16] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };
for(h = ~h; len--; ) { uint8_t b = *ptr++; h = (h >> 4) ^ tbl[(h & 15) ^ (b & 15)]; h = (h >> 4) ^ tbl[(h & 15) ^ (b >> 4)]; }
return ~h;
}
#endif
// ----------------------------------------------------------------------------
// entropy encoder
#if is(win32)
#include <winsock2.h>
#include <wincrypt.h>
#pragma comment(lib, "advapi32")
void entropy( void *buf, unsigned n ) {
HCRYPTPROV provider;
if( CryptAcquireContext( &provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT ) == 0 ) {
assert(!"CryptAcquireContext failed");
}
int rc = CryptGenRandom( provider, n, (BYTE *)buf );
assert( rc != 0 );
CryptReleaseContext( provider, 0 );
}
#elif is(linux) || is(osx)
void entropy( void *buf, unsigned n ) {
FILE *fp = fopen( "/dev/urandom", "r" );
if( !fp ) assert(!"/dev/urandom open failed");
size_t read = n * fread( buf, n, 1, fp );
assert( read == n && "/dev/urandom read failed" );
fclose( fp );
}
#else // unused for now. likely emscripten will hit this
// pseudo random number generator with 128 bit internal state... probably not suited for cryptographical usage.
// [src] http://github.com/kokke (UNLICENSE)
// [ref] http://burtleburtle.net/bob/rand/smallprng.html
#include <time.h>
#if is(win32)
#include <process.h>
#else
#include <unistd.h>
#endif
static uint32_t prng_next(void) {
#define prng_rotate(x,k) (x << k) | (x >> (32 - k))
#define prng_shuffle() do { \
uint32_t e = ctx[0] - prng_rotate(ctx[1], 27); \
ctx[0] = ctx[1] ^ prng_rotate(ctx[2], 17); \
ctx[1] = ctx[2] + ctx[3]; \
ctx[2] = ctx[3] + e; \
ctx[3] = e + ctx[0]; } while(0)
static __thread uint32_t ctx[4], *once = 0; if( !once ) {
uint32_t seed = (uint32_t)( ifdef(win32,_getpid,getpid)() + time(0) + ((uintptr_t)once) );
ctx[0] = 0xf1ea5eed;
ctx[1] = ctx[2] = ctx[3] = seed;
for (int i = 0; i < 31; ++i) {
prng_shuffle();
}
once = ctx;
}
prng_shuffle();
return ctx[3];
}
void entropy( void *buf, unsigned n ) {
for( ; n >= 4 ; n -= 4 ) {
uint32_t a = prng_next();
memcpy(buf, &a, 4);
buf = ((char*)buf) + 4;
}
if( n > 0 ) {
uint32_t a = prng_next();
memcpy(buf, &a, n);
}
}
#endif
#if 0
AUTORUN {
unsigned char buf[128];
entropy(buf, 128);
for( int i = 0; i < 128; ++i ) {
printf("%02x", buf[i]);
}
puts("");
}
#endif
#line 0
#line 1 "v4k_reflect.c"
// C reflection: enums, functions, structs, members and anotations.
// - rlyeh, public domain
//
// @todo: nested structs? pointers in members?
// @todo: declare TYPEDEF(vec3, float[3]), TYPEDEF(mat4, vec4[4]/*float[16]*/)
static map(unsigned, reflect_t) reflects;
static map(unsigned, array(reflect_t)) members;
void reflect_init() {
if(!reflects) map_init_int(reflects);
if(!members) map_init_int(members);
}
AUTORUN {
reflect_init();
}
const char* symbol_naked(const char *s) {
if( strbeg(s, "const ") ) s += 6;
if( strbeg(s, "union ") ) s += 6;
if( strbeg(s, "struct ") ) s += 7;
if(!strstr(s, " *") ) return s;
char *copy = va("%s", s);
do strswap(copy," *","*"); while( strstr(copy, " *") ); // char * -> char*
return (const char*)copy;
}
void type_inscribe(const char *TY,unsigned TYsz,const char *infos) {
reflect_init();
unsigned TYid = intern(TY = symbol_naked(TY));
map_find_or_add(reflects, TYid, ((reflect_t){TYid, 0, TYsz, STRDUP(TY), infos})); // @leak
}
void enum_inscribe(const char *E,unsigned Eval,const char *infos) {
reflect_init();
unsigned Eid = intern(E = symbol_naked(E));
map_find_or_add(reflects, Eid, ((reflect_t){Eid,0, Eval, STRDUP(E),infos})); // @leak
}
unsigned enum_find(const char *E) {
reflect_init();
E = symbol_naked(E);
return map_find(reflects, intern(E))->sz;
}
void function_inscribe(const char *F,void *func,const char *infos) {
reflect_init();
unsigned Fid = intern(F = symbol_naked(F));
map_find_or_add(reflects, Fid, ((reflect_t){Fid,0, 0, STRDUP(F),infos, func})); // @leak
reflect_t *found = map_find(reflects,Fid);
}
void *function_find(const char *F) {
reflect_init();
F = symbol_naked(F);
return map_find(reflects, intern(F))->addr;
}
void struct_inscribe(const char *T,unsigned Tsz,unsigned OBJTYPEid, const char *infos) {
reflect_init();
unsigned Tid = intern(T = symbol_naked(T));
map_find_or_add(reflects, Tid, ((reflect_t){Tid, OBJTYPEid, Tsz, STRDUP(T), infos})); // @leak
}
void member_inscribe(const char *T, const char *M,unsigned Msz, const char *infos, const char *TYPE, unsigned bytes) {
reflect_init();
unsigned Tid = intern(T = symbol_naked(T));
unsigned Mid = intern(M = symbol_naked(M));
unsigned Xid = intern(TYPE = symbol_naked(TYPE));
map_find_or_add(reflects, (Mid<<16)|Tid, ((reflect_t){Mid, 0, Msz, STRDUP(M), infos, NULL, Tid, STRDUP(TYPE) })); // @leak
// add member separately as well
if(!members) map_init_int(members);
array(reflect_t) *found = map_find_or_add(members, Tid, 0);
reflect_t data = {Mid, 0, Msz, STRDUP(M), infos, NULL, Tid, STRDUP(TYPE), bytes }; // @leak
// ensure member has not been added previously
#if 1
// works, without altering member order
reflect_t *index = 0;
for(int i = 0, end = array_count(*found); i < end; ++i) {
if( (*found)[i].id == Mid ) { index = (*found)+i; break; }
}
if( index ) *index = data; else array_push(*found, data);
#else
// works, although members get sorted
array_push(*found, data);
array_sort(*found, less_unsigned_ptr); //< first member type in reflect_t is `unsigned id`, so less_unsigned_ptr works
array_unique(*found, less_unsigned_ptr); //< first member type in reflect_t is `unsigned id`, so less_unsigned_ptr works
#endif
}
reflect_t member_find(const char *T, const char *M) {
reflect_init();
T = symbol_naked(T);
M = symbol_naked(M);
return *map_find(reflects, (intern(M)<<16)|intern(T));
}
void *member_findptr(void *obj, const char *T, const char *M) {
reflect_init();
T = symbol_naked(T);
M = symbol_naked(M);
return (char*)obj + member_find(T,M).sz;
}
array(reflect_t)* members_find(const char *T) {
reflect_init();
T = symbol_naked(T);
return map_find(members, intern(T));
}
static
void ui_reflect_(const reflect_t *R, const char *filter, int mask) {
// debug:
// ui_label(va("name:%s info:'%s' id:%u objtype:%u sz:%u addr:%p parent:%u type:%s\n",
// R->name ? R->name : "", R->info ? R->info : "", R->id, R->objtype, R->sz, R->addr, R->parent, R->type ? R->type : ""));
if( mask == *R->info ) {
static __thread char *buf = 0;
if( buf ) *buf = '\0';
struct nk_context *ui_ctx = (struct nk_context *)ui_handle();
for ui_push_hspace(16) {
array(reflect_t) *T = map_find(members, intern(R->name));
/**/ if( T ) {ui_label(strcatf(&buf,"S struct %s@%s", R->name, R->info+1));
for each_array_ptr(*T, reflect_t, it)
if(strmatchi(it->name,filter)) {
if( !R->type && !strcmp(it->name,R->name) ) // avoid recursion
ui_label(strcatf(&buf,"M %s %s@%s", it->type, it->name, it->info+1));
else
ui_reflect_(it,filter,'M');
}
}
else if( R->addr ) ui_label(strcatf(&buf,"F func %s()@%s", R->name, R->info+1));
else if( !R->parent ) ui_label(strcatf(&buf,"E enum %s = %d@%s", R->name, R->sz, R->info+1));
else ui_label(strcatf(&buf,"M %s %s@%s", R->type, R->name, R->info+1));
}
}
}
API void *ui_handle();
int ui_reflect(const char *filter) {
if( !filter ) filter = "*";
int enabled = ui_enabled();
ui_disable();
// ENUMS, then FUNCTIONS, then STRUCTS
unsigned masks[] = { 'E', 'F', 'S' };
for( int i = 0; i < countof(masks); ++i )
for each_map_ptr(reflects, unsigned, k, reflect_t, R) {
if( strmatchi(R->name, filter)) {
ui_reflect_(R, filter, masks[i]);
}
}
if( enabled ) ui_enable();
return 0;
}
// -- tests
// type0 is reserved (no type)
// type1 reserved for objs
// type2 reserved for entities
// @todo: type3 and 4 likely reserved for components and systems??
// enum { OBJTYPE_vec3 = 0x03 };
AUTOTEST {
// register structs, enums and functions. with and without comments+tags
STRUCT( vec3, float, x );
STRUCT( vec3, float, y );
STRUCT( vec3, float, z, "Up" );
ENUM( IMAGE_RGB );
ENUM( TEXTURE_RGB, "3-channel Red+Green+Blue texture flag" );
ENUM( TEXTURE_RGBA, "4-channel Red+Green+Blue+Alpha texture flag" );
FUNCTION( puts );
FUNCTION( printf, "function that prints formatted text to stdout" );
// verify some reflected infos
test( function_find("puts") == puts );
test( function_find("printf") == printf );
test( enum_find("TEXTURE_RGB") == TEXTURE_RGB );
test( enum_find("TEXTURE_RGBA") == TEXTURE_RGBA );
// iterate reflected struct
for each_member("vec3", R) {
//printf("+%s vec3.%s (+%x) // %s\n", R->type, R->name, R->member_offset, R->info);
}
//reflect_print("puts");
//reflect_print("TEXTURE_RGBA");
//reflect_print("vec3");
//reflect_dump("*");
}
#line 0
#line 1 "v4k_render.c"
// -----------------------------------------------------------------------------
// opengl
#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0
#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
#define GL_DEBUG_SEVERITY_HIGH 0x9146
#define GL_DEBUG_SEVERITY_NOTIFICATION 0x826B
#define GL_DEBUG_SOURCE_API 0x8246
#define GL_DEBUG_TYPE_ERROR 0x824C
//
void glDebugCallback(uint32_t source, uint32_t type, uint32_t id, uint32_t severity, int32_t length, const char * message, void * userdata) {
// whitelisted codes (also: 131169, 131204).
if( id == 131154 ) return; // Pixel-path performance warning: Pixel transfer is synchronized with 3D rendering.
if( id == 131185 ) return; // Buffer object 2 (bound to GL_ELEMENT_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as the source for buffer object operations
if( id == 131218 ) return; // Program/shader state performance warning: Vertex shader in program 9 is being recompiled based on GL state.
if( id == 2 ) return; // INFO: API_ID_RECOMPILE_FRAGMENT_SHADER performance warning has been generated. Fragment shader recompiled due to state change. [ID: 2]
const char * GL_ERROR_TYPE[] = { "ERROR", "DEPRECATED BEHAVIOR", "UNDEFINED DEHAVIOUR", "PORTABILITY", "PERFORMANCE", "OTHER" };
const char * GL_ERROR_SOURCE[] = { "API", "WINDOW SYSTEM", "SHADER COMPILER", "THIRD PARTY", "APPLICATION", "OTHER" };
const char * GL_ERROR_SEVERITY[] = { "HIGH", "MEDIUM", "LOW", "NOTIFICATION" };
type = type - GL_DEBUG_TYPE_ERROR;
source = source - GL_DEBUG_SOURCE_API;
severity = severity == GL_DEBUG_SEVERITY_NOTIFICATION ? 3 : severity - GL_DEBUG_SEVERITY_HIGH;
if(severity >= 2) return; // do not log low_severity or notifications
PRINTF( "!%s:%s [ID: %u]\n", type == 0 ? "ERROR":"WARNING", message, id );
// PANIC( "!%s:%s [ID: %u]\n", type == 0 ? "ERROR":"WARNING", message, id );
}
void glDebugEnable() {
do_once {
typedef void (*GLDEBUGPROC)(uint32_t, uint32_t, uint32_t, uint32_t, int32_t, const char *, const void *);
typedef void (*GLDEBUGMESSAGECALLBACKPROC)(GLDEBUGPROC, const void *);
void *func = glfwGetProcAddress("glDebugMessageCallback");
void (*glDebugMessageCallback)(GLDEBUGPROC, const void *) = (GLDEBUGMESSAGECALLBACKPROC)func;
if( func ) {
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
glDebugMessageCallback((GLDEBUGPROC)glDebugCallback, NULL);
}
}
}
static
void glCopyBackbufferToTexture( texture_t *tex ) { // unused
glActiveTexture( GL_TEXTURE0 + texture_unit() );
glBindTexture( GL_TEXTURE_2D, tex->id );
glCopyTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, 0, 0, window_width(), window_height(), 0 );
}
// ----------------------------------------------------------------------------
// renderstate
renderstate_t renderstate() {
renderstate_t state = {0};
// Set default clear color to black
state.clear_color[0] = 0.0f; // Red
state.clear_color[1] = 0.0f; // Green
state.clear_color[2] = 0.0f; // Blue
state.clear_color[3] = 1.0f; // Alpha
// Set default color mask to GL_TRUE
state.color_mask[0] = GL_TRUE;
state.color_mask[1] = GL_TRUE;
state.color_mask[2] = GL_TRUE;
state.color_mask[3] = GL_TRUE;
// Set default clear depth to maximum distance
state.clear_depth = 1.0;
// Enable depth test by default with less or equal function
state.depth_test_enabled = GL_TRUE;
state.depth_write_enabled = GL_TRUE;
state.depth_func = GL_LEQUAL;
// Disable polygon offset by default
state.polygon_offset_enabled = GL_FALSE;
state.polygon_offset_factor = 0.0f;
state.polygon_offset = 0.0f;
// Disable blending by default
state.blend_enabled = GL_FALSE;
state.blend_func = GL_FUNC_ADD;
state.blend_src = GL_ONE;
state.blend_dst = GL_ZERO;
// Disable culling by default but cull back faces
state.cull_face_enabled = GL_FALSE;
state.cull_face_mode = GL_BACK;
// Disable stencil test by default
state.stencil_test_enabled = GL_FALSE;
state.stencil_func = GL_ALWAYS;
state.stencil_op_fail = GL_KEEP;
state.stencil_op_zfail = GL_KEEP;
state.stencil_op_zpass = GL_KEEP;
state.stencil_ref = 0;
state.stencil_read_mask = 0xFFFFFFFF;
state.stencil_write_mask = 0xFFFFFFFF;
// Set default front face to counter-clockwise
state.front_face = GL_CCW;
// Set default line width
state.line_smooth_enabled = GL_FALSE;
state.line_width = 1.0f;
// Set default point size
state.point_size_enabled = GL_FALSE;
state.point_size = 1.0f;
// Set default polygon mode to fill
state.polygon_mode_face = GL_FRONT_AND_BACK;
state.polygon_mode_draw = GL_FILL;
// Disable scissor test by default
state.scissor_test_enabled = GL_FALSE;
return state;
}
bool renderstate_compare(const renderstate_t *stateA, const renderstate_t *stateB) {
return memcmp(stateA, stateB, sizeof(renderstate_t)) == 0;
}
static renderstate_t last_rs;
void renderstate_apply(const renderstate_t *state) {
if (state != NULL) {
// Compare renderstates and bail if they are the same
if (renderstate_compare(state, &last_rs)) {
return;
}
// Store renderstate
last_rs = *state;
// Apply clear color
glClearColor(state->clear_color[0], state->clear_color[1], state->clear_color[2], state->clear_color[3]);
// Apply color mask
glColorMask(state->color_mask[0], state->color_mask[1], state->color_mask[2], state->color_mask[3]);
// Apply clear depth
glClearDepth(state->clear_depth);
// Apply depth test
if (state->depth_test_enabled) {
glEnable(GL_DEPTH_TEST);
glDepthFunc(state->depth_func);
} else {
glDisable(GL_DEPTH_TEST);
}
// Apply polygon offset
if (state->polygon_offset_enabled) {
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(state->polygon_offset_factor, state->polygon_offset);
} else {
glDisable(GL_POLYGON_OFFSET_FILL);
}
// Apply depth write
glDepthMask(state->depth_write_enabled);
// Apply blending
if (state->blend_enabled) {
glEnable(GL_BLEND);
glBlendEquation(state->blend_func);
glBlendFunc(state->blend_src, state->blend_dst);
} else {
glDisable(GL_BLEND);
}
// Apply culling @fixme
if (state->cull_face_enabled) {
glEnable(GL_CULL_FACE);
glCullFace(state->cull_face_mode);
} else {
glDisable(GL_CULL_FACE);
}
// Apply stencil test
if (state->stencil_test_enabled) {
glEnable(GL_STENCIL_TEST);
glStencilMask(state->stencil_write_mask);
glStencilFunc(state->stencil_func, state->stencil_ref, state->stencil_read_mask);
glStencilOp(state->stencil_op_fail, state->stencil_op_zfail, state->stencil_op_zpass);
} else {
glDisable(GL_STENCIL_TEST);
}
// Apply front face direction @fixme
glFrontFace(state->front_face);
// Apply line width
glLineWidth(state->line_width);
// Apply smooth lines
if (state->line_smooth_enabled) {
glEnable(GL_LINE_SMOOTH);
} else {
glDisable(GL_LINE_SMOOTH);
}
#if !is(ems)
// Apply point size
if (state->point_size_enabled) {
glEnable(GL_PROGRAM_POINT_SIZE);
glPointSize(state->point_size);
} else {
glDisable(GL_PROGRAM_POINT_SIZE);
}
// Apply polygon mode
glPolygonMode(state->polygon_mode_face, state->polygon_mode_draw);
#endif
// Apply scissor test
if (state->scissor_test_enabled) {
glEnable(GL_SCISSOR_TEST);
} else {
glDisable(GL_SCISSOR_TEST);
}
}
}
// ----------------------------------------------------------------------------
// shaders
void shader_print(const char *source) {
for(int line = 0, i = 0; source[i] > 0; ) {
printf("\t%03d: ", line+1);
while( source[i] >= 32 || source[i] == '\t' ) fputc(source[i++], stdout);
while( source[i] > 0 && source[i] < 32 ) line += source[i++] == '\n';
puts("");
}
}
// sorted by shader handle. an array of properties per shader. properties are plain strings.
static __thread map(unsigned, array(char*)) shader_reflect;
static
GLuint shader_compile( GLenum type, const char *source ) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, (const char **)&source, NULL);
glCompileShader(shader);
GLint status = GL_FALSE, length;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if( status == GL_FALSE ) {
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
// ASSERT(length < 2048); char buf[2048] = { 0 };
char *buf = stack(length+1);
glGetShaderInfoLog(shader, length, NULL, buf);
// dump log with line numbers
shader_print( source );
PANIC("!ERROR: shader_compile(): %s\n%s\n", type == GL_VERTEX_SHADER ? "Vertex" : "Fragment", buf);
return 0;
}
return shader;
}
unsigned shader(const char *vs, const char *fs, const char *attribs, const char *fragcolor, const char *defines){
return shader_geom(NULL, vs, fs, attribs, fragcolor, defines);
}
2024-08-24 13:04:33 +00:00
static inline
char *shader_process_includes(const char *src) {
if (!src) return NULL;
char *includes = NULL;
for each_substring(src, "\n", line) {
if (line[0] == '#' && strstri(line, "#include")) {
const char *start = strstri(line, "\"");
const char *end = strstri(start+1, "\"");
if (start && end) {
char *filename = va("%.*s", (int)(end-start-1), start+1);
char *included = vfs_read(filename);
if (included) {
char *nested_includes = shader_process_includes(included);
includes = strcatf(&includes, "%s\n", nested_includes ? nested_includes : ""); //@leak
} else {
PANIC("!ERROR: shader(): Include file not found: %s\n", filename);
}
} else {
PANIC("!ERROR: shader(): Invalid #include directive: %s\n", line);
}
} else
{
includes = strcatf(&includes, "\n%s", line); //@leak
}
}
return includes;
}
2024-08-19 07:54:01 +00:00
static inline
char *shader_preprocess(const char *src, const char *defines) {
if (!src) return NULL;
const char *gles = "#version 300 es\n"
"#define textureQueryLod(t,uv) vec2(0.,0.)\n" // "#extension GL_EXT_texture_query_lod : enable\n"
"#define MEDIUMP mediump\n"
"precision MEDIUMP float;\n";
2024-08-24 16:03:23 +00:00
char *processed_src = shader_process_includes(src);
const char *desktop = strstr(processed_src, "textureQueryLod") ? "#version 400\n#define MEDIUMP\n" : "#version 330\n#define MEDIUMP\n";
2024-08-19 07:54:01 +00:00
const char *glsl_version = ifdef(ems, gles, desktop);
// detect GLSL version if set
2024-08-24 16:03:23 +00:00
if (processed_src[0] == '#' && processed_src[1] == 'v') {
2024-08-19 07:54:01 +00:00
#if 0
const char *end = strstri(src, "\n");
glsl_version = va("%.*s", (int)(end-src), src);
src = end+1;
#else
PANIC("!ERROR: shader with #version specified on it. we do not support this anymore.");
#endif
}
2024-08-24 13:04:33 +00:00
return va("%s\n%s\n%s", glsl_version, defines ? defines : "", processed_src);
2024-08-19 07:54:01 +00:00
}
unsigned shader_geom(const char *gs, const char *vs, const char *fs, const char *attribs, const char *fragcolor, const char *defines) {
PRINTF(/*"!"*/"Compiling shader\n");
char *glsl_defines = "";
if( defines ) {
for each_substring(defines, ",", def) {
glsl_defines = va("%s#define %s\n", glsl_defines, def);
}
}
if(gs)
gs = shader_preprocess(gs, glsl_defines);
vs = shader_preprocess(vs, glsl_defines);
fs = shader_preprocess(fs, glsl_defines);
GLuint vert = shader_compile(GL_VERTEX_SHADER, vs);
GLuint frag = shader_compile(GL_FRAGMENT_SHADER, fs);
GLuint geom = 0; if (gs) geom = shader_compile(GL_GEOMETRY_SHADER, gs);
GLuint program = 0;
if( vert && frag ) {
program = glCreateProgram();
glAttachShader(program, vert);
glAttachShader(program, frag);
if (geom) glAttachShader(program, geom);
for( int i = 0; attribs && attribs[0]; ++i ) {
char attrib[128] = {0};
sscanf(attribs, "%127[^,]", attrib);
while( attribs[0] && attribs[0] != ',' ) { attribs++; }
while( attribs[0] && attribs[0] == ',' ) { attribs++; break; }
if(!attrib[0]) continue;
glBindAttribLocation(program, i, attrib);
// PRINTF("Shader.attribute[%d]=%s\n", i, attrib);
}
#if !is(ems) // @fixme
if(fragcolor)
glBindFragDataLocation(program, 0, fragcolor);
#endif
glLinkProgram(program);
GLint status = GL_FALSE, length;
glGetProgramiv(program, GL_LINK_STATUS, &status);
#ifdef DEBUG_SHADER
if (status != GL_FALSE && program == DEBUG_SHADER) {
#else
if (status == GL_FALSE) {
#endif
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
// ASSERT(length < 2048); char buf[2048] = { 0 };
char *buf = stack(length+1);
glGetProgramInfoLog(program, length, NULL, buf);
puts("--- vs:");
shader_print(vs);
puts("--- fs:");
shader_print(fs);
if (geom) {
puts("--- gs:");
shader_print(gs);
}
}
if (status == GL_FALSE) {
PANIC("ERROR: shader(): Shader/program link: %s\n", buf);
return 0;
}
glDeleteShader(vert);
glDeleteShader(frag);
if (geom) glDeleteShader(geom);
//#ifdef DEBUG_ANY_SHADER
// PRINTF("Shader #%d:\n", program);
// shader_print(vs);
// shader_print(fs);
//#endif
}
/*
if( s->program ) {
strcatf(&s->name, "// vs (%s)\n%s\n\n\n", file_vs, vs);
strcatf(&s->name, "// fs (%s)\n%s\n\n\n", file_fs, fs);
}
*/
// shader compiled fine, before returning, let's parse the source and reflect the uniforms
array(char*) props = 0;
do_once map_init_int( shader_reflect );
if(vs) for each_substring(vs, "\r\n", line) {
const char *found = strstr(line, "/""//");
if( found > line && line[0] == '/' && line[1] == '/' ) continue;
if( found ) array_push(props, STRDUP(line));
}
if(fs) for each_substring(fs, "\r\n", line) {
const char *found = strstr(line, "/""//");
if( found > line && line[0] == '/' && line[1] == '/' ) continue;
if( found ) array_push(props, STRDUP(line));
}
if(gs) for each_substring(gs, "\r\n", line) {
const char *found = strstr(line, "/""//");
if( found > line && line[0] == '/' && line[1] == '/' ) continue;
if( found ) array_push(props, STRDUP(line));
}
if( props ) {
map_insert(shader_reflect, program, props);
for( int i = 0; i < array_count(props); ++i ) shader_apply_param(program, i);
}
return program;
}
unsigned shader_properties(unsigned shader) {
array(char*) *found = map_find(shader_reflect, shader);
return found ? array_count(*found) : 0;
}
char** shader_property(unsigned shader, unsigned property) {
array(char*) *found = map_find(shader_reflect, shader);
return found && property < array_count(*found) ? &(*found)[property] : NULL;
}
void shader_apply_param(unsigned shader, unsigned param_no) {
unsigned num_properties = shader_properties(shader);
if( param_no < num_properties ) {
char *buf = *shader_property(shader, param_no);
char type[32], name[32], line[128]; snprintf(line, 127, "%s", buf);
if( sscanf(line, "%*s %s %[^ =;/]", type, name) != 2 ) return;
char *mins = strstr(line, "min:");
char *sets = strstr(line, "set:");
char *maxs = strstr(line, "max:");
char *tips = strstr(line, "tip:");
if( mins ) *mins = 0, mins += 4;
if( sets ) *sets = 0, sets += 4;
if( maxs ) *maxs = 0, maxs += 4;
if( tips ) *tips = 0, tips += 4;
int is_color = !!strstri(name, "color"), top = is_color ? 1 : 10;
vec4 minv = mins ? atof4(mins) : vec4(0,0,0,0);
vec4 setv = sets ? atof4(sets) : vec4(0,0,0,0);
vec4 maxv = maxs ? atof4(maxs) : vec4(top,top,top,top);
if(minv.x > maxv.x) swapf(&minv.x, &maxv.x);
if(minv.y > maxv.y) swapf(&minv.y, &maxv.y);
if(minv.z > maxv.z) swapf(&minv.z, &maxv.z);
if(minv.w > maxv.w) swapf(&minv.w, &maxv.w);
if( !maxs ) {
if(setv.x > maxv.x) maxv.x = setv.x;
if(setv.y > maxv.y) maxv.y = setv.y;
if(setv.z > maxv.z) maxv.z = setv.z;
if(setv.w > maxv.w) maxv.w = setv.w;
}
setv = clamp4(setv, minv, maxv);
if( strchr("ibfv", type[0]) ) {
GLint shader_bak; glGetIntegerv(GL_CURRENT_PROGRAM, &shader_bak);
glUseProgram(shader);
/**/ if(type[0] == 'i') glUniform1i(glGetUniformLocation(shader, name), setv.x);
else if(type[0] == 'b') glUniform1i(glGetUniformLocation(shader, name), !!setv.x);
else if(type[0] == 'f') glUniform1f(glGetUniformLocation(shader, name), setv.x);
else if(type[3] == '2') glUniform2fv(glGetUniformLocation(shader, name), 1, &setv.x);
else if(type[3] == '3') glUniform3fv(glGetUniformLocation(shader, name), 1, &setv.x);
else if(type[3] == '4') glUniform4fv(glGetUniformLocation(shader, name), 1, &setv.x);
glUseProgram(shader_bak);
}
}
}
void shader_apply_params(unsigned shader, const char *parameter_mask) {
unsigned num_properties = shader_properties(shader);
for( unsigned i = 0; i < num_properties; ++i ) {
char *line = *shader_property(shader,i);
char name[32];
if( sscanf(line, "%*s %*s %s", name) != 1 ) continue;
if( !strmatch(name, parameter_mask) ) continue;
shader_apply_param(shader, i);
}
}
int ui_shader(unsigned shader) {
int changed = 0;
unsigned num_properties = shader_properties(shader);
for( unsigned i = 0; i < num_properties; ++i ) {
char **ptr = shader_property(shader,i);
char line[128]; snprintf(line, 127, "%s", *ptr); // debug: ui_label(line);
char uniform[32], type[32], name[32], early_exit = '\0';
if( sscanf(line, "%s %s %[^ =;/]", uniform, type, name) != 3 ) continue; // @todo optimize: move to shader()
char *mins = strstr(line, "min:");
char *sets = strstr(line, "set:");
char *maxs = strstr(line, "max:");
char *tips = strstr(line, "tip:");
if( mins ) *mins = 0, mins += 4;
if( sets ) *sets = 0, sets += 4;
if( maxs ) *maxs = 0, maxs += 4;
if( tips ) *tips = 0, tips += 4;
if( strcmp(uniform, "uniform") && strcmp(uniform, "}uniform") ) { if(tips) ui_label(va(ICON_MD_INFO "%s", tips)); continue; } // @todo optimize: move to shader()
int is_color = !!strstri(name, "color"), top = is_color ? 1 : 10;
vec4 minv = mins ? atof4(mins) : vec4(0,0,0,0);
vec4 setv = sets ? atof4(sets) : vec4(0,0,0,0);
vec4 maxv = maxs ? atof4(maxs) : vec4(top,top,top,top);
char *label = !tips ? va("%c%s", name[0] - 32 * !!(name[0] >= 'a'), name+1) :
va("%c%s " ICON_MD_INFO "@%s", name[0] - 32 * !!(name[0] >= 'a'), name+1, tips);
if(minv.x > maxv.x) swapf(&minv.x, &maxv.x); // @optimize: move to shader()
if(minv.y > maxv.y) swapf(&minv.y, &maxv.y); // @optimize: move to shader()
if(minv.z > maxv.z) swapf(&minv.z, &maxv.z); // @optimize: move to shader()
if(minv.w > maxv.w) swapf(&minv.w, &maxv.w); // @optimize: move to shader()
if( !maxs ) {
if(setv.x > maxv.x) maxv.x = setv.x;
if(setv.y > maxv.y) maxv.y = setv.y;
if(setv.z > maxv.z) maxv.z = setv.z;
if(setv.w > maxv.w) maxv.w = setv.w;
}
setv = clamp4(setv, minv, maxv);
// supports int,float,vec2/3/4,color3/4
int touched = 0;
if( type[0] == 'b' ) {
bool v = !!setv.x;
if( (touched = ui_bool(label, &v)) != 0 ) {
setv.x = v;
}
}
else if( type[0] == 'i' ) {
int v = setv.x;
if( (touched = ui_int(label, &v)) != 0 ) {
setv.x = clampi(v, minv.x, maxv.x); // min..max range
}
}
else if( type[0] == 'f' ) {
setv.x = clampf(setv.x, minv.x, maxv.x);
char *caption = va("%5.3f", setv.x);
setv.x = (setv.x - minv.x) / (maxv.x - minv.x);
if( (touched = ui_slider2(label, &setv.x, caption)) != 0 ) {
setv.x = clampf(minv.x + setv.x * (maxv.x-minv.x), minv.x, maxv.x); // min..max range
}
}
else if( type[0] == 'v' && type[3] == '2' ) {
setv.xy = clamp2(setv.xy,minv.xy,maxv.xy);
if( (touched = ui_float2(label, &setv.x)) != 0 ) {
setv.xy = clamp2(setv.xy,minv.xy,maxv.xy);
}
}
else if( type[0] == 'v' && type[3] == '3' ) {
setv.xyz = clamp3(setv.xyz,minv.xyz,maxv.xyz);
if( (touched = (is_color ? ui_color3f : ui_float3)(label, &setv.x)) != 0 ) {
setv.xyz = clamp3(setv.xyz,minv.xyz,maxv.xyz);
}
}
else if( type[0] == 'v' && type[3] == '4' ) {
setv = clamp4(setv,minv,maxv);
if( (touched = (is_color ? ui_color4f : ui_float4)(label, &setv.x)) != 0 ) {
setv = clamp4(setv,minv,maxv);
}
}
else if( tips ) ui_label( tips );
if( touched ) {
// upgrade value
*ptr = FREE(*ptr);
*ptr = stringf("%s %s %s ///set:%s min:%s max:%s tip:%s", uniform,type,name,ftoa4(setv),ftoa4(minv),ftoa4(maxv),tips?tips:"");
// apply
shader_apply_param(shader, i);
changed = 1;
}
}
return changed;
}
int ui_shaders() {
if( !map_count(shader_reflect) ) return ui_label(ICON_MD_WARNING " No shaders with annotations loaded."), 0;
int changed = 0;
for each_map_ptr(shader_reflect, unsigned, k, array(char*), v) {
int open = 0, clicked_or_toggled = 0;
char *id = va("##SHD%d", *k);
char *title = va("Shader %d", *k);
for( int p = (open = ui_collapse(title, id)), dummy = (clicked_or_toggled = ui_collapse_clicked()); p; ui_collapse_end(), p = 0) {
ui_label(va("Shader %d",*k));
changed |= ui_shader(*k);
}
}
return changed;
}
unsigned compute(const char *cs){
#if is(ems)
return 0;
#else
PRINTF(/*"!"*/"Compiling compute shader\n");
cs = cs[0] == '#' && cs[1] == 'c' ? cs : va("#version 450 core\n%s", cs ? cs : "");
GLuint comp = shader_compile(GL_COMPUTE_SHADER, cs);
GLuint program = 0;
if( comp ) {
program = glCreateProgram();
glAttachShader(program, comp);
glLinkProgram(program);
GLint status = GL_FALSE, length;
glGetProgramiv(program, GL_LINK_STATUS, &status);
#ifdef DEBUG_SHADER
if (status != GL_FALSE && program == DEBUG_SHADER) {
#else
if (status == GL_FALSE) {
#endif
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
char *buf = stack(length+1);
glGetProgramInfoLog(program, length, NULL, buf);
puts("--- cs:");
shader_print(cs);
}
if (status == GL_FALSE) {
PANIC("ERROR: shader(): Shader/program link: %s\n", buf);
return 0;
}
glDeleteShader(comp);
}
return program;
#endif
}
void compute_dispatch(unsigned wx, unsigned wy, unsigned wz){
glDispatchCompute(wx, wy, wz);
}
void write_barrier(){
glMemoryBarrier(GL_ALL_BARRIER_BITS);
}
void write_barrier_image(){
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}
void shader_destroy(unsigned program){
if( program == ~0u ) return;
glDeleteProgram(program);
// if(s->name) FREE(s->name), s->name = NULL;
}
unsigned ssbo_create(const void *data, int len, unsigned usage){
static GLuint gl_usage[] = { GL_STATIC_DRAW, GL_STATIC_READ, GL_STATIC_COPY, GL_DYNAMIC_DRAW, GL_DYNAMIC_READ, GL_DYNAMIC_COPY, GL_STREAM_DRAW, GL_STREAM_READ, GL_STREAM_COPY };
GLuint ssbo;
glGenBuffers(1, &ssbo);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBufferData(GL_SHADER_STORAGE_BUFFER, len, data, gl_usage[usage]);
return ssbo;
}
void ssbo_destroy(unsigned ssbo){
glDeleteBuffers(1, &ssbo);
}
void ssbo_update(int offset, int len, const void *data){
glBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, len, data);
}
void *ssbo_map(unsigned access){
static GLenum gl_access[] = {GL_READ_ONLY, GL_WRITE_ONLY, GL_READ_WRITE};
return glMapBuffer(GL_SHADER_STORAGE_BUFFER, gl_access[access]);
}
void ssbo_unmap(){
glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
}
void ssbo_bind(unsigned ssbo, unsigned unit){
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, unit, ssbo);
}
void ssbo_unbind(){
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
}
static __thread unsigned last_shader = -1;
int shader_uniform(const char *name) {
return glGetUniformLocation(last_shader, name);
}
unsigned shader_get_active() { return last_shader; }
unsigned shader_bind(unsigned program) { unsigned ret = last_shader; return glUseProgram(last_shader = program), ret; }
static inline void shader_int_(int uniform, int i) { glUniform1i(uniform, i); }
static inline void shader_float_(int uniform, float f) { glUniform1f(uniform, f); }
static inline void shader_vec2_(int uniform, vec2 v) { glUniform2fv(uniform, 1, &v.x); }
static inline void shader_vec3_(int uniform, vec3 v) { glUniform3fv(uniform, 1, &v.x); }
static inline void shader_vec3v_(int uniform, int count, vec3 *v) { glUniform3fv(uniform, count, &v[0].x); }
static inline void shader_vec4_(int uniform, vec4 v) { glUniform4fv(uniform, 1, &v.x); }
static inline void shader_mat44_(int uniform, mat44 m) { glUniformMatrix4fv(uniform, 1, GL_FALSE/*GL_TRUE*/, m); }
static inline void shader_cubemap_(int sampler, unsigned texture) {
int id = texture_unit();
glUniform1i(sampler, id);
glActiveTexture(GL_TEXTURE0 + id);
glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
}
static inline void shader_bool_(int uniform, bool x) { glUniform1i(uniform, x); }
static inline void shader_uint_(int uniform, unsigned x ) { glUniform1ui(uniform, x); }
static inline void shader_texture_unit_(int sampler, unsigned id, unsigned unit) {
glUniform1i(sampler, unit);
glActiveTexture(GL_TEXTURE0 + unit);
glBindTexture(GL_TEXTURE_2D, id);
}
static inline void shader_texture_(int sampler, texture_t t) { shader_texture_unit_(sampler, t.id, texture_unit()); }
// public api
void shader_int(const char *uniform, int i) { glUniform1i(shader_uniform(uniform), i); }
void shader_float(const char *uniform, float f) { glUniform1f(shader_uniform(uniform), f); }
void shader_vec2(const char *uniform, vec2 v) { glUniform2fv(shader_uniform(uniform), 1, &v.x); }
void shader_vec3(const char *uniform, vec3 v) { glUniform3fv(shader_uniform(uniform), 1, &v.x); }
void shader_vec3v(const char *uniform, int count, vec3 *v) { glUniform3fv(shader_uniform(uniform), count, &v[0].x); }
void shader_vec4(const char *uniform, vec4 v) { glUniform4fv(shader_uniform(uniform), 1, &v.x); }
void shader_mat44(const char *uniform, mat44 m) { glUniformMatrix4fv(shader_uniform(uniform), 1, GL_FALSE/*GL_TRUE*/, m); }
void shader_cubemap(const char *sampler, unsigned texture) {
int id = texture_unit();
glUniform1i(shader_uniform(sampler), id);
glActiveTexture(GL_TEXTURE0 + id);
glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
}
void shader_bool(const char *uniform, bool x) { glUniform1i(shader_uniform(uniform), x); }
void shader_uint(const char *uniform, unsigned x ) { glUniform1ui(shader_uniform(uniform), x); }
void shader_texture(const char *sampler, texture_t t) { shader_texture_unit(sampler, t.id, texture_unit()); }
void shader_texture_unit(const char *sampler, unsigned id, unsigned unit) {
glUniform1i(shader_uniform(sampler), unit);
glActiveTexture(GL_TEXTURE0 + unit);
glBindTexture(GL_TEXTURE_2D, id);
}
void shader_image(texture_t t, unsigned unit, unsigned level, int layer /* -1 to disable layered access */, unsigned access){
shader_image_unit(t.id, unit, level, layer, t.texel_type, access);
}
void shader_image_unit(unsigned texture, unsigned unit, unsigned level, int layer, unsigned texel_type, unsigned access){
static GLenum gl_access[] = {GL_READ_ONLY, GL_WRITE_ONLY, GL_READ_WRITE};
glBindImageTexture(unit, texture, level, layer!=-1, layer!=-1?layer:0, gl_access[access], texel_type);
}
void shader_colormap(const char *name, colormap_t c ) {
// assumes shader uses `struct { vec4 color; bool has_tex } name + sampler2D name_tex;`
shader_vec4( va("%s.color", name), c.color );
shader_bool( va("%s.has_tex", name), c.texture != NULL );
if( c.texture ) shader_texture( va("%s_tex", name), *c.texture );
}
// -----------------------------------------------------------------------------
// colors
unsigned rgba( uint8_t r, uint8_t g, uint8_t b, uint8_t a ) {
return (unsigned)a << 24 | b << 16 | g << 8 | r;
}
unsigned bgra( uint8_t b, uint8_t g, uint8_t r, uint8_t a ) {
return rgba(r,g,b,a);
}
unsigned alpha( unsigned rgba ) {
return rgba >> 24;
}
unsigned rgbaf(float r, float g, float b, float a) {
return rgba(r * 255, g * 255, b * 255, a * 255);
}
unsigned bgraf(float b, float g, float r, float a) {
return rgba(r * 255, g * 255, b * 255, a * 255);
}
unsigned atorgba(const char *s) {
if( s[0] != '#' ) return 0;
unsigned r = 0, g = 0, b = 0, a = 255;
int slen = strspn(s+1, "0123456789abcdefABCDEF");
if( slen > 8 ) slen = 8;
/**/ if( slen == 6 ) sscanf(s+1, "%2x%2x%2x", &r,&g,&b);
else if( slen == 8 ) sscanf(s+1, "%2x%2x%2x%2x", &r,&g,&b,&a);
else if( slen == 3 ) sscanf(s+1, "%1x%1x%1x", &r,&g,&b ), r=r<<4|r,g=g<<4|g,b=b<<4|b;
else if( slen == 4 ) sscanf(s+1, "%1x%1x%1x%1x", &r,&g,&b,&a), r=r<<4|r,g=g<<4|g,b=b<<4|b,a=a<<4|a;
return rgba(r,g,b,a);
}
char *rgbatoa(unsigned rgba) {
unsigned a = rgba >> 24;
unsigned b =(rgba >> 16) & 255;
unsigned g =(rgba >> 8) & 255;
unsigned r = rgba & 255;
char *s = va("# ");
sprintf(s+1, "%02x%02x%02x%02x", r,g,b,a);
return s;
}
// -----------------------------------------------------------------------------
// images
image_t image_create(int x, int y, int flags) {
int n = 3; // defaults to RGB
if(flags & IMAGE_R) n = 1;
if(flags & IMAGE_RG) n = 2;
if(flags & IMAGE_RGB) n = 3;
if(flags & IMAGE_RGBA) n = 4;
image_t img; img.x = x; img.y = y; img.n = n;
img.pixels = REALLOC(0, x * y * n ); // @fixme: image_destroy() requires stbi allocator to match REALLOC
return img;
}
image_t image_from_mem(const void *data, int size, int flags) {
image_t img = {0};
if( data && size ) {
stbi_set_flip_vertically_on_load(flags & IMAGE_FLIP ? 1 : 0);
int n = 0;
if(flags & IMAGE_R) n = 1;
if(flags & IMAGE_RG) n = 2;
if(flags & IMAGE_RGB) n = 3;
if(flags & IMAGE_RGBA) n = 4;
if(flags & IMAGE_FLOAT)
img.pixels = stbi_loadf_from_memory((const stbi_uc*)data, size, (int*)&img.x,(int*)&img.y,(int*)&img.n, n);
else
img.pixels = stbi_load_from_memory((const stbi_uc*)data, size, (int*)&img.x,(int*)&img.y,(int*)&img.n, n);
if( img.pixels ) {
PRINTF("Loaded image (%dx%d %.*s->%.*s)\n",img.w,img.h,img.n,"RGBA",n?n:img.n,"RGBA");
} else {
// PANIC("Error loading image (%s)\n", pathfile);
}
img.n = n ? n : img.n;
}
return img;
}
image_t image(const char *pathfile, int flags) {
//const char *fname = vfs_remap(pathfile);
// if( !fname[0] ) fname = vfs_remap(va("%s.png",pathfile)); // needed?
// if( !fname[0] ) fname = vfs_remap(va("%s.jpg",pathfile)); // needed?
// if( !fname[0] ) fname = vfs_remap(va("%s.tga",pathfile)); // needed?
// if( !fname[0] ) fname = vfs_remap(va("%s.jpg.png",pathfile)); // needed?
// if( !fname[0] ) fname = vfs_remap(va("%s.tga.png",pathfile)); // needed?
// if( !fname[0] ) fname = vfs_remap(va("%s.png.jpg",pathfile)); // needed?
// if( !fname[0] ) fname = vfs_remap(va("%s.tga.jpg",pathfile)); // needed?
int size = 0;
char *data = vfs_load(pathfile, &size);
return image_from_mem(data, size, flags);
}
void image_destroy(image_t *img) {
if(img->pixels) stbi_image_free(img->pixels);
img->pixels = 0; // *img = (image_t){0}; // do not clear fields yet. might be useful in the future.
}
// bilinear interpolation (uv must be in image coords, range [0..w-1,0..h-1])
static
vec3 bilinear(image_t in, vec2 uv) { // image_bilinear_pixel() ?
float w = in.x, h = in.y, u = uv.x, v = uv.y;
float u1 = (int)u, v1 = (int)v, u2 = minf(u1+1, w-1), v2 = minf(v1+1, h-1);
float c1 = u - u1, c2 = v - v1;
uint8_t *p1 = &in.pixels8[ in.n * (int)(u1 + v1 * in.w) ];
uint8_t *p2 = &in.pixels8[ in.n * (int)(u2 + v1 * in.w) ];
uint8_t *p3 = &in.pixels8[ in.n * (int)(u1 + v2 * in.w) ];
uint8_t *p4 = &in.pixels8[ in.n * (int)(u2 + v2 * in.w) ];
vec3 A = vec3( p1[0], p1[1], p1[2] );
vec3 B = vec3( p2[0], p2[1], p2[2] );
vec3 C = vec3( p3[0], p3[1], p3[2] );
vec3 D = vec3( p4[0], p4[1], p4[2] );
return mix3(mix3(A, B, c1), mix3(C, D, c1), c2);
}
// -----------------------------------------------------------------------------
// textures
int texture_unit() {
static int textureUnit = 0, totalTextureUnits = 0;
do_once glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &totalTextureUnits);
// ASSERT(textureUnit < totalTextureUnits, "%d texture units exceeded", totalTextureUnits);
return textureUnit++ % totalTextureUnits;
}
unsigned texture_update(texture_t *t, unsigned w, unsigned h, unsigned n, const void *pixels, int flags) {
if( t && !t->id ) {
glGenTextures( 1, &t->id );
return texture_update(t, w, h, n, pixels, flags);
}
ASSERT( t && t->id );
ASSERT( n <= 4 );
GLuint pixel_types[] = { GL_RED, GL_RED, GL_RG, GL_RGB, GL_RGBA, GL_R32F, GL_R32F, GL_RG32F, GL_RGB32F, GL_RGBA32F };
GLenum pixel_storage = flags & TEXTURE_FLOAT ? GL_FLOAT : GL_UNSIGNED_BYTE;
GLuint pixel_type = pixel_types[ n ];
GLuint texel_type = t->texel_type = pixel_types[ n + 5 * !!(flags & TEXTURE_FLOAT) ];
GLenum wrap = GL_CLAMP_TO_EDGE;
GLenum min_filter = GL_NEAREST, mag_filter = GL_NEAREST;
// GLfloat color = (flags&7)/7.f, border_color[4] = { color, color, color, 1.f };
if( flags & TEXTURE_BGR ) if( pixel_type == GL_RGB ) pixel_type = GL_BGR;
if( flags & TEXTURE_BGR ) if( pixel_type == GL_RGBA ) pixel_type = GL_BGRA;
if( flags & TEXTURE_SRGB ) if( texel_type == GL_RGB ) texel_type = GL_SRGB;
if( flags & TEXTURE_SRGB ) if( texel_type == GL_RGBA ) texel_type = GL_SRGB_ALPHA; // GL_SRGB8_ALPHA8 ?
if( flags & TEXTURE_BC1 ) texel_type = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
if( flags & TEXTURE_BC2 ) texel_type = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
if( flags & TEXTURE_BC3 ) texel_type = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
if( flags & TEXTURE_DEPTH ) texel_type = pixel_type = GL_DEPTH_COMPONENT; // GL_DEPTH_COMPONENT32
if( flags & TEXTURE_REPEAT ) wrap = GL_REPEAT;
if( flags & TEXTURE_BORDER ) wrap = GL_CLAMP_TO_BORDER;
if( flags & TEXTURE_LINEAR ) min_filter = GL_LINEAR, mag_filter = GL_LINEAR;
if( flags & TEXTURE_MIPMAPS ) min_filter = flags & TEXTURE_LINEAR ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_LINEAR; // : GL_LINEAR_MIPMAP_NEAREST; maybe?
if( flags & TEXTURE_MIPMAPS ) mag_filter = flags & TEXTURE_LINEAR ? GL_LINEAR : GL_NEAREST;
#if 0
if( 0 ) { // flags & TEXTURE_PREMULTIPLY_ALPHA )
uint8_t *p = pixels;
if(n == 2) for( unsigned i = 0; i < 2*w*h; i += 2 ) {
p[i] = (p[i] * p[i+1] + 128) >> 8;
}
if(n == 4) for( unsigned i = 0; i < 4*w*h; i += 4 ) {
p[i+0] = (p[i+0] * p[i+3] + 128) >> 8;
p[i+1] = (p[i+1] * p[i+3] + 128) >> 8;
p[i+2] = (p[i+2] * p[i+3] + 128) >> 8;
}
}
#endif
GLenum texture_type = t->flags & TEXTURE_ARRAY ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D; // @fixme: test GL_TEXTURE_2D_ARRAY
//glPixelStorei( GL_UNPACK_ALIGNMENT, n < 4 ? 1 : 4 ); // for framebuffer reading
//glActiveTexture(GL_TEXTURE0 + (flags&7));
glBindTexture(texture_type, t->id);
glTexImage2D(texture_type, 0, texel_type, w, h, 0, pixel_type, pixel_storage, pixels);
glTexParameteri(texture_type, GL_TEXTURE_WRAP_S, wrap);
glTexParameteri(texture_type, GL_TEXTURE_WRAP_T, wrap);
glTexParameteri(texture_type, GL_TEXTURE_MIN_FILTER, min_filter);
glTexParameteri(texture_type, GL_TEXTURE_MAG_FILTER, mag_filter);
if (flags & TEXTURE_ANISOTROPY) {
GLfloat value, max_anisotropy = 16.0f;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &value);
value = (value > max_anisotropy) ? max_anisotropy : value;
glTexParameterf(texture_type, GL_TEXTURE_MAX_ANISOTROPY, value);
}
#if 0 // only for sampler2DShadow
if( flags & TEXTURE_DEPTH ) glTexParameteri(texture_type, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
if( flags & TEXTURE_DEPTH ) glTexParameteri(texture_type, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
#endif
// if( flags & TEXTURE_BORDER ) glTexParameterfv(texture_type, GL_TEXTURE_BORDER_COLOR, border_color);
if( flags & TEXTURE_MIPMAPS ) glGenerateMipmap(texture_type);
if( flags & TEXTURE_MIPMAPS ) {
GLfloat max_aniso = 0;
// glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &max_aniso);
max_aniso = 4;
// glTexParameterf(texture_type, GL_TEXTURE_MAX_ANISOTROPY, max_aniso);
}
// glBindTexture(texture_type, 0); // do not unbind. current code expects texture to be bound at function exit
t->w = w;
t->h = h;
t->n = n;
t->flags = flags;
t->filename = t->filename ? t->filename : "";
2024-08-23 19:28:40 +00:00
t->transparent = 0;
if (t->n == 4 && pixels) {
for (int i = 0; i < w * h; i++) {
if (((uint8_t *)pixels)[i * 4 + 3] < 255) {
t->transparent = 1;
break;
}
}
}
2024-08-19 07:54:01 +00:00
return t->id;
}
texture_t texture_create(unsigned w, unsigned h, unsigned n, const void *pixels, int flags) {
texture_t texture = {0};
glGenTextures( 1, &texture.id );
texture_update( &texture, w, h, n, pixels, flags );
return texture;
}
texture_t texture_checker() {
static texture_t texture = {0};
if( !texture.id ) {
#if 0
float pixels[] = { 1,0.5,0.5,1 };
texture = texture_create(2,2,1, pixels, TEXTURE_FLOAT|TEXTURE_MIPMAPS|TEXTURE_REPEAT|TEXTURE_BORDER);
#else
uint32_t *pixels = REALLOC(0, 256*256*4);
for (int y = 0, i = 0; y < 256; y++) {
for (int x = 0; x < 256; x++) {
#if 0
extern const uint32_t secret_palette[32];
uint32_t rgb = secret_palette[ y / 8 ] * !!((x ^ y) & 0x8);
pixels[i++] = (rgb>>16) & 255;
pixels[i++] = (rgb>>8) & 255;
pixels[i++] = (rgb>>0) & 255;
pixels[i++] = 255;
#elif 0
extern const uint32_t secret_palette[32];
uint32_t rgb = ((x ^ y) & 0x8) ? secret_palette[6] : secret_palette[ 8 + ((x^y) / (256/6)) ];
pixels[i++] = (rgb>>16) & 255;
pixels[i++] = (rgb>>8) & 255;
pixels[i++] = (rgb>>0) & 255;
pixels[i++] = 255;
#elif 0
extern const uint32_t secret_palette[32];
uint32_t lum = (x^y) & 8 ? 128 : (x^y) & 128 ? 192 : 255;
uint32_t rgb = rgba(lum,lum,lum,255);
pixels[i++] = rgb;
#else
int j = y, i = x;
unsigned char *p = (unsigned char *)&pixels[x + y * 256];
p[0] = (i / 16) % 2 == (j / 16) % 2 ? 255 : 0; // r
p[1] = ((i - j) / 16) % 2 == 0 ? 255 : 0; // g
p[2] = ((i + j) / 16) % 2 == 0 ? 255 : 0; // b
p[3] = 255; // a
#endif
}
}
texture = texture_create(256,256,4, pixels, TEXTURE_RGBA|TEXTURE_MIPMAPS|TEXTURE_REPEAT|TEXTURE_BORDER);
FREE(pixels);
#endif
}
return texture;
}
texture_t texture_from_mem(const void *ptr, int len, int flags) {
image_t img = image_from_mem(ptr, len, flags);
if( img.pixels ) {
texture_t t = texture_create(img.x, img.y, img.n, img.pixels, flags);
image_destroy(&img);
return t;
}
return texture_checker();
}
texture_t texture(const char *pathfile, int flags) {
// PRINTF("Loading file %s\n", pathfile);
image_t img = image(pathfile, flags);
if( img.pixels ) {
texture_t t = texture_create(img.x, img.y, img.n, img.pixels, flags);
t.filename = STRDUP(file_name(pathfile));
image_destroy(&img);
return t;
}
return texture_checker();
}
void texture_destroy( texture_t *t ) {
if(t->filename && t->filename[0]) FREE(t->filename), t->filename = 0;
if(t->fbo) fbo_destroy(t->fbo), t->fbo = 0;
if(t->id) glDeleteTextures(1, &t->id), t->id = 0;
*t = (texture_t){0};
}
bool texture_rec_begin(texture_t *t, unsigned tw, unsigned th) {
for( unsigned w = tw ? tw : window_width(), h = th ? th : window_height(); w*h ; ) {
// resize if needed
if( t->w != w || t->h != h ) {
// re-create texture, set texture parameters and content
texture_update(t, w, h, 4, NULL, TEXTURE_RGBA);
if(!t->fbo) t->fbo = fbo(t->id, 0, 0);
}
// bind fbo to texture
fbo_bind(t->fbo);
return true;
}
return false;
}
void texture_rec_end(texture_t *t) {
fbo_unbind();
}
// ktx texture loader
// - rlyeh, public domain
//
// [ref] https://developer.nvidia.com/astc-texture-compression-for-game-assets
//
// # Compatibility and modes. What to choose.
// - iOS: PVRTC1_4_RGB or PVRTC1_4 (RGBA) with q:pvrtcnormal.
// - Desktop (OSX/Linux/Windows): BC1, BC1a or BC3 with q:normal.
// - Android: ETC2_RGB or ETC2_RGBA with q:etcfast. ASTC_4x4 or ASTC_8x8 with q:astcmedium, as a fallback.
#if 0
enum {
// for glFormat
GLFORMAT_RED = 0x1903,
GLFORMAT_RG = 0x8227,
GLFORMAT_RGB = 0x1907,
GLFORMAT_RGBA = 0x1908,
//GLFORMAT_ALPHA = 0x1906, // 8
//GLFORMAT_LUMINANCE = 0x1909, // 8
//GLFORMAT_LUMINANCE_ALPHA = 0x190A, // 88
// for glType
GLTYPE_UNSIGNED_BYTE = 0x1401,
// for glInternalFormat: RAW // @todo: SRGB, SRGBA, SBGR, SBGRA
UNCOMPRESSED_RGB = 0x8051, // 888, GL_RGB8_EXT
UNCOMPRESSED_RGB_565 = 0x8363,
UNCOMPRESSED_RGBA = 0x8058, // 8888, GL_RGBA8_EXT
UNCOMPRESSED_RGBA_4444 = 0x8033,
UNCOMPRESSED_RGBA_5551 = 0x8034,
UNCOMPRESSED_BGR = 0x80E0, // 888
UNCOMPRESSED_BGRA = 0x80E1, // 8888
// for glInternalFormat: S3TC/DXTn/BCn // @todo: BC4,5,6,7*
COMPRESSED_RGB_BC1 = 0x83F0, // DXT1
COMPRESSED_RGBA_BC1 = 0x83F1, // DXT1a, BC1a
COMPRESSED_RGBA_BC2 = 0x83F2, // DXT3
COMPRESSED_RGBA_BC3 = 0x83F3, // DXT5
COMPRESSED_RGBA_BC7 = 0x8E8C, // COMPRESSED_RGBA_BPTC_UNORM_ARB
COMPRESSED_SRGB_BC1 = 0x8C4C,
COMPRESSED_SRGBA_BC1 = 0x8C4D,
COMPRESSED_SRGBA_BC2 = 0x8C4E,
COMPRESSED_SRGBA_BC3 = 0x8C4F,
// RGB_BC7f COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB
// RGB_BC7uf COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB
// RGBA_BC7 COMPRESSED_RGBA_BPTC_UNORM_ARB
// SRGBA_BC7 COMPRESSED_SRGBA_BPTC_UNORM_ARB
// for glInternalFormat: ETC2+EAC
COMPRESSED_R_EAC = 0x9270, // 4bpp
COMPRESSED_R_EAC_SIGNED = 0x9271, // 4bpp. can preserve 0
COMPRESSED_RG_EAC = 0x9272, // 8bpp
COMPRESSED_RG_EAC_SIGNED = 0x9273, // 8bbp. can preserve 0
COMPRESSED_RGB_ETC2 = 0x9274, // 4bpp
COMPRESSED_RGBA_ETC2 = 0x9276, // 4bpp A1
COMPRESSED_RGBA_ETC2_EAC = 0x9278, // 8bpp
COMPRESSED_SRGB_ETC2 = 0x9275, // 4bpp
COMPRESSED_SRGBA_ETC2 = 0x9277, // 4bpp A1
COMPRESSED_SRGBA_ETC2_EAC = 0x9279, // 8bpp
// for glInternalFormat: PVR
COMPRESSED_RGB_PVR1_2 = 0x8C01,
COMPRESSED_RGB_PVR1_4 = 0x8C00,
COMPRESSED_RGBA_PVR1_2 = 0x8C03,
COMPRESSED_RGBA_PVR1_4 = 0x8C02,
COMPRESSED_SRGB_PVR1_2 = 0x8A54, // _EXT
COMPRESSED_SRGB_PVR1_4 = 0x8A55, // _EXT
COMPRESSED_SRGBA_PVR1_2 = 0x8A56, // _EXT
COMPRESSED_SRGBA_PVR1_4 = 0x8A57, // _EXT
COMPRESSED_RGBA_PVR2_2 = 0x9137,
COMPRESSED_RGBA_PVR2_4 = 0x9138,
COMPRESSED_SRGBA_PVR2_2 = 0x93F0,
COMPRESSED_SRGBA_PVR2_4 = 0x93F1,
// for glInternalFormat: ASTC
COMPRESSED_RGBA_ASTC4x4 = 0x93B0, // 8.00bpp
COMPRESSED_RGBA_ASTC5x4 = 0x93B1, // 6.40bpp
COMPRESSED_RGBA_ASTC5x5 = 0x93B2, // 5.12bpp
COMPRESSED_RGBA_ASTC6x5 = 0x93B3, // 4.27bpp
COMPRESSED_RGBA_ASTC6x6 = 0x93B4, // 3.56bpp
COMPRESSED_RGBA_ASTC8x5 = 0x93B5, // 3.20bpp
COMPRESSED_RGBA_ASTC8x6 = 0x93B6, // 2.67bpp
COMPRESSED_RGBA_ASTC8x8 = 0x93B7, // 2.56bpp
COMPRESSED_RGBA_ASTC10x5 = 0x93B8, // 2.13bpp
COMPRESSED_RGBA_ASTC10x6 = 0x93B9, // 2.00bpp
COMPRESSED_RGBA_ASTC10x8 = 0x93BA, // 1.60bpp
COMPRESSED_RGBA_ASTC10x10 = 0x93BB, // 1.28bpp
COMPRESSED_RGBA_ASTC12x10 = 0x93BC, // 1.07bpp
COMPRESSED_RGBA_ASTC12x12 = 0x93BD, // 0.89bpp
COMPRESSED_SRGBA_ASTC4x4 = 0x93D0, // 8.00bpp SRGB8 A8
COMPRESSED_SRGBA_ASTC5x4 = 0x93D1, // 6.40bpp SRGB8 A8
COMPRESSED_SRGBA_ASTC5x5 = 0x93D2, // 5.12bpp SRGB8 A8
COMPRESSED_SRGBA_ASTC6x5 = 0x93D3, // 4.27bpp SRGB8 A8
COMPRESSED_SRGBA_ASTC6x6 = 0x93D4, // 3.56bpp SRGB8 A8
COMPRESSED_SRGBA_ASTC8x5 = 0x93D5, // 3.20bpp SRGB8 A8
COMPRESSED_SRGBA_ASTC8x6 = 0x93D6, // 2.67bpp SRGB8 A8
COMPRESSED_SRGBA_ASTC8x8 = 0x93D7, // 2.56bpp SRGB8 A8
COMPRESSED_SRGBA_ASTC10x5 = 0x93D8, // 2.13bpp SRGB8 A8
COMPRESSED_SRGBA_ASTC10x6 = 0x93D9, // 2.00bpp SRGB8 A8
COMPRESSED_SRGBA_ASTC10x8 = 0x93DA, // 1.60bpp SRGB8 A8
COMPRESSED_SRGBA_ASTC10x10 = 0x93DB, // 1.28bpp SRGB8 A8
COMPRESSED_SRGBA_ASTC12x10 = 0x93DC, // 1.07bpp SRGB8 A8
COMPRESSED_SRGBA_ASTC12x12 = 0x93DD, // 0.89bpp SRGB8 A8
// others:
// COMPRESSED_RED_RGTC1
// COMPRESSED_SIGNED_RED_RGTC1
// COMPRESSED_RG_RGTC2
// COMPRESSED_SIGNED_RG_RGTC2
};
#endif
#pragma pack(push, 1) // not really needed. the struct is exactly 64 bytes, and all members are 32-bit unsigned
typedef struct ktx_header {
unsigned identifier[3]; // "«KTX 11»\r\n\x1A\n"
unsigned endianness; // 0x04030201 if match
unsigned glType; // 0 if compressed; otherwise: UNSIGNED_BYTE, UNSIGNED_SHORT_5_6_5, etc.
unsigned glTypeSize; // 1 if compressed; otherwise, size in bytes of glType for endianness conversion. not needed.
unsigned glFormat; // STENCIL_INDEX, DEPTH_COMPONENT, DEPTH_STENCIL, RED, GREEN, BLUE, RG, RGB, RGBA, BGR, BGRA, RED_INTEGER, GREEN_INTEGER, BLUE_INTEGER, RG_INTEGER, RGB_INTEGER, RGBA_INTEGER, BGR_INTEGER, BGRA_INTEGER,
unsigned glInternalFormat; // COMPRESSED_RED, COMPRESSED_RG, COMPRESSED_RGB, COMPRESSED_RGBA, COMPRESSED_SRGB, COMPRESSED_SRGB_ALPHA, COMPRESSED_RED_RGTC1, COMPRESSED_SIGNED_RED_RGTC1, COMPRESSED_RG_RGTC2, COMPRESSED_SIGNED_RG_RGTC2, COMPRESSED_RGBA_BPTC_UNORM, COMPRESSED_SRGB_ALPHA_BPTC_UNORM, COMPRESSED_RGB_BPTC_SIGNED_FLOAT, COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT, COMPRESSED_RGB8_ETC2, COMPRESSED_SRGB8_ETC2, COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2, COMPRESSED_RGBA8_ETC2_EAC, COMPRESSED_SRGB8_ALPHA8_ETC2_EAC, COMPRESSED_R11_EAC, COMPRESSED_SIGNED_R11_EAC, COMPRESSED_RG11_EAC, COMPRESSED_SIGNED_RG11_EAC,
unsigned glBaseInternalFormat; // DEPTH_COMPONENT, DEPTH_STENCIL, RED, RG, RGB, RGBA, STENCIL_INDEX,
unsigned width;
unsigned height;
unsigned depth;
unsigned num_surfaces; // >1 for material
unsigned num_faces; // =6 for cubemaps (+X,-X,+Y,-Y,+Z,-Z order), 1 otherwise
unsigned num_mipmaps; // >1 for mipmaps
unsigned metadata_size; // length of following header
// struct ktx_metadata {
// unsigned key_and_value_size;
// char key_and_value[key_and_value_size];
// char value_padding[3 - ((key_and_value_size + 3) % 4)];
// };
// struct ktx_texture_data {
// unsigned size;
// char data[0];
// } tx;
} ktx_header;
#pragma pack(pop)
typedef struct ktx_texture {
unsigned width;
unsigned height;
unsigned depth;
unsigned size;
const char* data;
} ktx_texture;
typedef struct ktx {
ktx_header hdr;
const char *error;
} ktx;
static __thread array(ktx_texture) ktx_textures;
static
ktx ktx_load(const void *data, unsigned int len) {
ktx ctx = {0};
// check ktx signature
bool is_ktx = (len > sizeof(ktx_header)) && !memcmp(data, "\xABKTX 11\xBB\r\n\x1A\n", 12);
if( !is_ktx ) {
return ctx.error = "ERROR_BAD_KTX_FILE", ctx;
}
// copy texture header
ktx_header *hdr = &ctx.hdr;
*hdr = *((const ktx_header *)data);
// sanity checks
STATIC_ASSERT(sizeof(ktx_header) == (16*4));
for( int i = 0; i < sizeof(ktx_header)/4; ++i) {
i[(unsigned*)hdr] = lil32(i[(unsigned*)hdr]);
}
if( hdr->endianness != 0x04030201 ) {
return ctx.error = "ERROR_BAD_ENDIANNESS", ctx;
}
if( (hdr->num_faces != 1) && (hdr->num_faces != 6) ) {
return ctx.error = "ERROR_BAD_NUMBER_OF_FACES", ctx;
}
// normalize glInternalFormat if uncompressed.
if( hdr->glType != 0 ) {
hdr->glInternalFormat = hdr->glBaseInternalFormat;
}
// normalize [1..N] range
hdr->num_mipmaps += !hdr->num_mipmaps;
hdr->num_surfaces += !hdr->num_surfaces;
hdr->num_faces += !hdr->num_faces;
// basically,
// for each level in num_mipmaps { UInt32 imageSize;
// for each surface in num_surfaces {
// for each face in num_faces {
// for each slice in depth {
// for each row in height {
// for each pixel in width {
// byte data[size_based_on_pixelformat]
// byte facePadding[0-3] }}}
// }
// Byte mipPadding[0-3] }
array_resize(ktx_textures, hdr->num_mipmaps * hdr->num_surfaces * hdr->num_faces);
const char *bitmap = ((const char*)data) + sizeof(ktx_header) + hdr->metadata_size;
for( unsigned m = 0; m < hdr->num_mipmaps; ++m ) {
for( unsigned s = 0; s < hdr->num_surfaces; ++s ) {
for( unsigned f = 0; f < hdr->num_faces; ++f ) {
ktx_texture *t = &ktx_textures[f+s*hdr->num_faces+m*hdr->num_faces*hdr->num_surfaces];
// set dimensions [1..N]
t->width = (hdr->width >> m) + !(hdr->width >> m);
t->height = (hdr->height >> m) + !(hdr->height >> m);
t->depth = (hdr->depth >> m) + !(hdr->depth >> m);
// seek to mip
const char *ptr = bitmap;
for( int i = 0; i <= m; i++ ) {
// if cubemap, *ptr holds unpadded size of single face,
// else, *ptr holds size of all surfaces+faces+slices for whole mipmap.
unsigned size = lil32(*(unsigned*)ptr);
unsigned padding = 3 - ((size + 3) % 4);
// seek to data
t->data = ptr + 4 + (size * f);
// seek to next mipmap
ptr = ptr + 4 + (size * hdr->num_faces) + padding;
// adjust size
t->size = (uintptr_t)(ptr - t->data); // -padding; needed?
}
// ensure we're in bounds
ASSERT(t->data < ((char*)data + len), "%p < %p", t->data, ((char*)data + len));
ASSERT(((char*)t->data+t->size) <= ((char*)data + len), "%p < %p", (char*)t->data + t->size, ((char*)data + len));
}
}
}
return ctx;
}
// ---
texture_t texture_compressed_from_mem(const void *data, int len, unsigned flags) {
ktx ctx = ktx_load(data, len);
if( ctx.error ) {
// puts(ctx.error);
// return texture_checker();
return texture_from_mem(data, len, flags);
}
ktx_header hdr = ctx.hdr;
// flags
int target = hdr.num_faces == 6 ? GL_TEXTURE_CUBE_MAP : hdr.depth > 0 ? GL_TEXTURE_3D : GL_TEXTURE_2D;
int dimensions = target == GL_TEXTURE_3D ? 3 : target == GL_TEXTURE_2D || target == GL_TEXTURE_CUBE_MAP ? 2 : 1;
// create texture
GLuint id;
glGenTextures(1, &id);
glBindTexture(target, id);
// filtering
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, hdr.num_mipmaps > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
// wrapping
if( dimensions > 0 ) glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_REPEAT);
if( dimensions > 1 ) glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_REPEAT);
if( dimensions > 2 ) glTexParameteri(target, GL_TEXTURE_WRAP_R, GL_REPEAT);
if( flags&TEXTURE_CLAMP && dimensions > 0 ) glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
if( flags&TEXTURE_CLAMP && dimensions > 1 ) glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if( flags&TEXTURE_CLAMP && dimensions > 2 ) glTexParameteri(target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
if( target == GL_TEXTURE_CUBE_MAP ) target = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
// GLenum internalFormat = flags & TEXTURE_SRGB ? GL_SRGB8_ALPHA8 : GL_RGBA8; // @fixme
int bytes = 0;
enum { border = 0 };
for( int m = 0; m < hdr.num_mipmaps; ++m ) {
for( int s = 0; s < hdr.num_surfaces; ++s ) {
for( int f = 0; f < hdr.num_faces; ++f ) {
int d3 = target == GL_TEXTURE_3D, compr = hdr.glType == 0, mode = d3+compr*2;
ktx_texture *t = &ktx_textures[f+s*hdr.num_faces+m*hdr.num_faces*hdr.num_surfaces];
/**/ if(mode==0) glTexImage2D(target+f,m,hdr.glInternalFormat,t->width,t->height, border,hdr.glFormat,hdr.glType,t->data);
else if(mode==1) glTexImage3D(target ,m,hdr.glInternalFormat,t->width,t->height,t->depth, border,hdr.glFormat,hdr.glType,t->data);
else if(mode==2) glCompressedTexImage2D(target+f,m,hdr.glInternalFormat,t->width,t->height, border,t->size,t->data);
else if(mode==3) glCompressedTexImage3D(target ,m,hdr.glInternalFormat,t->width,t->height,t->depth,border,t->size,t->data);
bytes += t->size;
}
}
}
// if( !hdr.num_mipmaps )
// if( flags & TEXTURE_MIPMAPS ) glGenerateMipmap(target);
texture_t t = {0};
t.id = id;
t.w = ktx_textures[0].width;
t.h = ktx_textures[0].height;
t.d = ktx_textures[0].depth;
// @todo: reconstruct flags
PRINTF("dims:%dx%dx%d,size:%.2fMiB,mips:%d,layers:%d,faces:%d\n", t.w, t.h, t.d, bytes / 1024.0 / 1024.0, hdr.num_mipmaps, hdr.num_surfaces, hdr.num_faces);
return t;
}
texture_t texture_compressed(const char *pathfile, unsigned flags) {
//const char *fname = vfs_remap(pathfile);
int size = 0;
char *data = vfs_load(pathfile, &size);
return texture_compressed_from_mem(data, size, flags);
}
// -----------------------------------------------------------------------------
// shadowmaps
shadowmap_t shadowmap(int texture_width) { // = 1024
shadowmap_t s = {0};
s.texture_width = texture_width;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &s.saved_fb);
glGenFramebuffers(1, &s.fbo);
glBindFramebuffer(GL_FRAMEBUFFER, s.fbo);
glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &s.texture);
glBindTexture(GL_TEXTURE_2D, s.texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, texture_width, texture_width, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, s.texture, 0);
#if is(ems)
GLenum nones[] = { GL_NONE };
glDrawBuffers(1, nones);
glReadBuffer(GL_NONE);
#else
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
#endif
glBindFramebuffer(GL_FRAMEBUFFER, s.saved_fb);
return s;
}
void shadowmap_destroy(shadowmap_t *s) {
if (s->texture) {
glDeleteTextures(1, &s->texture);
}
if (s->fbo) {
glDeleteFramebuffers(1, &s->fbo);
}
shadowmap_t z = {0};
*s = z;
}
void shadowmap_set_shadowmatrix(shadowmap_t *s, vec3 aLightPos, vec3 aLightAt, vec3 aLightUp, const mat44 projection) {
copy44(s->proj, projection);
s->light_position = vec4(aLightPos.x, aLightPos.y, aLightPos.z, 1);
lookat44(s->mv, aLightPos, aLightAt, aLightUp);
mat44 bias = {
0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0 };
// s->shadowmatrix = bias;
// s->shadowmatrix *= s->proj;
// s->shadowmatrix *= s->mv;
// multiply44x3(s->shadowmatrix, s->mv, s->proj, bias);
multiply44x3(s->shadowmatrix, bias, s->proj, s->mv);
// mvp = projection * s->mv;
// multiply44x2(s->mvp, s->mv, projection);
multiply44x2(s->mvp, projection, s->mv);
}
void shadowmap_begin(shadowmap_t *s) {
glGetIntegerv(GL_VIEWPORT, &s->saved_viewport[0]);
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &s->saved_fb);
glBindFramebuffer(GL_FRAMEBUFFER, s->fbo);
glViewport(0, 0, s->texture_width, s->texture_width);
glClearDepth(1);
glClear(GL_DEPTH_BUFFER_BIT);
}
void shadowmap_end(shadowmap_t *s) {
glViewport(s->saved_viewport[0], s->saved_viewport[1], s->saved_viewport[2], s->saved_viewport[3]);
glBindFramebuffer(GL_FRAMEBUFFER, s->saved_fb);
}
// shadowmap utils
void shadowmatrix_proj(mat44 shm_proj, float aLightFov, float znear, float zfar) {
perspective44(shm_proj, aLightFov, 1.0f, znear, zfar);
}
void shadowmatrix_ortho(mat44 shm_proj, float left, float right, float bottom, float top, float znear, float zfar) {
ortho44(shm_proj, left, right, bottom, top, znear, zfar);
}
// -----------------------------------------------------------------------------
// fullscreen quads
// usage: bind empty vao & commit call for 6 (quad) or 3 vertices (tri).
// ie, glBindVertexArray(empty_vao); glDrawArrays(GL_TRIANGLES, 0, 3);
static renderstate_t fullscreen_quad_rs;
static inline
void fullscreen_quad_rs_init() {
do_once {
fullscreen_quad_rs = renderstate();
fullscreen_quad_rs.depth_test_enabled = false;
fullscreen_quad_rs.blend_enabled = true;
fullscreen_quad_rs.blend_src = GL_SRC_ALPHA;
fullscreen_quad_rs.blend_dst = GL_ONE_MINUS_SRC_ALPHA;
fullscreen_quad_rs.front_face = GL_CW;
}
}
void fullscreen_quad_rgb( texture_t texture ) {
fullscreen_quad_rs_init();
static int program = -1, vao = -1, u_inv_gamma = -1;
if( program < 0 ) {
const char* vs = vfs_read("shaders/vs_0_2_fullscreen_quad_B_flipped.glsl");
const char* fs = vfs_read("shaders/fs_2_4_texel_inv_gamma.glsl");
program = shader(vs, fs, "", "fragcolor" , NULL);
u_inv_gamma = glGetUniformLocation(program, "u_inv_gamma");
glGenVertexArrays( 1, (GLuint*)&vao );
}
GLenum texture_type = texture.flags & TEXTURE_ARRAY ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D;
renderstate_apply(&fullscreen_quad_rs);
glUseProgram( program );
float gamma = 1;
glUniform1f( u_inv_gamma, gamma );
glBindVertexArray( vao );
glActiveTexture( GL_TEXTURE0 );
glBindTexture( texture_type, texture.id );
glDrawArrays( GL_TRIANGLES, 0, 6 );
profile_incstat("Render.num_drawcalls", +1);
profile_incstat("Render.num_triangles", +2);
glBindTexture( texture_type, 0 );
glBindVertexArray( 0 );
glUseProgram( 0 );
// glDisable( GL_BLEND );
}
void fullscreen_quad_rgb_flipped( texture_t texture ) {
fullscreen_quad_rs_init();
static int program = -1, vao = -1, u_inv_gamma = -1;
if( program < 0 ) {
const char* vs = vfs_read("shaders/vs_0_2_fullscreen_quad_B.glsl");
const char* fs = vfs_read("shaders/fs_2_4_texel_inv_gamma.glsl");
program = shader(vs, fs, "", "fragcolor" , NULL);
u_inv_gamma = glGetUniformLocation(program, "u_inv_gamma");
glGenVertexArrays( 1, (GLuint*)&vao );
}
GLenum texture_type = texture.flags & TEXTURE_ARRAY ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D;
renderstate_apply(&fullscreen_quad_rs);
glUseProgram( program );
float gamma = 1;
glUniform1f( u_inv_gamma, gamma );
glBindVertexArray( vao );
glActiveTexture( GL_TEXTURE0 );
glBindTexture( texture_type, texture.id );
glDrawArrays( GL_TRIANGLES, 0, 6 );
profile_incstat("Render.num_drawcalls", +1);
profile_incstat("Render.num_triangles", +2);
glBindTexture( texture_type, 0 );
glBindVertexArray( 0 );
glUseProgram( 0 );
// glDisable( GL_BLEND );
}
void fullscreen_quad_ycbcr( texture_t textureYCbCr[3] ) {
fullscreen_quad_rs_init();
static int program = -1, vao = -1, u_gamma = -1, uy = -1, ucb = -1, ucr = -1;
if( program < 0 ) {
const char* vs = vfs_read("shaders/vs_0_2_fullscreen_quad_B_flipped.glsl");
const char* fs = vfs_read("shaders/fs_2_4_texel_ycbr_gamma_saturation.glsl");
program = shader(vs, fs, "", "fragcolor" , NULL);
u_gamma = glGetUniformLocation(program, "u_gamma");
uy = glGetUniformLocation(program, "u_texture_y");
ucb = glGetUniformLocation(program, "u_texture_cb");
ucr = glGetUniformLocation(program, "u_texture_cr");
glGenVertexArrays( 1, (GLuint*)&vao );
}
renderstate_apply(&fullscreen_quad_rs);
glUseProgram( program );
// glUniform1f( u_gamma, gamma );
glBindVertexArray( vao );
glUniform1i(uy, 0);
glActiveTexture( GL_TEXTURE0 );
glBindTexture( GL_TEXTURE_2D, textureYCbCr[0].id );
glUniform1i(ucb, 1);
glActiveTexture( GL_TEXTURE1 );
glBindTexture( GL_TEXTURE_2D, textureYCbCr[1].id );
glUniform1i(ucr, 2);
glActiveTexture( GL_TEXTURE2 );
glBindTexture( GL_TEXTURE_2D, textureYCbCr[2].id );
glDrawArrays( GL_TRIANGLES, 0, 6 );
profile_incstat("Render.num_drawcalls", +1);
profile_incstat("Render.num_triangles", +2);
glBindTexture( GL_TEXTURE_2D, 0 );
glBindVertexArray( 0 );
glUseProgram( 0 );
// glDisable( GL_BLEND );
}
void fullscreen_quad_ycbcr_flipped( texture_t textureYCbCr[3] ) {
fullscreen_quad_rs_init();
static int program = -1, vao = -1, u_gamma = -1, uy = -1, ucb = -1, ucr = -1;
if( program < 0 ) {
const char* vs = vfs_read("shaders/vs_0_2_fullscreen_quad_B.glsl");
const char* fs = vfs_read("shaders/fs_2_4_texel_ycbr_gamma_saturation.glsl");
program = shader(vs, fs, "", "fragcolor" , NULL);
u_gamma = glGetUniformLocation(program, "u_gamma");
uy = glGetUniformLocation(program, "u_texture_y");
ucb = glGetUniformLocation(program, "u_texture_cb");
ucr = glGetUniformLocation(program, "u_texture_cr");
glGenVertexArrays( 1, (GLuint*)&vao );
}
renderstate_apply(&fullscreen_quad_rs);
glUseProgram( program );
// glUniform1f( u_gamma, gamma );
glBindVertexArray( vao );
glUniform1i(uy, 0);
glActiveTexture( GL_TEXTURE0 );
glBindTexture( GL_TEXTURE_2D, textureYCbCr[0].id );
glUniform1i(ucb, 1);
glActiveTexture( GL_TEXTURE1 );
glBindTexture( GL_TEXTURE_2D, textureYCbCr[1].id );
glUniform1i(ucr, 2);
glActiveTexture( GL_TEXTURE2 );
glBindTexture( GL_TEXTURE_2D, textureYCbCr[2].id );
glDrawArrays( GL_TRIANGLES, 0, 6 );
profile_incstat("Render.num_drawcalls", +1);
profile_incstat("Render.num_triangles", +2);
glBindTexture( GL_TEXTURE_2D, 0 );
glBindVertexArray( 0 );
glUseProgram( 0 );
// glDisable( GL_BLEND );
}
// -----------------------------------------------------------------------------
// cubemaps
// project cubemap coords into sphere normals
static
vec3 cubemap2polar(int face, int x, int y, int texture_width) {
float u = (x / (texture_width - 1.f)) * 2 - 1;
float v = (y / (texture_width - 1.f)) * 2 - 1;
/**/ if( face == 0 ) return vec3( u, -1, -v);
else if( face == 1 ) return vec3(-v, -u, 1);
else if( face == 2 ) return vec3(-1, -u, -v);
else if( face == 3 ) return vec3(-u, 1, -v);
else if( face == 4 ) return vec3( v, -u, -1);
else return vec3( 1, u, -v);
}
// project normal in a sphere as 2d texcoord
static
vec2 polar2uv(vec3 n) {
n = norm3(n);
float theta = atan2(n.y, n.x);
float phi = atan2(n.z, hypot(n.x, n.y));
float u = (theta + C_PI) / C_PI;
float v = (C_PI/2 - phi) / C_PI;
return vec2(u, v);
}
// equirectangular panorama (2:1) to cubemap - in RGB, out RGB
static
void panorama2cubemap_(image_t out[6], const image_t in, int width){
int face;
#pragma omp parallel for
for( face = 0; face < 6; ++face ) {
out[face] = image_create(width, width, IMAGE_RGB);
for (int j=0; j < width; ++j) {
uint32_t *line = &out[ face ].pixels32[ 0 + j * width ];
for (int i=0; i < width; ++i) {
vec3 polar = cubemap2polar(face, i, j, width);
vec2 uv = polar2uv(polar);
uv = scale2(uv, in.h-1); // source coords (assumes 2:1, 2*h == w)
vec3 rgb = bilinear(in, uv);
union color {
struct { uint8_t r,g,b,a; };
uint32_t rgba;
} c = { rgb.x, rgb.y, rgb.z, 255 };
line[i] = c.rgba;
}
}
}
}
// equirectangular panorama (2:1) to cubemap - in RGB, out RGBA
void panorama2cubemap(image_t out[6], const image_t in, int width) {
int face;
#pragma omp parallel for
for( face = 0; face < 6; ++face ) {
out[face] = image_create(width, width, IMAGE_RGBA);
for (int j=0; j < width; ++j) {
uint32_t *line = &out[ face ].pixels32[ 0 + j * width ];
for (int i=0; i < width; ++i) {
vec3 polar = cubemap2polar(face, i, j, width);
vec2 uv = polar2uv(polar);
uv = scale2(uv, in.h-1); // source coords (assumes 2:1, 2*h == w)
vec3 rgb = bilinear(in, uv);
union color {
struct { uint8_t r,g,b,a; };
uint32_t rgba;
} c = { rgb.x, rgb.y, rgb.z, 255 };
line[i] = c.rgba;
}
}
}
}
cubemap_t cubemap6( const image_t images[6], int flags ) {
cubemap_t c = {0}, z = {0};
glGenTextures(1, &c.id);
glBindTexture(GL_TEXTURE_CUBE_MAP, c.id);
int samples = 0;
for (int i = 0; i < 6; i++) {
image_t img = images[i]; //image(textures[i], IMAGE_RGB);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, img.w, img.h, 0, img.n == 3 ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, img.pixels);
// calculate SH coefficients (@ands)
const vec3 skyDir[] = {{ 1, 0, 0},{-1, 0, 0},{ 0, 1, 0},{ 0,-1, 0},{ 0, 0, 1},{ 0, 0,-1}};
const vec3 skyX[] = {{ 0, 0,-1},{ 0, 0, 1},{ 1, 0, 0},{ 1, 0, 0},{ 1, 0, 0},{-1, 0, 0}};
const vec3 skyY[] = {{ 0, 1, 0},{ 0, 1, 0},{ 0, 0,-1},{ 0, 0, 1},{ 0, 1, 0},{ 0, 1, 0}};
int step = 16;
for (int y = 0; y < img.h; y += step) {
unsigned char *p = (unsigned char*)img.pixels + y * img.w * img.n;
for (int x = 0; x < img.w; x += step) {
vec3 n = add3(
add3(
scale3(skyX[i], 2.0f * (x / (img.w - 1.0f)) - 1.0f),
scale3(skyY[i], -2.0f * (y / (img.h - 1.0f)) + 1.0f)),
skyDir[i]); // texelDirection;
float l = len3(n);
vec3 light = scale3(vec3(p[0], p[1], p[2]), 1 / (255.0f * l * l * l)); // texelSolidAngle * texel_radiance;
n = norm3(n);
c.sh[0] = add3(c.sh[0], scale3(light, 0.282095f));
c.sh[1] = add3(c.sh[1], scale3(light, -0.488603f * n.y * 2.0 / 3.0));
c.sh[2] = add3(c.sh[2], scale3(light, 0.488603f * n.z * 2.0 / 3.0));
c.sh[3] = add3(c.sh[3], scale3(light, -0.488603f * n.x * 2.0 / 3.0));
c.sh[4] = add3(c.sh[4], scale3(light, 1.092548f * n.x * n.y / 4.0));
c.sh[5] = add3(c.sh[5], scale3(light, -1.092548f * n.y * n.z / 4.0));
c.sh[6] = add3(c.sh[6], scale3(light, 0.315392f * (3.0f * n.z * n.z - 1.0f) / 4.0));
c.sh[7] = add3(c.sh[7], scale3(light, -1.092548f * n.x * n.z / 4.0));
c.sh[8] = add3(c.sh[8], scale3(light, 0.546274f * (n.x * n.x - n.y * n.y) / 4.0));
p += img.n * step;
samples++;
}
}
}
for (int s = 0; s < 9; s++) {
c.sh[s] = scale3(c.sh[s], 32.f / samples);
}
// if( glGenerateMipmap )
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, /* glGenerateMipmap ?*/ GL_LINEAR_MIPMAP_LINEAR /*: GL_LINEAR*/);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
return c;
}
cubemap_t cubemap( const image_t in, int flags ) {
ASSERT( in.n == 4 );
image_t out[6];
panorama2cubemap(out, in, in.h);
image_t swap[6] = { out[0],out[3],out[1],out[4],out[2],out[5] };
cubemap_t c = cubemap6(swap, flags);
int i;
#pragma omp parallel for
for( i = 0; i < 6; ++i) image_destroy(&out[i]);
return c;
}
void cubemap_destroy(cubemap_t *c) {
glDeleteTextures(1, &c->id);
c->id = 0; // do not destroy SH coefficients still. they might be useful in the future.
}
static cubemap_t *last_cubemap;
cubemap_t* cubemap_get_active() {
return last_cubemap;
}
// -----------------------------------------------------------------------------
// skyboxes
skybox_t skybox(const char *asset, int flags) {
skybox_t sky = {0};
// sky mesh
vec3 vertices[] = {{+1,-1,+1},{+1,+1,+1},{+1,+1,-1},{-1,+1,-1},{+1,-1,-1},{-1,-1,-1},{-1,-1,+1},{-1,+1,+1}};
unsigned indices[] = { 0, 1, 2, 3, 4, 5, 6, 3, 7, 1, 6, 0, 4, 2 };
mesh_update(&sky.geometry, "p3", 0,countof(vertices),vertices, countof(indices),indices, MESH_TRIANGLE_STRIP);
// sky program
sky.flags = flags && flags != SKYBOX_PBR ? flags : !!asset ? SKYBOX_CUBEMAP : SKYBOX_RAYLEIGH; // either cubemap or rayleigh
sky.program = shader(vfs_read("shaders/vs_3_3_skybox.glsl"),
sky.flags ? vfs_read("fs_3_4_skybox.glsl") : vfs_read("shaders/fs_3_4_skybox_rayleigh.glsl"),
"att_position", "fragcolor", NULL);
// sky cubemap & SH
if( asset ) {
int is_panorama = vfs_size( asset );
if( is_panorama ) { // is file
stbi_hdr_to_ldr_gamma(1.0f);
image_t panorama = image( asset, IMAGE_RGBA );
sky.cubemap = cubemap( panorama, 0 ); // RGBA required
image_destroy(&panorama);
} else { // is folder
image_t images[6] = {0};
images[0] = image( va("%s/posx", asset), IMAGE_RGB ); // cubepx
images[1] = image( va("%s/negx", asset), IMAGE_RGB ); // cubenx
images[2] = image( va("%s/posy", asset), IMAGE_RGB ); // cubepy
images[3] = image( va("%s/negy", asset), IMAGE_RGB ); // cubeny
images[4] = image( va("%s/posz", asset), IMAGE_RGB ); // cubepz
images[5] = image( va("%s/negz", asset), IMAGE_RGB ); // cubenz
sky.cubemap = cubemap6( images, 0 );
for( int i = 0; i < countof(images); ++i ) image_destroy(&images[i]);
}
} else {
// set up mie defaults // @fixme: use shader params instead
shader_bind(sky.program);
shader_vec3("uSunPos", vec3( 0, 0.1, -1 ));
shader_vec3("uRayOrigin", vec3(0.0, 6372000.0, 0.0));
shader_float("uSunIntensity", 22.0);
shader_float("uPlanetRadius", 6371000.0);
shader_float("uAtmosphereRadius", 6471000.0);
shader_vec3("uRayleighScattering", vec3(5.5e-6, 13.0e-6, 22.4e-6));
shader_float("uMieScattering", 21e-6);
shader_float("uRayleighScaleHeight", 8000.0);
shader_float("uMieScaleHeight", 1200.0);
shader_float("uMiePreferredDirection", 0.758);
skybox_mie_calc_sh(&sky, 1.2);
}
return sky;
}
static inline
texture_t load_env_tex( const char *pathfile, unsigned flags ) {
int flags_hdr = strendi(pathfile, ".hdr") ? TEXTURE_FLOAT | TEXTURE_RGBA : 0;
texture_t t = texture(pathfile, flags | TEXTURE_LINEAR | TEXTURE_MIPMAPS | TEXTURE_REPEAT | flags_hdr);
glBindTexture( GL_TEXTURE_2D, t.id );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
return t;
}
skybox_t skybox_pbr(const char *sky_map, const char *refl_map, const char *env_map) {
skybox_t sky = {0};
// sky mesh
vec3 vertices[] = {{+1,-1,+1},{+1,+1,+1},{+1,+1,-1},{-1,+1,-1},{+1,-1,-1},{-1,-1,-1},{-1,-1,+1},{-1,+1,+1}};
unsigned indices[] = { 0, 1, 2, 3, 4, 5, 6, 3, 7, 1, 6, 0, 4, 2 };
mesh_update(&sky.geometry, "p3", 0,countof(vertices),vertices, countof(indices),indices, MESH_TRIANGLE_STRIP);
// sky program
sky.flags = SKYBOX_PBR;
sky.program = shader(vfs_read("shaders/vs_3_3_skybox.glsl"),
vfs_read("fs_3_4_skybox.glsl"),
"att_position", "fragcolor", NULL);
// sky cubemap & SH
if( sky_map ) {
int is_panorama = vfs_size( sky_map );
if( is_panorama ) { // is file
stbi_hdr_to_ldr_gamma(1.0f);
image_t panorama = image( sky_map, IMAGE_RGBA );
sky.cubemap = cubemap( panorama, 0 ); // RGBA required
image_destroy(&panorama);
} else { // is folder
image_t images[6] = {0};
images[0] = image( va("%s/posx", sky_map), IMAGE_RGB ); // cubepx
images[1] = image( va("%s/negx", sky_map), IMAGE_RGB ); // cubenx
images[2] = image( va("%s/posy", sky_map), IMAGE_RGB ); // cubepy
images[3] = image( va("%s/negy", sky_map), IMAGE_RGB ); // cubeny
images[4] = image( va("%s/posz", sky_map), IMAGE_RGB ); // cubepz
images[5] = image( va("%s/negz", sky_map), IMAGE_RGB ); // cubenz
sky.cubemap = cubemap6( images, 0 );
for( int i = 0; i < countof(images); ++i ) image_destroy(&images[i]);
}
}
if( refl_map ) {
sky.refl = load_env_tex(refl_map, 0);
}
if( env_map ) {
sky.env = load_env_tex(env_map, 0);
}
return sky;
}
void skybox_mie_calc_sh(skybox_t *sky, float sky_intensity) {
unsigned WIDTH = 1024, HEIGHT = 1024;
int last_fb;
int vp[4];
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &last_fb);
glGetIntegerv(GL_VIEWPORT, vp);
if (!sky_intensity) {
sky_intensity = 1.0f;
}
if (!sky->pixels)
sky->pixels = MALLOC(WIDTH*HEIGHT*12);
if (!sky->framebuffers[0]) {
for(int i = 0; i < 6; ++i) {
glGenFramebuffers(1, &sky->framebuffers[i]);
glBindFramebuffer(GL_FRAMEBUFFER, sky->framebuffers[i]);
glGenTextures(1, &sky->textures[i]);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, sky->textures[i]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, WIDTH, HEIGHT, 0, GL_RGB, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, sky->textures[i], 0);
}
}
static vec3 directions[6] = {{ 1, 0, 0},{-1, 0, 0},{ 0, 1, 0},{ 0,-1, 0},{ 0, 0, 1},{ 0, 0,-1}};
int samples = 0;
for(int i = 0; i < 6; ++i) {
glBindFramebuffer(GL_FRAMEBUFFER, sky->framebuffers[i]);
glViewport(0, 0, WIDTH, HEIGHT);
glUseProgram(sky->program);
mat44 proj; perspective44(proj, 90.0f, WIDTH / (float)HEIGHT, 0.1f, 500.f);
mat44 view; lookat44(view, vec3(0,0,0), directions[i], vec3(0,-1,0));
skybox_render(sky, proj, view);
glReadPixels(0, 0, WIDTH, HEIGHT, GL_RGB, GL_FLOAT, sky->pixels);
// calculate SH coefficients (@ands)
// copied from cubemap6 method
const vec3 skyDir[] = {{ 1, 0, 0},{-1, 0, 0},{ 0, 1, 0},{ 0,-1, 0},{ 0, 0, 1},{ 0, 0,-1}};
const vec3 skyX[] = {{ 0, 0,-1},{ 0, 0, 1},{ 1, 0, 0},{ 1, 0, 0},{ 1, 0, 0},{-1, 0, 0}};
const vec3 skyY[] = {{ 0, 1, 0},{ 0, 1, 0},{ 0, 0,-1},{ 0, 0, 1},{ 0, 1, 0},{ 0, 1, 0}};
int step = 16;
for (int y = 0; y < HEIGHT; y += step) {
float *p = (float*)(sky->pixels + y * WIDTH * 3);
for (int x = 0; x < WIDTH; x += step) {
vec3 n = add3(
add3(
scale3(skyX[i], 2.0f * (x / (WIDTH - 1.0f)) - 1.0f),
scale3(skyY[i], -2.0f * (y / (HEIGHT - 1.0f)) + 1.0f)),
skyDir[i]); // texelDirection;
float l = len3(n);
vec3 light = scale3(vec3(p[0], p[1], p[2]), (1 / (l * l * l)) * sky_intensity); // texelSolidAngle * texel_radiance;
n = norm3(n);
sky->cubemap.sh[0] = add3(sky->cubemap.sh[0], scale3(light, 0.282095f));
sky->cubemap.sh[1] = add3(sky->cubemap.sh[1], scale3(light, -0.488603f * n.y * 2.0 / 3.0));
sky->cubemap.sh[2] = add3(sky->cubemap.sh[2], scale3(light, 0.488603f * n.z * 2.0 / 3.0));
sky->cubemap.sh[3] = add3(sky->cubemap.sh[3], scale3(light, -0.488603f * n.x * 2.0 / 3.0));
sky->cubemap.sh[4] = add3(sky->cubemap.sh[4], scale3(light, 1.092548f * n.x * n.y / 4.0));
sky->cubemap.sh[5] = add3(sky->cubemap.sh[5], scale3(light, -1.092548f * n.y * n.z / 4.0));
sky->cubemap.sh[6] = add3(sky->cubemap.sh[6], scale3(light, 0.315392f * (3.0f * n.z * n.z - 1.0f) / 4.0));
sky->cubemap.sh[7] = add3(sky->cubemap.sh[7], scale3(light, -1.092548f * n.x * n.z / 4.0));
sky->cubemap.sh[8] = add3(sky->cubemap.sh[8], scale3(light, 0.546274f * (n.x * n.x - n.y * n.y) / 4.0));
p += 3 * step;
samples++;
}
}
}
for (int s = 0; s < 9; s++) {
sky->cubemap.sh[s] = scale3(sky->cubemap.sh[s], 32.f / samples);
}
glBindFramebuffer(GL_FRAMEBUFFER, last_fb);
glViewport(vp[0], vp[1], vp[2], vp[3]);
}
void skybox_sh_reset(skybox_t *sky) {
for (int s = 0; s < 9; s++) {
sky->cubemap.sh[s] = vec3(0,0,0);
}
}
2024-08-19 09:38:59 +00:00
void skybox_sh_shader(skybox_t *sky) {
shader_vec3v("u_coefficients_sh", 9, sky->cubemap.sh);
}
2024-08-19 07:54:01 +00:00
void skybox_sh_add_light(skybox_t *sky, vec3 light, vec3 dir, float strength) {
// Normalize the direction
vec3 norm_dir = norm3(dir);
// Scale the light color and intensity
vec3 scaled_light = scale3(light, strength);
// Add light to the SH coefficients
sky->cubemap.sh[0] = add3(sky->cubemap.sh[0], scale3(scaled_light, 0.282095f));
sky->cubemap.sh[1] = add3(sky->cubemap.sh[1], scale3(scaled_light, -0.488603f * norm_dir.y));
sky->cubemap.sh[2] = add3(sky->cubemap.sh[2], scale3(scaled_light, 0.488603f * norm_dir.z));
sky->cubemap.sh[3] = add3(sky->cubemap.sh[3], scale3(scaled_light, -0.488603f * norm_dir.x));
}
API vec4 window_getcolor_(); // internal use, not public
static renderstate_t skybox_rs;
int skybox_push_state(skybox_t *sky, mat44 proj, mat44 view) {
last_cubemap = &sky->cubemap;
do_once {
skybox_rs = renderstate();
skybox_rs.depth_test_enabled = 1;
skybox_rs.cull_face_enabled = 0;
skybox_rs.front_face = GL_CCW;
}
// we have to reset clear color here, because of wrong alpha compositing issues on native transparent windows otherwise
vec4 bgcolor = window_getcolor_();
skybox_rs.clear_color[0] = bgcolor.r;
skybox_rs.clear_color[1] = bgcolor.g;
skybox_rs.clear_color[2] = bgcolor.b;
skybox_rs.clear_color[3] = 1; // @transparent
mat44 mvp; multiply44x2(mvp, proj, view);
//glDepthMask(GL_FALSE);
shader_bind(sky->program);
shader_mat44("u_mvp", mvp);
if( sky->flags ) {
shader_cubemap("u_cubemap", sky->cubemap.id);
}
renderstate_apply(&skybox_rs);
return 0; // @fixme: return sortable hash here?
}
int skybox_pop_state() {
//vec4 bgcolor = window_getcolor_(); glClearColor(bgcolor.r, bgcolor.g, bgcolor.b, window_has_transparent() ? 0 : bgcolor.a); // @transparent
//glDepthMask(GL_TRUE);
//glClear(GL_DEPTH_BUFFER_BIT);
return 0;
}
int skybox_render(skybox_t *sky, mat44 proj, mat44 view) {
skybox_push_state(sky, proj, view);
mesh_render(&sky->geometry);
skybox_pop_state();
return 0;
}
void skybox_destroy(skybox_t *sky) {
glDeleteProgram(sky->program);
cubemap_destroy(&sky->cubemap);
mesh_destroy(&sky->geometry);
if (sky->pixels) {
FREE(sky->pixels);
glDeleteFramebuffers(6, sky->framebuffers);
glDeleteTextures(6, sky->textures);
}
}
// -----------------------------------------------------------------------------
// meshes
mesh_t mesh() {
mesh_t z = {0};
return z;
}
aabb mesh_bounds(mesh_t *m) {
aabb b = {{1e9,1e9,1e9},{-1e9,-1e9,-1e9}};
for( int i = 0; i < array_count(m->in_vertex3); ++i ) {
if( m->in_vertex3[i].x < b.min.x ) b.min.x = m->in_vertex3[i].x;
if( m->in_vertex3[i].x > b.max.x ) b.max.x = m->in_vertex3[i].x;
if( m->in_vertex3[i].y < b.min.y ) b.min.y = m->in_vertex3[i].y;
if( m->in_vertex3[i].y > b.max.y ) b.max.y = m->in_vertex3[i].y;
if( m->in_vertex3[i].z < b.min.z ) b.min.z = m->in_vertex3[i].z;
if( m->in_vertex3[i].z > b.max.z ) b.max.z = m->in_vertex3[i].z;
}
return b;
}
void mesh_update(mesh_t *m, const char *format, int vertex_stride,int vertex_count,const void *vertex_data, int index_count,const void *index_data, int flags) {
m->flags = flags;
// setup
unsigned sizeof_index = sizeof(GLuint);
unsigned sizeof_vertex = 0;
m->index_count = index_count;
m->vertex_count = vertex_count;
// iterate vertex attributes { position, normal + uv + tangent + bitangent + ... }
struct vertex_descriptor {
int vertex_type, num_attribute, num_components, alt_normalized;
int stride, offset;
} descriptor[16] = {0}, *dc = &descriptor[0];
do switch( *format ) {
break; case '*': dc->alt_normalized = 1;
break; case '0': dc->num_components = 0;
break; case '1': dc->num_components = 1;
break; case '2': dc->num_components = 2;
break; case '3': dc->num_components = 3;
break; case '4': dc->num_components = 4;
break; case 'F': dc->vertex_type = GL_FLOAT;
break; case 'U': case 'I': dc->vertex_type = GL_UNSIGNED_INT;
break; case 'B': if(format[-1] >= '0' && format[-1] <= '9') dc->vertex_type = GL_UNSIGNED_BYTE; //else bitangent.
break; case ' ': while (format[1] == ' ') format++; case '\0':
if (!dc->vertex_type) dc->vertex_type = GL_FLOAT;
dc->offset = sizeof_vertex;
sizeof_vertex += (dc->stride = dc->num_components * (dc->vertex_type == GL_UNSIGNED_BYTE ? 1 : 4));
++dc;
break; default: if( !strchr("pntbcwai", *format) ) PANIC("unsupported vertex type '%c'", *format);
} while (*format++);
if(vertex_stride > 0) sizeof_vertex = vertex_stride;
// layout
if(!m->vao) glGenVertexArrays(1, &m->vao);
glBindVertexArray(m->vao);
// index data
if( index_data && index_count ) {
m->index_count = index_count;
if(!m->ibo) glGenBuffers(1, &m->ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, m->index_count * sizeof_index, index_data, flags & MESH_STREAM ? GL_STREAM_DRAW : GL_STATIC_DRAW);
}
// vertex data
if( vertex_data && vertex_count ) {
m->vertex_count = vertex_count;
if(!m->vbo) glGenBuffers(1, &m->vbo);
glBindBuffer(GL_ARRAY_BUFFER, m->vbo);
glBufferData(GL_ARRAY_BUFFER, m->vertex_count * sizeof_vertex, vertex_data, flags & MESH_STREAM ? GL_STREAM_DRAW : GL_STATIC_DRAW);
}
for( int i = 0; i < 8; ++i ) {
// glDisableVertexAttribArray(i);
}
// vertex setup: iterate descriptors
for( int i = 0; i < countof(descriptor); ++i ) {
if( descriptor[i].num_components ) {
glDisableVertexAttribArray(i);
glVertexAttribPointer(i,
descriptor[i].num_components, descriptor[i].vertex_type, (descriptor[i].vertex_type == GL_UNSIGNED_BYTE ? GL_TRUE : GL_FALSE) ^ (descriptor[i].alt_normalized ? GL_TRUE : GL_FALSE),
sizeof_vertex, (GLchar*)NULL + descriptor[i].offset);
glEnableVertexAttribArray(i);
} else {
glDisableVertexAttribArray(i);
}
}
glBindVertexArray(0);
}
void mesh_render(mesh_t *sm) {
if( sm->vao ) {
glBindVertexArray(sm->vao);
if( sm->ibo ) { // with indices
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, sm->ibo); // <-- why intel?
glDrawElements(sm->flags & MESH_TRIANGLE_STRIP ? GL_TRIANGLE_STRIP : GL_TRIANGLES, sm->index_count, GL_UNSIGNED_INT, (char*)0);
profile_incstat("Render.num_drawcalls", +1);
profile_incstat("Render.num_triangles", sm->index_count/3);
} else { // with vertices only
glDrawArrays(sm->flags & MESH_TRIANGLE_STRIP ? GL_TRIANGLE_STRIP : GL_TRIANGLES, 0, sm->vertex_count /* / 3 */);
profile_incstat("Render.num_drawcalls", +1);
profile_incstat("Render.num_triangles", sm->vertex_count/3);
}
}
}
void mesh_render_prim(mesh_t *sm, unsigned prim) {
if( sm->vao ) {
glBindVertexArray(sm->vao);
if( sm->ibo ) { // with indices
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, sm->ibo); // <-- why intel?
glDrawElements(prim, sm->index_count, GL_UNSIGNED_INT, (char*)0);
profile_incstat("Render.num_drawcalls", +1);
profile_incstat("Render.num_triangles", sm->index_count/3);
} else { // with vertices only
glDrawArrays(prim, 0, sm->vertex_count /* / 3 */);
profile_incstat("Render.num_drawcalls", +1);
profile_incstat("Render.num_triangles", sm->vertex_count/3);
}
}
}
void mesh_destroy(mesh_t *m) {
// @todo
glDeleteBuffers(1, &m->vbo);
glDeleteBuffers(1, &m->ibo);
glDeleteVertexArrays(1, &m->vao);
}
// -----------------------------------------------------------------------------
// screenshots
void* screenshot( int n ) { // 3 RGB, 4 RGBA, -3 BGR, -4 BGRA
// sync, 10 ms -- pixel perfect
int w = window_width(), h = window_height();
int mode = n == 3 ? GL_RGB : n == -3 ? GL_BGR : n == 4 ? GL_RGBA : GL_BGRA;
static __thread uint8_t *pixels = 0;
pixels = (uint8_t*)REALLOC(pixels, w * h * 4 ); // @leak per thread
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); // disable any pbo, in case somebody did for us
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadBuffer(GL_FRONT);
glReadPixels(0, 0, w, h, mode, GL_UNSIGNED_BYTE, pixels);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
return pixels;
}
void* screenshot_async( int n ) { // 3 RGB, 4 RGBA, -3 BGR, -4 BGRA
#if is(ems)
return screenshot(n); // no glMapBuffer() on emscripten
#else
// async, 0 ms -- @fixme: MSAA can cause some artifacts with PBOs: either use glDisable(GL_MULTISAMPLE) when recording or do not create window with WINDOW_MSAAx options at all.
int w = window_width(), h = window_height();
int mode = n == 3 ? GL_RGB : n == -3 ? GL_BGR : n == 4 ? GL_RGBA : GL_BGRA;
static __thread uint8_t *pixels = 0;
pixels = (uint8_t*)REALLOC(pixels, w * h * 4 ); // @leak per thread
enum { NUM_PBOS = 16 };
static __thread GLuint pbo[NUM_PBOS] = {0}, lastw = 0, lasth = 0, bound = 0;
if( lastw != w || lasth != h ) {
lastw = w, lasth = h;
bound = 0;
for( int i = 0; i < NUM_PBOS; ++i ) {
if(!pbo[i]) glGenBuffers(1, &pbo[i]);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo[i]);
glBufferData(GL_PIXEL_PACK_BUFFER, w * h * 4, NULL, GL_STREAM_READ); // GL_STATIC_READ);
//glReadPixels(0, 0, w, h, mode, GL_UNSIGNED_BYTE, (GLvoid*)((GLchar*)NULL+0));
}
}
// read from oldest bound pbo
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo[bound]);
void *ptr = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
memcpy(pixels, ptr, w * h * abs(n));
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
// trigger next read
glReadBuffer(GL_FRONT);
glReadPixels(0, 0, w, h, mode, GL_UNSIGNED_BYTE, (GLvoid*)((GLchar*)NULL+0));
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
bound = (bound + 1) % NUM_PBOS;
return pixels;
#endif
}
// -----------------------------------------------------------------------------
// viewports
void viewport_color(unsigned color) {
unsigned r = (color >> 0) & 255;
unsigned g = (color >> 8) & 255;
unsigned b = (color >> 16) & 255;
unsigned a = (color >> 24) & 255;
glClearColor(r, g, b, a);
}
void viewport_clear(bool color, bool depth) {
glClearDepthf(1);
glClearStencil(0);
glClear((color ? GL_COLOR_BUFFER_BIT : 0) | (depth ? GL_DEPTH_BUFFER_BIT : 0));
}
void viewport_clip(vec2 from, vec2 to) {
float x = from.x, y = from.y, w = to.x-from.x, h = to.y-from.y;
y = window_height()-y-h;
glViewport(x, y, w, h);
glScissor(x, y, w, h);
}
// -----------------------------------------------------------------------------
// fbos
unsigned fbo(unsigned color_texture_id, unsigned depth_texture_id, int flags) {
int last_fb;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &last_fb);
GLuint fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
if( color_texture_id ) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color_texture_id, 0);
if( depth_texture_id ) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_texture_id, 0);
#if 0 // this is working; it's just not enabled for now
else {
// create a non-sampleable renderbuffer object for depth and stencil attachments
unsigned int rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, color.width, color.height); // use a single renderbuffer object for both a depth AND stencil buffer.
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); // now actually attach it
}
#endif
#if is(ems)
GLenum nones[] = { GL_NONE };
if(flags) glDrawBuffers(1, nones);
if(flags) glReadBuffer(GL_NONE);
#else
if(flags) glDrawBuffer(GL_NONE);
if(flags) glReadBuffer(GL_NONE);
#endif
#if 1
GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if( GL_FRAMEBUFFER_COMPLETE != result ) {
PANIC("ERROR: Framebuffer not complete.");
}
#else
switch (glCheckFramebufferStatus(GL_FRAMEBUFFER)) {
case GL_FRAMEBUFFER_COMPLETE: break;
case GL_FRAMEBUFFER_UNDEFINED: PANIC("GL_FRAMEBUFFER_UNDEFINED");
case GL_FRAMEBUFFER_UNSUPPORTED: PANIC("GL_FRAMEBUFFER_UNSUPPORTED");
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: PANIC("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: PANIC("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER");
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: PANIC("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER");
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: PANIC("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE");
// case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: PANIC("GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT");
case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: PANIC("GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS");
// case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: PANIC("GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT");
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: PANIC("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
default: PANIC("ERROR: Framebuffer not complete. glCheckFramebufferStatus returned %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
}
#endif
glBindFramebuffer (GL_FRAMEBUFFER, last_fb);
return fbo;
}
static __thread array(handle) fbos;
void fbo_bind(unsigned id) {
glBindFramebuffer(GL_FRAMEBUFFER, id);
array_push(fbos, id);
}
void fbo_unbind() {
handle id = 0;
if (array_count(fbos)) {
array_pop(fbos);
id = *array_back(fbos);
}
glBindFramebuffer(GL_FRAMEBUFFER, id);
}
void fbo_destroy(unsigned id) {
// glDeleteRenderbuffers(1, &renderbuffer);
glDeleteFramebuffers(1, &id);
}
// -----------------------------------------------------------------------------
// post-fxs swapchain
typedef struct passfx passfx;
typedef struct postfx postfx;
void postfx_create(postfx *fx, int flags);
void postfx_destroy(postfx *fx);
bool postfx_load(postfx *fx, const char *name, const char *fragment);
bool postfx_begin(postfx *fx, int width, int height);
bool postfx_end(postfx *fx);
bool postfx_enabled(postfx *fx, int pass_number);
bool postfx_enable(postfx *fx, int pass_number, bool enabled);
// bool postfx_toggle(postfx *fx, int pass_number);
void postfx_clear(postfx *fx);
void postfx_order(postfx *fx, int pass, unsigned priority);
char* postfx_name(postfx *fx, int slot);
int ui_postfx(postfx *fx, int slot);
struct passfx {
mesh_t m;
char *name;
unsigned program;
int uniforms[16];
unsigned priority;
bool enabled;
};
struct postfx {
// renderbuffers: color & depth textures
unsigned fb[2];
texture_t diffuse[2], depth[2];
// shader passes
array(passfx) pass;
// global enable flag
bool enabled;
};
enum {
u_color,
u_depth,
u_time,
u_frame,
u_width, u_height,
u_mousex, u_mousey,
u_channelres0x, u_channelres0y,
u_channelres1x, u_channelres1y,
};
void postfx_create(postfx *fx, int flags) {
postfx z = {0};
*fx = z;
fx->enabled = 1;
(void)flags;
}
void postfx_destroy( postfx *fx ) {
for( int i = 0; i < array_count(fx->pass); ++i ) {
FREE(fx->pass[i].name);
}
array_free(fx->pass);
texture_destroy(&fx->diffuse[0]);
texture_destroy(&fx->diffuse[1]);
texture_destroy(&fx->depth[0]);
texture_destroy(&fx->depth[1]);
fbo_destroy(fx->fb[0]);
fbo_destroy(fx->fb[1]);
postfx z = {0};
*fx = z;
}
char* postfx_name(postfx *fx, int slot) {
return slot < 0 || slot >= array_count(fx->pass) ? "" : fx->pass[ slot ].name;
}
int postfx_find(postfx *fx, const char *name) {
name = file_name(name);
for( int i = 0; i < array_count(fx->pass); ++i) if(!strcmpi(fx->pass[i].name, name)) return i;
return -1;
}
static
int postfx_sort_fn(const void *a, const void *b) {
unsigned p1 = ((passfx*)a)->priority;
unsigned p2 = ((passfx*)b)->priority;
return (p1 > p2) - (p1 < p2);
}
void postfx_order(postfx *fx, int pass, unsigned priority) {
if (pass < 0 || pass >= array_count(fx->pass)) return;
if (priority >= array_count(fx->pass)) return;
fx->pass[priority].priority = pass;
fx->pass[pass].priority = priority;
array_sort(fx->pass, postfx_sort_fn);
}
int postfx_load_from_mem( postfx *fx, const char *name, const char *fs ) {
PRINTF("%s\n", name);
if(!fs || !fs[0]) return -1; // PANIC("!invalid fragment shader");
passfx pass={0};
array_push(fx->pass, pass);
passfx *p = array_back(fx->pass);
p->name = STRDUP(name);
p->priority = array_count(fx->pass)-1;
// preload stuff
static const char *vs = 0;
static const char *preamble = 0;
static const char *shadertoy = 0;
static char *fs2 = 0;
do_once {
vs = STRDUP(vfs_read("shaders/vs_0_2_fullscreen_quad_B.glsl"));
preamble = STRDUP(vfs_read("shaders/fs_2_4_preamble.glsl"));
shadertoy = STRDUP(vfs_read("shaders/fs_main_shadertoy.glsl"));
fs2 = (char*)CALLOC(1, 128*1024);
}
// patch fragment
snprintf(fs2, 128*1024, "%s%s%s", preamble, strstr(fs, "mainImage") ? shadertoy : "", fs );
p->program = shader(vs, fs2, "vtexcoord", "fragColor" , NULL);
glUseProgram(p->program); // needed?
for( int i = 0; i < countof(p->uniforms); ++i ) p->uniforms[i] = -1;
if( p->uniforms[u_time] == -1 ) p->uniforms[u_time] = glGetUniformLocation(p->program, "iTime");
if( p->uniforms[u_frame] == -1 ) p->uniforms[u_frame] = glGetUniformLocation(p->program, "iFrame");
if( p->uniforms[u_width] == -1 ) p->uniforms[u_width] = glGetUniformLocation(p->program, "iWidth");
if( p->uniforms[u_height] == -1 ) p->uniforms[u_height] = glGetUniformLocation(p->program, "iHeight");
if( p->uniforms[u_mousex] == -1 ) p->uniforms[u_mousex] = glGetUniformLocation(p->program, "iMousex");
if( p->uniforms[u_mousey] == -1 ) p->uniforms[u_mousey] = glGetUniformLocation(p->program, "iMousey");
if( p->uniforms[u_color] == -1 ) p->uniforms[u_color] = glGetUniformLocation(p->program, "tex");
if( p->uniforms[u_color] == -1 ) p->uniforms[u_color] = glGetUniformLocation(p->program, "tex0");
if( p->uniforms[u_color] == -1 ) p->uniforms[u_color] = glGetUniformLocation(p->program, "tColor");
if( p->uniforms[u_color] == -1 ) p->uniforms[u_color] = glGetUniformLocation(p->program, "tDiffuse");
if( p->uniforms[u_color] == -1 ) p->uniforms[u_color] = glGetUniformLocation(p->program, "iChannel0");
if( p->uniforms[u_depth] == -1 ) p->uniforms[u_depth] = glGetUniformLocation(p->program, "tex1");
if( p->uniforms[u_depth] == -1 ) p->uniforms[u_depth] = glGetUniformLocation(p->program, "tDepth");
if( p->uniforms[u_depth] == -1 ) p->uniforms[u_depth] = glGetUniformLocation(p->program, "iChannel1");
if( p->uniforms[u_channelres0x] == -1 ) p->uniforms[u_channelres0x] = glGetUniformLocation(p->program, "iChannelRes0x");
if( p->uniforms[u_channelres0y] == -1 ) p->uniforms[u_channelres0y] = glGetUniformLocation(p->program, "iChannelRes0y");
if( p->uniforms[u_channelres1x] == -1 ) p->uniforms[u_channelres1x] = glGetUniformLocation(p->program, "iChannelRes1x");
if( p->uniforms[u_channelres1y] == -1 ) p->uniforms[u_channelres1y] = glGetUniformLocation(p->program, "iChannelRes1y");
// set quad
glGenVertexArrays(1, &p->m.vao);
return array_count(fx->pass)-1;
}
bool postfx_enable(postfx *fx, int pass, bool enabled) {
if( pass < 0 || pass >= array_count(fx->pass) ) return false;
fx->pass[pass].enabled = enabled;
fx->enabled = !!array_count(fx->pass);
return fx->enabled;
}
bool postfx_enabled(postfx *fx, int pass) {
if( pass < 0 || pass >= array_count(fx->pass) ) return false;
return fx->pass[pass].enabled;
}
bool postfx_toggle(postfx *fx, int pass) {
if( pass < 0 || pass >= array_count(fx->pass) ) return false;
return postfx_enable(fx, pass, 1 ^ postfx_enabled(fx, pass));
}
void postfx_clear(postfx *fx) {
for (int i = 0; i < array_count(fx->pass); i++) {
fx->pass[i].enabled = 0;
}
fx->enabled = 0;
}
unsigned postfx_program(postfx *fx, int pass) {
if( pass < 0 || pass >= array_count(fx->pass) ) return 0;
return fx->pass[pass].program;
}
int ui_postfx(postfx *fx, int pass) {
if (pass < 0 || pass >= array_count(fx->pass)) return 0;
int on = ui_enabled();
( postfx_enabled(fx,pass) ? ui_enable : ui_disable )();
int rc = ui_shader(fx->pass[pass].program);
ui_separator();
int btn = ui_buttons(2, "Move up", "Move down");
if (btn == 1) {
postfx_order(fx, pass, fx->pass[pass].priority-1);
}
else if (btn == 2) {
postfx_order(fx, pass, fx->pass[pass].priority+1);
}
( on ? ui_enable : ui_disable )();
return rc;
}
static
int postfx_active_passes(postfx *fx) {
int num_passes = 0;
for (int i = 0; i < array_count(fx->pass); i++)
if (fx->pass[i].enabled)
++num_passes;
return num_passes;
}
bool postfx_begin(postfx *fx, int width, int height) {
// reset clear color: needed in case transparent window is being used (alpha != 0)
glClearColor(0,0,0,0); // @transparent
width += !width;
height += !height;
// resize if needed
if( fx->diffuse[0].w != width || fx->diffuse[0].h != height ) {
texture_destroy(&fx->diffuse[0]);
texture_destroy(&fx->diffuse[1]);
texture_destroy(&fx->depth[0]);
texture_destroy(&fx->depth[1]);
fbo_destroy(fx->fb[0]);
fbo_destroy(fx->fb[1]);
// create texture, set texture parameters and content
fx->diffuse[0] = texture_create(width, height, 4, NULL, TEXTURE_RGBA|TEXTURE_FLOAT);
fx->depth[0] = texture_create(width, height, 1, NULL, TEXTURE_DEPTH|TEXTURE_FLOAT);
fx->fb[0] = fbo(fx->diffuse[0].id, fx->depth[0].id, 0);
// create texture, set texture parameters and content
fx->diffuse[1] = texture_create(width, height, 4, NULL, TEXTURE_RGBA|TEXTURE_FLOAT);
fx->depth[1] = texture_create(width, height, 1, NULL, TEXTURE_DEPTH|TEXTURE_FLOAT);
fx->fb[1] = fbo(fx->diffuse[1].id, fx->depth[1].id, 0);
}
uint64_t num_active_passes = postfx_active_passes(fx);
bool active = fx->enabled && num_active_passes;
if( !active ) {
return false;
}
fbo_bind(fx->fb[1]);
viewport_clear(true, true);
viewport_clip(vec2(0,0), vec2(width, height));
fbo_unbind();
fbo_bind(fx->fb[0]);
viewport_clear(true, true);
viewport_clip(vec2(0,0), vec2(width, height));
// we keep fbo_0 bound so that user can render into it.
return true;
}
static renderstate_t postfx_rs;
bool postfx_end(postfx *fx) {
uint64_t num_active_passes = postfx_active_passes(fx);
bool active = fx->enabled && num_active_passes;
if( !active ) {
return false;
}
do_once {
postfx_rs = renderstate();
// disable depth test in 2d rendering
postfx_rs.depth_test_enabled = 0;
postfx_rs.cull_face_enabled = 0;
postfx_rs.blend_enabled = 1;
postfx_rs.blend_src = GL_ONE;
postfx_rs.blend_dst = GL_ONE_MINUS_SRC_ALPHA;
}
// unbind postfx fbo
fbo_unbind();
renderstate_apply(&postfx_rs);
int frame = 0;
float t = time_ms() / 1000.f;
float w = fx->diffuse[0].w;
float h = fx->diffuse[0].h;
float mx = input(MOUSE_X);
float my = input(MOUSE_Y);
for(int i = 0, e = array_count(fx->pass); i < e; ++i) {
passfx *pass = &fx->pass[i];
if( pass->enabled ) {
if( !pass->program ) { --num_active_passes; continue; }
glUseProgram(pass->program);
// bind texture to texture unit 0
// shader_texture_unit(fx->diffuse[frame], 0);
glActiveTexture(GL_TEXTURE0 + 0); glBindTexture(GL_TEXTURE_2D, fx->diffuse[frame].id);
glUniform1i(pass->uniforms[u_color], 0);
glUniform1f(pass->uniforms[u_channelres0x], fx->diffuse[frame].w);
glUniform1f(pass->uniforms[u_channelres0y], fx->diffuse[frame].h);
// bind depth to texture unit 1
// shader_texture_unit(fx->depth[frame], 1);
glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_2D, fx->depth[frame].id);
glUniform1i(pass->uniforms[u_depth], 1);
// bind uniforms
static unsigned f = 0; ++f;
glUniform1f(pass->uniforms[u_time], t);
glUniform1f(pass->uniforms[u_frame], f-1);
glUniform1f(pass->uniforms[u_width], w);
glUniform1f(pass->uniforms[u_height], h);
glUniform1f(pass->uniforms[u_mousex], mx);
glUniform1f(pass->uniforms[u_mousey], my);
// bind the vao
int bound = --num_active_passes;
if (bound) fbo_bind(fx->fb[frame ^= 1]);
// fullscreen quad
glBindVertexArray(pass->m.vao);
glDrawArrays(GL_TRIANGLES, 0, 6);
profile_incstat("Render.num_drawcalls", +1);
profile_incstat("Render.num_triangles", +2);
glBindVertexArray(0);
if (bound) fbo_unbind();
}
}
glUseProgram(0);
// restore clear color: needed in case transparent window is being used (alpha != 0)
glClearColor(0,0,0,1); // @transparent
return true;
}
static postfx fx;
int fx_load_from_mem(const char *nameid, const char *content) {
do_once postfx_create(&fx, 0);
return postfx_load_from_mem(&fx, nameid, content);
}
int fx_load(const char *filemask) {
static set(char*) added = 0; do_once set_init_str(added);
for each_array( vfs_list(filemask), char*, list ) {
if( set_find(added, list) ) continue;
char *name = STRDUP(list); // @leak
set_insert(added, name);
(void)postfx_load_from_mem(&fx, file_name(name), vfs_read(name));
}
if( 1 )
for each_array( file_list(filemask), char*, list ) {
if( set_find(added, list) ) continue;
char *name = STRDUP(list); // @leak
set_insert(added, name);
(void)postfx_load_from_mem(&fx, file_name(name), file_read(name));
}
return 1;
}
void fx_begin() {
postfx_begin(&fx, window_width(), window_height());
}
void fx_begin_res(int w, int h) {
postfx_begin(&fx, w, h);
}
void fx_end() {
postfx_end(&fx);
}
int fx_enabled(int pass) {
return postfx_enabled(&fx, pass);
}
void fx_enable(int pass, int enabled) {
postfx_enable(&fx, pass, enabled);
}
void fx_enable_all(int enabled) {
for( int i = 0; i < array_count(fx.pass); ++i ) fx_enable(i, enabled);
}
char *fx_name(int pass) {
return postfx_name(&fx, pass);
}
int fx_find(const char *name) {
return postfx_find(&fx, name);
}
void fx_order(int pass, unsigned priority) {
postfx_order(&fx, pass, priority);
}
unsigned fx_program(int pass) {
return postfx_program(&fx, pass);
}
void fx_setparam(int pass, const char *name, float value) {
unsigned program = fx_program(pass);
if( !program ) return;
unsigned oldprogram = shader_bind(program);
shader_float(name, value);
shader_bind(oldprogram);
}
int ui_fx(int pass) {
return ui_postfx(&fx, pass);
}
int ui_fxs() {
if(!array_count(fx.pass)) return ui_label(ICON_MD_WARNING " No Post FXs with annotations loaded."), 0;
int changed = 0;
for( int i = 0; i < array_count(fx.pass); ++i ) {
char *name = fx_name(i); if( !name ) break;
bool b = fx_enabled(i);
if( ui_bool(name, &b) ) fx_enable(i, fx_enabled(i) ^ 1);
ui_fx(i);
ui_separator();
}
return changed;
}
// -----------------------------------------------------------------------------
// brdf
static texture_t brdf = {0};
static void brdf_load() {
// generate texture
unsigned tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RG16F, 512, 512, 0, GL_RG, GL_FLOAT, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
brdf.id = tex;
brdf.w = 512;
brdf.h = 512;
// create program and generate BRDF LUT
unsigned lut_fbo = fbo(tex, 0, 0), rbo=0;
fbo_bind(lut_fbo);
static int program = -1, vao = -1;
if( program < 0 ) {
const char* vs = vfs_read("shaders/vs_0_2_fullscreen_quad_B_flipped.glsl");
2024-08-24 13:29:05 +00:00
const char* fs = vfs_read("shaders/brdf_lut.glsl");
2024-08-19 07:54:01 +00:00
program = shader(vs, fs, "", "fragcolor", NULL);
glGenVertexArrays( 1, (GLuint*)&vao );
}
glDisable(GL_BLEND);
handle old_shader = last_shader;
glUseProgram( program );
glViewport(0, 0, 512, 512);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glBindVertexArray( vao );
glDrawArrays( GL_TRIANGLES, 0, 6 );
profile_incstat("Render.num_drawcalls", +1);
profile_incstat("Render.num_triangles", +2);
glBindVertexArray( 0 );
glUseProgram( last_shader );
fbo_unbind();
fbo_destroy(lut_fbo);
}
texture_t brdf_lut() {
do_once brdf_load();
return brdf;
}
// -----------------------------------------------------------------------------
// materials
bool colormap( colormap_t *cm, const char *texture_name, bool load_as_srgb ) {
if( !texture_name ) return false;
if( cm->texture ) {
texture_destroy(cm->texture);
FREE(cm->texture), cm->texture = NULL;
}
2024-08-24 16:03:23 +00:00
// int srgb = load_as_srgb ? TEXTURE_SRGB : 0;
int srgb = 0;
2024-08-19 07:54:01 +00:00
int hdr = strendi(texture_name, ".hdr") ? TEXTURE_FLOAT|TEXTURE_RGBA : 0;
texture_t t = texture_compressed(texture_name, TEXTURE_LINEAR | TEXTURE_ANISOTROPY | TEXTURE_MIPMAPS | TEXTURE_REPEAT | hdr | srgb);
if( t.id == texture_checker().id ) {
cm->texture = NULL;
return false;
}
cm->texture = CALLOC(1, sizeof(texture_t));
*cm->texture = t;
return true;
}
// ----------------------------------------------------------------------------
// shadertoys
//
// @todo: multipass
// - https://www.shadertoy.com/view/Mst3Wr - la calanque
// - https://www.shadertoy.com/view/XsyGWV - sirenian dawn
// - https://www.shadertoy.com/view/Xst3zX - wordtoy
// - https://www.shadertoy.com/view/MddGzf - bricks game
// - https://www.shadertoy.com/view/Ms33WB - post process - ssao
// - https://www.shadertoy.com/view/Xds3zN
enum shadertoy_uniforms {
iFrame,
iTime,
iDate,
iGlobalTime,
iGlobalFrame,
iGlobalDelta,
iChannel0,
iChannel1,
iChannel2,
iChannel3,
iResolution,
iMouse,
iOffset,
iSampleRate,
iChannelResolution,
iChannelTime,
// iCameraScreen
// iCameraPosition
// iCameraActive
};
shadertoy_t shadertoy( const char *shaderfile, unsigned flags ) {
shadertoy_t s = {0};
s.flags = flags;
char *file = vfs_read(shaderfile);
if( !file ) return s;
glGenVertexArrays(1, &s.vao);
char *fs = stringf("%s%s", vfs_read("header_shadertoy.glsl"), file);
s.program = shader((flags&SHADERTOY_FLIP_Y) ? vfs_read("shaders/vs_shadertoy_flip.glsl") : vfs_read("shaders/vs_shadertoy.glsl"), fs, "", "fragColor", NULL);
FREE(fs);
if( strstr(file, "noise3.jpg"))
s.texture_channels[0] = texture("shadertoys/tex12.png", 0).id;
else
s.texture_channels[0] = texture("shadertoys/tex04.jpg", 0).id;
s.uniforms[iFrame] = glGetUniformLocation(s.program, "iFrame");
s.uniforms[iTime] = glGetUniformLocation(s.program, "iTime");
s.uniforms[iDate] = glGetUniformLocation(s.program, "iDate");
s.uniforms[iGlobalTime] = glGetUniformLocation(s.program, "iGlobalTime");
s.uniforms[iGlobalDelta] = glGetUniformLocation(s.program, "iGlobalDelta");
s.uniforms[iGlobalFrame] = glGetUniformLocation(s.program, "iGlobalFrame");
s.uniforms[iResolution] = glGetUniformLocation(s.program, "iResolution");
s.uniforms[iChannel0] = glGetUniformLocation(s.program, "iChannel0");
s.uniforms[iChannel1] = glGetUniformLocation(s.program, "iChannel1");
s.uniforms[iChannel2] = glGetUniformLocation(s.program, "iChannel2");
s.uniforms[iChannel3] = glGetUniformLocation(s.program, "iChannel3");
s.uniforms[iMouse] = glGetUniformLocation(s.program, "iMouse");
s.uniforms[iOffset] = glGetUniformLocation(s.program, "iOffset");
s.uniforms[iSampleRate] = glGetUniformLocation(s.program, "iSampleRate");
s.uniforms[iChannelResolution] = glGetUniformLocation(s.program, "iChannelResolution");
s.uniforms[iChannelTime] = glGetUniformLocation(s.program, "iChannelTime");
return s;
}
shadertoy_t* shadertoy_render(shadertoy_t *s, float delta) {
if( s->program && s->vao ) {
if( s->dims.x && !(s->flags&SHADERTOY_IGNORE_FBO) && !texture_rec_begin(&s->tx, s->dims.x, s->dims.y) ) {
return s;
}
if(input_down(MOUSE_L) || input_down(MOUSE_R) ) s->mouse.z = input(MOUSE_X), s->mouse.w = -(window_height() - input(MOUSE_Y));
if(input(MOUSE_L) || input(MOUSE_R)) s->mouse.x = input(MOUSE_X), s->mouse.y = (window_height() - input(MOUSE_Y));
vec4 m = mul4(s->mouse, vec4(1,1,1-2*(!input(MOUSE_L) && !input(MOUSE_R)),1-2*(input_down(MOUSE_L) || input_down(MOUSE_R))));
time_t tmsec = time(0);
struct tm *tm = localtime(&tmsec);
s->t += delta * 1000;
glUseProgram(s->program);
glUniform1f(s->uniforms[iGlobalTime], s->t / 1000.f );
glUniform1f(s->uniforms[iGlobalFrame], s->frame++);
glUniform1f(s->uniforms[iGlobalDelta], delta / 1000.f );
glUniform2f(s->uniforms[iResolution], s->dims.x ? s->dims.x : window_width(), s->dims.y ? s->dims.y : window_height());
if (!(s->flags&SHADERTOY_IGNORE_MOUSE)) glUniform4f(s->uniforms[iMouse], m.x,m.y,m.z,m.w );
glUniform1i(s->uniforms[iFrame], (int)window_frame());
glUniform1f(s->uniforms[iTime], time_ss());
glUniform4f(s->uniforms[iDate], tm->tm_year, tm->tm_mon, tm->tm_mday, tm->tm_sec + tm->tm_min * 60 + tm->tm_hour * 3600);
int unit = 0;
for( int i = 0; i < 4; i++ ) {
if( s->texture_channels[i] ) {
glActiveTexture(GL_TEXTURE0 + unit);
glBindTexture(GL_TEXTURE_2D, s->texture_channels[i]);
glUniform1i(s->uniforms[iChannel0+i], unit);
unit++;
}
}
glViewport(0, 0, s->dims.x ? s->dims.x : window_width(), s->dims.y ? s->dims.y : window_height());
glBindVertexArray(s->vao);
glDrawArrays(GL_TRIANGLES, 0, 3);
if(s->dims.x && !(s->flags&SHADERTOY_IGNORE_FBO)) texture_rec_end(&s->tx); // texture_rec
}
return s;
}
// -----------------------------------------------------------------------------
// skeletal meshes (iqm)
#define IQM_MAGIC "INTERQUAKEMODEL"
#define IQM_VERSION 2
struct iqmheader {
char magic[16];
unsigned version;
unsigned filesize;
unsigned flags;
unsigned num_text, ofs_text;
unsigned num_meshes, ofs_meshes;
unsigned num_vertexarrays, num_vertexes, ofs_vertexarrays;
unsigned num_triangles, ofs_triangles, ofs_adjacency;
unsigned num_joints, ofs_joints;
unsigned num_poses, ofs_poses;
unsigned num_anims, ofs_anims;
unsigned num_frames, num_framechannels, ofs_frames, ofs_bounds;
unsigned num_comment, ofs_comment;
unsigned num_extensions, ofs_extensions;
};
struct iqmmesh {
unsigned name;
unsigned material;
unsigned first_vertex, num_vertexes;
unsigned first_triangle, num_triangles;
};
enum {
IQM_POSITION,
IQM_TEXCOORD,
IQM_NORMAL,
IQM_TANGENT,
IQM_BLENDINDEXES,
IQM_BLENDWEIGHTS,
IQM_COLOR,
IQM_CUSTOM = 0x10
};
enum {
IQM_BYTE,
IQM_UBYTE,
IQM_SHORT,
IQM_USHORT,
IQM_INT,
IQM_UINT,
IQM_HALF,
IQM_FLOAT,
IQM_DOUBLE,
};
struct iqmtriangle {
unsigned vertex[3];
};
struct iqmadjacency {
unsigned triangle[3];
};
struct iqmjoint {
unsigned name;
int parent;
float translate[3], rotate[4], scale[3];
};
struct iqmpose {
int parent;
unsigned mask;
float channeloffset[10];
float channelscale[10];
};
struct iqmanim {
unsigned name;
unsigned first_frame, num_frames;
float framerate;
unsigned flags;
};
enum {
IQM_LOOP = 1<<0
};
struct iqmvertexarray {
unsigned type;
unsigned flags;
unsigned format;
unsigned size;
unsigned offset;
};
struct iqmbounds {
union {
struct { float bbmin[3], bbmax[3]; };
struct { vec3 min3, max3; };
aabb box;
};
float xyradius, radius;
};
// -----------------------------------------------------------------------------
typedef struct iqm_vertex {
GLfloat position[3];
GLfloat texcoord[2];
GLfloat normal[3];
GLfloat tangent[4];
GLubyte blendindexes[4];
GLubyte blendweights[4];
GLfloat blendvertexindex;
2024-08-23 12:15:56 +00:00
GLfloat color[4];
2024-08-19 07:54:01 +00:00
GLfloat texcoord2[2];
} iqm_vertex;
typedef struct iqm_t {
int nummeshes, numtris, numverts, numjoints, numframes, numanims;
GLuint vao, ibo, vbo;
GLuint *textures;
uint8_t *buf, *meshdata, *animdata;
struct iqmmesh *meshes;
struct iqmjoint *joints;
struct iqmpose *poses;
struct iqmanim *anims;
struct iqmbounds *bounds;
mat34 *baseframe, *inversebaseframe, *outframe, *frames;
GLint bonematsoffset;
vec4 *colormaps;
} iqm_t;
2024-08-24 09:49:16 +00:00
void model_set_texture(model_t *m, texture_t t) {
if(!m->iqm) return;
iqm_t *q = m->iqm;
2024-08-19 07:54:01 +00:00
for( int i = 0; i < q->nummeshes; ++i) { // assume 1 texture per mesh
q->textures[i] = t.id;
2024-08-24 09:49:16 +00:00
if (m->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture)
*m->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture = t;
2024-08-19 07:54:01 +00:00
}
}
//@fixme: some locations are invalid, find out why
#if 0
static
void model_set_uniforms(model_t m, int shader, mat44 mv, mat44 proj, mat44 view, mat44 model) { // @todo: cache uniform locs
if(!m.iqm) return;
iqm_t *q = m.iqm;
shader_bind(shader);
int loc;
//if( (loc = glGetUniformLocation(shader, "M")) >= 0 ) glUniformMatrix4fv( loc, 1, GL_FALSE/*GL_TRUE*/, m); // RIM
if( (loc = m.uniforms[MODEL_UNIFORM_MV]) >= 0 ) {
shader_mat44_(loc, mv);
}
if( (loc = m.uniforms[MODEL_UNIFORM_MVP]) >= 0 ) {
mat44 mvp; multiply44x2(mvp, proj, mv); // multiply44x3(mvp, proj, view, model);
shader_mat44_(loc, mvp);
}
if( (loc = m.uniforms[MODEL_UNIFORM_VP]) >= 0 ) {
mat44 vp; multiply44x2(vp, proj, view);
shader_mat44_(loc, vp);
}
if( (loc = m.uniforms[MODEL_UNIFORM_CAM_POS]) >= 0 ) {
vec3 pos = vec3(view[12], view[13], view[14]);
shader_vec3_(loc, pos);
}
if( (loc = m.uniforms[MODEL_UNIFORM_CAM_DIR]) >= 0 ) {
vec3 dir = norm3(vec3(view[2], view[6], view[10]));
shader_vec3_(loc, dir);
}
if( (loc = m.uniforms[MODEL_UNIFORM_BILLBOARD]) >= 0 ) {
shader_int_(loc, m.billboard);
}
if( (loc = m.uniforms[MODEL_UNIFORM_TEXLIT]) >= 0 ) {
shader_bool_(loc, (m.lightmap.w != 0));
}
if ((loc = m.uniforms[MODEL_UNIFORM_MODEL]) >= 0) {
shader_mat44_(loc, model);
}
if ((loc = m.uniforms[MODEL_UNIFORM_VIEW]) >= 0) {
shader_mat44_(loc, view);
}
if ((loc = m.uniforms[MODEL_UNIFORM_INV_VIEW]) >= 0) {
mat44 inv_view;
invert44(inv_view, view);
shader_mat44_(loc, inv_view);
}
if ((loc = m.uniforms[MODEL_UNIFORM_PROJ]) >= 0) {
shader_mat44_(loc, proj);
}
if( (loc = m.uniforms[MODEL_UNIFORM_SKINNED]) >= 0 ) shader_int_(loc, q->numanims ? GL_TRUE : GL_FALSE);
if( q->numanims )
if( (loc = m.uniforms[MODEL_UNIFORM_VS_BONE_MATRIX]) >= 0 ) glUniformMatrix3x4fv( loc, q->numjoints, GL_FALSE, q->outframe[0]);
if ((loc = m.uniforms[MODEL_UNIFORM_U_MATCAPS]) >= 0) {
shader_bool_(loc, m.flags & MODEL_MATCAPS ? GL_TRUE:GL_FALSE);
}
if (m.shading == SHADING_PBR) {
handle old_shader = last_shader;
shader_bind(shader);
shader_vec2_( m.uniforms[MODEL_UNIFORM_RESOLUTION], vec2(window_width(),window_height()));
bool has_tex_skysphere = m.sky_refl.id != texture_checker().id;
bool has_tex_skyenv = m.sky_env.id != texture_checker().id;
shader_bool_( m.uniforms[MODEL_UNIFORM_HAS_TEX_SKYSPHERE], has_tex_skysphere );
shader_bool_( m.uniforms[MODEL_UNIFORM_HAS_TEX_SKYENV], has_tex_skyenv );
if( has_tex_skysphere ) {
float mipCount = floor( log2( max(m.sky_refl.w, m.sky_refl.h) ) );
shader_texture_(m.uniforms[MODEL_UNIFORM_TEX_SKYSPHERE], m.sky_refl);
shader_float_( m.uniforms[MODEL_UNIFORM_SKYSPHERE_MIP_COUNT], mipCount );
}
if( has_tex_skyenv ) {
shader_texture_( m.uniforms[MODEL_UNIFORM_TEX_SKYENV], m.sky_env );
}
shader_texture_( m.uniforms[MODEL_UNIFORM_TEX_BRDF_LUT], brdf_lut() );
shader_uint_( m.uniforms[MODEL_UNIFORM_FRAME_COUNT], (unsigned)window_frame() );
shader_bind(old_shader);
}
}
#else
static
void model_set_uniforms(model_t m, int shader, mat44 mv, mat44 proj, mat44 view, mat44 model) { // @todo: cache uniform locs
if(!m.iqm) return;
iqm_t *q = m.iqm;
shader_bind(shader);
int loc;
//if( (loc = glGetUniformLocation(shader, "M")) >= 0 ) glUniformMatrix4fv( loc, 1, GL_FALSE/*GL_TRUE*/, m); // RIM
if( (loc = glGetUniformLocation(shader, "MV")) >= 0 ) {
glUniformMatrix4fv( loc, 1, GL_FALSE, mv);
}
else
if( (loc = glGetUniformLocation(shader, "u_mv")) >= 0 ) {
glUniformMatrix4fv( loc, 1, GL_FALSE, mv);
}
if( (loc = glGetUniformLocation(shader, "MVP")) >= 0 ) {
mat44 mvp; multiply44x2(mvp, proj, mv); // multiply44x3(mvp, proj, view, model);
glUniformMatrix4fv( loc, 1, GL_FALSE, mvp);
}
else
if( (loc = glGetUniformLocation(shader, "u_mvp")) >= 0 ) {
mat44 mvp; multiply44x2(mvp, proj, mv); // multiply44x3(mvp, proj, view, model);
glUniformMatrix4fv( loc, 1, GL_FALSE, mvp);
}
if( (loc = glGetUniformLocation(shader, "VP")) >= 0 ) {
mat44 vp; multiply44x2(vp, proj, view);
glUniformMatrix4fv( loc, 1, GL_FALSE, vp);
}
else
if( (loc = glGetUniformLocation(shader, "u_vp")) >= 0 ) {
mat44 vp; multiply44x2(vp, proj, view);
glUniformMatrix4fv( loc, 1, GL_FALSE, vp);
}
if( (loc = glGetUniformLocation(shader, "u_cam_pos")) >= 0 ) {
2024-08-23 19:28:40 +00:00
vec3 pos = pos44(view);
2024-08-19 07:54:01 +00:00
glUniform3fv( loc, 1, &pos.x );
}
else
if( (loc = glGetUniformLocation(shader, "cam_pos")) >= 0 ) {
2024-08-23 19:28:40 +00:00
vec3 pos = pos44(view);
2024-08-19 07:54:01 +00:00
glUniform3fv( loc, 1, &pos.x );
}
if( (loc = glGetUniformLocation(shader, "u_cam_dir")) >= 0 ) {
vec3 dir = norm3(vec3(view[2], view[6], view[10]));
glUniform3fv( loc, 1, &dir.x );
}
else
if( (loc = glGetUniformLocation(shader, "cam_dir")) >= 0 ) {
vec3 dir = norm3(vec3(view[2], view[6], view[10]));
glUniform3fv( loc, 1, &dir.x );
}
if( (loc = glGetUniformLocation(shader, "billboard")) >= 0 ) {
glUniform1i( loc, m.billboard );
}
else
if( (loc = glGetUniformLocation(shader, "u_billboard")) >= 0 ) {
glUniform1i( loc, m.billboard );
}
if( (loc = glGetUniformLocation(shader, "texlit")) >= 0 ) {
glUniform1i( loc, (m.lightmap.w != 0) );
}
else
if( (loc = glGetUniformLocation(shader, "u_texlit")) >= 0 ) {
glUniform1i( loc, (m.lightmap.w != 0) );
}
#if 0
// @todo: mat44 projview (useful?)
#endif
if ((loc = glGetUniformLocation(shader, "M")) >= 0) {
glUniformMatrix4fv(loc, 1, GL_FALSE, model);
}
else
if ((loc = glGetUniformLocation(shader, "model")) >= 0) {
glUniformMatrix4fv(loc, 1, GL_FALSE, model);
}
if ((loc = glGetUniformLocation(shader, "V")) >= 0) {
glUniformMatrix4fv(loc, 1, GL_FALSE, view);
}
else
if ((loc = glGetUniformLocation(shader, "view")) >= 0) {
glUniformMatrix4fv(loc, 1, GL_FALSE, view);
}
if ((loc = glGetUniformLocation(shader, "inv_view")) >= 0) {
mat44 inv_view;
invert44( inv_view, view);
glUniformMatrix4fv(loc, 1, GL_FALSE, inv_view);
}
if ((loc = glGetUniformLocation(shader, "P")) >= 0) {
glUniformMatrix4fv(loc, 1, GL_FALSE, proj);
}
else
if ((loc = glGetUniformLocation(shader, "proj")) >= 0) {
glUniformMatrix4fv(loc, 1, GL_FALSE, proj);
}
if( (loc = glGetUniformLocation(shader, "SKINNED")) >= 0 ) glUniform1i( loc, q->numanims ? GL_TRUE : GL_FALSE);
if( q->numanims )
if( (loc = glGetUniformLocation(shader, "vsBoneMatrix")) >= 0 ) glUniformMatrix3x4fv( loc, q->numjoints, GL_FALSE, q->outframe[0]);
if ((loc = glGetUniformLocation(shader, "u_matcaps")) >= 0) {
glUniform1i(loc, m.flags & MODEL_MATCAPS ? GL_TRUE:GL_FALSE);
}
if ((loc = glGetUniformLocation(shader, "frame_count")) >= 0) {
glUniform1i(loc, (unsigned)window_frame());
}
if ((loc = glGetUniformLocation(shader, "frame_time")) >= 0) {
glUniform1f(loc, (float)window_time());
}
if (m.shading == SHADING_PBR) {
handle old_shader = last_shader;
shader_bind(shader);
shader_vec2( "resolution", vec2(window_width(),window_height()));
bool has_tex_skysphere = m.sky_refl.id != texture_checker().id;
bool has_tex_skyenv = m.sky_env.id != texture_checker().id;
shader_bool( "has_tex_skysphere", has_tex_skysphere );
shader_bool( "has_tex_skyenv", has_tex_skyenv );
if( has_tex_skysphere ) {
float mipCount = floor( log2( max(m.sky_refl.w, m.sky_refl.h) ) );
shader_texture("tex_skysphere", m.sky_refl);
shader_float( "skysphere_mip_count", mipCount );
}
if( has_tex_skyenv ) {
shader_texture( "tex_skyenv", m.sky_env );
}
shader_texture( "tex_brdf_lut", brdf_lut() );
shader_bind(old_shader);
}
}
#endif
static
void model_set_state(model_t m) {
if(!m.iqm) return;
iqm_t *q = m.iqm;
glBindVertexArray( q->vao );
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, q->ibo);
glBindBuffer(GL_ARRAY_BUFFER, q->vbo);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(iqm_vertex), (GLvoid*)offsetof(iqm_vertex, position) );
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(iqm_vertex), (GLvoid*)offsetof(iqm_vertex, texcoord) );
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(iqm_vertex), (GLvoid*)offsetof(iqm_vertex, normal) );
glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(iqm_vertex), (GLvoid*)offsetof(iqm_vertex, tangent) );
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
// vertex color
2024-08-23 12:15:56 +00:00
glVertexAttribPointer(11, 4, GL_FLOAT, GL_FALSE, sizeof(iqm_vertex), (GLvoid*)offsetof(iqm_vertex,color) );
2024-08-19 07:54:01 +00:00
glEnableVertexAttribArray(11);
// lmap data
glVertexAttribPointer(12, 2, GL_FLOAT, GL_FALSE, sizeof(iqm_vertex), (GLvoid*)offsetof(iqm_vertex, texcoord2) );
glEnableVertexAttribArray(12);
// animation
if(q->numframes > 0) {
glVertexAttribPointer( 8, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof(iqm_vertex), (GLvoid*)offsetof(iqm_vertex,blendindexes) );
glVertexAttribPointer( 9, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(iqm_vertex), (GLvoid*)offsetof(iqm_vertex,blendweights) );
glVertexAttribPointer(10, 1, GL_FLOAT, GL_FALSE, sizeof(iqm_vertex), (GLvoid*)offsetof(iqm_vertex, blendvertexindex) );
glEnableVertexAttribArray(8);
glEnableVertexAttribArray(9);
glEnableVertexAttribArray(10);
}
// mat4 attribute; for instanced rendering
if( 1 ) {
unsigned vec4_size = sizeof(vec4);
unsigned mat4_size = sizeof(vec4) * 4;
// vertex buffer object
glBindBuffer(GL_ARRAY_BUFFER, m.vao_instanced);
glBufferData(GL_ARRAY_BUFFER, m.num_instances * mat4_size, m.instanced_matrices, GL_STREAM_DRAW);
glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, 4 * vec4_size, (GLvoid*)(((char*)NULL)+(0 * vec4_size)));
glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, 4 * vec4_size, (GLvoid*)(((char*)NULL)+(1 * vec4_size)));
glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, 4 * vec4_size, (GLvoid*)(((char*)NULL)+(2 * vec4_size)));
glVertexAttribPointer(7, 4, GL_FLOAT, GL_FALSE, 4 * vec4_size, (GLvoid*)(((char*)NULL)+(3 * vec4_size)));
glEnableVertexAttribArray(4);
glEnableVertexAttribArray(5);
glEnableVertexAttribArray(6);
glEnableVertexAttribArray(7);
glVertexAttribDivisor(4, 1);
glVertexAttribDivisor(5, 1);
glVertexAttribDivisor(6, 1);
glVertexAttribDivisor(7, 1);
}
// 7 bitangent? into texcoord.z?
glBindVertexArray( 0 );
}
static
bool model_load_meshes(iqm_t *q, const struct iqmheader *hdr, model_t *m) {
if(q->meshdata) return false;
lil32p(&q->buf[hdr->ofs_vertexarrays], hdr->num_vertexarrays*sizeof(struct iqmvertexarray)/sizeof(uint32_t));
lil32p(&q->buf[hdr->ofs_triangles], hdr->num_triangles*sizeof(struct iqmtriangle)/sizeof(uint32_t));
lil32p(&q->buf[hdr->ofs_meshes], hdr->num_meshes*sizeof(struct iqmmesh)/sizeof(uint32_t));
lil32p(&q->buf[hdr->ofs_joints], hdr->num_joints*sizeof(struct iqmjoint)/sizeof(uint32_t));
q->meshdata = q->buf;
q->nummeshes = hdr->num_meshes;
q->numtris = hdr->num_triangles;
q->numverts = hdr->num_vertexes;
q->numjoints = hdr->num_joints;
q->outframe = CALLOC(hdr->num_joints, sizeof(mat34));
float *inposition = NULL, *innormal = NULL, *intangent = NULL, *intexcoord = NULL, *invertexindex = NULL;
uint8_t *inblendindex8 = NULL, *inblendweight8 = NULL;
int *inblendindexi = NULL; float *inblendweightf = NULL;
2024-08-23 12:15:56 +00:00
float *invertexcolor = NULL;
2024-08-19 07:54:01 +00:00
struct iqmvertexarray *vas = (struct iqmvertexarray *)&q->buf[hdr->ofs_vertexarrays];
for(int i = 0; i < (int)hdr->num_vertexarrays; i++) {
struct iqmvertexarray *va = &vas[i];
switch(va->type) {
default: continue; // return PANIC("unknown iqm vertex type (%d)", va->type), false;
break; case IQM_POSITION: ASSERT(va->format == IQM_FLOAT && va->size == 3); inposition = (float *)&q->buf[va->offset]; lil32pf(inposition, 3*hdr->num_vertexes);
break; case IQM_NORMAL: ASSERT(va->format == IQM_FLOAT && va->size == 3); innormal = (float *)&q->buf[va->offset]; lil32pf(innormal, 3*hdr->num_vertexes);
break; case IQM_TANGENT: ASSERT(va->format == IQM_FLOAT && va->size == 4); intangent = (float *)&q->buf[va->offset]; lil32pf(intangent, 4*hdr->num_vertexes);
break; case IQM_TEXCOORD: ASSERT(va->format == IQM_FLOAT && va->size == 2); intexcoord = (float *)&q->buf[va->offset]; lil32pf(intexcoord, 2*hdr->num_vertexes);
2024-08-23 12:15:56 +00:00
break; case IQM_COLOR: ASSERT(va->size == 4); ASSERT(va->format == IQM_FLOAT); invertexcolor = (float *)&q->buf[va->offset];
2024-08-19 07:54:01 +00:00
break; case IQM_BLENDINDEXES: ASSERT(va->size == 4); ASSERT(va->format == IQM_UBYTE || va->format == IQM_INT);
if(va->format == IQM_UBYTE) inblendindex8 = (uint8_t *)&q->buf[va->offset];
else inblendindexi = (int *)&q->buf[va->offset];
break; case IQM_BLENDWEIGHTS: ASSERT(va->size == 4); ASSERT(va->format == IQM_UBYTE || va->format == IQM_FLOAT);
if(va->format == IQM_UBYTE) inblendweight8 = (uint8_t *)&q->buf[va->offset];
else inblendweightf = (float *)&q->buf[va->offset];
invertexindex = (inblendweight8 ? (float*)(inblendweight8 + 4) : inblendweightf + 4 );
}
}
if (hdr->ofs_bounds) lil32p(q->buf + hdr->ofs_bounds, hdr->num_frames * sizeof(struct iqmbounds));
if (hdr->ofs_bounds) q->bounds = (struct iqmbounds *) &q->buf[hdr->ofs_bounds];
q->meshes = (struct iqmmesh *)&q->buf[hdr->ofs_meshes];
q->joints = (struct iqmjoint *)&q->buf[hdr->ofs_joints];
q->baseframe = CALLOC(hdr->num_joints, sizeof(mat34));
q->inversebaseframe = CALLOC(hdr->num_joints, sizeof(mat34));
for(int i = 0; i < (int)hdr->num_joints; i++) {
struct iqmjoint *j = &q->joints[i];
compose34(q->baseframe[i], ptr3(j->translate), normq(ptrq(j->rotate)), ptr3(j->scale));
invert34(q->inversebaseframe[i], q->baseframe[i]);
if(j->parent >= 0) {
multiply34x2(q->baseframe[i], q->baseframe[j->parent], q->baseframe[i]);
multiply34(q->inversebaseframe[i], q->inversebaseframe[j->parent]);
}
}
struct iqmtriangle *tris = (struct iqmtriangle *)&q->buf[hdr->ofs_triangles];
m->num_tris = hdr->num_triangles;
m->tris = (void*)tris;
glGenVertexArrays(1, &q->vao);
glBindVertexArray(q->vao);
if(!q->ibo) glGenBuffers(1, &q->ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, q->ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, hdr->num_triangles*sizeof(struct iqmtriangle), tris, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
iqm_vertex *verts = CALLOC(hdr->num_vertexes, sizeof(iqm_vertex));
for(int i = 0; i < (int)hdr->num_vertexes; i++) {
iqm_vertex *v = &verts[i];
if(inposition) memcpy(v->position, &inposition[i*3], sizeof(v->position));
if(innormal) memcpy(v->normal, &innormal[i*3], sizeof(v->normal));
if(intangent) memcpy(v->tangent, &intangent[i*4], sizeof(v->tangent));
if(intexcoord) {
memcpy(v->texcoord, &intexcoord[i*2], sizeof(v->texcoord));
memcpy(v->texcoord2, &intexcoord[i*2], sizeof(v->texcoord2)); // populate UV1 with the same value, used by lightmapper
}
if(inblendindex8) memcpy(v->blendindexes, &inblendindex8[i*4], sizeof(v->blendindexes));
if(inblendweight8) memcpy(v->blendweights, &inblendweight8[i*4], sizeof(v->blendweights));
if(inblendindexi) {
uint8_t conv[4] = { inblendindexi[i*4], inblendindexi[i*4+1], inblendindexi[i*4+2], inblendindexi[i*4+3] };
memcpy(v->blendindexes, conv, sizeof(v->blendindexes));
}
if(inblendweightf) {
uint8_t conv[4] = { inblendweightf[i*4] * 255, inblendweightf[i*4+1] * 255, inblendweightf[i*4+2] * 255, inblendweightf[i*4+3] * 255 };
memcpy(v->blendweights, conv, sizeof(v->blendweights));
}
if(invertexindex) {
float conv = i;
memcpy(&v->blendvertexindex, &conv, 4);
}
2024-08-23 12:15:56 +00:00
if(invertexcolor) {
v->color[0] = invertexcolor[i*4+0];
v->color[1] = invertexcolor[i*4+1];
v->color[2] = invertexcolor[i*4+2];
v->color[3] = invertexcolor[i*4+3];
}
else {
v->color[0] = 1.0f;
v->color[1] = 1.0f;
v->color[2] = 1.0f;
v->color[3] = 1.0f;
}
/* handle vertex colors for parts of mesh that don't utilise it. */
if (v->color[0] + v->color[1] + v->color[2] + v->color[3] < 0.001f) {
v->color[0] = 1.0f;
v->color[1] = 1.0f;
v->color[2] = 1.0f;
v->color[3] = 1.0f;
}
2024-08-19 07:54:01 +00:00
}
if(!q->vbo) glGenBuffers(1, &q->vbo);
glBindBuffer(GL_ARRAY_BUFFER, q->vbo);
glBufferData(GL_ARRAY_BUFFER, hdr->num_vertexes*sizeof(iqm_vertex), verts, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
m->stride = sizeof(iqm_vertex);
#if 0
m->stride = 0;
if(inposition) m->stride += sizeof(verts[0].position);
if(innormal) m->stride += sizeof(verts[0].normal);
if(intangent) m->stride += sizeof(verts[0].tangent);
if(intexcoord) m->stride += sizeof(verts[0].texcoord);
if(inblendindex8) m->stride += sizeof(verts[0].blendindexes); // no index8? bug?
if(inblendweight8) m->stride += sizeof(verts[0].blendweights); // no weight8? bug?
if(inblendindexi) m->stride += sizeof(verts[0].blendindexes);
if(inblendweightf) m->stride += sizeof(verts[0].blendweights);
if(invertexcolor8) m->stride += sizeof(verts[0].color);
#endif
//for( int i = 0; i < 16; ++i ) printf("%.9g%s", ((float*)verts)[i], (i % 3) == 2 ? "\n" : ",");
m->verts = verts;
/*m->verts = 0; FREE(verts);*/
q->textures = CALLOC(hdr->num_meshes * 8, sizeof(GLuint));
q->colormaps = CALLOC(hdr->num_meshes * 8, sizeof(vec4));
2024-08-23 19:28:40 +00:00
m->meshcenters = CALLOC(hdr->num_meshes, sizeof(vec3));
m->meshbounds = CALLOC(hdr->num_meshes, sizeof(aabb));
m->meshradii = CALLOC(hdr->num_meshes, sizeof(float));
2024-08-23 19:28:40 +00:00
2024-08-19 07:54:01 +00:00
for(int i = 0; i < (int)hdr->num_meshes; i++) {
int invalid = texture_checker().id;
q->textures[i] = invalid;
struct iqmmesh *mesh = &q->meshes[i];
2024-08-24 11:33:05 +00:00
#if 0
2024-08-23 19:28:40 +00:00
GLfloat *pos = verts[q->meshes[i].first_vertex].position;
m->meshcenters[i] = vec3(pos[0], pos[1], pos[2]);
#else
2024-08-24 11:33:05 +00:00
int first_triangle = mesh->first_triangle;
int num_triangles = mesh->num_triangles;
int vertex_count = 0;
vec3 center = {0};
aabb box = {
.min = {FLT_MAX, FLT_MAX, FLT_MAX},
.max = {-FLT_MAX, -FLT_MAX, -FLT_MAX}
};
float max_distance_squared = 0.0f;
for (int j = first_triangle; j < num_triangles+first_triangle; ++j) {
struct iqmtriangle *tri = &tris[j];
// calculate mesh center
for (int k = 0; k < 3; ++k) {
iqm_vertex *v = &verts[tri->vertex[k]];
GLfloat *pos = v->position;
center.x += pos[0];
center.y += pos[1];
center.z += pos[2];
vertex_count++;
// Update AABB
box.min.x = fminf(box.min.x, pos[0]);
box.min.y = fminf(box.min.y, pos[1]);
box.min.z = fminf(box.min.z, pos[2]);
box.max.x = fmaxf(box.max.x, pos[0]);
box.max.y = fmaxf(box.max.y, pos[1]);
box.max.z = fmaxf(box.max.z, pos[2]);
}
}
if (vertex_count) {
center.x /= vertex_count;
center.y /= vertex_count;
center.z /= vertex_count;
}
// Compute bounding sphere radius
for (int j = first_triangle; j < num_triangles + first_triangle; ++j) {
struct iqmtriangle *tri = &tris[j];
for (int k = 0; k < 3; ++k) {
int vertex_index = tri->vertex[k];
GLfloat *pos = verts[vertex_index].position;
float dx = pos[0] - center.x;
float dy = pos[1] - center.y;
float dz = pos[2] - center.z;
float distance_squared = dx*dx + dy*dy + dz*dz;
max_distance_squared = fmaxf(max_distance_squared, distance_squared);
}
}
m->meshcenters[i] = center;
m->meshbounds[i] = box;
m->meshradii[i] = sqrtf(max_distance_squared);
#endif
2024-08-19 07:54:01 +00:00
}
const char *str = hdr->ofs_text ? (char *)&q->buf[hdr->ofs_text] : "";
for(int i = 0; i < (int)hdr->num_meshes; i++) {
struct iqmmesh *m = &q->meshes[i];
PRINTF("loaded mesh: %s\n", &str[m->name]);
}
return true;
}
static
bool model_load_anims(iqm_t *q, const struct iqmheader *hdr) {
if((int)hdr->num_poses != q->numjoints) return false;
if(q->animdata) {
if(q->animdata != q->meshdata) FREE(q->animdata);
FREE(q->frames);
q->animdata = NULL;
q->anims = NULL;
q->frames = 0;
q->numframes = 0;
q->numanims = 0;
}
lil32p(&q->buf[hdr->ofs_poses], hdr->num_poses*sizeof(struct iqmpose)/sizeof(uint32_t));
lil32p(&q->buf[hdr->ofs_anims], hdr->num_anims*sizeof(struct iqmanim)/sizeof(uint32_t));
lil16p((uint16_t *)&q->buf[hdr->ofs_frames], hdr->num_frames*hdr->num_framechannels);
q->animdata = q->buf;
q->numanims = hdr->num_anims;
q->numframes = hdr->num_frames;
q->anims = (struct iqmanim *)&q->buf[hdr->ofs_anims];
q->poses = (struct iqmpose *)&q->buf[hdr->ofs_poses];
q->frames = CALLOC(hdr->num_frames * hdr->num_poses, sizeof(mat34));
uint16_t *framedata = (uint16_t *)&q->buf[hdr->ofs_frames];
for(int i = 0; i < (int)hdr->num_frames; i++) {
for(int j = 0; j < (int)hdr->num_poses; j++) {
struct iqmpose *p = &q->poses[j];
quat rotate;
vec3 translate, scale;
translate.x = p->channeloffset[0]; if(p->mask&0x01) translate.x += *framedata++ * p->channelscale[0];
translate.y = p->channeloffset[1]; if(p->mask&0x02) translate.y += *framedata++ * p->channelscale[1];
translate.z = p->channeloffset[2]; if(p->mask&0x04) translate.z += *framedata++ * p->channelscale[2];
rotate.x = p->channeloffset[3]; if(p->mask&0x08) rotate.x += *framedata++ * p->channelscale[3];
rotate.y = p->channeloffset[4]; if(p->mask&0x10) rotate.y += *framedata++ * p->channelscale[4];
rotate.z = p->channeloffset[5]; if(p->mask&0x20) rotate.z += *framedata++ * p->channelscale[5];
rotate.w = p->channeloffset[6]; if(p->mask&0x40) rotate.w += *framedata++ * p->channelscale[6];
scale.x = p->channeloffset[7]; if(p->mask&0x80) scale.x += *framedata++ * p->channelscale[7];
scale.y = p->channeloffset[8]; if(p->mask&0x100) scale.y += *framedata++ * p->channelscale[8];
scale.z = p->channeloffset[9]; if(p->mask&0x200) scale.z += *framedata++ * p->channelscale[9];
// Concatenate each pose with the inverse base pose to avoid doing this at animation time.
// If the joint has a parent, then it needs to be pre-concatenated with its parent's base pose.
// Thus it all negates at animation time like so:
// (parentPose * parentInverseBasePose) * (parentBasePose * childPose * childInverseBasePose) =>
// parentPose * (parentInverseBasePose * parentBasePose) * childPose * childInverseBasePose =>
// parentPose * childPose * childInverseBasePose
mat34 m; compose34(m, translate, normq(rotate), scale);
if(p->parent >= 0) multiply34x3(q->frames[i*hdr->num_poses + j], q->baseframe[p->parent], m, q->inversebaseframe[j]);
else multiply34x2(q->frames[i*hdr->num_poses + j], m, q->inversebaseframe[j]);
}
}
// const char *str = hdr->ofs_text ? (char *)&q->buf[hdr->ofs_text] : "";
// for(int i = 0; i < (int)hdr->num_anims; i++) {
// struct iqmanim *a = &anims[i];
// PRINTF("loaded anim[%d]: %s\n", i, &str[a->name]);
// }
return true;
}
// prevents crash on osx when strcpy'ing non __restrict arguments
static char* strcpy_safe(char *d, const char *s) {
sprintf(d, "%s", s);
return d;
}
static
void model_load_pbr_layer(material_layer_t *layer, const char *texname, bool load_as_srgb) {
strcpy_safe(layer->texname, texname);
2024-08-24 16:03:23 +00:00
colormap(&layer->map, texname, load_as_srgb);
2024-08-19 07:54:01 +00:00
}
static
void model_load_pbr(material_t *mt) {
// initialise default colors
2024-08-23 19:28:40 +00:00
mt->layer[MATERIAL_CHANNEL_DIFFUSE].map.color = vec4(0.5,0.5,0.5,1.0);
2024-08-19 07:54:01 +00:00
mt->layer[MATERIAL_CHANNEL_NORMALS].map.color = vec4(0,0,0,0);
mt->layer[MATERIAL_CHANNEL_SPECULAR].map.color = vec4(0,0,0,0);
mt->layer[MATERIAL_CHANNEL_SPECULAR].value = 1.0f; // specular_shininess
mt->layer[MATERIAL_CHANNEL_ALBEDO].map.color = vec4(0.5,0.5,0.5,1.0);
mt->layer[MATERIAL_CHANNEL_ROUGHNESS].map.color = vec4(1,1,1,1);
mt->layer[MATERIAL_CHANNEL_METALLIC].map.color = vec4(0,0,0,0);
mt->layer[MATERIAL_CHANNEL_AO].map.color = vec4(1,1,1,1);
mt->layer[MATERIAL_CHANNEL_AMBIENT].map.color = vec4(0,0,0,1);
mt->layer[MATERIAL_CHANNEL_EMISSIVE].map.color = vec4(0,0,0,0);
// load colormaps
array(char*) tokens = strsplit(mt->name, "+");
for( int j = 0, end = array_count(tokens); j < end; ++j ) {
char *t = tokens[j];
if( strstri(t, "_D.") || strstri(t, "Diffuse") || strstri(t, "BaseColor") || strstri(t, "Base_Color") ) model_load_pbr_layer(&mt->layer[MATERIAL_CHANNEL_DIFFUSE], t, 1);
if( strstri(t, "_N.") || strstri(t, "Normal") ) model_load_pbr_layer(&mt->layer[MATERIAL_CHANNEL_NORMALS], t, 0);
if( strstri(t, "_S.") || strstri(t, "Specular") ) model_load_pbr_layer(&mt->layer[MATERIAL_CHANNEL_SPECULAR], t, 0);
if( strstri(t, "_A.") || strstri(t, "Albedo") ) model_load_pbr_layer(&mt->layer[MATERIAL_CHANNEL_ALBEDO], t, 1); // 0?
if( strstri(t, "Roughness") ) model_load_pbr_layer(&mt->layer[MATERIAL_CHANNEL_ROUGHNESS], t, 0);
if( strstri(t, "_MR.")|| strstri(t, "MetallicRoughness") || strstri(t, "OcclusionRoughnessMetallic") ) model_load_pbr_layer(&mt->layer[MATERIAL_CHANNEL_ROUGHNESS], t, 0);
else
if( strstri(t, "_M.") || strstri(t, "Metallic") ) model_load_pbr_layer(&mt->layer[MATERIAL_CHANNEL_METALLIC], t, 0);
//if( strstri(t, "_S.") || strstri(t, "Shininess") ) model_load_pbr_layer(&mt->layer[MATERIAL_CHANNEL_ROUGHNESS], t, 0);
//if( strstri(t, "_A.") || strstri(t, "Ambient") ) model_load_pbr_layer(&mt->layer[MATERIAL_CHANNEL_AMBIENT], t, 0);
if( strstri(t, "_E.") || strstri(t, "Emissive") ) model_load_pbr_layer(&mt->layer[MATERIAL_CHANNEL_EMISSIVE], t, 1);
if( strstri(t, "_AO.") || strstri(t, "AO") || strstri(t, "Occlusion") ) model_load_pbr_layer(&mt->layer[MATERIAL_CHANNEL_AO], t, 0);
}
}
static
bool model_load_textures(iqm_t *q, const struct iqmheader *hdr, model_t *model, int _flags) {
q->textures = q->textures ? q->textures : CALLOC(hdr->num_meshes * 8, sizeof(GLuint)); // up to 8 textures per mesh
q->colormaps = q->colormaps ? q->colormaps : CALLOC(hdr->num_meshes * 8, sizeof(vec4)); // up to 8 colormaps per mesh
GLuint *out = q->textures;
const char *str = hdr->ofs_text ? (char *)&q->buf[hdr->ofs_text] : "";
for(int i = 0; i < (int)hdr->num_meshes; i++) {
struct iqmmesh *m = &q->meshes[i];
// reuse texture+material if already decoded
bool reused = 0;
for( int j = 0; !reused && j < model->num_textures; ++j ) {
if( !strcmpi(model->texture_names[j], &str[m->material])) {
*out++ = model->materials[j].layer[0].map.texture->id;
{
model->num_textures++;
array_push(model->texture_names, STRDUP(&str[m->material]));
array_push(model->materials, model->materials[j]);
array_back(model->materials)->name = STRDUP(&str[m->material]);
}
reused = true;
}
}
if( reused ) continue;
// decode texture+material
int flags = TEXTURE_MIPMAPS|TEXTURE_REPEAT|TEXTURE_ANISOTROPY; // LINEAR, NEAREST
if (!(_flags & MODEL_NO_FILTERING))
flags |= TEXTURE_LINEAR;
int invalid = texture_checker().id;
#if 1
char *material_embedded_texture = strstr(&str[m->material], "+b64:");
if( material_embedded_texture ) {
*material_embedded_texture = '\0';
material_embedded_texture += 5;
array(char) embedded_texture = base64_decode(material_embedded_texture, strlen(material_embedded_texture));
//printf("%s %d\n", material_embedded_texture, array_count(embedded_texture));
//hexdump(embedded_texture, array_count(embedded_texture));
*out = texture_compressed_from_mem( embedded_texture, array_count(embedded_texture), flags ).id;
array_free(embedded_texture);
}
char* material_color_hex = strstr(&str[m->material], "+$");
vec4 material_color = vec4(1,1,1,1);
if( material_color_hex ) {
*material_color_hex = '\0';
material_color_hex += 2;
material_color.r = ((material_color_hex[0] >= 'a') ? material_color_hex[0] - 'a' + 10 : material_color_hex[0] - '0') / 15.f;
material_color.g = ((material_color_hex[1] >= 'a') ? material_color_hex[1] - 'a' + 10 : material_color_hex[1] - '0') / 15.f;
material_color.b = ((material_color_hex[2] >= 'a') ? material_color_hex[2] - 'a' + 10 : material_color_hex[2] - '0') / 15.f;
2024-08-23 16:24:24 +00:00
material_color.a = ((material_color_hex[3] >= 'a') ? material_color_hex[3] - 'a' + 10 : material_color_hex[3] - '0') / 15.f;
2024-08-19 07:54:01 +00:00
#if 0 // not enabled because of some .obj files like suzanne, with color_hex=9990 found
if(material_color_hex[3])
material_color.a = ((material_color_hex[3] >= 'a') ? material_color_hex[3] - 'a' + 10 : material_color_hex[3] - '0') / 15.f;
else
#endif
}
if( !material_embedded_texture ) {
char* material_name;
// remove any material+name from materials (.fbx)
// try left token first
if( 1 ) {
material_name = va("%s", &str[m->material]);
char* plus = strrchr(material_name, '+');
if (plus) { strcpy_safe(plus, file_ext(material_name)); }
*out = texture_compressed(material_name, flags).id;
}
// else try right token
if (*out == invalid) {
material_name = file_normalize( va("%s", &str[m->material]) );
char* plus = strrchr(material_name, '+'), *slash = strrchr(material_name, '/');
if (plus) {
strcpy_safe(slash ? slash + 1 : material_name, plus + 1);
*out = texture_compressed(material_name, flags).id;
}
}
// else last resort
if (*out == invalid) {
*out = texture_compressed(material_name, flags).id; // needed?
}
}
if( *out != invalid) {
PRINTF("loaded material[%d]: %s\n", i, &str[m->material]);
} else {
PRINTF("warn: material[%d] not found: %s\n", i, &str[m->material]);
PRINTF("warn: using placeholder material[%d]=texture_checker\n", i);
*out = texture_checker().id; // placeholder
}
inscribe_tex:;
{
model->num_textures++;
array_push(model->texture_names, STRDUP(&str[m->material]));
material_t mt = {0};
mt.name = STRDUP(&str[m->material]);
// initialise basic texture layer
mt.layer[MATERIAL_CHANNEL_DIFFUSE].map.color = material_color_hex ? material_color : vec4(1,1,1,1);
mt.layer[MATERIAL_CHANNEL_DIFFUSE].map.texture = CALLOC(1, sizeof(texture_t));
mt.layer[MATERIAL_CHANNEL_DIFFUSE].map.texture->id = *out++;
array_push(model->materials, mt);
}
#else
material_t mt = {0};
mt.name = STRDUP(&str[m->material]);
array(char*) tokens = strsplit(&str[m->material], "+");
for each_array(tokens, char*, it) {
*out = texture(it, flags).id;
if( *out == invalid ) {
PRINTF("warn: material[%d] not found: %s\n", i, it);
} else {
PRINTF("loaded material[%d]: %s\n", i, it);
mt.layer[mt.count++].texture = *out;
++out;
}
}
// if no materials were loaded, try to signal a checkered placeholder
if( out == textures ) {
PRINTF("warn: using placeholder material[%d]=texture_checker\n", i);
*out++ = invalid;
}
int count = (int)(intptr_t)(out - textures);
model->num_textures += count;
array_push(model->texture_names, STRDUP(&str[m->material]));
array_push(model->materials, mt);
#endif
}
if( array_count(model->materials) == 0 ) {
material_t mt = {0};
mt.name = "placeholder";
mt.layer[0].map.color = vec4(1,1,1,1);
mt.layer[0].map.texture = CALLOC(1, sizeof(texture_t));
mt.layer[0].map.texture->id = texture_checker().id;
array_push(model->materials, mt);
}
return true;
}
static
void model_set_renderstates(model_t *m) {
for (int i = 0; i<NUM_RENDER_PASSES; ++i) {
m->rs[i] = renderstate();
}
2024-08-23 16:24:24 +00:00
// Opaque pass
renderstate_t *opaque_rs = &m->rs[RENDER_PASS_OPAQUE];
2024-08-19 07:54:01 +00:00
{
2024-08-24 06:26:43 +00:00
#if 1 // @todo: we should keep blend_enabled=0, however our transparency detection still needs work
2024-08-23 19:28:40 +00:00
opaque_rs->blend_enabled = 0;
2024-08-23 22:21:38 +00:00
#else
opaque_rs->blend_enabled = 1;
opaque_rs->blend_src = GL_SRC_ALPHA;
opaque_rs->blend_dst = GL_ONE_MINUS_SRC_ALPHA;
#endif
2024-08-23 16:24:24 +00:00
opaque_rs->cull_face_mode = GL_BACK;
opaque_rs->front_face = GL_CW;
2024-08-19 07:54:01 +00:00
}
2024-08-23 19:28:40 +00:00
// Transparent pass
renderstate_t *transparent_rs = &m->rs[RENDER_PASS_TRANSPARENT];
{
transparent_rs->blend_enabled = 1;
transparent_rs->blend_src = GL_SRC_ALPHA;
transparent_rs->blend_dst = GL_ONE_MINUS_SRC_ALPHA;
transparent_rs->cull_face_mode = GL_BACK;
transparent_rs->front_face = GL_CW;
}
2024-08-19 07:54:01 +00:00
// Shadow pass @todo
renderstate_t *shadow_rs = &m->rs[RENDER_PASS_SHADOW];
{
shadow_rs->blend_enabled = 1;
shadow_rs->blend_src = GL_SRC_ALPHA;
shadow_rs->blend_dst = GL_ONE_MINUS_SRC_ALPHA;
shadow_rs->cull_face_mode = GL_BACK;
shadow_rs->front_face = GL_CW;
}
// Lightmap pass
renderstate_t *lightmap_rs = &m->rs[RENDER_PASS_LIGHTMAP];
{
lightmap_rs->blend_enabled = 0;
lightmap_rs->cull_face_enabled = 0;
lightmap_rs->front_face = GL_CW;
}
}
model_t model_from_mem(const void *mem, int len, int flags) {
model_t m = {0};
m.stored_flags = flags;
m.shading = SHADING_PHONG;
model_set_renderstates(&m);
const char *ptr = (const char *)mem;
iqm_t *q = CALLOC(1, sizeof(iqm_t));
int error = 1;
if( ptr && len ) {
struct iqmheader hdr; memcpy(&hdr, ptr, sizeof(hdr)); ptr += sizeof(hdr);
if( !memcmp(hdr.magic, IQM_MAGIC, sizeof(hdr.magic))) {
lil32p(&hdr.version, (sizeof(hdr) - sizeof(hdr.magic))/sizeof(uint32_t));
if(hdr.version == IQM_VERSION) {
q->buf = CALLOC(hdr.filesize, sizeof(uint8_t));
memcpy(q->buf + sizeof(hdr), ptr, hdr.filesize - sizeof(hdr));
error = 0;
if( hdr.num_meshes > 0 && !(flags & MODEL_NO_MESHES) ) error |= !model_load_meshes(q, &hdr, &m);
if( hdr.num_meshes > 0 && !(flags & MODEL_NO_TEXTURES) ) error |= !model_load_textures(q, &hdr, &m, flags);
else {
// setup fallback
material_t mt = {0};
mt.name = "placeholder";
mt.layer[0].map.color = vec4(1,1,1,1);
mt.layer[0].map.texture = CALLOC(1, sizeof(texture_t));
mt.layer[0].map.texture->id = texture_checker().id;
array_push(m.materials, mt);
}
if( hdr.num_anims > 0 && !(flags & MODEL_NO_ANIMATIONS) ) error |= !model_load_anims(q, &hdr);
if( q->buf != q->meshdata && q->buf != q->animdata ) FREE(q->buf);
}
}
}
if( error ) {
PRINTF("Error: cannot load %s", "model");
FREE(q), q = 0;
} else {
m.vao = q->vao;
m.ibo = q->ibo;
m.vbo = q->vbo;
m.num_verts = q->numverts;
// m.boxes = bounds; // <@todo
m.num_meshes = q->nummeshes;
m.num_triangles = q->numtris;
m.num_joints = q->numjoints;
//m.num_poses = numposes;
m.num_anims = q->numanims;
m.num_frames = q->numframes;
m.iqm = q;
m.curframe = model_animate(m, 0);
//m.num_textures = q->nummeshes; // assume 1 texture only per mesh
m.textures = (q->textures);
m.flags = flags;
id44(m.pivot);
m.num_instances = 0;
m.instanced_matrices = m.pivot;
glGenBuffers(1, &m.vao_instanced);
model_set_state(m);
model_shading(&m, (flags & MODEL_PBR) ? SHADING_PBR : SHADING_PHONG);
}
return m;
}
model_t model(const char *filename, int flags) {
int len; // vfs_pushd(filedir(filename))
char *ptr = vfs_load(filename, &len); // + vfs_popd
return model_from_mem( ptr, len, flags );
}
bool model_get_bone_pose(model_t m, unsigned joint, mat34 *out) {
if(!m.iqm) return false;
iqm_t *q = m.iqm;
if(joint >= q->numjoints) return false;
multiply34x2(*out, q->outframe[joint], q->baseframe[joint]);
return true;
}
anim_t clip(float minframe, float maxframe, float blendtime, unsigned flags) {
return ((anim_t){minframe, maxframe, blendtime, flags, 1e6});
}
anim_t loop(float minframe, float maxframe, float blendtime, unsigned flags) {
return clip(minframe, maxframe, blendtime, flags | ANIM_LOOP);
}
array(anim_t) animlist(const char *pathfile) {
anim_t *animlist = 0;
char *anim_file = vfs_read(strendi(pathfile,".txt") ? pathfile : va("%s@animlist.txt", pathfile));
if( anim_file ) {
// deserialize anim
for each_substring(anim_file, "\r\n", anim) {
int from, to;
char anim_name[128] = {0};
if( sscanf(anim, "%*s %d-%d %127[^\r\n]", &from, &to, anim_name) != 3) continue;
array_push(animlist, !!strstri(anim_name, "loop") || !strcmpi(anim_name, "idle") ? loop(from, to, 0, 0) : clip(from, to, 0, 0)); // [from,to,flags]
array_back(animlist)->name = strswap(strswap(strswap(STRDUP(anim_name), "Loop", ""), "loop", ""), "()", ""); // @leak
}
} else {
// placeholder
array_push(animlist, clip(0,1,0,0));
array_back(animlist)->name = STRDUP("Error"); // @leak
}
return animlist;
}
static
void anim_tick(anim_t *p, bool is_primary, float delta) { // delta can be negative (reverses anim)
if( !is_primary ) p->active = 0;
if( is_primary && !p->active ) {
p->active = 1;
p->timer = 0;
p->alpha = 0;
if( p->flags & ANIM_DONT_RESET_AFTER_USE ) {} else p->curframe = 1e6;
}
p->alpha = 1 - ease(p->timer / p->blendtime, p->easing);
p->timer += window_delta();
p->curframe += delta;
if(p->curframe < p->from || p->curframe > p->to ) p->curframe = delta >= 0 ? p->from : p->to;
p->pose = pose(delta >= 0, p->curframe, p->from, p->to, p->flags & ANIM_LOOP, NULL);
}
float model_animate_blends(model_t m, anim_t *primary, anim_t *secondary, float delta) {
if(!m.iqm) return -1;
iqm_t *q = m.iqm;
anim_tick(primary, 1, delta);
anim_tick(secondary, 0, delta);
float alpha = primary->alpha;
// if( alpha <= 0 ) return model_animate(m, primary.pose.x);
// if( alpha >= 1 ) return model_animate(m, secondary.pose.x);
unsigned frame1 = primary->pose.x;
unsigned frame2 = primary->pose.y;
float alphaA = primary->pose.z;
unsigned frame3 = secondary->pose.x;
unsigned frame4 = secondary->pose.y;
float alphaB = secondary->pose.z;
mat34 *mat1 = &q->frames[frame1 * q->numjoints];
mat34 *mat2 = &q->frames[frame2 * q->numjoints];
mat34 *mat3 = &q->frames[frame3 * q->numjoints];
mat34 *mat4 = &q->frames[frame4 * q->numjoints];
for(int i = 0; i < q->numjoints; i++) {
mat34 matA, matB, matF;
lerp34(matA, mat1[i], mat2[i], alphaA);
lerp34(matB, mat3[i], mat4[i], alphaB);
lerp34(matF, matA, matB, alpha );
if(q->joints[i].parent >= 0) multiply34x2(q->outframe[i], q->outframe[q->joints[i].parent], matF);
else copy34(q->outframe[i], matF);
}
return frame1 + alpha;
}
vec3 pose(bool forward_time, float curframe, int minframe, int maxframe, bool loop, float *retframe) {
float offset = curframe - (int)curframe;
#if 1
int frame1 = (int)curframe;
int frame2 = frame1 + (forward_time ? 1 : -1);
#else
float frame1 = curframe;
float frame2 = curframe + (forward_time ? 1 : -1);
#endif
if( loop ) {
int distance = maxframe - minframe;
frame1 = fmod(frame1 - minframe, distance) + minframe; // frame1 >= maxframe ? minframe : frame1 < minframe ? maxframe - clampf(minframe - frame1, 0, distance) : frame1;
frame2 = fmod(frame2 - minframe, distance) + minframe; // frame2 >= maxframe ? minframe : frame2 < minframe ? maxframe - clampf(minframe - frame2, 0, distance) : frame2;
if(retframe) *retframe = fmod(frame1 + offset - minframe, distance) + minframe;
} else {
frame1 = clampf(frame1, minframe, maxframe);
frame2 = clampf(frame2, minframe, maxframe);
if(retframe) *retframe = clampf(frame1 + offset, minframe, maxframe);
}
return vec3(frame1 + (offset > 0 && offset < 1 ? offset : 0),frame2,offset);
}
float model_animate_clip(model_t m, float curframe, int minframe, int maxframe, bool loop) {
if(!m.iqm) return -1;
iqm_t *q = m.iqm;
float retframe = -1;
if( q->numframes > 0 ) {
vec3 p = pose(curframe >= m.curframe, curframe, minframe, maxframe, loop, &retframe);
int frame1 = p.x;
int frame2 = p.y;
float offset = p.z;
mat34 *mat1 = &q->frames[frame1 * q->numjoints];
mat34 *mat2 = &q->frames[frame2 * q->numjoints];
// @todo: add animation blending and inter-frame blending here
for(int i = 0; i < q->numjoints; i++) {
mat34 mat; lerp34(mat, mat1[i], mat2[i], offset);
if(q->joints[i].parent >= 0) multiply34x2(q->outframe[i], q->outframe[q->joints[i].parent], mat);
else copy34(q->outframe[i], mat);
}
}
return retframe;
}
void model_render_skeleton(model_t m, mat44 M) {
if(!m.iqm) return;
iqm_t *q = m.iqm;
if(!q->numjoints) return;
ddraw_ontop_push(true);
ddraw_color_push(RED);
for( int joint = 0; joint < q->numjoints; joint++ ) {
if( q->joints[joint].parent < 0) continue;
// bone space...
mat34 f;
model_get_bone_pose(m, joint, &f);
vec3 pos = vec3(f[3],f[7],f[11]);
model_get_bone_pose(m, q->joints[joint].parent, &f);
vec3 src = vec3(f[3],f[7],f[11]);
// ...to model space
src = transform344(M, src);
pos = transform344(M, pos);
// red line
ddraw_color(RED);
// ddraw_line(src, pos);
ddraw_bone(src, pos);
// green dot
ddraw_color(GREEN);
ddraw_point(pos);
// yellow text
ddraw_color(YELLOW);
ddraw_text(pos, 0.005, va("%d", joint));
}
ddraw_color_pop();
ddraw_ontop_pop();
}
float model_animate(model_t m, float curframe) {
if(!m.iqm) return -1;
iqm_t *q = m.iqm;
return model_animate_clip(m, curframe, 0, q->numframes-1, true);
}
// @fixme: store uniform handles into model_t/colormap_t and rely on those directly
static inline
void shader_colormap_model_internal(const char *col_name, const char *bool_name, const char *tex_name, colormap_t c ) {
// assumes shader uses `struct { vec4 color; bool has_tex } name + sampler2D name_tex;`
shader_vec4( col_name, c.color );
shader_bool( bool_name, c.texture != NULL );
if( c.texture ) shader_texture( tex_name, *c.texture );
}
2024-08-23 19:28:40 +00:00
typedef struct drawcall_t {
int mesh;
union {
uint64_t order;
struct {
uint32_t tex;
float distance;
};
};
} drawcall_t;
static
int drawcall_compare(const void *a, const void *b) {
const drawcall_t *da = a, *db = b;
return da->order < db->order ? 1 : da->order > db->order ? -1 : 0;
}
bool model_has_transparency_mesh(model_t m, int mesh) {
if(!m.iqm) return false;
iqm_t *q = m.iqm;
if (m.flags & MODEL_TRANSPARENT) {
return true;
}
if (m.materials[mesh].layer[0].map.color.a < 1 || (m.materials[mesh].layer[0].map.texture && m.materials[mesh].layer[0].map.texture->transparent)) {
return true;
}
if (m.shading == SHADING_PBR && (m.materials[mesh].layer[MATERIAL_CHANNEL_ALBEDO].map.color.a < 1 || (m.materials[mesh].layer[MATERIAL_CHANNEL_ALBEDO].map.texture && m.materials[mesh].layer[MATERIAL_CHANNEL_ALBEDO].map.texture->transparent))){
return true;
}
return false;
}
bool model_has_transparency(model_t m) {
if(!m.iqm) return false;
iqm_t *q = m.iqm;
for (int i = 0; i < q->nummeshes; i++) {
if (model_has_transparency_mesh(m, i)) {
return true;
}
}
return false;
}
void model_set_frustum(model_t *m, frustum f) {
m->frustum_enabled = 1;
m->frustum_state = f;
}
void model_clear_frustum(model_t *m) {
m->frustum_enabled = 0;
}
2024-08-24 11:33:05 +00:00
static inline
bool model_is_visible(model_t m, int mesh, mat44 model_mat) {
if(!m.iqm) return false;
if(!m.frustum_enabled) return true;
sphere s; s.c = transform344(model_mat, m.meshcenters[mesh]); s.r = m.meshradii[mesh];
if (!frustum_test_sphere(m.frustum_state, s)) {
return false;
}
aabb box = m.meshbounds[mesh];
box.min = transform344(model_mat, box.min);
box.max = transform344(model_mat, box.max);
2024-08-24 12:23:06 +00:00
#if 0
// ddraw_sphere(s.c, s.r);
ddraw_aabb(box.min, box.max);
ddraw_position(s.c, 3.0f);
#endif
2024-08-24 11:33:05 +00:00
if (!frustum_test_aabb(m.frustum_state, box)) {
return false;
}
return true;
}
2024-08-19 07:54:01 +00:00
static
2024-08-23 19:28:40 +00:00
void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_mat) {
2024-08-19 07:54:01 +00:00
if(!m.iqm) return;
iqm_t *q = m.iqm;
handle old_shader = last_shader;
shader_bind(shader);
2024-08-23 19:28:40 +00:00
int rs_idx = model_getpass();
renderstate_t *rs = &m.rs[rs_idx];
2024-08-19 07:54:01 +00:00
glBindVertexArray( q->vao );
2024-08-23 19:28:40 +00:00
static array(int) required_rs = 0;
array_resize(required_rs, q->nummeshes);
2024-08-19 07:54:01 +00:00
for(int i = 0; i < q->nummeshes; i++) {
struct iqmmesh *im = &q->meshes[i];
2024-08-23 19:28:40 +00:00
required_rs[i] = rs_idx;
if (required_rs[i] < RENDER_PASS_OVERRIDES_BEGIN) {
if (model_has_transparency_mesh(m, i)) {
2024-08-23 19:28:40 +00:00
required_rs[i] = RENDER_PASS_TRANSPARENT;
}
}
}
static array(drawcall_t) drawcalls = 0;
array_resize(drawcalls, 0);
if (rs_idx > RENDER_PASS_OVERRIDES_BEGIN) {
for(int i = 0; i < q->nummeshes; i++) {
2024-08-24 11:33:05 +00:00
if (!model_is_visible(m, i, model_mat)) continue;
2024-08-24 06:22:19 +00:00
array_push(drawcalls, (drawcall_t){i, -1});
2024-08-23 19:28:40 +00:00
}
} else {
if(pass == -1 || pass == RENDER_PASS_OPAQUE) {
for(int i = 0; i < q->nummeshes; i++) {
2024-08-24 11:33:05 +00:00
if (!model_is_visible(m, i, model_mat)) continue;
2024-08-23 19:28:40 +00:00
// collect opaque drawcalls
if (required_rs[i] == RENDER_PASS_OPAQUE) {
drawcall_t call;
call.mesh = i;
call.tex = m.textures[i];
2024-08-24 06:22:19 +00:00
call.distance = -1;
2024-08-23 19:28:40 +00:00
if (m.shading == SHADING_PBR)
call.tex = m.materials[i].layer[MATERIAL_CHANNEL_ALBEDO].map.texture ? m.materials[i].layer[MATERIAL_CHANNEL_ALBEDO].map.texture->id : m.materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture ? m.materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture->id : texture_checker().id;
array_push(drawcalls, call);
}
}
}
if(pass == -1 || pass == RENDER_PASS_TRANSPARENT) {
for(int i = 0; i < q->nummeshes; i++) {
2024-08-24 11:33:05 +00:00
if (!model_is_visible(m, i, model_mat)) continue;
2024-08-23 19:28:40 +00:00
// collect transparent drawcalls
if (required_rs[i] == RENDER_PASS_TRANSPARENT) {
drawcall_t call;
call.mesh = i;
call.tex = m.textures[i];
// calculate distance from camera
// @todo: improve me, uses first mesh triangle
{
call.distance = len3sq(sub3(cam_pos, transform344(model_mat, m.meshcenters[i])));
2024-08-23 19:28:40 +00:00
}
if (m.shading == SHADING_PBR)
call.tex = m.materials[i].layer[MATERIAL_CHANNEL_ALBEDO].map.texture ? m.materials[i].layer[MATERIAL_CHANNEL_ALBEDO].map.texture->id : m.materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture ? m.materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture->id : texture_checker().id;
array_push(drawcalls, call);
}
}
}
}
// sort drawcalls by order
array_sort(drawcalls, drawcall_compare);
struct iqmtriangle *tris = NULL;
for(int di = 0; di < array_count(drawcalls); di++) {
int i = drawcalls[di].mesh;
struct iqmmesh *im = &q->meshes[i];
if (pass != -1 && pass != required_rs[i]) continue;
if (rs_idx != required_rs[i]) {
rs_idx = required_rs[i];
rs = &m.rs[rs_idx];
renderstate_apply(rs);
}
2024-08-19 07:54:01 +00:00
if (m.shading != SHADING_PBR) {
shader_texture_unit("u_texture2d", q->textures[i], texture_unit());
shader_texture("u_lightmap", m.lightmap);
int loc;
if ((loc = glGetUniformLocation(shader, "u_textured")) >= 0) {
bool textured = !!q->textures[i] && q->textures[i] != texture_checker().id; // m.materials[i].layer[0].texture != texture_checker().id;
glUniform1i(loc, textured ? GL_TRUE : GL_FALSE);
if ((loc = glGetUniformLocation(shader, "u_diffuse")) >= 0) {
glUniform4f(loc, m.materials[i].layer[0].map.color.r, m.materials[i].layer[0].map.color.g, m.materials[i].layer[0].map.color.b, m.materials[i].layer[0].map.color.a);
}
}
} else {
const material_t *material = &m.materials[i];
shader_colormap_model_internal( "map_diffuse.color", "map_diffuse.has_tex", "map_diffuse_tex", material->layer[MATERIAL_CHANNEL_DIFFUSE].map );
shader_colormap_model_internal( "map_normals.color", "map_normals.has_tex", "map_normals_tex", material->layer[MATERIAL_CHANNEL_NORMALS].map );
shader_colormap_model_internal( "map_specular.color", "map_specular.has_tex", "map_specular_tex", material->layer[MATERIAL_CHANNEL_SPECULAR].map );
shader_colormap_model_internal( "map_albedo.color", "map_albedo.has_tex", "map_albedo_tex", material->layer[MATERIAL_CHANNEL_ALBEDO].map );
shader_colormap_model_internal( "map_roughness.color", "map_roughness.has_tex", "map_roughness_tex", material->layer[MATERIAL_CHANNEL_ROUGHNESS].map );
shader_colormap_model_internal( "map_metallic.color", "map_metallic.has_tex", "map_metallic_tex", material->layer[MATERIAL_CHANNEL_METALLIC].map );
shader_colormap_model_internal( "map_ao.color", "map_ao.has_tex", "map_ao_tex", material->layer[MATERIAL_CHANNEL_AO].map );
shader_colormap_model_internal( "map_ambient.color", "map_ambient.has_tex", "map_ambient_tex", material->layer[MATERIAL_CHANNEL_AMBIENT].map );
shader_colormap_model_internal( "map_emissive.color", "map_emissive.has_tex", "map_emissive_tex", material->layer[MATERIAL_CHANNEL_EMISSIVE].map );
// shader_float( "specular_shininess", material->specular_shininess ); // unused, basic_specgloss.fs only
}
glDrawElementsInstanced(GL_TRIANGLES, 3*im->num_triangles, GL_UNSIGNED_INT, &tris[im->first_triangle], m.num_instances);
profile_incstat("Render.num_drawcalls", +1);
profile_incstat("Render.num_triangles", +im->num_triangles);
}
glBindVertexArray( 0 );
shader_bind(old_shader);
}
2024-08-23 19:28:40 +00:00
void model_render_instanced_pass(model_t m, mat44 proj, mat44 view, mat44* models, int shader, unsigned count, int pass) {
2024-08-19 07:54:01 +00:00
if(!m.iqm) return;
iqm_t *q = m.iqm;
mat44 mv; multiply44x2(mv, view, models[0]);
if( count != m.num_instances ) {
m.num_instances = count;
m.instanced_matrices = (float*)models;
model_set_state(m);
}
model_set_uniforms(m, shader > 0 ? shader : m.program, mv, proj, view, models[0]);
2024-08-23 19:28:40 +00:00
model_draw_call(m, shader > 0 ? shader : m.program, pass, pos44(view), models[0]);
}
void model_render_instanced(model_t m, mat44 proj, mat44 view, mat44* models, int shader, unsigned count) {
model_render_instanced_pass(m, proj, view, models, shader, count, -1);
}
void model_render_pass(model_t m, mat44 proj, mat44 view, mat44 model, int shader, int pass) {
model_render_instanced_pass(m, proj, view, (mat44*)model, shader, 1, pass);
2024-08-19 07:54:01 +00:00
}
void model_render(model_t m, mat44 proj, mat44 view, mat44 model, int shader) {
2024-08-23 19:28:40 +00:00
model_render_pass(m, proj, view, model, shader, -1);
2024-08-19 07:54:01 +00:00
}
static inline
void model_init_uniforms(model_t *m) {
for (int i=0; i<NUM_MODEL_UNIFORMS; ++i) m->uniforms[i] = -1;
unsigned shader = m->program;
int loc;
if ((loc = glGetUniformLocation(shader, "u_mv")) >= 0)
m->uniforms[MODEL_UNIFORM_MV] = loc;
else
if ((loc = glGetUniformLocation(shader, "MV")) >= 0)
m->uniforms[MODEL_UNIFORM_MV] = loc;
if ((loc = glGetUniformLocation(shader, "u_mvp")) >= 0)
m->uniforms[MODEL_UNIFORM_MVP] = loc;
else
if ((loc = glGetUniformLocation(shader, "MVP")) >= 0)
m->uniforms[MODEL_UNIFORM_MVP] = loc;
if ((loc = glGetUniformLocation(shader, "u_vp")) >= 0)
m->uniforms[MODEL_UNIFORM_VP] = loc;
else
if ((loc = glGetUniformLocation(shader, "VP")) >= 0)
m->uniforms[MODEL_UNIFORM_VP] = loc;
if ((loc = glGetUniformLocation(shader, "u_cam_pos")) >= 0)
m->uniforms[MODEL_UNIFORM_CAM_POS] = loc;
else
if ((loc = glGetUniformLocation(shader, "cam_pos")) >= 0)
m->uniforms[MODEL_UNIFORM_CAM_POS] = loc;
if ((loc = glGetUniformLocation(shader, "u_cam_dir")) >= 0)
m->uniforms[MODEL_UNIFORM_CAM_DIR] = loc;
else
if ((loc = glGetUniformLocation(shader, "cam_dir")) >= 0)
m->uniforms[MODEL_UNIFORM_CAM_DIR] = loc;
if ((loc = glGetUniformLocation(shader, "u_billboard")) >= 0)
m->uniforms[MODEL_UNIFORM_BILLBOARD] = loc;
else
if ((loc = glGetUniformLocation(shader, "billboard")) >= 0)
m->uniforms[MODEL_UNIFORM_BILLBOARD] = loc;
if ((loc = glGetUniformLocation(shader, "u_texlit")) >= 0)
m->uniforms[MODEL_UNIFORM_TEXLIT] = loc;
else
if ((loc = glGetUniformLocation(shader, "texlit")) >= 0)
m->uniforms[MODEL_UNIFORM_TEXLIT] = loc;
if ((loc = glGetUniformLocation(shader, "M")) >= 0)
m->uniforms[MODEL_UNIFORM_MODEL] = loc;
else
if ((loc = glGetUniformLocation(shader, "model")) >= 0)
m->uniforms[MODEL_UNIFORM_MODEL] = loc;
if ((loc = glGetUniformLocation(shader, "V")) >= 0)
m->uniforms[MODEL_UNIFORM_VIEW] = loc;
else
if ((loc = glGetUniformLocation(shader, "view")) >= 0)
m->uniforms[MODEL_UNIFORM_VIEW] = loc;
if ((loc = glGetUniformLocation(shader, "inv_view")) >= 0)
m->uniforms[MODEL_UNIFORM_INV_VIEW] = loc;
if ((loc = glGetUniformLocation(shader, "P")) >= 0)
m->uniforms[MODEL_UNIFORM_PROJ] = loc;
else
if ((loc = glGetUniformLocation(shader, "proj")) >= 0)
m->uniforms[MODEL_UNIFORM_PROJ] = loc;
if ((loc = glGetUniformLocation(shader, "SKINNED")) >= 0)
m->uniforms[MODEL_UNIFORM_SKINNED] = loc;
if ((loc = glGetUniformLocation(shader, "vsBoneMatrix")) >= 0)
m->uniforms[MODEL_UNIFORM_VS_BONE_MATRIX] = loc;
if ((loc = glGetUniformLocation(shader, "u_matcaps")) >= 0)
m->uniforms[MODEL_UNIFORM_U_MATCAPS] = loc;
if ((loc = glGetUniformLocation(shader, "has_tex_skysphere")) >= 0)
m->uniforms[MODEL_UNIFORM_HAS_TEX_SKYSPHERE] = loc;
if ((loc = glGetUniformLocation(shader, "has_tex_skyenv")) >= 0)
m->uniforms[MODEL_UNIFORM_HAS_TEX_SKYENV] = loc;
if ((loc = glGetUniformLocation(shader, "tex_skysphere")) >= 0)
m->uniforms[MODEL_UNIFORM_TEX_SKYSPHERE] = loc;
if ((loc = glGetUniformLocation(shader, "skysphere_mip_count")) >= 0)
m->uniforms[MODEL_UNIFORM_SKYSPHERE_MIP_COUNT] = loc;
if ((loc = glGetUniformLocation(shader, "tex_skyenv")) >= 0)
m->uniforms[MODEL_UNIFORM_TEX_SKYENV] = loc;
if ((loc = glGetUniformLocation(shader, "tex_brdf_lut")) >= 0)
m->uniforms[MODEL_UNIFORM_TEX_BRDF_LUT] = loc;
if ((loc = glGetUniformLocation(shader, "frame_count")) >= 0)
m->uniforms[MODEL_UNIFORM_FRAME_COUNT] = loc;
if ((loc = glGetUniformLocation(shader, "resolution")) >= 0)
m->uniforms[MODEL_UNIFORM_RESOLUTION] = loc;
}
2024-08-24 17:01:57 +00:00
void model_shading_custom(model_t *m, int shading, const char *vs, const char *fs, const char *defines) {
2024-08-19 07:54:01 +00:00
m->shading = shading;
int flags = m->stored_flags;
// load pbr material if SHADING_PBR was selected
if (shading == SHADING_PBR) {
for (int i = 0; i < array_count(m->materials); ++i) {
model_load_pbr(&m->materials[i]);
}
}
2024-08-24 17:01:57 +00:00
if (!vs) {
vs = vfs_read("shaders/vs_323444143_16_3322_model.glsl");
}
if (!fs) {
fs = vfs_read("shaders/fs_32_4_model.glsl");
}
2024-08-19 07:54:01 +00:00
// rebind shader
// @fixme: app crashes rn
// glUseProgram(0);
// glDeleteProgram(m->program);
const char *symbols[] = { "{{include-shadowmap}}", vfs_read("shaders/fs_0_0_shadowmap_lit.glsl") }; // #define RIM
2024-08-24 17:01:57 +00:00
int shaderprog = shader(strlerp(1,symbols,vs), strlerp(1,symbols,fs), //fs,
2024-08-19 07:54:01 +00:00
"att_position,att_texcoord,att_normal,att_tangent,att_instanced_matrix,,,,att_indexes,att_weights,att_vertexindex,att_color,att_bitangent,att_texcoord2","fragColor",
2024-08-24 17:01:57 +00:00
va("%s,%s,%s", defines ? defines : "NO_CUSTOM_DEFINES", shading == SHADING_PBR ? "SHADING_PBR" : shading == SHADING_VERTEXLIT ? "SHADING_VERTEXLIT" : "SHADING_PHONG", (flags&MODEL_RIMLIGHT)?"RIM":""));
2024-08-19 07:54:01 +00:00
m->program = shaderprog;
model_init_uniforms(m);
}
2024-08-24 17:01:57 +00:00
void model_shading(model_t *m, int shading) {
model_shading_custom(m, shading, NULL, NULL, NULL);
}
2024-08-19 07:54:01 +00:00
void model_skybox(model_t *mdl, skybox_t sky, bool load_sh) {
if (load_sh) {
shader_vec3v("u_coefficients_sh", 9, sky.cubemap.sh);
}
mdl->sky_refl = sky.refl;
mdl->sky_env = sky.env;
}
// static
aabb aabb_transform( aabb A, mat44 M ) {
// Based on "Transforming Axis-Aligned Bounding Boxes" by Jim Arvo, 1990
aabb B = { {M[12],M[13],M[14]}, {M[12],M[13],M[14]} }; // extract translation from mat44
for( int i = 0; i < 3; i++ )
for( int j = 0; j < 3; j++ ) {
float a = M[i*4+j] * j[&A.min.x]; // use mat33 from mat44
float b = M[i*4+j] * j[&A.max.x]; // use mat33 from mat44
if( a < b ) {
i[&B.min.x] += a;
i[&B.max.x] += b;
} else {
i[&B.min.x] += b;
i[&B.max.x] += a;
}
}
return B;
}
aabb model_aabb(model_t m, mat44 transform) {
iqm_t *q = m.iqm;
if( q && q->bounds ) {
int f = ( (int)m.curframe ) % (q->numframes + !q->numframes);
vec3 bbmin = ptr3(q->bounds[f].bbmin);
vec3 bbmax = ptr3(q->bounds[f].bbmax);
return aabb_transform(aabb(bbmin,bbmax), transform);
}
return aabb(vec3(0,0,0),vec3(0,0,0));
}
2024-08-19 09:38:59 +00:00
static inline int MapReduce(array(int) collapse_map, int n, int mx) {
while( n >= mx ) n = collapse_map[n];
return n;
}
API void ProgressiveMesh(int vert_n, int vert_stride, const float *v, int tri_n, const int *tri, int *map, int *permutation);
2024-08-19 12:10:35 +00:00
static inline
void MorphVertex(struct iqm_vertex *v, struct iqm_vertex *v0, struct iqm_vertex *v1, float t) {
v->position[0] = mixf(v0->position[0], v1->position[0], t);
v->position[1] = mixf(v0->position[1], v1->position[1], t);
v->position[2] = mixf(v0->position[2], v1->position[2], t);
v->normal[0] = mixf(v0->normal[0], v1->normal[0], t);
v->normal[1] = mixf(v0->normal[1], v1->normal[1], t);
v->normal[2] = mixf(v0->normal[2], v1->normal[2], t);
v->tangent[0] = mixf(v0->tangent[0], v1->tangent[0], t);
v->tangent[1] = mixf(v0->tangent[1], v1->tangent[1], t);
v->tangent[2] = mixf(v0->tangent[2], v1->tangent[2], t);
v->texcoord[0] = mixf(v0->texcoord[0], v1->texcoord[0], t);
v->texcoord[1] = mixf(v0->texcoord[1], v1->texcoord[1], t);
}
2024-08-19 09:38:59 +00:00
void model_lod(model_t *mdl, float lo_detail, float hi_detail, float morph) {
assert(mdl->num_meshes == 1);
if (array_count(mdl->lod_collapse_map) == 0) {
array(int) permutation = 0;
2024-08-19 12:10:35 +00:00
array(float) positions = 0;
2024-08-19 09:38:59 +00:00
array_resize(mdl->lod_collapse_map, mdl->num_verts);
array_resize(permutation, mdl->num_verts);
2024-08-19 12:10:35 +00:00
array_resize(positions, mdl->num_verts*3);
for (int i = 0; i < mdl->num_verts; i++) {
struct iqm_vertex *v = (struct iqm_vertex *)((char *)mdl->verts + i*mdl->stride);
positions[i*3 + 0] = v->position[0];
positions[i*3 + 1] = v->position[1];
positions[i*3 + 2] = v->position[2];
}
ProgressiveMesh(mdl->num_verts, sizeof(float)*3, (const float *)positions, mdl->num_tris, (const int *)mdl->tris, mdl->lod_collapse_map, permutation);
array_free(positions);
2024-08-19 09:38:59 +00:00
// PermuteVertices {
ASSERT(array_count(permutation) == mdl->num_verts);
// rearrange the vertex Array
char *tmp = REALLOC(0, mdl->stride*mdl->num_verts);
char *verts = (char *)mdl->verts;
memcpy(tmp, verts, mdl->stride*mdl->num_verts);
for(int i = 0; i < mdl->num_verts; i++) {
int index = permutation[i];
int src_offset = i * mdl->stride;
int offset = index * mdl->stride;
memcpy(verts + offset, tmp + src_offset, mdl->stride);
}
int *tris = (int *)mdl->tris;
// update the changes in the entries in the triangle Array
2024-08-19 12:10:35 +00:00
for (int i = 0; i < mdl->num_tris; i++) {
tris[i*3 + 0] = permutation[tris[i*3 + 0]];
tris[i*3 + 1] = permutation[tris[i*3 + 1]];
tris[i*3 + 2] = permutation[tris[i*3 + 2]];
2024-08-19 09:38:59 +00:00
}
// upload modified data
glBindVertexArray(mdl->vao);
glBindBuffer(GL_ARRAY_BUFFER, mdl->vbo);
glBufferData(GL_ARRAY_BUFFER, mdl->num_verts*mdl->stride, mdl->verts, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mdl->ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, mdl->num_tris*3*sizeof(int), mdl->tris, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
FREE(tmp);
// } PermuteVertices
array_free(permutation);
}
ASSERT(array_count(mdl->lod_collapse_map));
int max_verts_to_render = hi_detail * mdl->num_verts;
int min_verts_to_render = lo_detail * mdl->num_verts;
if( max_verts_to_render <= 0 || min_verts_to_render <= 0 )
return;
FREE(mdl->lod_verts);
FREE(mdl->lod_tris);
char *verts = (char *)mdl->verts;
int *tris = (int *)mdl->tris;
int max_lod_tris = 0;
//@fixme: optimise
2024-08-19 12:10:35 +00:00
for( unsigned int i = 0; i < mdl->num_tris; i++ ) {
int p0 = MapReduce(mdl->lod_collapse_map, tris[i*3 + 0], max_verts_to_render);
int p1 = MapReduce(mdl->lod_collapse_map, tris[i*3 + 1], max_verts_to_render);
int p2 = MapReduce(mdl->lod_collapse_map, tris[i*3 + 2], max_verts_to_render);
2024-08-19 09:38:59 +00:00
if(p0==p1 || p0==p2 || p1==p2) continue;
++max_lod_tris;
}
mdl->lod_verts = REALLOC(0, max_lod_tris*3*mdl->stride);
mdl->lod_tris = REALLOC(0, max_lod_tris*3*sizeof(int));
mdl->lod_num_verts = 0;
mdl->lod_num_tris = 0;
struct iqm_vertex *lod_verts = (struct iqm_vertex *)mdl->lod_verts;
int *lod_tris = (int *)mdl->lod_tris;
2024-08-19 12:10:35 +00:00
for( int i = 0; i < mdl->num_tris; i++ ) {
int p0 = MapReduce(mdl->lod_collapse_map, tris[i*3 + 0], max_verts_to_render);
int p1 = MapReduce(mdl->lod_collapse_map, tris[i*3 + 1], max_verts_to_render);
int p2 = MapReduce(mdl->lod_collapse_map, tris[i*3 + 2], max_verts_to_render);
2024-08-19 09:38:59 +00:00
if(p0==p1 || p0==p2 || p1==p2) continue;
2024-08-19 12:10:35 +00:00
2024-08-19 09:38:59 +00:00
int q0 = MapReduce(mdl->lod_collapse_map, p0, min_verts_to_render);
int q1 = MapReduce(mdl->lod_collapse_map, p1, min_verts_to_render);
int q2 = MapReduce(mdl->lod_collapse_map, p2, min_verts_to_render);
2024-08-19 12:10:35 +00:00
// if(q0==q1 || q0==q2 || q1==q2) continue;
2024-08-19 09:38:59 +00:00
struct iqm_vertex v0 = *(struct iqm_vertex *)(verts + (p0*mdl->stride));
struct iqm_vertex v1 = *(struct iqm_vertex *)(verts + (p1*mdl->stride));
struct iqm_vertex v2 = *(struct iqm_vertex *)(verts + (p2*mdl->stride));
2024-08-19 12:10:35 +00:00
struct iqm_vertex u0 = *(struct iqm_vertex *)(verts + (q0*mdl->stride));
struct iqm_vertex u1 = *(struct iqm_vertex *)(verts + (q1*mdl->stride));
struct iqm_vertex u2 = *(struct iqm_vertex *)(verts + (q2*mdl->stride));
struct iqm_vertex f0=v0,f1=v1,f2=v2;
2024-08-19 09:38:59 +00:00
2024-08-19 12:10:35 +00:00
if (morph == 0.0f) {
f0=u0,f1=u1,f2=u2;
}
else if (morph < 1.0f) {
MorphVertex(&f0, &v0, &u0, 1.0f - morph);
MorphVertex(&f1, &v1, &u1, 1.0f - morph);
MorphVertex(&f2, &v2, &u2, 1.0f - morph);
}
lod_verts[mdl->lod_num_verts + 0] = f0;
lod_verts[mdl->lod_num_verts + 1] = f1;
lod_verts[mdl->lod_num_verts + 2] = f2;
2024-08-19 09:38:59 +00:00
int idx = mdl->lod_num_verts;
2024-08-19 12:10:35 +00:00
lod_tris[mdl->lod_num_tris*3 + 0] = idx+0;
lod_tris[mdl->lod_num_tris*3 + 1] = idx+1;
lod_tris[mdl->lod_num_tris*3 + 2] = idx+2;
mdl->lod_num_verts += 3;
2024-08-19 09:38:59 +00:00
++mdl->lod_num_tris;
}
// upload modified data
glBindVertexArray(mdl->vao);
glBindBuffer(GL_ARRAY_BUFFER, mdl->vbo);
glBufferData(GL_ARRAY_BUFFER, mdl->lod_num_verts*mdl->stride, mdl->lod_verts, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mdl->ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, mdl->lod_num_tris*3*sizeof(int), mdl->lod_tris, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
2024-08-19 07:54:01 +00:00
void model_destroy(model_t m) {
FREE(m.verts);
for( int i = 0, end = array_count(m.texture_names); i < end; ++i ) {
FREE(m.texture_names[i]);
}
array_free(m.texture_names);
2024-08-23 19:28:40 +00:00
FREE(m.meshcenters);
FREE(m.meshbounds);
FREE(m.meshradii);
2024-08-19 07:54:01 +00:00
iqm_t *q = m.iqm;
// if(m.mesh) mesh_destroy(m.mesh);
FREE(q->outframe);
FREE(q->colormaps);
FREE(q->textures);
FREE(q->baseframe);
FREE(q->inversebaseframe);
if(q->animdata != q->meshdata) FREE(q->animdata);
//FREE(q->meshdata);
FREE(q->frames);
FREE(q->buf);
FREE(q);
}
2024-08-23 16:24:24 +00:00
static unsigned model_renderpass = RENDER_PASS_OPAQUE;
2024-08-19 07:54:01 +00:00
unsigned model_getpass() {
return model_renderpass;
}
unsigned model_setpass(unsigned pass) {
ASSERT(pass < NUM_RENDER_PASSES);
2024-08-23 19:28:40 +00:00
ASSERT(pass != RENDER_PASS_OVERRIDES_BEGIN && pass != RENDER_PASS_OVERRIDES_END);
2024-08-19 07:54:01 +00:00
unsigned old_pass = model_renderpass;
model_renderpass = pass;
return old_pass;
}
anims_t animations(const char *pathfile, int flags) {
anims_t a = {0};
a.anims = animlist(pathfile);
if(a.anims) a.speed = 1.0;
return a;
}
// -----------------------------------------------------------------------------
// lightmapping utils
// @fixme: support xatlas uv packing, add UV1 coords to vertex model specs
lightmap_t lightmap(int hmsize, float cnear, float cfar, vec3 color, int passes, float threshold, float distmod) {
lightmap_t lm = {0};
lm.ctx = lmCreate(hmsize, cnear, cfar, color.x, color.y, color.z, passes, threshold, distmod);
if (!lm.ctx) {
PANIC("Error: Could not initialize lightmapper.\n");
return lm;
}
const char *symbols[] = { "{{include-shadowmap}}", vfs_read("shaders/fs_0_0_shadowmap_lit.glsl") }; // #define RIM
lm.shader = shader(strlerp(1,symbols,vfs_read("shaders/vs_323444143_16_3322_model.glsl")), strlerp(1,symbols,vfs_read("shaders/fs_32_4_model.glsl")), //fs,
"att_position,att_texcoord,att_normal,att_tangent,att_instanced_matrix,,,,att_indexes,att_weights,att_vertexindex,att_color,att_bitangent,att_texcoord2","fragColor",
va("%s", "LIGHTMAP_BAKING"));
return lm;
}
void lightmap_destroy(lightmap_t *lm) {
lmDestroy(lm->ctx);
shader_destroy(lm->shader);
//
}
void lightmap_setup(lightmap_t *lm, int w, int h) {
lm->ready=1;
//@fixme: prep atlas for lightmaps
lm->w = w;
lm->h = h;
}
void lightmap_bake(lightmap_t *lm, int bounces, void (*drawscene)(lightmap_t *lm, model_t *m, float *view, float *proj, void *userdata), void (*progressupdate)(float progress), void *userdata) {
ASSERT(lm->ready);
// @fixme: use xatlas to UV pack all models, update their UV1 and upload them to GPU.
int w = lm->w, h = lm->h;
for (int i = 0; i < array_count(lm->models); i++) {
model_t *m = lm->models[i];
if (m->lightmap.w != 0) {
texture_destroy(&m->lightmap);
}
m->lightmap = texture_create(w, h, 4, 0, TEXTURE_LINEAR|TEXTURE_FLOAT);
glBindTexture(GL_TEXTURE_2D, m->lightmap.id);
unsigned char emissive[] = { 0, 0, 0, 255 };
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, emissive);
glBindTexture(GL_TEXTURE_2D, 0);
}
unsigned old_pass = model_setpass(RENDER_PASS_LIGHTMAP);
for (int b = 0; b < bounces; b++) {
2024-08-23 19:28:40 +00:00
model_setpass(RENDER_PASS_LIGHTMAP);
2024-08-19 07:54:01 +00:00
for (int i = 0; i < array_count(lm->models); i++) {
model_t *m = lm->models[i];
if (!m->lmdata) {
m->lmdata = CALLOC(w*h*4, sizeof(float));
}
memset(m->lmdata, 0, w*h*4);
lmSetTargetLightmap(lm->ctx, m->lmdata, w, h, 4);
lmSetGeometry(lm->ctx, m->pivot,
LM_FLOAT, (uint8_t*)m->verts + offsetof(iqm_vertex, position), sizeof(iqm_vertex),
LM_FLOAT, (uint8_t*)m->verts + offsetof(iqm_vertex, normal), sizeof(iqm_vertex),
LM_FLOAT, (uint8_t*)m->verts + offsetof(iqm_vertex, texcoord), sizeof(iqm_vertex),
m->num_tris*3, LM_UNSIGNED_INT, m->tris);
int vp[4];
float view[16], projection[16];
while (lmBegin(lm->ctx, vp, view, projection))
{
// render to lightmapper framebuffer
glViewport(vp[0], vp[1], vp[2], vp[3]);
drawscene(lm, m, view, projection, userdata);
if (progressupdate) progressupdate(lmProgress(lm->ctx));
lmEnd(lm->ctx);
}
}
model_setpass(old_pass);
// postprocess texture
for (int i = 0; i < array_count(lm->models); i++) {
model_t *m = lm->models[i];
float *temp = CALLOC(w * h * 4, sizeof(float));
for (int i = 0; i < 16; i++)
{
lmImageDilate(m->lmdata, temp, w, h, 4);
lmImageDilate(temp, m->lmdata, w, h, 4);
}
lmImageSmooth(m->lmdata, temp, w, h, 4);
lmImageDilate(temp, m->lmdata, w, h, 4);
lmImagePower(m->lmdata, w, h, 4, 1.0f / 2.2f, 0x7); // gamma correct color channels
FREE(temp);
// save result to a file
// if (lmImageSaveTGAf("result.tga", m->lmdata, w, h, 4, 1.0f))
// printf("Saved result.tga\n");
// upload result
glBindTexture(GL_TEXTURE_2D, m->lightmap.id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_FLOAT, m->lmdata);
FREE(m->lmdata); m->lmdata = NULL;
}
}
}
#line 0
#line 1 "v4k_renderdd.c"
static const char *dd_vs = "//" FILELINE "\n"
"in vec3 att_position;\n"
"uniform mat4 u_MVP;\n"
"uniform vec3 u_color;\n"
"out vec3 out_color;\n"
"void main() {\n"
" gl_Position = u_MVP * vec4(att_position, 1.0);\n"
" gl_PointSize = 4.0; /* for GL_POINTS draw commands */\n"
" out_color = u_color;\n"
"}";
static const char *dd_fs = "//" FILELINE "\n"
// "precision mediump float;\n"
"in vec3 out_color;\n"
"out vec4 fragcolor;\n"
"void main() {\n"
" fragcolor = vec4(out_color, 1.0);\n"
"}";
#define X(x) RGBX(x,255)
const uint32_t secret_palette[32] = { // pico8 secret palette (CC0, public domain)
X(0x000000),X(0x1D2B53),X(0x7E2553),X(0x008751),X(0xAB5236),X(0x5F574F),X(0xC2C3C7),X(0xFFF1E8), /*00.07*/
X(0xFF004D),X(0xFFA300),X(0xFFEC27),X(0x00E436),X(0x29ADFF),X(0x83769C),X(0xFF77A8),X(0xFFCCAA), /*08.15*/
X(0x291814),X(0x111D35),X(0x422136),X(0x125359),X(0x742F29),X(0x49333B),X(0xA28879),X(0xF3EF7D), /*16.23*/
X(0xBE1250),X(0xFF6C24),X(0xA8E72E),X(0x00B543),X(0x065AB5),X(0x754665),X(0xFF6E59),X(0xFF9D81), /*24.31*/
};
#undef X
typedef struct text2d_cmd {
const char *str;
uint32_t col;
vec3 pos;
float sca;
} text2d_cmd;
static renderstate_t dd_rs;
static uint32_t dd_color = ~0u;
static float dd_line_width = 1.0f;
static GLuint dd_program = -1;
static int dd_u_color = -1;
static map(uint64_t,array(vec3)) dd_lists[2][3] = {0}; // [0/1 ontop][0/1/2 thin lines/thick lines/points]
static bool dd_use_line = 0;
static bool dd_ontop = 0;
static array(text2d_cmd) dd_text2d;
static array(vec4) dd_matrix2d;
static inline
uint64_t convert_key_from_color_width(uint32_t color, float width) {
union { float f; uint32_t i; } u = { .f = width };
return ((uint64_t)color << 32) | u.i;
}
static inline
void convert_key_to_color_width(uint64_t key, uint32_t *color, float *width) {
*color = key >> 32;
union { float f; uint64_t i; } u = { .i = key };
*width = u.f;
}
void ddraw_push_2d() {
float width = window_width();
float height = window_height();
float zdepth_max = window_height();
array_push(dd_matrix2d, vec4(width,height,zdepth_max,0));
ddraw_flush();
}
void ddraw_pop_2d() {
vec4 dim = *array_back(dd_matrix2d);
array_pop(dd_matrix2d);
mat44 id, proj;
id44(id);
ortho44(proj, 0,dim.x,dim.y,0, -dim.z, +dim.z);
ddraw_flush_projview(proj, id);
}
void ddraw_flush() {
ddraw_flush_projview(camera_get_active()->proj, camera_get_active()->view);
}
void ddraw_flush_projview(mat44 proj, mat44 view) {
do_once dd_rs = renderstate();
dd_rs.depth_test_enabled = dd_ontop;
dd_rs.cull_face_enabled = 0;
glActiveTexture(GL_TEXTURE0);
mat44 mvp;
multiply44x2(mvp, proj, view); // MVP where M=id
glUseProgram(dd_program);
glUniformMatrix4fv(glGetUniformLocation(dd_program, "u_MVP"), 1, GL_FALSE, mvp);
static GLuint vao, vbo;
if(!vao) glGenVertexArrays(1, &vao); glBindVertexArray(vao);
if(!vbo) glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo);
glEnableVertexAttribArray(0);
dd_rs.point_size_enabled = 1;
dd_rs.line_smooth_enabled = 1;
for( int i = 0; i < 3; ++i ) { // [0] thin, [1] thick, [2] points
GLenum mode = i < 2 ? GL_LINES : GL_POINTS;
for each_map(dd_lists[dd_ontop][i], uint64_t, meta, array(vec3), list) {
int count = array_count(list);
if(!count) continue;
unsigned rgbi = 0;
convert_key_to_color_width(meta, &rgbi, &dd_line_width);
dd_rs.line_width = (i == 1 ? dd_line_width : 0.3); // 0.625);
renderstate_apply(&dd_rs);
// color
vec3 rgbf = {((rgbi>>0)&255)/255.f,((rgbi>>8)&255)/255.f,((rgbi>>16)&255)/255.f};
glUniform3fv(dd_u_color, GL_TRUE, &rgbf.x);
// config vertex data
glBufferData(GL_ARRAY_BUFFER, count * 3 * 4, list, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), 0);
// feed vertex data
glDrawArrays(mode, 0, count);
profile_incstat("Render.num_drawcalls", +1);
profile_incstat(i < 2 ? "Render.num_lines" : "Render.num_points", count);
array_clear(list);
}
}
if(array_count(dd_text2d)) { // text 2d
// queue
for(int i = 0; i < array_count(dd_text2d); ++i) {
ddraw_color(dd_text2d[i].col);
ddraw_text(dd_text2d[i].pos, dd_text2d[i].sca, dd_text2d[i].str);
}
// flush
float mvp[16]; float zdepth_max = 1;
ortho44(mvp, -window_width()/2, window_width()/2, -window_height()/2, window_height()/2, -1, 1);
translate44(mvp, -window_width()/2, window_height()/2, 0);
glUniformMatrix4fv(glGetUniformLocation(dd_program, "u_MVP"), 1, GL_FALSE, mvp);
for( int i = 0; i < 3; ++i ) { // [0] thin, [1] thick, [2] points
GLenum mode = i < 2 ? GL_LINES : GL_POINTS;
dd_rs.line_width = (i == 1 ? 1 : 0.3); // 0.625);
for each_map(dd_lists[dd_ontop][i], uint64_t, meta, array(vec3), list) {
int count = array_count(list);
if(!count) continue;
unsigned rgbi = 0;
convert_key_to_color_width(meta, &rgbi, &dd_line_width);
dd_rs.line_width = (i == 1 ? dd_line_width : 0.3);
renderstate_apply(&dd_rs);
// color
vec3 rgbf = {((rgbi>>0)&255)/255.f,((rgbi>>8)&255)/255.f,((rgbi>>16)&255)/255.f};
glUniform3fv(dd_u_color, GL_TRUE, &rgbf.x);
// config vertex data
glBufferData(GL_ARRAY_BUFFER, count * 3 * 4, list, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), 0);
// feed vertex data
glDrawArrays(mode, 0, count);
profile_incstat("Render.num_drawcalls", +1);
profile_incstat(i < 2 ? "Render.num_lines" : "Render.num_points", count);
array_clear(list);
}
}
// clear
array_resize(dd_text2d, 0);
}
glBindVertexArray(0);
ddraw_color(WHITE); // reset color for next drawcall
}
static array(bool) dd_ontops;
void ddraw_ontop(int enabled) {
dd_ontop = !!enabled;
}
void ddraw_ontop_push(int enabled) {
array_push(dd_ontops, dd_ontop);
dd_ontop = !!enabled;
}
void ddraw_ontop_pop() {
bool *pop = array_pop(dd_ontops);
if(pop) dd_ontop = *pop;
}
static array(float) dd_line_scales;
void ddraw_line_width(float width) {
dd_line_width = width;
}
void ddraw_line_width_push(float scale) {
array_push(dd_line_scales, dd_line_width);
dd_line_width = scale;
}
void ddraw_line_width_pop() {
float *pop = array_pop(dd_line_scales);
if(pop) dd_line_width = *pop;
}
static array(uint32_t) dd_colors;
void ddraw_color(unsigned rgb) {
dd_color = rgb;
}
void ddraw_color_push(unsigned rgb) {
array_push(dd_colors, dd_color);
dd_color = rgb;
}
void ddraw_color_pop() {
unsigned *pop = array_pop(dd_colors);
if(pop) dd_color = *pop;
}
void ddraw_point(vec3 from) {
uint64_t key = convert_key_from_color_width(dd_color, dd_line_width);
array(vec3) *found = map_find_or_add(dd_lists[dd_ontop][2], key, 0);
array_push(*found, from);
}
void ddraw_line_thin(vec3 from, vec3 to) { // thin lines
uint64_t key = convert_key_from_color_width(dd_color, dd_line_width);
array(vec3) *found = map_find_or_add(dd_lists[dd_ontop][0], key, 0);
array_push(*found, from);
array_push(*found, to);
}
void ddraw_line(vec3 from, vec3 to) { // thick lines
uint64_t key = convert_key_from_color_width(dd_color, dd_line_width);
array(vec3) *found = map_find_or_add(dd_lists[dd_ontop][1], key, 0);
array_push(*found, from);
array_push(*found, to);
}
void ddraw_line_dashed(vec3 from, vec3 to) { // thick lines
vec3 dist = sub3(to, from); vec3 unit = norm3(dist);
for( float len = 0, mag = len3(dist) / 2; len < mag; ++len ) {
to = add3(from, unit);
ddraw_line(from, to);
from = add3(to, unit);
}
}
void ddraw_triangle(vec3 pa, vec3 pb, vec3 pc) {
ddraw_line(pa, pb);
ddraw_line(pa, pc);
ddraw_line(pb, pc);
}
void ddraw_axis(float units) {
ddraw_color(RED); ddraw_line(vec3(0,0,0), vec3(units,0,0)); ddraw_line_dashed(vec3(0,0,0), vec3(-units,0,0));
ddraw_color(GREEN); ddraw_line(vec3(0,0,0), vec3(0,units,0)); ddraw_line_dashed(vec3(0,0,0), vec3(0,-units,0));
ddraw_color(BLUE); ddraw_line(vec3(0,0,0), vec3(0,0,units)); ddraw_line_dashed(vec3(0,0,0), vec3(0,0,-units));
}
void ddraw_ground_(float scale) { // 10x10
ddraw_color( WHITE ); // outer
for( float i = -scale, c = 0; c <= 20; c += 20, i += c * (scale/10) ) {
ddraw_line(vec3(-scale,0,i), vec3(+scale,0,i)); // horiz
ddraw_line(vec3(i,0,-scale), vec3(i,0,+scale)); // vert
}
ddraw_color( RGB3(149,149,149) ); // inner, light grey
for( float i = -scale + scale/10, c = 1; c < 20; ++c, i += (scale/10) ) {
ddraw_line_thin(vec3(-scale,0,i), vec3(+scale,0,i)); // horiz
ddraw_line_thin(vec3(i,0,-scale), vec3(i,0,+scale)); // vert
}
}
void ddraw_ground(float scale) {
if( scale ) {
ddraw_ground_(scale);
} else {
ddraw_ground_(100);
ddraw_ground_(10);
ddraw_ground_(1);
ddraw_ground_(0.1);
ddraw_ground_(0.01);
}
}
void ddraw_grid(float scale) {
ddraw_ground(scale);
ddraw_axis(scale ? scale : 100);
}
void ddraw_text2d(vec2 pos, const char *text) {
struct text2d_cmd t = {0};
t.sca = 0.5f; // 0.5 is like vertical 12units each
t.pos = vec3(pos.x, 0 - pos.y - 12, 0);
t.str = text;
t.col = dd_color;
array_push(dd_text2d, t);
}
void (ddraw_text)(vec3 pos, float scale, const char *text) {
// [ref] http://paulbourke.net/dataformats/hershey/ (PD)
// [ref] https://sol.gfxile.net/hershey/fontprev.html (WTFPL2)
static const char *hershey[] = { /* simplex font */
"AQ","IKFVFH@@FCEBFAGBFC","FQEVEO@@MVMO","LVLZE:@@RZK:@@EMSM@@DGRG","[UIZI=@@MZ"
"M=@@RSPUMVIVFUDSDQEOFNHMNKPJQIRGRDPBMAIAFBDD","`YVVDA@@IVKTKRJPHOFODQDSEUGVIVK"
"UNTQTTUVV@@RHPGOEOCQASAUBVDVFTHRH","c[XMXNWOVOUNTLRGPDNBLAHAFBECDEDGEIFJMNNOOQ"
"OSNULVJUISIQJNLKQDSBUAWAXBXC","HKFTEUFVGUGSFQEP","KOLZJXHUFQELEHFCH?J<L:","KOD"
"ZFXHUJQKLKHJCH?F<D:","IQIVIJ@@DSNM@@NSDM","F[NSNA@@EJWJ","IKGBFAEBFCGBG@F>E=",\
"C[EJWJ","FKFCEBFAGBFC","CWUZC:","RUJVGUERDMDJEEGBJALAOBQERJRMQROULVJV","EUGRIS"
"LVLA","OUEQERFTGUIVMVOUPTQRQPPNNKDARA","PUFVQVKNNNPMQLRIRGQDOBLAIAFBECDE","GUN"
"VDHSH@@NVNA","RUPVFVEMFNIOLOONQLRIRGQDOBLAIAFBECDE","XUQSPUMVKVHUFREMEHFDHBKAL"
"AOBQDRGRHQKOMLNKNHMFKEH","FURVHA@@DVRV","^UIVFUESEQFOHNLMOLQJRHREQCPBMAIAFBECD"
"EDHEJGLJMNNPOQQQSPUMVIV","XUQOPLNJKIJIGJELDODPESGUJVKVNUPSQOQJPENBKAIAFBED","L"
"KFOENFMGNFO@@FCEBFAGBFC","OKFOENFMGNFO@@GBFAEBFCGBG@F>E=","DYUSEJUA","F[EMWM@@"
"EGWG","DYESUJEA","USDQDRETFUHVLVNUOTPRPPONNMJKJH@@JCIBJAKBJC","x\\SNRPPQMQKPJO"
"ILIIJGLFOFQGRI@@MQKOJLJIKGLF@@SQRIRGTFVFXHYKYMXPWRUTSUPVMVJUHTFREPDMDJEGFEHCJB"
"MAPASBUCVD@@TQSISGTF","ISJVBA@@JVRA@@EHOH","XVEVEA@@EVNVQURTSRSPRNQMNL@@ELNLQK"
"RJSHSERCQBNAEA","SVSQRSPUNVJVHUFSEQDNDIEFFDHBJANAPBRDSF","PVEVEA@@EVLVOUQSRQSN"
"SIRFQDOBLAEA","LTEVEA@@EVRV@@ELML@@EARA","ISEVEA@@EVRV@@ELML","WVSQRSPUNVJVHUF"
"SEQDNDIEFFDHBJANAPBRDSFSI@@NISI","IWEVEA@@SVSA@@ELSL","CIEVEA","KQMVMFLCKBIAGA"
"EBDCCFCH","IVEVEA@@SVEH@@JMSA","FREVEA@@EAQA","LYEVEA@@EVMA@@UVMA@@UVUA","IWEV"
"EA@@EVSA@@SVSA","VWJVHUFSEQDNDIEFFDHBJANAPBRDSFTITNSQRSPUNVJV","NVEVEA@@EVNVQU"
"RTSRSORMQLNKEK","YWJVHUFSEQDNDIEFFDHBJANAPBRDSFTITNSQRSPUNVJV@@MES?","QVEVEA@@"
"EVNVQURTSRSPRNQMNLEL@@LLSA","UURSPUMVIVFUDSDQEOFNHMNKPJQIRGRDPBMAIAFBDD","FQIV"
"IA@@BVPV","KWEVEGFDHBKAMAPBRDSGSV","FSBVJA@@RVJA","LYCVHA@@MVHA@@MVRA@@WVRA",""
"FUDVRA@@RVDA","GSBVJLJA@@RVJL","IURVDA@@DVRV@@DARA","LOEZE:@@FZF:@@EZLZ@@E:L:",
"COAVO>","LOJZJ:@@KZK:@@DZKZ@@D:K:","KQGPISKP@@DMIRNM@@IRIA","CQA?Q?","HKGVFUES"
"EQFPGQFR","RTPOPA@@PLNNLOIOGNELDIDGEDGBIALANBPD","RTEVEA@@ELGNIOLONNPLQIQGPDNB"
"LAIAGBED","OSPLNNLOIOGNELDIDGEDGBIALANBPD","RTPVPA@@PLNNLOIOGNELDIDGEDGBIALANB"
"PD","RSDIPIPKOMNNLOIOGNELDIDGEDGBIALANBPD","IMKVIVGUFRFA@@COJO","WTPOP?O<N;L:I"
":G;@@PLNNLOIOGNELDIDGEDGBIALANBPD","KTEVEA@@EKHNJOMOONPKPA","IIDVEUFVEWDV@@EOE"
"A","LKFVGUHVGWFV@@GOG>F;D:B:","IREVEA@@OOEE@@IIPA","CIEVEA","S_EOEA@@EKHNJOMOO"
"NPKPA@@PKSNUOXOZN[K[A","KTEOEA@@EKHNJOMOONPKPA","RTIOGNELDIDGEDGBIALANBPDQGQIP"
"LNNLOIO","RTEOE:@@ELGNIOLONNPLQIQGPDNBLAIAGBED","RTPOP:@@PLNNLOIOGNELDIDGEDGBI"
"ALANBPD","INEOEA@@EIFLHNJOMO","RROLNNKOHOENDLEJGILHNGOEODNBKAHAEBDD","IMFVFEGB"
"IAKA@@COJO","KTEOEEFBHAKAMBPE@@POPA","FQCOIA@@OOIA","LWDOHA@@LOHA@@LOPA@@TOPA",
"FRDOOA@@OODA","JQCOIA@@OOIAG=E;C:B:","IROODA@@DOOO@@DAOA","hOJZHYGXFVFTGRHQIOI"
"MGK@@HYGWGUHSIRJPJNILEJIHJFJDIBHAG?G=H;@@GIIGIEHCGBF@F>G<H;J:","CIEZE:","hOFZH"
"YIXJVJTIRHQGOGMIK@@HYIWIUHSGRFPFNGLKJGHFFFDGBHAI?I=H;@@IIGGGEHCIBJ@J>I<H;F:",""
"XYDGDIELGMIMKLOIQHSHUIVK@@DIEKGLILKKOHQGSGUHVKVM" };
vec3 src = pos, old = {0}; float abs_scale = absf(scale);
for( signed char c; (c = *text++, c > 0 && c < 127); ) {
if( c == '\n' || c == '\r' ) {
pos.x = src.x, pos.y -= scale * ((signed char)hershey['W'-32][1] - 65) * 1.25f; // spacing @1
} else {
const char *glyph = (const char*)hershey[c - 32];
if( c > 32 ) for( int pen = 0, i = 0; i < (glyph[0] - 65); i++ ) { // verts @0
int x = glyph[2 + i*2 + 0] - 65, y = glyph[2 + i*2 + 1] - 65;
if( x == -1 && y == -1 ) pen = 0; else {
vec3 next = add3(pos, vec3(abs_scale*x, scale*y, 0));
if( !pen ) pen = 1; else ddraw_line(old, next);
old = next;
}
}
pos.x += abs_scale * (glyph[1] - 65); // spacing @1
}
}
}
void ddraw_prism(vec3 center, float radius, float height, vec3 normal, int segments) {
vec3 left = {0}, up = {0};
ortho3(&left, &up, normal);
vec3 point, lastPoint;
up = scale3(up, radius);
left = scale3(left, radius);
lastPoint = add3(center, up);
vec3 pivot = add3(center, scale3(normal, height));
for (int i = 1; i <= segments; ++i) {
const float radians = (C_PI * 2) * i / segments;
vec3 vs = scale3(left, sinf(radians));
vec3 vc = scale3(up, cosf(radians));
point = add3(center, vs);
point = add3(point, vc);
ddraw_line(lastPoint, point);
if( height > 0 ) ddraw_line(point, pivot);
else if(height < 0) {
ddraw_line(point, add3(point,scale3(normal, -height)));
}
lastPoint = point;
}
if(height < 0) ddraw_prism(add3(center, scale3(normal, -height)), radius, 0, normal, segments);
}
void ddraw_cube(vec3 center, float radius) { // draw_prism(center, 1, -1, vec3(0,1,0), 4);
float half = radius * 0.5f;
vec3 l = vec3(center.x-half,center.y+half,center.z-half); // left-top-far
vec3 r = vec3(center.x+half,center.y-half,center.z+half); // right-bottom-near
ddraw_line(l, vec3(r.x,l.y,l.z));
ddraw_line(vec3(r.x,l.y,l.z), vec3(r.x,l.y,r.z));
ddraw_line(vec3(r.x,l.y,r.z), vec3(l.x,l.y,r.z));
ddraw_line(vec3(l.x,l.y,r.z), l);
ddraw_line(l, vec3(l.x,r.y,l.z));
ddraw_line(r, vec3(l.x,r.y,r.z));
ddraw_line(vec3(l.x,r.y,r.z), vec3(l.x,r.y,l.z));
ddraw_line(vec3(l.x,r.y,l.z), vec3(r.x,r.y,l.z));
ddraw_line(vec3(r.x,r.y,l.z), r);
ddraw_line(r, vec3(r.x,l.y,r.z));
ddraw_line(vec3(l.x,l.y,r.z), vec3(l.x,r.y,r.z));
ddraw_line(vec3(r.x,l.y,l.z), vec3(r.x,r.y,l.z));
}
#if 0 // @fixme: broken
void ddraw_cube44(vec3 radius, mat44 M) {
float m33[9]; extract33(m33, M); // = { M[0,1,2], M[4,5,6], M[8,9,10] }
ddraw_cube33( vec3(M[12], M[13], M[14]), radius, m33 );
}
#endif
void ddraw_cube33(vec3 center, vec3 radius, mat33 M) {
vec3 half = scale3(radius, 0.5f);
vec3 l = vec3(-half.x,+half.y,-half.z); // left-top-far
vec3 r = vec3(+half.x,-half.y,+half.z); // right-bottom-near
vec3 points[8] = {
vec3(l.x, r.y, r.z),
vec3(l.x, r.y, l.z),
vec3(r.x, r.y, l.z),
vec3(r.x, r.y, r.z),
vec3(l.x, l.y, r.z),
vec3(l.x, l.y, l.z),
vec3(r.x, l.y, l.z),
vec3(r.x, l.y, r.z),
};
for( int i = 0; i < 8; ++i ) {
points[i] = add3(center, transform33(M, points[i]));
}
ddraw_bounds(points);
}
void ddraw_normal(vec3 pos, vec3 n) {
ddraw_color(YELLOW);
ddraw_line(pos, add3(pos, norm3(n)));
}
void ddraw_circle(vec3 pos, vec3 n, float r) { ddraw_prism(pos, r, 0, n, 32); }
void ddraw_ring(vec3 pos, vec3 n, float r) { ddraw_circle(pos,n,r);ddraw_circle(pos,n,r*0.90); }
void ddraw_hexagon(vec3 pos, float r) { ddraw_prism(pos, r, 0, vec3(0,1,0), 6); }
void ddraw_pentagon(vec3 pos, float r) { ddraw_prism(pos, r, 0, vec3(0,1,0), 5); }
void ddraw_square(vec3 pos, float r) { ddraw_prism(pos, r, 0, vec3(0,1,0), 4); }
//void ddraw_triangle(vec3 pos, float r) { ddraw_prism(pos, r, 0, vec3(0,1,0), 3); }
void ddraw_sphere(vec3 center, float radius) {
float lod = 8, yp = -radius, rp = 0, y, r, x, z;
for( int j = 1; j <= lod / 2; ++j, yp = y, rp = r ) {
y = j * 2.f / (lod / 2) - 1;
r = cosf(y * 3.14159f / 2) * radius;
y = sinf(y * 3.14159f / 2) * radius;
float xp = 1, zp = 0;
for( int i = 1; i <= lod; ++i, xp = x, zp = z ) {
x = 3.14159f * 2 * i / lod;
z = sinf(x);
x = cosf(x);
vec3 a1 = add3(center, vec3(xp * rp, yp, zp * rp));
vec3 b1 = add3(center, vec3(xp * r, y, zp * r));
vec3 c1 = add3(center, vec3(x * r, y, z * r));
ddraw_line(a1,b1);
ddraw_line(b1,c1);
ddraw_line(c1,a1);
vec3 a2 = add3(center, vec3(xp * rp, yp, zp * rp));
vec3 b2 = add3(center, vec3(x * r, y, z * r));
vec3 c2 = add3(center, vec3(x * rp, yp, z * rp));
ddraw_line(a2,b2);
ddraw_line(b2,c2);
ddraw_line(c2,a2);
}
}
}
void ddraw_box(vec3 c, vec3 extents) {
vec3 points[8], whd = scale3(extents, 0.5f);
#define DD_BOX_V(v, op1, op2, op3) (v).x = c.x op1 whd.x; (v).y = c.y op2 whd.y; (v).z = c.z op3 whd.z
DD_BOX_V(points[0], -, +, +);
DD_BOX_V(points[1], -, +, -);
DD_BOX_V(points[2], +, +, -);
DD_BOX_V(points[3], +, +, +);
DD_BOX_V(points[4], -, -, +);
DD_BOX_V(points[5], -, -, -);
DD_BOX_V(points[6], +, -, -);
DD_BOX_V(points[7], +, -, +);
#undef DD_BOX_V
ddraw_bounds(points);
}
void ddraw_capsule(vec3 from, vec3 to, float r) {
/* calculate axis */
vec3 up, right, forward;
forward = sub3(to, from);
forward = norm3(forward);
ortho3(&right, &up, forward);
/* calculate first two cone verts (buttom + top) */
vec3 lastf, lastt;
lastf = scale3(up,r);
lastt = add3(to,lastf);
lastf = add3(from,lastf);
/* step along circle outline and draw lines */
enum { step_size = 20 };
for (int i = step_size; i <= 360; i += step_size) {
/* calculate current rotation */
vec3 ax = scale3(right, sinf(i*TO_RAD));
vec3 ay = scale3(up, cosf(i*TO_RAD));
/* calculate current vertices on cone */
vec3 tmp = add3(ax, ay);
vec3 pf = scale3(tmp, r);
vec3 pt = scale3(tmp, r);
pf = add3(pf, from);
pt = add3(pt, to);
/* draw cone vertices */
ddraw_line(lastf, pf);
ddraw_line(lastt, pt);
ddraw_line(pf, pt);
lastf = pf;
lastt = pt;
/* calculate first top sphere vert */
vec3 prevt = scale3(tmp, r);
vec3 prevf = add3(prevt, from);
prevt = add3(prevt, to);
/* sphere (two half spheres )*/
for (int j = 1; j < 180/step_size; j++) {
/* angles */
float ta = j*step_size;
float fa = 360-(j*step_size);
/* top half-sphere */
ax = scale3(forward, sinf(ta*TO_RAD));
ay = scale3(tmp, cosf(ta*TO_RAD));
vec3 t = add3(ax, ay);
pf = scale3(t, r);
pf = add3(pf, to);
ddraw_line(pf, prevt);
prevt = pf;
/* bottom half-sphere */
ax = scale3(forward, sinf(fa*TO_RAD));
ay = scale3(tmp, cosf(fa*TO_RAD));
t = add3(ax, ay);
pf = scale3(t, r);
pf = add3(pf, from);
ddraw_line(pf, prevf);
prevf = pf;
}
}
}
void ddraw_pyramid(vec3 center, float height, int segments) {
ddraw_prism(center, 1, height, vec3(0,1,0), segments);
}
void ddraw_cylinder(vec3 center, float height, int segments) {
ddraw_prism(center, 1, -height, vec3(0,1,0), segments);
}
void ddraw_diamond(vec3 from, vec3 to, float size) {
poly p = diamond(from, to, size);
vec3 *dmd = p.verts;
vec3 *a = dmd + 0;
vec3 *b = dmd + 1;
vec3 *c = dmd + 2;
vec3 *d = dmd + 3;
vec3 *t = dmd + 4;
vec3 *f = dmd + 5;
/* draw vertices */
ddraw_line(*a, *b);
ddraw_line(*b, *c);
ddraw_line(*c, *d);
ddraw_line(*d, *a);
/* draw roof */
ddraw_line(*a, *t);
ddraw_line(*b, *t);
ddraw_line(*c, *t);
ddraw_line(*d, *t);
/* draw floor */
ddraw_line(*a, *f);
ddraw_line(*b, *f);
ddraw_line(*c, *f);
ddraw_line(*d, *f);
poly_free(&p);
}
void ddraw_cone(vec3 center, vec3 top, float radius) {
vec3 diff3 = sub3(top, center);
ddraw_prism(center, radius ? radius : 1, len3(diff3), norm3(diff3), 24);
}
void ddraw_cone_lowres(vec3 center, vec3 top, float radius) {
vec3 diff3 = sub3(top, center);
ddraw_prism(center, radius ? radius : 1, len3(diff3), norm3(diff3), 3);
}
void ddraw_bone(vec3 center, vec3 end) {
vec3 diff3 = sub3(end, center);
float len = len3(diff3), len10 = len / 10;
ddraw_prism(center, len10, 0, vec3(1,0,0), 24);
ddraw_prism(center, len10, 0, vec3(0,1,0), 24);
ddraw_prism(center, len10, 0, vec3(0,0,1), 24);
ddraw_line(end, add3(center, vec3(0,+len10,0)));
ddraw_line(end, add3(center, vec3(0,-len10,0)));
}
void ddraw_bounds(const vec3 points[8]) {
for( int i = 0; i < 4; ++i ) {
ddraw_line(points[i], points[(i + 1) & 3]);
ddraw_line(points[i], points[4 + i]);
ddraw_line(points[4 + i], points[4 + ((i + 1) & 3)]);
}
}
void ddraw_bounds_corners(const vec3 points[8]) {
for( int i = 0; i < 4; ++i ) {
#define ddraw_unit(a,b) ddraw_line(a,add3(a,norm3(sub3(b,a)))), ddraw_line(b,add3(b,norm3(sub3(a,b))))
ddraw_unit(points[i], points[(i + 1) & 3]);
ddraw_unit(points[i], points[4 + i]);
ddraw_unit(points[4 + i], points[4 + ((i + 1) & 3)]);
#undef ddraw_unit
}
}
void ddraw_aabb(vec3 minbb, vec3 maxbb) {
vec3 points[8], bb[2] = { minbb, maxbb };
for (int i = 0; i < 8; ++i) {
points[i].x = bb[(i ^ (i >> 1)) & 1].x;
points[i].y = bb[ (i >> 1) & 1].y;
points[i].z = bb[ (i >> 2) & 1].z;
}
ddraw_bounds/*_corners*/(points);
}
void ddraw_frustum(float projview[16]) {
mat44 clipmatrix = {0}; // clip matrix
invert44(clipmatrix, projview);
// Start with the standard clip volume, then bring it back to world space.
const vec3 planes[8] = {
{-1,-1,-1}, {+1,-1,-1}, {+1,+1,-1}, {-1,+1,-1}, // near plane
{-1,-1,+1}, {+1,-1,+1}, {+1,+1,+1}, {-1,+1,+1}, // far plane
};
vec3 points[8];
float wCoords[8];
// Transform the planes by the inverse clip matrix:
for( int i = 0; i < 8; ++i ) {
// wCoords[i] = matTransformPointXYZW2(&points[i], planes[i], clipmatrix);
vec3 *out = &points[i], in = planes[i]; const float *m = clipmatrix;
out->x = (m[0] * in.x) + (m[4] * in.y) + (m[ 8] * in.z) + m[12]; // in.w (vec4) assumed to be 1
out->y = (m[1] * in.x) + (m[5] * in.y) + (m[ 9] * in.z) + m[13];
out->z = (m[2] * in.x) + (m[6] * in.y) + (m[10] * in.z) + m[14];
wCoords[i] = (m[3] * in.x) + (m[7] * in.y) + (m[11] * in.z) + m[15]; // rw
// bail if any W ended up as zero.
const float epsilon = 1e-9f;
if (absf(wCoords[i]) < epsilon) {
return;
}
}
// Divide by the W component of each:
for( int i = 0; i < 8; ++i ) {
points[i].x /= wCoords[i];
points[i].y /= wCoords[i];
points[i].z /= wCoords[i];
}
// Connect the dots:
ddraw_bounds(points);
}
void ddraw_camera(camera_t *cam) {
vec3 center = cam->position;
vec3 rightdir = cross3(cam->lookdir,cam->updir);
float proj[16]; // reproject perspective matrix with a smaller view distance (100 units)
perspective44(proj, cam->fov, window_width() / ((float)window_height()+!window_height()), 0.01f, 100.f);
ddraw_color_push(YELLOW);
// frustum
mat44 projview; multiply44x2(projview, /*cam->*/proj, cam->view);
ddraw_frustum(projview);
// top circles
ddraw_circle(add3(center,add3(cam->lookdir,cam->updir)), rightdir, 2);
ddraw_circle(add3(center,add3(neg3(cam->lookdir),cam->updir)), rightdir, 2);
// orientation
ddraw_color(RED);
ddraw_arrow(cam->position, add3(cam->position,cam->lookdir));
ddraw_color(GREEN);
ddraw_arrow(cam->position, add3(cam->position,cam->updir));
ddraw_color(BLUE);
ddraw_arrow(cam->position, add3(cam->position,rightdir));
ddraw_color_pop();
}
void ddraw_arrow(vec3 begin, vec3 end) {
vec3 diff = sub3(end, begin);
float len = len3(diff), stick_len = len * 2 / 3;
ddraw_line(begin, end);
ddraw_cone_lowres(add3(begin, scale3(norm3(diff), stick_len)), end, len / 6);
}
void ddraw_plane(vec3 p, vec3 n, float scale) {
// if n is too similar to up vector, use right. else use up vector
vec3 v1 = cross3(n, dot3(n, vec3(0,1,0)) > 0.8f ? vec3(1,0,0) : vec3(0,1,0));
vec3 v2 = cross3(n, v1);
// draw axis
ddraw_line(p, add3(p,n));
ddraw_line(p, add3(p,v1));
ddraw_line(p, add3(p,v2));
// get plane coords
v1 = scale3(v1, scale);
v2 = scale3(v2, scale);
vec3 p1 = add3(add3(p, v1), v2);
vec3 p2 = add3(sub3(p, v1), v2);
vec3 p3 = sub3(sub3(p, v1), v2);
vec3 p4 = sub3(add3(p, v1), v2);
// draw plane
ddraw_line(p1, p2);
ddraw_line(p2, p3);
ddraw_line(p3, p4);
ddraw_line(p4, p1);
}
void ddraw_boid(vec3 position, vec3 dir) {
dir = norm3(dir);
// if n is too similar to up vector, use right. else use up vector
vec3 v1 = cross3(dir, dot3(dir, vec3(0,1,0)) > 0.8f ? vec3(1,0,0) : vec3(0,1,0));
vec3 v2 = cross3(dir, v1);
v1 = cross3(dir, v2);
uint32_t bak = dd_color;
ddraw_color( position.y < 0 ? ORANGE : CYAN );
vec3 front = add3(position, scale3(dir, 1));
vec3 back = add3(position, scale3(dir, -0.25f));
vec3 right = add3(back, scale3(v1, 0.5f));
vec3 left = add3(back, scale3(v1, -0.5f));
ddraw_line( front, left );
ddraw_line( left, position );
ddraw_line( position, right );
ddraw_line( right, front );
dd_color = bak;
}
void ddraw_circle__with_orientation(vec3 center, vec3 dir, float radius) {
// we'll skip 3 segments out of 32. 1.5 per half circle.
int segments = 32, skip = 3, drawn_segments = segments-skip;
// dir = norm3(dir);
vec3 right = cross3(dir, vec3(0,1,0));
vec3 up = cross3(dir, right);
right = cross3(dir, up);
vec3 point, lastPoint;
dir = scale3(dir, radius);
right = scale3(right, radius);
//lastPoint = add3(center, dir);
{
const float radians = (C_PI * 2) * (0+skip/2.f) / segments;
vec3 vs = scale3(right, sinf(radians));
vec3 vc = scale3(dir, cosf(radians));
lastPoint = add3(center, vs);
lastPoint = add3(lastPoint, vc);
}
//ddraw_color(GREEN);
ddraw_line(lastPoint, add3(center, scale3(dir, radius * (radius <= 1 ? 1.25 : 0.65) )));
//ddraw_color(YELLOW);
for (int i = 0; i <= drawn_segments; ++i) {
const float radians = (C_PI * 2) * (i+skip/2.f) / segments;
vec3 vs = scale3(right, sinf(radians));
vec3 vc = scale3(dir, cosf(radians));
point = add3(center, vs);
point = add3(point, vc);
ddraw_line(lastPoint, point);
lastPoint = point;
}
//ddraw_color(RED);
ddraw_line(lastPoint, add3(center, scale3(dir, radius * (radius <= 1 ? 1.25 : 0.65) )));
}
void ddraw_position_dir( vec3 position, vec3 direction, float radius ) {
// idea from http://www.cs.caltech.edu/~keenan/m3drv.pdf and flotilla game UI
uint32_t bak = dd_color;
vec3 ground = vec3(position.x, 0, position.z);
ddraw_color( position.y < 0 ? PINK/*ORANGE*/ : CYAN );
ddraw_point( ground );
ddraw_point( position );
(position.y < 0 ? ddraw_line_dashed : ddraw_line)( ground, position );
vec3 n = norm3(direction), up = vec3(0,1,0);
for( int i = 0; i < 10 && i <= fabs(position.y); ++i ) {
if( i < 2 && len3(direction) )
ddraw_circle__with_orientation(ground, n, radius);
else
ddraw_circle(ground, up, radius);
radius *= 0.9f;
}
dd_color = bak;
}
void ddraw_position( vec3 position, float radius ) {
ddraw_position_dir(position, vec3(0,0,0), radius);
}
void ddraw_init() {
do_once {
for( int i = 0; i < 2; ++i )
for( int j = 0; j < 3; ++j ) map_init(dd_lists[i][j], less_64, hash_64);
dd_program = shader(dd_vs,dd_fs,"att_position","fragcolor", NULL);
dd_u_color = glGetUniformLocation(dd_program, "u_color");
ddraw_flush(); // alloc vao & vbo, also resets color
}
}
void ddraw_demo() {
ddraw_color_push(YELLOW);
// freeze current frame for (frustum) camera forensics
static mat44 projview_copy;
do_once {
multiply44x2(projview_copy, camera_get_active()->proj, camera_get_active()->view);
}
ddraw_frustum(projview_copy);
//ddraw_grid();
vec3 origin = {0,0,0};
ddraw_color(ORANGE);
ddraw_arrow(origin, vec3(-1,1,1));
ddraw_color(YELLOW);
ddraw_text(vec3(-1,1,1), 0.008f, va("hello 1%s2!", "world"));
const char abc[] = " !\"#$%&'()*+,-./\n"
"0123456789:;<=>?@\n"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`\n"
"abcdefghijklmnopqrstuvwxyz{|}~";
ddraw_text(vec3(2,2,2), 0.008f, abc);
for( int i = -5; i <= 5; ++i ) {
ddraw_pyramid(vec3(i*2,0,3), 0, i+5+2); ddraw_text(vec3(i*2,0,3), 0.008f, va("%d/1", i));
ddraw_pyramid(vec3(i*2,0,6), -2, i+5+2); ddraw_text(vec3(i*2,0,6), 0.008f, va("%d/2", i));
ddraw_pyramid(vec3(i*2,0,9), +2, i+5+2); ddraw_text(vec3(i*2,0,9), 0.008f, va("%d/3", i));
}
#if 1 // @fixme: add positions to these
// ddraw_triangle(origin, 1);
ddraw_square(origin, 1);
ddraw_pentagon(origin, 1);
ddraw_hexagon(origin, 1);
ddraw_cube(origin, 1);
ddraw_pyramid(origin, 2, 3);
ddraw_pyramid(origin, 2, 16);
ddraw_cone(origin, add3(origin, vec3(0,1,0)), 0.5f);
ddraw_arrow(origin, vec3(0,1,0));
ddraw_bone(vec3(0,0,0), vec3(3,3,3));
ddraw_aabb(vec3(0,0,0), vec3(1,1,1));
#endif
ddraw_plane(vec3(0,10,0), vec3(0,1,0), 10);
//ddraw_boid(vec3(15,0,15), vec3(-15,0,-15) );
ddraw_position(vec3(10,10,10), 1);
ddraw_position(vec3(-10,-10,10), 1);
ddraw_point(vec3(-2,0,-2));
ddraw_color(PURPLE);
ddraw_sphere(vec3(-3,0,-3),1);
ddraw_color_pop();
}
static int gizmo__mode;
static int gizmo__active;
static int gizmo__hover;
bool gizmo_active() {
return gizmo__active;
}
bool gizmo_hover() {
return gizmo__hover;
}
int gizmo(vec3 *pos, vec3 *rot, vec3 *sca) {
#if 0
ddraw_flush();
mat44 copy; copy44(copy, camera_get_active()->view);
if( 1 ) {
float *mv = camera_get_active()->view;
float d = sqrt(mv[4*0+0] * mv[4*0+0] + mv[4*1+1] * mv[4*1+1] + mv[4*2+2] * mv[4*2+2]);
if(4) mv[4*0+0] = d, mv[4*0+1] = 0, mv[4*0+2] = 0;
if(2) mv[4*1+0] = 0, mv[4*1+1] = d, mv[4*1+2] = 0;
if(1) mv[4*2+0] = 0, mv[4*2+1] = 0, mv[4*2+2] = d;
}
#endif
ddraw_color_push(dd_color);
ddraw_ontop_push(1);
int enabled = !ui_active() && !ui_hover();
vec3 mouse = enabled ? vec3(input(MOUSE_X),input(MOUSE_Y),input_down(MOUSE_L)) : vec3(0,0,0); // x,y,l
vec3 from = camera_get_active()->position;
vec3 to = editor_pick(mouse.x, mouse.y);
ray r = ray(from, to);
static vec3 src3, hit3, off3; static vec2 src2;
#define on_gizmo_dragged(X,Y,Z,COLOR,DRAWCMD, ...) do { \
vec3 dir = vec3(X,Y,Z); \
line axis = {add3(*pos, scale3(dir,100)), add3(*pos, scale3(dir,-100))}; \
plane ground = { vec3(0,0,0), vec3(Y?1:0,Y?0:1,0) }; \
vec3 unit = vec3(X+(1.0-X)*0.3,Y+(1.0-Y)*0.3,Z+(1.0-Z)*0.3); \
aabb arrow = { sub3(*pos,unit), add3(*pos,unit) }; \
hit *hit_arrow = ray_hit_aabb(r, arrow), *hit_ground = ray_hit_plane(r, ground); \
ddraw_color( hit_arrow || gizmo__active == (X*4+Y*2+Z) ? gizmo__hover = 1, YELLOW : COLOR ); \
DRAWCMD; \
if( !gizmo__active && hit_arrow && mouse.z ) src2 = vec2(mouse.x,mouse.y), src3 = *pos, hit3 = hit_ground->p, off3 = mul3(sub3(src3,hit3),vec3(X,Y,Z)), gizmo__active = X*4+Y*2+Z; \
if( (gizmo__active && gizmo__active==(X*4+Y*2+Z)) || (!gizmo__active && hit_arrow) ) { ddraw_color( COLOR ); ( 1 ? ddraw_line : ddraw_line_dashed)(axis.a, axis.b); } \
if( gizmo__active == (X*4+Y*2+Z) && hit_ground ) {{ __VA_ARGS__ }; modified = 1; gizmo__active *= !!input(MOUSE_L); } \
} while(0)
#define gizmo_translate(X,Y,Z,COLOR) \
on_gizmo_dragged(X,Y,Z,COLOR, ddraw_arrow(*pos,add3(*pos,vec3(X,Y,Z))), { \
*pos = add3(line_closest_point(axis, hit_ground->p), off3); \
} )
#define gizmo_scale(X,Y,Z,COLOR) \
on_gizmo_dragged(X,Y,Z,COLOR, (ddraw_line(*pos,add3(*pos,vec3(X,Y,Z))),ddraw_sphere(add3(*pos,vec3(X-0.1*X,Y-0.1*Y,Z-0.1*Z)),0.1)), { /*ddraw_aabb(arrow.min,arrow.max)*/ \
int component = (X*1+Y*2+Z*3)-1; \
float mag = len2(sub2(vec2(mouse.x, mouse.y), src2)); \
float magx = (mouse.x - src2.x) * (mouse.x - src2.x); \
float magy = (mouse.y - src2.y) * (mouse.y - src2.y); \
float sgn = (magx > magy ? mouse.x > src2.x : mouse.y > src2.y) ? 1 : -1; \
sca->v3[component] -= sgn * mag * 0.01; \
src2 = vec2(mouse.x, mouse.y); \
} )
#define gizmo_rotate(X,Y,Z,COLOR) do { \
vec3 dir = vec3(X,Y,Z); \
line axis = {add3(*pos, scale3(dir,100)), add3(*pos, scale3(dir,-100))}; \
plane ground = { vec3(0,0,0), vec3(0,1,0) }; \
vec3 unit = vec3(X+(1.0-X)*0.3,Y+(1.0-Y)*0.3,Z+(1.0-Z)*0.3); \
aabb arrow = { sub3(*pos,unit), add3(*pos,unit) }; \
hit *hit_arrow = ray_hit_aabb(r, arrow), *hit_ground = ray_hit_plane(r, ground); \
int hover = (hit_arrow ? (X*4+Y*2+Z) : 0); \
if( gizmo__active == (X*4+Y*2+Z) ) { ddraw_color(gizmo__active ? gizmo__hover = 1, YELLOW : WHITE); ddraw_circle(*pos, vec3(X,Y,Z), 1); } \
else if( !gizmo__active && hover == (X*4+Y*2+Z) ) { gizmo__hover = 1; ddraw_color(COLOR); ddraw_circle(*pos, vec3(X,Y,Z), 1); } \
else if( !gizmo__active ) { ddraw_color(WHITE); ddraw_circle(*pos, vec3(X,Y,Z), 1); } \
if( !gizmo__active && hit_arrow && mouse.z ) src2 = vec2(mouse.x,mouse.y), gizmo__active = hover; \
if( (!gizmo__active && hover == (X*4+Y*2+Z)) || gizmo__active == (X*4+Y*2+Z) ) { gizmo__hover = 1; ddraw_color( COLOR ); ( 1 ? ddraw_line_thin : ddraw_line_dashed)(axis.a, axis.b); } \
if( gizmo__active && gizmo__active == (X*4+Y*2+Z) && hit_ground && enabled ) { \
int component = (Y*1+X*2+Z*3)-1; /*pitch,yaw,roll*/ \
float mag = len2(sub2(vec2(mouse.x, mouse.y), src2)); \
float magx = (mouse.x - src2.x) * (mouse.x - src2.x); \
float magy = (mouse.y - src2.y) * (mouse.y - src2.y); \
float sgn = (magx > magy ? mouse.x > src2.x : mouse.y > src2.y) ? 1 : -1; \
rot->v3[component] += sgn * mag; \
/*rot->v3[component] = clampf(rot->v3[component], -360, +360);*/ \
src2 = vec2(mouse.x, mouse.y); \
\
} \
gizmo__active *= enabled && !!input(MOUSE_L); \
} while(0)
gizmo__hover = 0;
int modified = 0;
if(enabled && input_down(KEY_SPACE)) gizmo__active = 0, gizmo__mode = (gizmo__mode + 1) % 3;
if(gizmo__mode == 0) gizmo_translate(1,0,0, RED);
if(gizmo__mode == 0) gizmo_translate(0,1,0, GREEN);
if(gizmo__mode == 0) gizmo_translate(0,0,1, BLUE);
if(gizmo__mode == 1) gizmo_scale(1,0,0, RED);
if(gizmo__mode == 1) gizmo_scale(0,1,0, GREEN);
if(gizmo__mode == 1) gizmo_scale(0,0,1, BLUE);
if(gizmo__mode == 2) gizmo_rotate(1,0,0, RED);
if(gizmo__mode == 2) gizmo_rotate(0,1,0, GREEN);
if(gizmo__mode == 2) gizmo_rotate(0,0,1, BLUE);
#if 0
ddraw_flush();
copy44(camera_get_active()->view, copy);
#endif
ddraw_ontop_pop();
ddraw_color_pop();
return modified;
}
#line 0
#line 1 "v4k_scene.c"
//
// @todo: remove explicit GL code from here
static camera_t *last_camera;
camera_t camera() {
camera_t *old = last_camera;
static camera_t cam = {0};
do_once {
cam.speed = 0.50f;
cam.position = vec3(10,10,10);
cam.updir = vec3(0,1,0);
cam.fov = 45;
cam.orthographic = false;
cam.distance = 3; // len3(cam.position);
cam.damping = false;
cam.move_friction = 0.09f;
cam.move_damping = 0.96f;
cam.look_friction = 0.30f;
cam.look_damping = 0.96f;
cam.last_look = vec3(0,0,0);
cam.last_move = vec3(0,0,0);
// update proj & view
camera_lookat(&cam,vec3(-5,0,-5));
// @todo: remove this hack that is used to consolidate dampings
if( 1 ) {
vec3 zero = {0};
for( int i = 0; i < 1000; ++i ) {
camera_moveby(&cam, zero);
camera_fps(&cam,0,0);
}
}
}
last_camera = old;
*camera_get_active() = cam;
return cam;
}
camera_t *camera_get_active() {
static camera_t defaults = {0};
if( !last_camera ) {
identity44(defaults.view);
identity44(defaults.proj);
last_camera = &defaults;
}
return last_camera;
}
void camera_moveby(camera_t *cam, vec3 inc) {
// calculate camera damping
if( cam->damping ) {
float fr = cam->move_friction; fr *= fr; fr *= fr; fr *= fr;
float sm = clampf(cam->move_damping, 0, 0.999f); sm *= sm; sm *= sm;
cam->last_move = scale3(cam->last_move, 1 - fr);
inc.x = cam->last_move.x = inc.x * (1 - sm) + cam->last_move.x * sm;
inc.y = cam->last_move.y = inc.y * (1 - sm) + cam->last_move.y * sm;
inc.z = cam->last_move.z = inc.z * (1 - sm) + cam->last_move.z * sm;
}
vec3 dir = norm3(cross3(cam->lookdir, cam->updir));
cam->position = add3(cam->position, scale3(dir, inc.x)); // right
cam->position = add3(cam->position, scale3(cam->updir, inc.y)); // up
cam->position = add3(cam->position, scale3(cam->lookdir, inc.z)); // front
camera_fps(cam, 0, 0);
}
void camera_teleport(camera_t *cam, vec3 pos) {
bool damping = cam->damping;
cam->damping = 0;
cam->last_move = vec3(0,0,0);
cam->position = pos;
camera_fps(cam, 0, 0);
cam->damping = damping;
}
void camera_lookat(camera_t *cam, vec3 target) {
// invert expression that cam->lookdir = norm3(vec3(cos(y) * cos(p), sin(p), sin(y) * cos(p)));
// look.y = sin p > y = asin(p)
// look.x = cos y * cos p; -> cos p = look.x / cos y \ look.x / cos y = look.z / sin y
// look.z = sin y * cos p; -> cos p = look.z / sin y /
// so, sin y / cos y = look x / look z > tan y = look x / look z > y = atan(look x / look z)
vec3 look = norm3(sub3(target, cam->position));
const float rad2deg = 1 / 0.0174532f;
float pitch = asin(look.y) * rad2deg;
float yaw = atan2(look.z, look.x) * rad2deg; // coords swapped. it was (look.x, look.z) before. @todo: testme
camera_fps(cam, yaw-cam->yaw, pitch-cam->pitch);
}
void camera_enable(camera_t *cam) {
// camera_t *other = camera_get_active(); // init default camera in case there is none
last_camera = cam;
// trigger a dummy update -> update matrices
camera_fps(cam, 0, 0);
}
void camera_fov(camera_t *cam, float fov) {
last_camera = cam;
float aspect = window_width() / ((float)window_height()+!window_height());
cam->fov = fov;
if( cam->orthographic ) {
ortho44(cam->proj, -cam->fov * aspect, cam->fov * aspect, -cam->fov, cam->fov, 0.01f, 2000);
// [ref] https://commons.wikimedia.org/wiki/File:Isometric_dimetric_camera_views.png
// float pitch = cam->dimetric ? 30.000f : 35.264f; // dimetric or isometric
// cam->pitch = -pitch; // quickly reorient towards origin
} else {
perspective44(cam->proj, cam->fov, aspect, 0.01f, 2000.f);
}
}
void camera_fps2(camera_t *cam, float yaw, float pitch, float roll) {
last_camera = cam;
// camera damping
if( cam->damping ) {
float fr = cam->look_friction; fr *= fr; fr *= fr; fr *= fr;
float sm = clampf(cam->look_damping, 0, 0.999f); sm *= sm; sm *= sm;
cam->last_look = scale3(cam->last_look, 1 - fr);
yaw = cam->last_look.y = yaw * (1 - sm) + cam->last_look.y * sm;
pitch = cam->last_look.x = pitch * (1 - sm) + cam->last_look.x * sm;
roll = cam->last_look.z = roll * (1 - sm) + cam->last_look.z * sm;
}
cam->yaw += yaw;
cam->yaw = fmod(cam->yaw, 360);
cam->pitch += pitch;
cam->pitch = cam->pitch > 89 ? 89 : cam->pitch < -89 ? -89 : cam->pitch;
cam->roll += roll;
cam->roll += fmod(cam->roll, 360);
const float deg2rad = 0.0174532f, y = cam->yaw * deg2rad, p = cam->pitch * deg2rad, r = cam->roll * deg2rad;
cam->lookdir = norm3(vec3(cos(y) * cos(p), sin(p), sin(y) * cos(p)));
vec3 up = vec3(0,1,0);
// calculate updir
{
float cosfa = cosf(r);
float sinfa = sinf(r);
vec3 right = cross3(cam->lookdir, up);
float th = dot3(cam->lookdir, up);
cam->updir.x = up.x * cosfa + right.x * sinfa + cam->lookdir.x * th * (1.0f - cosfa);
cam->updir.y = up.y * cosfa + right.y * sinfa + cam->lookdir.y * th * (1.0f - cosfa);
cam->updir.z = up.z * cosfa + right.z * sinfa + cam->lookdir.z * th * (1.0f - cosfa);
}
lookat44(cam->view, cam->position, add3(cam->position, cam->lookdir), cam->updir); // eye,center,up
camera_fov(cam, cam->fov);
}
void camera_fps(camera_t *cam, float yaw, float pitch) {
camera_fps2(cam, yaw, pitch, 0.0f);
}
void camera_orbit( camera_t *cam, float yaw, float pitch, float inc_distance ) {
last_camera = cam;
// update dummy state
camera_fps(cam, 0,0);
// @todo: add damping
vec3 _mouse = vec3(yaw, pitch, inc_distance);
cam->yaw += _mouse.x;
cam->pitch += _mouse.y;
cam->distance += _mouse.z;
// look: limit pitch angle [-89..89]
cam->pitch = cam->pitch > 89 ? 89 : cam->pitch < -89 ? -89 : cam->pitch;
// compute view matrix
float x = rad(cam->yaw), y = rad(-cam->pitch), cx = cosf(x), cy = cosf(y), sx = sinf(x), sy = sinf(y);
lookat44(cam->view, vec3( cx*cy*cam->distance, sy*cam->distance, sx*cy*cam->distance ), vec3(0,0,0), vec3(0,1,0) );
// save for next call
cam->last_move.x = _mouse.x;
cam->last_move.y = _mouse.y;
}
int ui_camera( camera_t *cam ) {
int changed = 0;
changed |= ui_bool("Orthographic", &cam->orthographic);
changed |= ui_bool("Damping", &cam->damping);
if( !cam->damping ) ui_disable();
changed |= ui_slider2("Move friction", &cam->move_friction, va("%5.3f", cam->move_friction));
changed |= ui_slider2("Move damping", &cam->move_damping, va("%5.3f", cam->move_damping));
changed |= ui_slider2("View friction", &cam->look_friction, va("%5.3f", cam->look_friction));
changed |= ui_slider2("View damping", &cam->look_damping, va("%5.3f", cam->look_damping));
if( !cam->damping ) ui_enable();
ui_separator();
changed |= ui_float("Speed", &cam->speed);
changed |= ui_float3("Position", cam->position.v3);
changed |= ui_float3("LookDir", cam->lookdir.v3);
changed |= ui_float3("UpDir", cam->updir.v3);
ui_disable();
changed |= ui_mat44("View matrix", cam->view);
ui_enable();
ui_separator();
changed |= ui_float("FOV (degrees)", &cam->fov);
changed |= ui_float("Orbit distance", &cam->distance);
ui_disable();
changed |= ui_mat44("Projection matrix", cam->proj);
ui_enable();
return changed;
}
// -----------------------------------------------------------------------------
static
void object_update(object_t *obj) {
quat p = eulerq(vec3(obj->pivot.x,obj->pivot.y,obj->pivot.z));
quat e = eulerq(vec3(obj->euler.x,obj->euler.y,obj->euler.z));
compose44(obj->transform, obj->pos, mulq(e, p), obj->sca);
}
object_t object() {
object_t obj = {0};
identity44(obj.transform);
//obj.rot = idq();
obj.sca = vec3(1,1,1);
//obj.bounds = aabb(vec3(0,0,0),vec3(1,1,1)); // defaults to small 1-unit cube
object_rotate(&obj, vec3(0,0,0));
//array_init(obj.textures);
return obj;
}
void object_pivot(object_t *obj, vec3 euler) {
obj->pivot = euler;
object_update(obj);
}
void object_rotate(object_t *obj, vec3 euler) {
quat p = eulerq(vec3(obj->pivot.x,obj->pivot.y,obj->pivot.z));
quat e = eulerq(vec3(euler.x,euler.y,euler.z));
obj->rot = mulq(p,e);
obj->euler = euler;
object_update(obj);
}
void object_teleport(object_t *obj, vec3 pos) {
obj->pos = pos;
object_update(obj);
}
void object_move(object_t *obj, vec3 inc) {
obj->pos = add3(obj->pos, inc);
object_update(obj);
}
void object_scale(object_t *obj, vec3 sca) {
obj->sca = vec3(sca.x, sca.y, sca.z);
object_update(obj);
}
vec3 object_position(object_t *obj) {
return vec3(obj->transform[12], obj->transform[13], obj->transform[14]);
}
void object_model(object_t *obj, model_t model) {
obj->model = model;
}
void object_anim(object_t *obj, anim_t anim, float speed) {
obj->anim = anim;
obj->anim_speed = speed;
}
void object_push_diffuse(object_t *obj, texture_t tex) {
2024-08-24 09:49:16 +00:00
array_push(obj->textures, tex);
2024-08-19 07:54:01 +00:00
}
void object_pop_diffuse(object_t *obj) {
array_pop(obj->textures);
}
void object_diffuse(object_t *obj, texture_t tex) {
array_clear(obj->textures);
object_push_diffuse(obj, tex);
}
void object_billboard(object_t *obj, unsigned mode) {
obj->billboard = mode;
}
// -----------------------------------------------------------------------------
light_t light() {
light_t l = {0};
l.diffuse = vec3(1,1,1);
l.dir = vec3(1,-1,-1);
l.falloff.constant = 1.0f;
l.falloff.linear = 0.09f;
l.falloff.quadratic = 0.0032f;
l.specularPower = 32.f;
l.innerCone = 0.9f; // 25 deg
l.outerCone = 0.85f; // 31 deg
return l;
}
void light_type(light_t* l, char type) {
l->cached = 0;
l->type = type;
}
void light_diffuse(light_t* l, vec3 color) {
l->cached = 0;
l->diffuse = color;
}
void light_specular(light_t* l, vec3 color) {
l->cached = 0;
l->specular = color;
}
void light_ambient(light_t* l, vec3 color) {
l->cached = 0;
l->ambient = color;
}
void light_teleport(light_t* l, vec3 pos) {
l->cached = 0;
l->pos = pos;
}
void light_dir(light_t* l, vec3 dir) {
l->cached = 0;
l->dir = dir;
}
void light_power(light_t* l, float power) {
l->cached = 0;
l->specularPower = power;
}
void light_falloff(light_t* l, float constant, float linear, float quadratic) {
l->cached = 0;
l->falloff.constant = constant;
l->falloff.linear = linear;
l->falloff.quadratic = quadratic;
}
2024-08-23 15:57:19 +00:00
void light_radius(light_t* l, float radius) {
l->cached = 0;
l->radius = radius;
}
2024-08-19 07:54:01 +00:00
void light_cone(light_t* l, float innerCone, float outerCone) {
l->cached = 0;
l->innerCone = acos(innerCone);
l->outerCone = acos(outerCone);
}
void light_update(unsigned num_lights, light_t *lv) {
shader_int("u_num_lights", num_lights);
for (unsigned i=0; i < num_lights; ++i) {
lv[i].cached = 1;
shader_int(va("u_lights[%d].type", i), lv[i].type);
shader_vec3(va("u_lights[%d].pos", i), lv[i].pos);
shader_vec3(va("u_lights[%d].dir", i), lv[i].dir);
shader_vec3(va("u_lights[%d].diffuse", i), lv[i].diffuse);
shader_vec3(va("u_lights[%d].specular", i), lv[i].specular);
shader_vec3(va("u_lights[%d].ambient", i), lv[i].ambient);
shader_float(va("u_lights[%d].power", i), lv[i].specularPower);
2024-08-23 15:57:19 +00:00
shader_float(va("u_lights[%d].radius", i), lv[i].radius);
2024-08-19 07:54:01 +00:00
shader_float(va("u_lights[%d].constant", i), lv[i].falloff.constant);
shader_float(va("u_lights[%d].linear", i), lv[i].falloff.linear);
shader_float(va("u_lights[%d].quadratic", i), lv[i].falloff.quadratic);
shader_float(va("u_lights[%d].innerCone", i), lv[i].innerCone);
shader_float(va("u_lights[%d].outerCone", i), lv[i].outerCone);
}
}
// -----------------------------------------------------------------------------
array(scene_t*) scenes;
scene_t* last_scene;
static void scene_init() {
#ifndef __EMSCRIPTEN__ // @fixme ems -> shaders
do_once scene_push(); // create an empty scene by default
#endif
}
scene_t* scene_get_active() {
return last_scene;
}
scene_t* scene_push() {
scene_t *s = REALLOC(0, sizeof(scene_t)), clear = {0}; *s = clear;
s->skybox = skybox(NULL, 0);
array_push(scenes, s);
last_scene = s;
return s;
}
void scene_pop() {
// @fixme: fix leaks, scene_cleanup();
scene_t clear = {0};
*last_scene = clear;
array_pop(scenes);
last_scene = *array_back(scenes);
}
int scene_merge(const char *source) {
int count = 0;
if( json_push(source) ) {
for(int i = 0, e = json_count("/") - 1; i <= e ; ++i) {
const char *skybox_folder = json_string("/[%d]/skybox",i);
if( skybox_folder[0] ) {
PRINTF("Loading skybox folder: %s\n", skybox_folder);
last_scene->skybox = skybox( skybox_folder, 0 );
continue;
}
const char *mesh_file = json_string("/[%d]/mesh",i);
const char *texture_file = json_string("/[%d]/texture",i);
const char *animation_file = json_string("/[%d]/animation",i);
vec3 position = vec3(json_float("/[%d]/position[0]",i),json_float("/[%d]/position[1]",i),json_float("/[%d]/position[2]",i));
vec3 rotation = vec3(json_float("/[%d]/rotation[0]",i),json_float("/[%d]/rotation[1]",i),json_float("/[%d]/rotation[2]",i));
vec3 scale = scale3(vec3(1,1,1), json_float("/[%d]/scale",i));
bool opt_swap_zy = json_int("/[%d]/swapzy",i);
bool opt_flip_uv = json_int("/[%d]/flipuv",i);
PRINTF("Scene %d/%d Loading: %s\n", i, e, mesh_file);
PRINTF("Scene %d/%d Texture: %s\n", i, e, texture_file);
PRINTF("Scene %d/%d Animation: %s\n", i, e, animation_file);
PRINTF("Scene %d/%d Position: (%f,%f,%f)\n", i, e, position.x, position.y, position.z);
PRINTF("Scene %d/%d Rotation: (%f,%f,%f)\n", i, e, rotation.x, rotation.y, rotation.z);
PRINTF("Scene %d/%d Scale: (%f,%f,%f)\n", i, e, scale.x, scale.y, scale.z);
PRINTF("Scene %d/%d Swap_ZY: %d\n", i, e, opt_swap_zy );
PRINTF("Scene %d/%d Flip_UV: %d\n", i, e, opt_flip_uv );
model_t m = model_from_mem(vfs_read(mesh_file), vfs_size(mesh_file), 0/*opt_swap_zy*/);
//char *a = archive_read(animation_file);
object_t *o = scene_spawn();
object_model(o, m);
if( texture_file[0] ) object_diffuse(o, texture_from_mem(vfs_read(texture_file), vfs_size(texture_file), (opt_flip_uv ? IMAGE_FLIP : 0)) );
object_scale(o, scale);
object_teleport(o, position);
object_pivot(o, rotation); // object_rotate(o, rotation);
//object_name(x), scene_find(name)
// o->bounds = aabb(mul3(m.bounds.min,o->sca),mul3(m.bounds.max,o->sca));
// PRINTF("aabb={%f,%f,%f},{%f,%f,%f}\n", o->bounds.min.x, o->bounds.min.y, o->bounds.min.z, o->bounds.max.x, o->bounds.max.y, o->bounds.max.z);
/*
if(opt_swap_zy) {
// swap zy bounds
vec3 min = o->bounds.min, max = o->bounds.max;
o->bounds = aabb( vec3(min.x,min.z,min.y), vec3(max.x,max.z,max.y) );
}
*/
count++;
}
json_pop();
}
// PRINTF("scene loading took %5.2fs\n", secs);
PRINTF("%d objects merged into scene\n", count);
return count;
}
object_t* scene_spawn() {
object_t obj = object();
array_push(last_scene->objs, obj);
return array_back(last_scene->objs);
}
unsigned scene_count() {
return array_count(last_scene->objs);
}
object_t* scene_index(unsigned obj_index) {
unsigned obj_count = scene_count();
ASSERT(obj_index < obj_count, "Object index %d exceeds number (%d) of spawned objects", obj_index, obj_count);
return &last_scene->objs[obj_index];
}
light_t* scene_spawn_light() {
light_t l = light();
array_push(last_scene->lights, l);
return array_back(last_scene->lights);
}
unsigned scene_count_light() {
return array_count(last_scene->lights);
}
light_t* scene_index_light(unsigned light_index) {
unsigned light_count = scene_count_light();
ASSERT(light_index < light_count, "Light index %d exceeds number (%d) of spawned lights", light_index, light_count);
return &last_scene->lights[light_index];
}
static
int scene_obj_distance_compare(const void *a, const void *b) {
const object_t *da = a, *db = b;
return da->distance < db->distance ? 1 : da->distance > db->distance ? -1 : 0;
}
2024-08-19 07:54:01 +00:00
void scene_render(int flags) {
camera_t *cam = camera_get_active();
2024-08-24 11:33:05 +00:00
#if 1
mat44 projview; multiply44x2(projview, cam->proj, cam->view);
frustum frustum_state = frustum_build(projview);
2024-08-24 11:33:05 +00:00
#else
static frustum frustum_state;
do_once {
mat44 projview; multiply44x2(projview, cam->proj, cam->view);
frustum_state = frustum_build(projview);
}
if (input_down(KEY_T)) {
mat44 projview; multiply44x2(projview, cam->proj, cam->view);
frustum_state = frustum_build(projview);
}
#endif
2024-08-19 07:54:01 +00:00
if(flags & SCENE_BACKGROUND) {
if(last_scene->skybox.program) {
skybox_push_state(&last_scene->skybox, cam->proj, cam->view);
mesh_render(&last_scene->skybox.geometry);
skybox_pop_state();
}
ddraw_flush();
}
if( flags & SCENE_FOREGROUND ) {
bool do_relighting = 0;
for (unsigned j = 0; j < array_count(last_scene->lights); ++j) {
if (!last_scene->lights[j].cached) {
do_relighting = 1;
break;
}
}
for(unsigned j = 0, obj_count = scene_count(); j < obj_count; ++j ) {
object_t *obj = scene_index(j);
model_t *model = &obj->model;
anim_t *anim = &obj->anim;
mat44 *views = (mat44*)(&cam->view);
obj->skip_draw = !obj->disable_frustum_check && !frustum_test_aabb(frustum_state, model_aabb(*model, obj->transform));
2024-08-19 07:54:01 +00:00
if (obj->skip_draw) continue;
2024-08-19 07:54:01 +00:00
2024-08-24 09:49:16 +00:00
int do_retexturing = model->iqm && model->shading != SHADING_PBR && array_count(obj->textures) > 0;
2024-08-19 07:54:01 +00:00
if( do_retexturing ) {
for(int i = 0; i < model->iqm->nummeshes; ++i) {
array_push(obj->old_texture_ids, model->iqm->textures[i]);
2024-08-24 09:49:16 +00:00
model->iqm->textures[i] = (*array_back(obj->textures)).id;
if (model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture) {
array_push(obj->old_textures, *model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture);
2024-08-24 09:49:16 +00:00
*model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture = (*array_back(obj->textures));
}
2024-08-19 07:54:01 +00:00
}
}
if ( do_relighting || !obj->light_cached ) {
obj->light_cached = 1;
shader_bind(model->program);
light_update(array_count(last_scene->lights), last_scene->lights);
}
if ( flags&SCENE_UPDATE_SH_COEF ) {
shader_bind(model->program);
skybox_sh_shader(&last_scene->skybox);
2024-08-19 07:54:01 +00:00
}
2024-08-19 07:54:01 +00:00
model_skybox(model, last_scene->skybox, 0);
if (!obj->disable_frustum_check)
model_set_frustum(model, frustum_state);
else
model_clear_frustum(model);
2024-08-19 07:54:01 +00:00
if (anim) {
float delta = window_delta() * obj->anim_speed;
model->curframe = model_animate_clip(*model, model->curframe + delta, anim->from, anim->to, anim->flags & ANIM_LOOP );
}
model->billboard = obj->billboard;
for (int p = 0; p < RENDER_PASS_OVERRIDES_BEGIN; ++p) {
model->rs[p].cull_face_enabled = flags&SCENE_CULLFACE ? 1 : 0;
model->rs[p].polygon_mode_draw = flags&SCENE_WIREFRAME ? GL_LINE : GL_FILL;
}
}
/* Collect all transparency enabled models and sort them by distance */
static array(object_t*) transparent_objects = 0;
for(unsigned j = 0, obj_count = scene_count(); j < obj_count; ++j ) {
object_t *obj = scene_index(j);
model_t *model = &obj->model;
if (obj->skip_draw) continue;
if (model_has_transparency(*model)) {
obj->distance = len3sq(sub3(cam->position, transform344(model->pivot, add3(obj->pos, model->meshcenters[0]))));
array_push(transparent_objects, obj);
}
}
2024-08-19 07:54:01 +00:00
array_sort(transparent_objects, scene_obj_distance_compare);
/* Opaque pass */
for(unsigned j = 0, obj_count = scene_count(); j < obj_count; ++j ) {
object_t *obj = scene_index(j);
model_t *model = &obj->model;
if (obj->skip_draw) continue;
model_render_pass(*model, cam->proj, cam->view, obj->transform, model->program, RENDER_PASS_OPAQUE);
}
/* Transparency pass */
for (unsigned j = 0; j < array_count(transparent_objects); ++j) {
object_t *obj = transparent_objects[j];
model_t *model = &obj->model;
if (obj->skip_draw) continue;
model_render_pass(*model, cam->proj, cam->view, obj->transform, model->program, RENDER_PASS_TRANSPARENT);
}
array_resize(transparent_objects, 0);
for(unsigned j = 0, obj_count = scene_count(); j < obj_count; ++j ) {
object_t *obj = scene_index(j);
model_t *model = &obj->model;
if (obj->skip_draw) continue;
int do_retexturing = model->iqm && model->shading != SHADING_PBR && array_count(obj->textures) > 0;
2024-08-19 07:54:01 +00:00
if( do_retexturing ) {
for(int i = 0; i < model->iqm->nummeshes; ++i) {
model->iqm->textures[i] = obj->old_texture_ids[i];
if (i < array_count(obj->old_textures)) {
2024-08-24 09:49:16 +00:00
if (model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture)
*model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture = obj->old_textures[i];
2024-08-24 09:49:16 +00:00
}
2024-08-19 07:54:01 +00:00
}
array_resize(obj->old_texture_ids, 0);
array_resize(obj->old_textures, 0);
2024-08-19 07:54:01 +00:00
}
}
glBindVertexArray(0);
}
}
#line 0
#line 1 "v4k_sprite.c"
// ----------------------------------------------------------------------------
// sprites
typedef struct sprite_static_t {
float px, py, pz; // origin x, y, depth
float ox, oy, cos, sin; // offset x, offset y, cos/sin of rotation degree
float sx, sy; // scale x,y
float cellw, cellh; // dimensions of any cell in spritesheet
union {
struct {
int frame, ncx, ncy; // frame in a (num cellx, num celly) spritesheet
};
struct {
float x, y, w, h; // normalized[0..1] within texture bounds
};
};
uint32_t rgba, flags; // vertex color and flags
} sprite_static_t;
// sprite batching
typedef struct batch_t { array(sprite_static_t) sprites; mesh_t mesh; int dirty; } batch_t;
typedef map(int, batch_t) batch_group_t; // mapkey is anything that forces a flush. texture_id for now, might be texture_id+program_id soon
// sprite stream
typedef struct sprite_vertex { vec3 pos; vec2 uv; uint32_t rgba; } sprite_vertex;
typedef struct sprite_index { GLuint triangle[3]; } sprite_index;
#define sprite_vertex(...) C_CAST(sprite_vertex, __VA_ARGS__)
#define sprite_index(...) C_CAST(sprite_index, __VA_ARGS__)
// sprite impl
static int sprite_count = 0;
static int sprite_program = -1;
static array(sprite_index) sprite_indices = 0;
static array(sprite_vertex) sprite_vertices = 0;
// center_wh << 2 | additive << 1 | projected << 0
static batch_group_t sprite_group[8] = {0};
// rect(x,y,w,h) is [0..1] normalized, pos(xyz,z-index), scale_offset(sx,sy,offx,offy), rotation (degrees), color (rgba)
void sprite_rect( texture_t t, vec4 rect, vec4 pos, vec4 scale_offset, float tilt_deg, unsigned tint_rgba, unsigned flags) {
float zindex = pos.w;
float scalex = scale_offset.x;
float scaley = scale_offset.y;
float offsetx = scale_offset.z;
float offsety = scale_offset.w;
// do not queue if either scales or alpha are zero
if( 0 == (scalex * scaley * ((tint_rgba>>24) & 255)) ) return;
ASSERT( (flags & SPRITE_CENTERED) == 0 );
if( flags & SPRITE_PROJECTED ) {
tilt_deg += 180, scalex = -scalex; // flip texture Y on mvp3d (same than turn 180Âş then flip X)
}
sprite_static_t s = {0};
s.px = pos.x, s.py = pos.y, s.pz = pos.z - zindex;
s.sx = scalex, s.sy = scaley;
s.x = rect.x, s.y = rect.y, s.w = rect.z, s.h = rect.w;
s.cellw = s.w * s.sx * t.w, s.cellh = s.h * s.sy * t.h;
s.rgba = tint_rgba;
s.flags = flags;
#if 0
s.ox = 0/*ox*/ * s.sx;
s.oy = 0/*oy*/ * s.sy;
#else
s.ox += offsetx * scalex;
s.oy += offsety * scaley;
#endif
if( tilt_deg ) {
tilt_deg = (tilt_deg + 0) * ((float)C_PI / 180);
s.cos = cosf(tilt_deg);
s.sin = sinf(tilt_deg);
} else {
s.cos = 1;
s.sin = 0;
}
batch_group_t *batches = &sprite_group[ flags & 7 ];
batch_t *found = map_find_or_add(*batches, t.id, (batch_t){0});
array_push(found->sprites, s);
}
void sprite_sheet( texture_t texture, float spritesheet[3], float position[3], float rotation, float offset[2], float scale[2], unsigned rgba, unsigned flags) {
flags |= SPRITE_CENTERED;
ASSERT( flags & SPRITE_CENTERED );
const float px = position[0], py = position[1], pz = position[2];
const float ox = offset[0], oy = offset[1], sx = scale[0], sy = scale[1];
const float frame = spritesheet[0], xcells = spritesheet[1], ycells = spritesheet[2];
if (frame < 0) return;
if (frame > 0 && frame >= (xcells * ycells)) return;
// no need to queue if alpha or scale are zero
if( sx && sy && alpha(rgba) ) {
vec3 bak = camera_get_active()->position;
if( flags & SPRITE_RESOLUTION_INDEPENDANT ) { // @todo: optimize me
sprite_flush();
camera_get_active()->position = vec3(window_width()/2,window_height()/2,1);
}
sprite_static_t s;
s.px = px;
s.py = py;
s.pz = pz;
s.frame = frame;
s.ncx = xcells ? xcells : 1;
s.ncy = ycells ? ycells : 1;
s.sx = sx;
s.sy = sy;
s.ox = ox * sx;
s.oy = oy * sy;
s.cellw = (texture.x * sx / s.ncx);
s.cellh = (texture.y * sy / s.ncy);
s.rgba = rgba;
s.flags = flags;
s.cos = 1;
s.sin = 0;
if(rotation) {
rotation = (rotation + 0) * ((float)C_PI / 180);
s.cos = cosf(rotation);
s.sin = sinf(rotation);
}
batch_group_t *batches = &sprite_group[ flags & 7 ];
#if 0
batch_t *found = map_find(*batches, texture.id);
if( !found ) found = map_insert(*batches, texture.id, (batch_t){0});
#else
batch_t *found = map_find_or_add(*batches, texture.id, (batch_t){0});
#endif
array_push(found->sprites, s);
if( flags & SPRITE_RESOLUTION_INDEPENDANT ) { // @todo: optimize me
sprite_flush();
camera_get_active()->position = bak;
}
}
}
void sprite( texture_t texture, float position[3], float rotation, unsigned color, unsigned flags) {
float offset[2] = {0,0}, scale[2] = {1,1}, spritesheet[3] = {0,0,0};
sprite_sheet( texture, spritesheet, position, rotation, offset, scale, color, flags );
}
static void sprite_rebuild_meshes() {
sprite_count = 0;
// w/2,h/2 centered
for( int l = countof(sprite_group) / 2; l < countof(sprite_group); ++l) {
for each_map_ptr(sprite_group[l], int,_, batch_t,bt) {
bt->dirty = array_count(bt->sprites) ? 1 : 0;
if( !bt->dirty ) continue;
int index = 0;
array_clear(sprite_indices);
array_clear(sprite_vertices);
array_foreach_ptr(bt->sprites, sprite_static_t,it ) {
float x0 = it->ox - it->cellw/2, x3 = x0 + it->cellw;
float y0 = it->oy - it->cellh/2, y3 = y0;
float x1 = x0, x2 = x3;
float y1 = y0 + it->cellh, y2 = y1;
// @todo: move this affine transform into glsl shader
vec3 v0 = { it->px + ( x0 * it->cos - y0 * it->sin ), it->py + ( x0 * it->sin + y0 * it->cos ), it->pz };
vec3 v1 = { it->px + ( x1 * it->cos - y1 * it->sin ), it->py + ( x1 * it->sin + y1 * it->cos ), it->pz };
vec3 v2 = { it->px + ( x2 * it->cos - y2 * it->sin ), it->py + ( x2 * it->sin + y2 * it->cos ), it->pz };
vec3 v3 = { it->px + ( x3 * it->cos - y3 * it->sin ), it->py + ( x3 * it->sin + y3 * it->cos ), it->pz };
float cx = (1.0f / it->ncx) - 1e-9f;
float cy = (1.0f / it->ncy) - 1e-9f;
int idx = (int)it->frame;
int px = idx % it->ncx;
int py = idx / it->ncx;
float ux = px * cx, uy = py * cy;
float vx = ux + cx, vy = uy + cy;
vec2 uv0 = vec2(ux, uy);
vec2 uv1 = vec2(ux, vy);
vec2 uv2 = vec2(vx, vy);
vec2 uv3 = vec2(vx, uy);
array_push( sprite_vertices, sprite_vertex(v0, uv0, it->rgba) ); // Vertex 0 (A)
array_push( sprite_vertices, sprite_vertex(v1, uv1, it->rgba) ); // Vertex 1 (B)
array_push( sprite_vertices, sprite_vertex(v2, uv2, it->rgba) ); // Vertex 2 (C)
array_push( sprite_vertices, sprite_vertex(v3, uv3, it->rgba) ); // Vertex 3 (D)
// A--B A A-B
// quad | | becomes triangle |\ and triangle \|
// D--C D-C C
GLuint A = (index+0), B = (index+1), C = (index+2), D = (index+3); index += 4;
array_push( sprite_indices, sprite_index(C, D, A) ); // Triangle 1
array_push( sprite_indices, sprite_index(C, A, B) ); // Triangle 2
}
mesh_update(&bt->mesh, "p3 t2 c4B", 0,array_count(sprite_vertices),sprite_vertices, 3*array_count(sprite_indices),sprite_indices, MESH_STATIC);
// clear elements from queue
sprite_count += array_count(bt->sprites);
array_clear(bt->sprites);
}
}
// (0,0) centered
for( int l = 0; l < countof(sprite_group) / 2; ++l) {
for each_map_ptr(sprite_group[l], int,_, batch_t,bt) {
bt->dirty = array_count(bt->sprites) ? 1 : 0;
if( !bt->dirty ) continue;
int index = 0;
array_clear(sprite_indices);
array_clear(sprite_vertices);
array_foreach_ptr(bt->sprites, sprite_static_t,it ) {
float x0 = it->ox - it->cellw/2, x3 = x0 + it->cellw;
float y0 = it->oy - it->cellh/2, y3 = y0;
float x1 = x0, x2 = x3;
float y1 = y0 + it->cellh, y2 = y1;
// @todo: move this affine transform into glsl shader
vec3 v0 = { it->px + ( x0 * it->cos - y0 * it->sin ), it->py + ( x0 * it->sin + y0 * it->cos ), it->pz };
vec3 v1 = { it->px + ( x1 * it->cos - y1 * it->sin ), it->py + ( x1 * it->sin + y1 * it->cos ), it->pz };
vec3 v2 = { it->px + ( x2 * it->cos - y2 * it->sin ), it->py + ( x2 * it->sin + y2 * it->cos ), it->pz };
vec3 v3 = { it->px + ( x3 * it->cos - y3 * it->sin ), it->py + ( x3 * it->sin + y3 * it->cos ), it->pz };
float ux = it->x, vx = ux + it->w;
float uy = it->y, vy = uy + it->h;
vec2 uv0 = vec2(ux, uy);
vec2 uv1 = vec2(ux, vy);
vec2 uv2 = vec2(vx, vy);
vec2 uv3 = vec2(vx, uy);
array_push( sprite_vertices, sprite_vertex(v0, uv0, it->rgba) ); // Vertex 0 (A)
array_push( sprite_vertices, sprite_vertex(v1, uv1, it->rgba) ); // Vertex 1 (B)
array_push( sprite_vertices, sprite_vertex(v2, uv2, it->rgba) ); // Vertex 2 (C)
array_push( sprite_vertices, sprite_vertex(v3, uv3, it->rgba) ); // Vertex 3 (D)
// A--B A A-B
// quad | | becomes triangle |\ and triangle \|
// D--C D-C C
GLuint A = (index+0), B = (index+1), C = (index+2), D = (index+3); index += 4;
array_push( sprite_indices, sprite_index(C, D, A) ); // Triangle 1
array_push( sprite_indices, sprite_index(C, A, B) ); // Triangle 2
}
mesh_update(&bt->mesh, "p3 t2 c4B", 0,array_count(sprite_vertices),sprite_vertices, 3*array_count(sprite_indices),sprite_indices, MESH_STATIC);
// clear elements from queue
sprite_count += array_count(bt->sprites);
array_clear(bt->sprites);
}
}
}
static void sprite_render_meshes_group(batch_group_t* sprites, int alpha_key, int alpha_value, float mvp[16]) {
if( map_count(*sprites) > 0 ) {
// setup shader
if( sprite_program < 0 ) {
sprite_program = shader( vfs_read("shaders/vs_324_24_sprite.glsl"), vfs_read("shaders/fs_24_4_sprite.glsl"),
"att_Position,att_TexCoord,att_Color",
"fragColor", NULL
);
}
shader_bind(sprite_program);
shader_mat44("u_mvp", mvp);
// set (unit 0) in the uniform texture sampler, and render batch
glActiveTexture(GL_TEXTURE0);
glBlendFunc( alpha_key, alpha_value );
for each_map_ptr(*sprites, int,texture_id, batch_t,bt) {
if( bt->dirty ) {
shader_texture_unit("u_texture", *texture_id, 0);
mesh_render(&bt->mesh);
}
}
// map_clear(*sprites);
}
}
static void sprite_init() {
do_once for(int i = 0; i < countof(sprite_group); ++i) {
map_init(sprite_group[i], less_int, hash_int);
}
}
static renderstate_t sprite_rs;
void sprite_flush() {
do_once {
sprite_rs = renderstate();
sprite_rs.depth_test_enabled = 1;
sprite_rs.blend_enabled = 1;
sprite_rs.cull_face_enabled = 0;
sprite_rs.front_face = GL_CCW;
}
profile("Sprite.rebuild_time") {
sprite_rebuild_meshes();
}
profile("Sprite.render_time") {
// setup rendering state
renderstate_apply(&sprite_rs);
// 3d
mat44 mvp3d; multiply44x2(mvp3d, camera_get_active()->proj, camera_get_active()->view);
// render all additive then translucent groups
sprite_render_meshes_group(&sprite_group[SPRITE_PROJECTED], GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, mvp3d );
sprite_render_meshes_group(&sprite_group[SPRITE_PROJECTED|SPRITE_CENTERED], GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, mvp3d );
sprite_render_meshes_group(&sprite_group[SPRITE_PROJECTED|SPRITE_CENTERED|SPRITE_ADDITIVE], GL_SRC_ALPHA, GL_ONE, mvp3d );
sprite_render_meshes_group(&sprite_group[SPRITE_PROJECTED|SPRITE_ADDITIVE], GL_SRC_ALPHA, GL_ONE, mvp3d );
// 2d: (0,0) is center of screen
mat44 mvp2d;
vec3 pos = camera_get_active()->position;
float zoom = absf(pos.z); if(zoom < 0.1f) zoom = 0.1f; zoom = 1.f / (zoom + !zoom);
float zdepth_max = window_height(); // 1;
float l = pos.x - window_width() * zoom / 2;
float r = pos.x + window_width() * zoom / 2;
float b = pos.y + window_height() * zoom / 2;
float t = pos.y - window_height() * zoom / 2;
ortho44(mvp2d, l,r,b,t, -zdepth_max, +zdepth_max);
// render all additive then translucent groups
sprite_render_meshes_group(&sprite_group[0], GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, mvp2d );
sprite_render_meshes_group(&sprite_group[SPRITE_CENTERED], GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, mvp2d );
sprite_render_meshes_group(&sprite_group[SPRITE_CENTERED|SPRITE_ADDITIVE], GL_SRC_ALPHA, GL_ONE, mvp2d );
sprite_render_meshes_group(&sprite_group[SPRITE_ADDITIVE], GL_SRC_ALPHA, GL_ONE, mvp2d );
// restore rendering state
glUseProgram(0);
}
}
// -----------------------------------------------------------------------------
// tilemaps
tilemap_t tilemap(const char *map, int blank_chr, int linefeed_chr) {
tilemap_t t = {0};
t.tint = ~0u; // WHITE
t.blank_chr = blank_chr;
for( ; *map ; ++map ) {
if( map[0] == linefeed_chr ) ++t.rows;
else {
array_push(t.map, map[0]);
++t.cols;
}
}
return t;
}
void tilemap_render_ext( tilemap_t m, tileset_t t, float zindex, float xy_zoom[3], float tilt, unsigned tint, bool is_additive ) {
vec3 old_pos = camera_get_active()->position;
sprite_flush();
camera_get_active()->position = vec3(window_width()/2,window_height()/2,1);
float scale[2] = {xy_zoom[2], xy_zoom[2]};
xy_zoom[2] = zindex;
float offset[2] = {0,0};
float spritesheet[3] = {0,t.cols,t.rows}; // selected tile index and spritesheet dimensions (cols,rows)
for( unsigned y = 0, c = 0; y < m.rows; ++y ) {
for( unsigned x = 0; x < m.cols; ++x, ++c ) {
if( m.map[c] != m.blank_chr ) {
spritesheet[0] = m.map[c];
sprite_sheet(t.tex, spritesheet, xy_zoom, tilt, offset, scale, tint, is_additive ? SPRITE_ADDITIVE : 0);
}
offset[0] += t.tile_w;
}
offset[0] = 0, offset[1] += t.tile_h;
}
sprite_flush();
camera_get_active()->position = old_pos;
}
void tilemap_render( tilemap_t map, tileset_t set ) {
map.position.x += set.tile_w;
map.position.y += set.tile_h;
tilemap_render_ext( map, set, map.zindex, &map.position.x, map.tilt, map.tint, map.is_additive );
}
tileset_t tileset(texture_t tex, unsigned tile_w, unsigned tile_h, unsigned cols, unsigned rows) {
tileset_t t = {0};
t.tex = tex;
t.cols = cols, t.rows = rows;
t.tile_w = tile_w, t.tile_h = tile_h;
return t;
}
int ui_tileset( tileset_t t ) {
ui_subimage(va("Selection #%d (%d,%d)", t.selected, t.selected % t.cols, t.selected / t.cols), t.tex.id, t.tex.w, t.tex.h, (t.selected % t.cols) * t.tile_w, (t.selected / t.cols) * t.tile_h, t.tile_w, t.tile_h);
int choice;
if( (choice = ui_image(0, t.tex.id, t.tex.w,t.tex.h)) ) {
int px = ((choice / 100) / 100.f) * t.tex.w / t.tile_w;
int py = ((choice % 100) / 100.f) * t.tex.h / t.tile_h;
t.selected = px + py * t.cols;
}
// if( (choice = ui_buttons(3, "load", "save", "clear")) ) {}
return t.selected;
}
// -----------------------------------------------------------------------------
// tiled
tiled_t tiled(const char *file_tmx) {
tiled_t zero = {0}, ti = zero;
// read file and parse json
if( !xml_push(file_tmx) ) return zero;
// sanity checks
bool supported = !strcmp(xml_string("/map/@orientation"), "orthogonal") && !strcmp(xml_string("/map/@renderorder"), "right-down");
if( !supported ) return xml_pop(), zero;
// tileset
const char *file_tsx = xml_string("/map/tileset/@source");
if( !xml_push(vfs_read(file_tsx)) ) return zero;
const char *set_src = xml_string("/tileset/image/@source");
int set_w = xml_int("/tileset/@tilewidth");
int set_h = xml_int("/tileset/@tileheight");
int set_c = xml_int("/tileset/@columns");
int set_r = xml_int("/tileset/@tilecount") / set_c;
tileset_t set = tileset(texture(set_src,0), set_w, set_h, set_c, set_r );
xml_pop();
// actual parsing
ti.w = xml_int("/map/@width");
ti.h = xml_int("/map/@height");
ti.tilew = xml_int("/map/@tilewidth");
ti.tileh = xml_int("/map/@tileheight");
ti.first_gid = xml_int("/map/tileset/@firstgid");
ti.map_name = STRDUP( xml_string("/map/tileset/@source") ); // @leak
for(int l = 0, layers = xml_count("/map/layer"); l < layers; ++l ) {
if( strcmp(xml_string("/map/layer[%d]/data/@encoding",l), "base64") || strcmp(xml_string("/map/layer[%d]/data/@compression",l), "zlib") ) {
PRINTF("Warning: layer encoding not supported: '%s' -> layer '%s'\n", file_tmx, *array_back(ti.names));
continue;
}
int cols = xml_int("/map/layer[%d]/@width",l);
int rows = xml_int("/map/layer[%d]/@height",l);
tilemap_t tm = tilemap("", ' ', '\n');
tm.blank_chr = ~0u; //ti.first_gid - 1;
tm.cols = cols;
tm.rows = rows;
array_resize(tm.map, tm.cols * tm.rows);
memset(tm.map, 0xFF, tm.cols * tm.rows * sizeof(int));
for( int c = 0, chunks = xml_count("/map/layer[%d]/data/chunk", l); c <= chunks; ++c ) {
int cw, ch;
int cx, cy;
array(char) b64 = 0;
if( !chunks ) { // non-infinite mode
b64 = xml_blob("/map/layer[%d]/data/$",l);
cw = tm.cols, ch = tm.rows;
cx = 0, cy = 0;
} else { // infinite mode
b64 = xml_blob("/map/layer[%d]/data/chunk[%d]/$",l,c);
cw = xml_int("/map/layer[%d]/data/chunk[%d]/@width",l,c), ch = xml_int("/map/layer[%d]/data/chunk[%d]/@height",l,c); // 20x20
cx = xml_int("/map/layer[%d]/data/chunk[%d]/@x",l,c), cy = xml_int("/map/layer[%d]/data/chunk[%d]/@y",l,c); // (-16,-32)
cx = abs(cx), cy = abs(cy);
}
int outlen = cw * ch * 4;
static __thread int *out = 0; out = (int *)REALLOC( 0, outlen + zexcess(COMPRESS_ZLIB) ); // @leak
if( zdecode( out, outlen, b64, array_count(b64), COMPRESS_ZLIB ) > 0 ) {
for( int y = 0, p = 0; y < ch; ++y ) {
for( int x = 0; x < cw; ++x, ++p ) {
if( out[p] >= ti.first_gid ) {
int offset = (x + cx) + (y + cy) * tm.cols;
if( offset >= 0 && offset < (cw * ch) )
tm.map[ offset ] = out[ p ] - ti.first_gid;
}
}
}
}
else {
PRINTF("Warning: bad zlib stream: '%s' -> layer #%d -> chunk #%d\n", file_tmx, l, c);
}
array_free(b64);
}
array_push(ti.layers, tm);
array_push(ti.names, STRDUP(xml_string("/map/layer[%d]/@name",l)));
array_push(ti.visible, true);
array_push(ti.sets, set);
}
xml_pop();
return ti;
}
void tiled_render(tiled_t tmx, vec3 pos) {
for( unsigned i = 0, end = array_count(tmx.layers); i < end; ++i ) {
tmx.layers[i].position = pos; // add3(camera_get_active()->position, pos);
if( tmx.parallax ) tmx.layers[i].position.x /= (3+i), tmx.layers[i].position.y /= (5+i);
if( tmx.visible[i] ) tilemap_render(tmx.layers[i], tmx.sets[i]);
}
}
void ui_tiled(tiled_t *t) {
ui_label2("Loaded map", t->map_name ? t->map_name : "(none)");
ui_label2("Map dimensions", va("%dx%d", t->w, t->h));
ui_label2("Tile dimensions", va("%dx%d", t->tilew, t->tileh));
ui_separator();
ui_bool("Parallax", &t->parallax);
ui_separator();
ui_label2("Layers", va("%d", array_count(t->layers)));
for( int i = 0; i < array_count(t->layers); ++i ) {
if( ui_label2_toolbar(va("- %s (%dx%d)", t->names[i], t->layers[i].cols, t->layers[i].rows ), t->visible[i] ? "\xee\xa3\xb4" : "\xee\xa3\xb5") > 0 ) { // ICON_MD_VISIBILITY / ICON_MD_VISIBILITY_OFF
t->visible[i] ^= true;
}
}
ui_separator();
if( ui_collapse(va("Sets: %d", array_count(t->layers)), va("%p",t))) {
for( int i = 0; i < array_count(t->layers); ++i ) {
if( ui_collapse(va("%d", i+1), va("%p%d",t,i)) ) {
t->sets[i].selected = ui_tileset( t->sets[i] );
ui_collapse_end();
}
}
ui_collapse_end();
}
}
// -----------------------------------------------------------------------------
// spine json loader (wip)
// - rlyeh, public domain
//
// [ref] http://es.esotericsoftware.com/spine-json-format
//
// notable misses:
// - mesh deforms
// - cubic beziers
// - shears
// - bounding boxes
enum { SPINE_MAX_BONES = 64 }; // max bones
typedef struct spine_bone_t {
char *name, *parent;
struct spine_bone_t *parent_bone;
float z; // draw order usually matches bone-id. ie, zindex == bone_id .. root(0) < chest (mid) < finger(top)
float len;
float x, y, deg; // base
float x2, y2, deg2; // accum / temporaries during bone transform time
float x3, y3, deg3; // values from timeline
unsigned rect_id;
unsigned atlas_id;
} spine_bone_t;
typedef struct spine_slot_t {
char *name, *bone, *attach;
} spine_slot_t;
typedef struct spine_rect_t {
char *name;
float x,y,w,h,sx,sy,deg;
} spine_rect_t;
typedef struct spine_skin_t {
char *name;
array(spine_rect_t) rects;
} spine_skin_t;
typedef struct spine_animkey_t { // offline; only during loading
float time, curve[4]; // time is mandatory, curve is optional
union {
char *name; // type: attachment (mode-1)
struct { float deg; }; // type: rotate (mode-2)
struct { float x,y; }; // type: translate (mode-3)
};
} spine_animkey_t;
#if 0
typedef struct spine_pose_t { // runtime; only during playing
unsigned frame;
array(vec4) xform; // entry per bone. translation(x,y),rotation(z),attachment-id(w)
} spine_pose_t;
#endif
typedef struct spine_anim_t {
char *name;
union {
#if 0
struct {
unsigned frames;
array(spine_pose_t) poses;
};
#endif
struct {
array(spine_animkey_t) attach_keys[SPINE_MAX_BONES];
array(spine_animkey_t) rotate_keys[SPINE_MAX_BONES];
array(spine_animkey_t) translate_keys[SPINE_MAX_BONES];
};
};
} spine_anim_t;
typedef struct spine_atlas_t {
char *name;
float x,y,w,h,deg;
} spine_atlas_t;
typedef struct spine_t {
char *name;
texture_t texture;
unsigned skin;
array(spine_bone_t) bones;
array(spine_slot_t) slots;
array(spine_skin_t) skins;
array(spine_anim_t) anims;
array(spine_atlas_t) atlas;
// anim controller
unsigned inuse;
float time, maxtime;
unsigned debug_atlas_id;
} spine_t;
// ---
static
void spine_convert_animkeys_to_animpose(spine_anim_t *input) {
spine_anim_t copy = *input; // @todo
// @leak: attach/rot/tra keys
}
static
int find_bone_id(spine_t *s, const char *bone_name) {
for( unsigned i = 0, end = array_count(s->bones); i < end; ++i )
if( !strcmp(s->bones[i].name, bone_name)) return i;
return -1;
}
static
spine_bone_t *find_bone(spine_t *s, const char *bone_name) {
int bone_id = find_bone_id(s, bone_name);
return bone_id >= 0 ? &s->bones[bone_id] : NULL;
}
void spine_skin(spine_t *p, unsigned skin) {
if( !p->texture.id ) return;
if( skin >= array_count(p->skins) ) return;
p->skin = skin;
char *skin_name = va("%s/", p->skins[skin].name);
int header = strlen(skin_name);
for( int i = 0; i < array_count(p->atlas); ++i) {
if(!strbeg(p->atlas[i].name, skin_name)) continue;
int bone_id = find_bone_id(p, p->atlas[i].name+header );
if( bone_id < 0 ) continue;
p->bones[bone_id].atlas_id = i;
}
for( int i = 0; i < array_count(p->skins[p->skin].rects); ++i) {
int bone_id = find_bone_id(p, p->skins[p->skin].rects[i].name );
if( bone_id < 0 ) continue;
p->bones[bone_id].rect_id = i;
}
}
static
bool spine_(spine_t *t, const char *file_json, const char *file_atlas, unsigned flags) {
char *atlas = vfs_read(file_atlas);
if(!atlas || !atlas[0]) return false;
memset(t, 0, sizeof(spine_t));
// goblins.png
// size: 1024, 128
// filter: Linear, Linear
// pma: true
// dagger
// bounds: 2, 18, 26, 108
// goblin/eyes-closed
// bounds: 2, 4, 34, 12
spine_atlas_t *sa = 0;
const char *last_id = 0;
const char *texture_name = 0;
const char *texture_filter = 0;
const char *texture_format = 0;
const char *texture_repeat = 0;
float texture_width = 0, texture_height = 0, temp;
for each_substring(atlas, "\r\n", it) {
it += strspn(it, " \t\f\v");
/**/ if( strbeg(it, "pma:" ) || strbeg(it, "index:") ) {} // ignored
else if( strbeg(it, "size:" ) ) sscanf(it+5, "%f,%f", &texture_width, &texture_height);
else if( strbeg(it, "rotate:" ) ) { float tmp; tmp=sa->w,sa->w=sa->h,sa->h=tmp; sa->deg = 90; } // assert(val==90)
else if( strbeg(it, "repeat:" ) ) texture_repeat = it+7; // temp string
else if( strbeg(it, "filter:" ) ) texture_filter = it+7; // temp string
else if( strbeg(it, "format:" ) ) texture_format = it+7; // temp string
else if( strbeg(it, "bounds:" ) ) {
sscanf(it+7, "%f,%f,%f,%f", &sa->x, &sa->y, &sa->w, &sa->h);
}
else if( !texture_name ) texture_name = va("%s", it);
else {
array_push(t->atlas, ((spine_atlas_t){0}) );
sa = &t->atlas[array_count(t->atlas) - 1];
sa->name = STRDUP(it);
}
}
for( int i = 0; i < array_count(t->atlas); ++i ) {
sa = &t->atlas[i];
sa->x /= texture_width, sa->y /= texture_height;
sa->w /= texture_width, sa->h /= texture_height;
}
if(!texture_name) return false;
t->texture = texture(texture_name, TEXTURE_LINEAR);
json_push(vfs_read(file_json)); // @fixme: json_push_from_file() ?
array_resize(t->bones, json_count("/bones"));
array_reserve(t->slots, json_count("/slots"));
array_resize(t->skins, json_count("/skins"));
array_resize(t->anims, json_count("/animations"));
for( int i = 0, end = json_count("/bones"); i < end; ++i ) {
spine_bone_t v = {0};
v.name = STRDUP(json_string("/bones[%d]/name", i));
v.parent = STRDUP(json_string("/bones[%d]/parent", i));
v.x = json_float("/bones[%d]/x", i);
v.y = json_float("/bones[%d]/y", i);
v.z = i;
v.len = json_float("/bones[%d]/length", i);
v.deg = json_float("/bones[%d]/rotation", i);
t->bones[i] = v;
for( int j = i-1; j > 0; --j ) {
if( strcmp(t->bones[j].name,v.parent) ) continue;
t->bones[i].parent_bone = &t->bones[j];
break;
}
}
for( int i = 0, end = json_count("/slots"); i < end; ++i ) {
spine_slot_t v = {0};
v.name = STRDUP(json_string("/slots[%d]/name", i));
v.bone = STRDUP(json_string("/slots[%d]/bone", i));
v.attach = STRDUP(json_string("/slots[%d]/attachment", i));
array_push(t->slots, v);
// slots define draw-order. so, update draw-order/zindex in bone
spine_bone_t *b = find_bone(t, v.name);
if( b ) b->z = i;
}
for( int i = 0, end = json_count("/skins"); i < end; ++i ) {
spine_skin_t v = {0};
v.name = STRDUP(json_string("/skins[%d]/name", i));
for( int j = 0, jend = json_count("/skins[%d]/attachments",i); j < jend; ++j ) // /skins/default/
for( int k = 0, kend = json_count("/skins[%d]/attachments[%d]",i,j); k < kend; ++k ) { // /skins/default/left hand item/
spine_rect_t r = {0};
r.name = STRDUP(json_key("/skins[%d]/attachments[%d][%d]",i,j,k)); // stringf("%s-%s-%s", json_key("/skins[%d]",i), json_key("/skins[%d][%d]",i,j), json_key("/skins[%d][%d][%d]",i,j,k));
r.x = json_float("/skins[%d]/attachments[%d][%d]/x",i,j,k);
r.y = json_float("/skins[%d]/attachments[%d][%d]/y",i,j,k);
r.sx= json_float("/skins[%d]/attachments[%d][%d]/scaleX",i,j,k); r.sx += !r.sx;
r.sy= json_float("/skins[%d]/attachments[%d][%d]/scaleY",i,j,k); r.sy += !r.sy;
r.w = json_float("/skins[%d]/attachments[%d][%d]/width",i,j,k);
r.h = json_float("/skins[%d]/attachments[%d][%d]/height",i,j,k);
r.deg = json_float("/skins[%d]/attachments[%d][%d]/rotation",i,j,k);
array_push(v.rects, r);
}
t->skins[i] = v;
}
#if 1
// simplify:
// merge /skins/default into existing /skins/*, then delete /skins/default
if( array_count(t->skins) > 1 ) {
for( int i = 1; i < array_count(t->skins); ++i ) {
for( int j = 0; j < array_count(t->skins[0].rects); ++j ) {
array_push(t->skins[i].rects, t->skins[0].rects[j]);
}
}
// @leak @fixme: FREE(t->skins[0])
for( int i = 0; i < array_count(t->skins)-1; ++i ) {
t->skins[i] = t->skins[i+1];
}
array_pop(t->skins);
}
#endif
for( int i = 0, end = json_count("/animations"); i < end; ++i ) {
int id;
const char *name;
spine_anim_t v = {0};
v.name = STRDUP(json_key("/animations[%d]", i));
// slots / attachments
for( int j = 0, jend = json_count("/animations[%d]/slots",i); j < jend; ++j )
for( int k = 0, kend = json_count("/animations[%d]/slots[%d]",i,j); k < kend; ++k ) // ids
{
int bone_id = find_bone_id(t, json_key("/animations[%d]/bones[%d]",i,j));
if( bone_id < 0 ) continue;
for( int l = 0, lend = json_count("/animations[%d]/slots[%d][%d]",i,j,k); l < lend; ++l ) { // channels (rot,tra,attach)
spine_animkey_t key = {0};
key.name = STRDUP(json_string("/animations[%d]/slots[%d][%d][%d]/name",i,j,k,l));
key.time = json_float("/animations[%d]/slots[%d][%d][%d]/time",i,j,k,l);
if( json_count("/animations[%d]/slots[%d][%d][%d]/curve",i,j,k,l) == 4 ) {
key.curve[0] = json_float("/animations[%d]/slots[%d][%d][%d]/curve[0]",i,j,k,l);
key.curve[1] = json_float("/animations[%d]/slots[%d][%d][%d]/curve[1]",i,j,k,l);
key.curve[2] = json_float("/animations[%d]/slots[%d][%d][%d]/curve[2]",i,j,k,l);
key.curve[3] = json_float("/animations[%d]/slots[%d][%d][%d]/curve[3]",i,j,k,l);
}
// @todo: convert name to id
// for(id = 0; t->bones[id].name && strcmp(t->bones[id].name,key.name); ++id)
// printf("%s vs %s\n", key.name, t->bones[id].name);
array_push(v.attach_keys[bone_id], key);
}
}
// bones
for( int j = 0, jend = json_count("/animations[%d]/bones",i); j < jend; ++j ) // slots or bones
for( int k = 0, kend = json_count("/animations[%d]/bones[%d]",i,j); k < kend; ++k ) { // bone ids
int bone_id = find_bone_id(t, json_key("/animations[%d]/bones[%d]",i,j));
if( bone_id < 0 ) continue;
// parse bones
for( int l = 0, lend = json_count("/animations[%d]/bones[%d][%d]",i,j,k); l < lend; ++l ) { // channels (rot,tra,attach)
const char *channel = json_key("/animations[%d]/bones[%d][%d]",i,j,k);
int track = !strcmp(channel, "rotate") ? 1 : !strcmp(channel, "translate") ? 2 : 0;
if( !track ) continue;
spine_animkey_t key = {0};
key.time = json_float("/animations[%d]/bones[%d][%d][%d]/time",i,j,k,l);
if( json_count("/animations[%d]/bones[%d][%d][%d]/curve",i,j,k,l) == 4 ) {
key.curve[0] = json_float("/animations[%d]/bones[%d][%d][%d]/curve[0]",i,j,k,l);
key.curve[1] = json_float("/animations[%d]/bones[%d][%d][%d]/curve[1]",i,j,k,l);
key.curve[2] = json_float("/animations[%d]/bones[%d][%d][%d]/curve[2]",i,j,k,l);
key.curve[3] = json_float("/animations[%d]/bones[%d][%d][%d]/curve[3]",i,j,k,l);
}
if( track == 1 )
key.deg = json_float("/animations[%d]/bones[%d][%d][%d]/value",i,j,k,l), // "/angle"
array_push(v.rotate_keys[bone_id], key);
else
key.x = json_float("/animations[%d]/bones[%d][%d][%d]/x",i,j,k,l),
key.y = json_float("/animations[%d]/bones[%d][%d][%d]/y",i,j,k,l),
array_push(v.translate_keys[bone_id], key);
}
}
t->anims[i] = v;
}
json_pop();
spine_skin(t, 0);
return true;
}
spine_t* spine(const char *file_json, const char *file_atlas, unsigned flags) {
spine_t *t = MALLOC(sizeof(spine_t));
if( !spine_(t, file_json, file_atlas, flags) ) return FREE(t), NULL;
return t;
}
void spine_render(spine_t *p, vec3 offset, unsigned flags) {
if( !p->texture.id ) return;
if( !flags ) return;
ddraw_push_2d();
// if( flags & 2 ) ddraw_line(vec3(0,0,0), vec3(window_width(),window_height(),0));
// if( flags & 2 ) ddraw_line(vec3(window_width(),0,0), vec3(0,window_height(),0));
// int already_computed[SPINE_MAX_BONES] = {0}; // @fixme: optimize: update longest chains first, then remnant branches
for( int i = 1; i < array_count(p->bones); ++i ) {
spine_bone_t *self = &p->bones[i];
if( !self->rect_id ) continue;
int num_bones = 0;
static array(spine_bone_t*) chain = 0; array_resize(chain, 0);
for( spine_bone_t *next = self; next ; next = next->parent_bone, ++num_bones ) {
array_push(chain, next);
}
vec3 target = {0}, prev = {0};
for( int j = 0, end = array_count(chain); j < end; ++j ) { // traverse from root(skipped) -> `i` bone direction
int j_opposite = end - 1 - j;
spine_bone_t *b = chain[j_opposite]; // bone
spine_bone_t *pb = b->parent_bone; // parent bone
float pb_x2 = 0, pb_y2 = 0, pb_deg2 = 0;
if( pb ) pb_x2 = pb->x2, pb_y2 = pb->y2, pb_deg2 = pb->deg2;
const float deg2rad = C_PI / 180;
b->x2 = b->x3 + pb_x2 + b->x * cos( -pb_deg2 * deg2rad ) - b->y * sin( -pb_deg2 * deg2rad );
b->y2 = -b->y3 + pb_y2 - b->y * cos( pb_deg2 * deg2rad ) + b->x * sin( pb_deg2 * deg2rad );
b->deg2 = -b->deg3 + pb_deg2 - b->deg;
prev = target;
target = vec3(b->x2,b->y2,b->deg2);
}
target.z = 0;
target = add3(target, offset);
prev.z = 0;
prev = add3(prev, offset);
if( flags & 2 ) {
ddraw_point( target );
ddraw_text( target, -0.25f, self->name );
ddraw_bone( prev, target ); // from parent to bone
}
if( flags & 1 ) {
spine_atlas_t *a = &p->atlas[self->atlas_id];
spine_rect_t *r = &p->skins[p->skin].rects[self->rect_id];
vec4 rect = ptr4(&a->x);
float zindex = self->z;
float offsx = 0;
float offsy = 0;
float tilt = self->deg2 + (a->deg - r->deg);
unsigned tint = self->atlas_id == p->debug_atlas_id ? 0xFF<<24 | 0xFF : ~0u;
if( 1 ) {
vec3 dir = vec3(r->x,r->y,0);
dir = rotatez3(dir, self->deg2);
offsx = dir.x * r->sx;
offsy = dir.y * r->sy;
}
sprite_rect(p->texture, rect, vec4(target.x,target.y,0,zindex), vec4(1,1,offsx,offsy), tilt, tint, 0);
}
}
ddraw_pop_2d();
ddraw_flush();
}
static
void spine_animate_(spine_t *p, float *time, float *maxtime, float delta) {
if( !p->texture.id ) return;
if( delta > 1/120.f ) delta = 1/120.f;
if( *time >= *maxtime ) *time = 0; else *time += delta;
// reset root // needed?
p->bones[0].x2 = 0;
p->bones[0].y2 = 0;
p->bones[0].deg2 = 0;
p->bones[0].x3 = 0;
p->bones[0].y3 = 0;
p->bones[0].deg3 = 0;
for( int i = 0, end = array_count(p->bones); i < end; ++i) {
// @todo: attach channel
// @todo: per channel: if curve == linear || curve == stepped || array_count(curve) == 4 {...}
for each_array_ptr(p->anims[p->inuse].rotate_keys[i], spine_animkey_t, r) {
double r0 = r->time;
*maxtime = maxf( *maxtime, r0 );
if( absf(*time - r0) < delta ) {
p->bones[i].deg3 = r->deg;
}
}
for each_array_ptr(p->anims[p->inuse].translate_keys[i], spine_animkey_t, r) {
double r0 = r->time;
*maxtime = maxf( *maxtime, r0 );
if( absf(*time - r0) < delta ) {
p->bones[i].x3 = r->x;
p->bones[i].y3 = r->y;
}
}
}
}
void spine_animate(spine_t *p, float delta) {
spine_animate_(p, &p->time, &p->maxtime, delta);
}
void ui_spine(spine_t *p) {
if( ui_collapse(va("Anims: %d", array_count(p->anims)), va("%p-a", p))) {
for each_array_ptr(p->anims, spine_anim_t, q) {
if(ui_slider2("", &p->time, va("%.2f/%.0f %.2f%%", p->time, p->maxtime, p->time * 100.f))) {
spine_animate(p, 0);
}
int choice = ui_label2_toolbar(q->name, ICON_MD_PAUSE_CIRCLE " " ICON_MD_PLAY_CIRCLE);
if( choice == 1 ) window_pause( 0 ); // play
if( choice == 2 ) window_pause( 1 ); // pause
for( int i = 0; i < SPINE_MAX_BONES; ++i ) {
ui_separator();
ui_label(va("Bone %d: Attachment keys", i));
for each_array_ptr(q->attach_keys[i], spine_animkey_t, r) {
ui_label(va("%.2f [%.2f %.2f %.2f %.2f] %s", r->time, r->curve[0], r->curve[1], r->curve[2], r->curve[3], r->name));
}
ui_label(va("Bone %d: Rotate keys", i));
for each_array_ptr(q->rotate_keys[i], spine_animkey_t, r) {
ui_label(va("%.2f [%.2f %.2f %.2f %.2f] %.2f deg", r->time, r->curve[0], r->curve[1], r->curve[2], r->curve[3], r->deg));
}
ui_label(va("Bone %d: Translate keys", i));
for each_array_ptr(q->translate_keys[i], spine_animkey_t, r) {
ui_label(va("%.2f [%.2f %.2f %.2f %.2f] (%.2f,%.2f)", r->time, r->curve[0], r->curve[1], r->curve[2], r->curve[3], r->x, r->y));
}
}
}
ui_collapse_end();
}
if( ui_collapse(va("Bones: %d", array_count(p->bones)), va("%p-b", p))) {
for each_array_ptr(p->bones, spine_bone_t, q)
if( ui_collapse(q->name, va("%p-b2", q)) ) {
ui_label2("Parent:", q->parent);
ui_label2("X:", va("%.2f", q->x));
ui_label2("Y:", va("%.2f", q->y));
ui_label2("Length:", va("%.2f", q->len));
ui_label2("Rotation:", va("%.2f", q->deg));
ui_collapse_end();
}
ui_collapse_end();
}
if( ui_collapse(va("Slots: %d", array_count(p->slots)), va("%p-s", p))) {
for each_array_ptr(p->slots, spine_slot_t, q)
if( ui_collapse(q->name, va("%p-s2", q)) ) {
ui_label2("Bone:", q->bone);
ui_label2("Attachment:", q->attach);
ui_collapse_end();
}
ui_collapse_end();
}
if( ui_collapse(va("Skins: %d", array_count(p->skins)), va("%p-k", p))) {
for each_array_ptr(p->skins, spine_skin_t, q)
if( ui_collapse(q->name, va("%p-k2", q)) ) {
for each_array_ptr(q->rects, spine_rect_t, r)
if( ui_collapse(r->name, va("%p-k3", r)) ) {
ui_label2("X:", va("%.2f", r->x));
ui_label2("Y:", va("%.2f", r->y));
ui_label2("Scale X:", va("%.2f", r->sx));
ui_label2("Scale Y:", va("%.2f", r->sy));
ui_label2("Width:", va("%.2f", r->w));
ui_label2("Height:", va("%.2f", r->h));
ui_label2("Rotation:", va("%.2f", r->deg));
ui_collapse_end();
spine_bone_t *b = find_bone(p, r->name);
if( b ) {
p->debug_atlas_id = b->atlas_id;
static float tilt = 0;
if( input(KEY_LCTRL) ) tilt += 60*1/60.f; else tilt = 0;
spine_atlas_t *r = p->atlas + b->atlas_id;
sprite_flush();
camera_get_active()->position = vec3(0,0,2);
vec4 rect = ptr4(&r->x); float zindex = 0; vec4 scale_offset = vec4(1,1,0,0);
sprite_rect(p->texture, ptr4(&r->x), vec4(0,0,0,zindex), scale_offset, r->deg + tilt, ~0u, 0);
sprite_flush();
camera_get_active()->position = vec3(+window_width()/3,window_height()/2.25,2);
}
}
ui_collapse_end();
}
ui_collapse_end();
}
if( ui_int("Use skin", &p->skin) ) {
p->skin = clampf(p->skin, 0, array_count(p->skins) - 1);
spine_skin(p, p->skin);
}
if( p->texture.id ) ui_texture(0, p->texture);
}
// ----------------------------------------------------------------------------
// texture_t texture_createclip(unsigned cx,unsigned cy,unsigned cw,unsigned ch, unsigned tw,unsigned th,unsigned tn,void *pixels, unsigned flags) {
// return texture_create(tw,th,tn,pixels,flags);
// static array(unsigned) clip = 0;
// array_resize(clip, cw*ch*4);
// for( unsigned y = 0; y < ch; ++y )
// memcpy((char *)clip + (0+(0+y)*cw)*tn, (char*)pixels + (cx+(cy+y)*tw)*tn, cw*tn);
// return texture_create(cw,ch,tn,clip,flags);
// }
typedef unsigned quark_t;
#define array_reserve_(arr,x) (array_count(arr) > (x) ? (arr) : array_resize(arr, 1+(x)))
#define ui_array(label,type,ptr) do { \
int changed = 0; \
if( ui_collapse(label, va(#type "%p",ptr)) ) { \
char label_ex[8]; \
for( int idx = 0, iend = array_count(*(ptr)); idx < iend; ++idx ) { \
type* it = *(ptr) + idx; \
snprintf(label_ex, sizeof(label_ex), "[%d]", idx); \
changed |= ui_##type(label_ex, it); \
} \
ui_collapse_end(); \
} \
} while(0)
int ui_vec2i(const char *label, vec2i *v) { return ui_unsigned2(label, (unsigned*)v); }
int ui_vec3i(const char *label, vec3i *v) { return ui_unsigned3(label, (unsigned*)v); }
int ui_vec2(const char *label, vec2 *v) { return ui_float2(label, (float*)v); }
int ui_vec3(const char *label, vec3 *v) { return ui_float3(label, (float*)v); }
int ui_vec4(const char *label, vec4 *v) { return ui_float4(label, (float*)v); }
char *trimspace(char *str) {
for( char *s = str; *s; ++s )
if(*s <= 32) memmove(s, s+1, strlen(s));
return str;
}
char *file_parent(const char *f) { // folder/folder/abc
char *p = file_path(f); // folder/folder/
char *last = strrchr(p, '/'); // ^
if( !last ) return p; // return parent if no sep
*last = '\0'; // folder/folder
last = strrchr(p, '/'); // ^
return last ? last + 1 : p; // return parent if no sep
}
int ui_obj(const char *fmt, obj *o) {
int changed = 0, item = 1;
for each_objmember(o, TYPE,NAME,PTR) {
char *label = va(fmt, NAME);
/**/ if(!strcmp(TYPE,"float")) { if(ui_float(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"int")) { if(ui_int(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"unsigned")) { if(ui_unsigned(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"vec2")) { if(ui_float2(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"vec3")) { if(ui_float3(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"vec4")) { if(ui_float4(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"rgb")) { if(ui_color3(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"rgba")) { if(ui_color4(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"color")) { if(ui_color4f(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"color3f")) { if(ui_color3f(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"color4f")) { if(ui_color4f(label, PTR)) changed = item; }
else if(!strcmp(TYPE,"char*")) { if(ui_string(label, PTR)) changed = item; }
else ui_label2(label, va("(%s)", TYPE)); // INFO instead of (TYPE)?
++item;
}
return changed;
}
#define OBJTYPEDEF2(...) OBJTYPEDEF(__VA_ARGS__); AUTORUN
// ----------------------------------------------------------------------------
// atlas
int ui_atlas_frame(atlas_frame_t *f) {
ui_unsigned("delay", &f->delay);
ui_vec4("sheet", &f->sheet);
ui_array("indices", vec3i, &f->indices);
ui_array("coords", vec2, &f->coords);
ui_array("uvs", vec2, &f->uvs);
return 0;
}
int ui_atlas_slice_frame(atlas_slice_frame_t *f) {
ui_vec4("bounds", &f->bounds);
ui_bool("9-slice", &f->has_9slice);
ui_vec4("core", &f->core);
return 0;
}
int ui_atlas(atlas_t *a) {
int changed = 0;
ui_texture(NULL, a->tex);
for( int i = 0; i < array_count(a->anims); ++i ) {
if( ui_collapse(quark_string(&a->db, a->anims[i].name), va("%p%d", a, a->anims[i].name) ) ) {
changed = i+1;
for( int j = 0; j < array_count(a->anims[i].frames); ++j ) {
if( ui_collapse(va("[%d]",j), va("%p%d.%d", a, a->anims[i].name,j) ) ) {
ui_unsigned("Frame", &a->anims[i].frames[j]);
ui_atlas_frame(a->frames + a->anims[i].frames[j]);
ui_collapse_end();
}
}
ui_collapse_end();
}
}
for( int i = 0; i < array_count(a->slices); ++i ) {
if( ui_collapse(quark_string(&a->db, a->slices[i].name), va("%p%d", a, a->slices[i].name) ) ) {
changed = i+1;
for( int j = 0; j < array_count(a->slices[i].frames); ++j ) {
if( ui_collapse(va("[%d]",j), va("%p%d.%d", a, a->slices[i].name,j) ) ) {
// ui_unsigned("Frame", &a->slices[i].frames[j]);
ui_atlas_slice_frame(a->slice_frames + a->slices[i].frames[j]);
ui_collapse_end();
}
}
ui_collapse_end();
}
}
return changed;
}
void atlas_destroy(atlas_t *a) {
if( a ) {
texture_destroy(&a->tex);
memset(a, 0, sizeof(atlas_t));
}
}
atlas_t atlas_create(const char *inifile, unsigned flags) {
atlas_t a = {0};
int padding = 0, border = 0;
ini_t kv = ini(inifile);
for each_map(kv, char*,k, char*,v ) {
unsigned index = atoi(k);
// printf("entry %s=%s\n", k, v);
/**/ if( strend(k, ".name") ) {
array_reserve_(a.anims, index);
a.anims[index].name = quark_intern(&a.db, v);
}
else if ( strend(k, ".sl_name") ) {
array_reserve_(a.slices, index);
a.slices[index].name = quark_intern(&a.db, v);
}
else if ( strend(k, ".sl_frames") ) {
array_reserve_(a.slices, index);
const char *text = v;
array(char*) frames = strsplit(text, ",");
for( int i = 0; i < array_count(frames); i++ ) {
unsigned frame = atoi(frames[i]);
array_push(a.slices[index].frames, frame);
}
}
else if ( strend(k, ".sl_bounds") ) {
array_reserve_(a.slice_frames, index);
float x,y,z,w;
sscanf(v, "%f,%f,%f,%f", &x, &y, &z, &w);
a.slice_frames[index].bounds = vec4(x,y,x+z,y+w);
}
else if ( strend(k, ".sl_9slice") ) {
array_reserve_(a.slice_frames, index);
a.slice_frames[index].has_9slice = atoi(v);
}
else if ( strend(k, ".sl_core") ) {
array_reserve_(a.slice_frames, index);
float x,y,z,w;
sscanf(v, "%f,%f,%f,%f", &x, &y, &z, &w);
a.slice_frames[index].core = vec4(x,y,x+z,y+w);
}
else if ( strend(k, ".sl_pivot") ) {
array_reserve_(a.slice_frames, index);
float x,y;
sscanf(v, "%f,%f", &x, &y);
a.slice_frames[index].pivot = vec2(x,y);
}
else if ( strend(k, ".sl_color") ) {
array_reserve_(a.slice_frames, index);
unsigned color;
sscanf(v, "%u", &color);
a.slice_frames[index].color = color;
}
else if ( strend(k, ".sl_text") ) {
array_reserve_(a.slice_frames, index);
a.slice_frames[index].text = STRDUP(v);
}
else if( strend(k, ".frames") ) {
array_reserve_(a.anims, index);
array(char*) pairs = strsplit(v, ",");
for( int i = 0, end = array_count(pairs); i < end; i += 2 ) {
unsigned frame = atoi(pairs[i]);
unsigned delay = atoi(pairs[i+1]);
array_reserve_(a.frames, frame);
a.frames[frame].delay = delay;
array_push(a.anims[index].frames, frame);
}
}
else if( strend(k, ".sheet") ) {
array_reserve_(a.frames, index);
vec4 sheet = atof4(v); //x,y,x2+2,y2+2 -> x,y,w,h (for 2,2 padding)
a.frames[index].sheet = vec4(sheet.x,sheet.y,sheet.z-sheet.x,sheet.w-sheet.y);
}
else if( strend(k, ".indices") ) {
array_reserve_(a.frames, index);
const char *text = v;
array(char*) tuples = strsplit(text, ",");
for( int i = 0, end = array_count(tuples); i < end; i += 3 ) {
unsigned p1 = atoi(tuples[i]);
unsigned p2 = atoi(tuples[i+1]);
unsigned p3 = atoi(tuples[i+2]);
array_push(a.frames[index].indices, vec3i(p1,p2,p3));
}
}
else if( strend(k, ".coords") ) {
array_reserve_(a.frames, index);
const char *text = v;
array(char*) pairs = strsplit(text, ",");
for( int i = 0, end = array_count(pairs); i < end; i += 2 ) {
unsigned x = atoi(pairs[i]);
unsigned y = atoi(pairs[i+1]);
array_push(a.frames[index].coords, vec2(x,y));
}
}
else if( strend(k, ".uvs") ) {
array_reserve_(a.frames, index);
const char *text = v;
array(char*) pairs = strsplit(text, ",");
for( int i = 0, end = array_count(pairs); i < end; i += 2 ) {
unsigned u = atoi(pairs[i]);
unsigned v = atoi(pairs[i+1]);
array_push(a.frames[index].uvs, vec2(u,v));
}
}
else if( strend(k, "padding") ) {
padding = atoi(v);
}
else if( strend(k, "border") ) {
border = atoi(v);
}
else if( strend(k, "file") ) {
a.tex = texture(v, 0);
}
else if( strend(k, "bitmap") ) {
const char *text = v;
array(char) bin = base64_decode(text, strlen(text));
a.tex = texture_from_mem(bin, array_count(bin), 0);
array_free(bin);
}
#if 0
else if( strend(k, ".frame") ) {
array_reserve_(a.frames, index);
puts(k), puts(v);
}
#endif
}
// post-process: normalize uvs and coords into [0..1] ranges
for each_array_ptr(a.frames, atlas_frame_t, f) {
for each_array_ptr(f->uvs, vec2, uv) {
uv->x /= a.tex.w;
uv->y /= a.tex.h;
}
for each_array_ptr(f->coords, vec2, xy) {
xy->x /= a.tex.w;
xy->y /= a.tex.h;
}
// @todo: adjust padding/border
}
for each_array_ptr(a.slice_frames, atlas_slice_frame_t, f) {
f->bounds.x += padding+border;
f->bounds.y += padding+border;
f->bounds.z += padding+border;
f->bounds.w += padding+border;
}
#if 0
// post-process: specify an anchor for each anim based on 1st frame dims
for each_array_ptr(a.anims, atlas_anim_t, anim) {
atlas_frame_t *first = a.frames + *anim->frames;
for( int i = 0; i < array_count(anim->frames); i += 2) {
atlas_frame_t *ff = a.frames + anim->frames[ i ];
ff->anchor.x = (ff->sheet.z - first->sheet.z) / 2;
ff->anchor.y = (ff->sheet.w - first->sheet.w) / 2;
}
}
#endif
return a;
}
// ----------------------------------------------------------------------------
// sprite v2
void sprite_ctor(sprite_t *s) {
s->tint = WHITE;
s->timer_ms = 100;
s->flipped = 1;
s->sca.x += !s->sca.x;
s->sca.y += !s->sca.y;
}
void sprite_dtor(sprite_t *s) {
memset(s, 0, sizeof(*s));
}
void sprite_tick(sprite_t *s) {
int right = input(s->gamepad.array[3]) - input(s->gamepad.array[2]); // RIGHT - LEFT
int forward = input(s->gamepad.array[1]) - input(s->gamepad.array[0]); // DOWN - UP
int move = right || forward;
int dt = 16; // window_delta() * 1000;
unsigned over = (s->timer - dt) > s->timer;
if(!s->paused) s->timer -= dt;
if( over ) {
int len = array_count(s->a->anims[s->play].frames);
unsigned next = (s->frame + 1) % (len + !len);
unsigned eoa = next < s->frame;
s->frame = next;
atlas_frame_t *f = &s->a->frames[ s->a->anims[s->play].frames[s->frame] ];
s->timer_ms = f->delay;
s->timer += s->timer_ms;
}
if( s->play == 0 && move ) sprite_setanim(s, 1);
if( s->play == 1 ) { //<
if(right) s->flip_ = right < 0, sprite_setanim(s, 1);
if(!right && !forward) sprite_setanim(s, 0);
float speed = s->sca.x*2;
s->pos = add4(s->pos, scale4(norm4(vec4(right,0,forward,0)),speed));
}
}
void sprite_draw(sprite_t *s) {
atlas_frame_t *f = &s->a->frames[ s->a->anims[s->play].frames[s->frame] ];
#if 1
// @todo {
unsigned sample = s->a->anims[s->play].frames[s->frame];
sample = 0;
f->anchor.x = (-s->a->frames[sample].sheet.z + f->sheet.z) / 2;
f->anchor.y = (+s->a->frames[sample].sheet.w - f->sheet.w) / 2;
// }
#endif
// rect(x,y,w,h) is [0..1] normalized, z-index, pos(x,y,scale), rotation (degrees), color (rgba)
vec4 rect = { f->sheet.x / s->a->tex.w, f->sheet.y / s->a->tex.h, f->sheet.z / s->a->tex.w, f->sheet.w / s->a->tex.h };
sprite_rect(s->a->tex, rect, s->pos, vec4(s->flip_ ^ s->flipped?s->sca.x:-s->sca.x,s->sca.y,f->anchor.x,f->anchor.y), s->tilt, s->tint, 0|SPRITE_PROJECTED);
}
void sprite_edit(sprite_t *s) {
const char *name = obj_name(s);
const char *id = vac("%p", s);
if( s && ui_collapse(name ? name : id, id) ) {
ui_obj("%s", (obj*)s);
ui_bool("paused", &s->paused);
ui_label(va("frame anim [%d]", s->a->anims[s->play].frames[s->frame]));
int k = s->play;
if( ui_int("anim", &k) ) {
sprite_setanim(s, k);
}
int selected = ui_atlas(s->a);
if( selected ) sprite_setanim(s, selected - 1);
ui_collapse_end();
}
}
sprite_t* sprite_new(const char *ase, int bindings[6]) {
sprite_t *s = obj_new(sprite_t, {bindings[0],bindings[1],bindings[2],bindings[3]}, {bindings[4],bindings[5]});
atlas_t own = atlas_create(ase, 0);
memcpy(s->a = MALLOC(sizeof(atlas_t)), &own, sizeof(atlas_t)); // s->a = &s->own;
return s;
}
void sprite_del(sprite_t *s) {
if( s ) {
if( s->a ) atlas_destroy(s->a), FREE(s->a); // if( s->a == &s->own )
obj_free(s);
memset(s, 0, sizeof(sprite_t));
}
}
void sprite_setanim(sprite_t *s, unsigned name) {
if( s->play != name ) {
s->play = name;
s->frame = 0;
atlas_frame_t *f = &s->a->frames[ s->a->anims[s->play].frames[s->frame] ];
s->timer_ms = f->delay;
s->timer = s->timer_ms;
}
}
AUTORUN {
STRUCT(sprite_t, vec4, pos);
STRUCT(sprite_t, vec2, sca);
STRUCT(sprite_t, float, tilt);
STRUCT(sprite_t, vec4, gamepad);
STRUCT(sprite_t, vec2, fire);
STRUCT(sprite_t, rgba, tint);
STRUCT(sprite_t, unsigned, frame);
STRUCT(sprite_t, unsigned, timer);
STRUCT(sprite_t, unsigned, timer_ms);
STRUCT(sprite_t, unsigned, flipped);
STRUCT(sprite_t, unsigned, play);
EXTEND_T(sprite, ctor,edit,draw,tick);
}
#line 0
#line 1 "v4k_system.c"
#if (is(tcc) && is(linux)) || (is(gcc) && !is(mingw)) // || is(clang)
int __argc; char **__argv;
#if !is(ems)
__attribute__((constructor)) void init_argcv(int argc, char **argv) { __argc = argc; __argv = argv; }
#endif
#endif
void argvadd(const char *arg) {
char **argv = MALLOC( sizeof(char*) * (__argc+1) );
for( int i = 0; i < __argc; ++i ) {
argv[i] = __argv[i];
}
argv[__argc] = STRDUP(arg);
__argv = argv;
++__argc;
}
const char *app_path() { // @fixme: should return absolute path always. see tcc -g -run
static char buffer[1024] = {0};
if( buffer[0] ) return buffer;
#if is(win32)
unsigned length = GetModuleFileNameA(NULL, buffer, sizeof(buffer)); // @todo: use GetModuleFileNameW+wchar_t && convert to utf8 instead
char *a = strrchr(buffer, '/'); if(!a) a = buffer + strlen(buffer);
char *b = strrchr(buffer, '\\'); if(!b) b = buffer + strlen(buffer);
char slash = (a < b ? *a : b < a ? *b : '/');
snprintf(buffer, 1024, "%.*s%c", length - (int)(a < b ? b - a : a - b), buffer, slash);
if( strendi(buffer, "tools\\tcc\\") ) { // fix tcc -g -run case. @fixme: use TOOLS instead
strcat(buffer, "..\\..\\");
}
#else // #elif is(linux)
char path[32] = {0};
sprintf(path, "/proc/%d/exe", getpid());
readlink(path, buffer, sizeof(buffer));
if(strrchr(buffer,'/')) 1[strrchr(buffer,'/')] = '\0';
#endif
return buffer;
}
const char *app_temp() {
static char buffer[256] = {0};
if( !buffer[0] ) {
snprintf(buffer, 256, "%s", ifdef(win32, getenv("TEMP"), P_tmpdir));
for( int i = 0; buffer[i]; ++i ) if( buffer[i] == '\\' ) buffer[i] = '/';
if(buffer[strlen(buffer)-1] != '/') strcat(buffer, "/");
}
return buffer;
}
/*
bool exporting_dll = !strcmp(STRINGIZE(API), STRINGIZE(EXPORT));
bool importing_dll = !strcmp(STRINGIZE(API), STRINGIZE(IMPORT));
else static_build
*/
#ifndef APP_NAME
#define APP_NAME ifdef(ems, "", (__argv ? __argv[0] : ""))
#endif
const char *app_name() {
static char buffer[256] = {0};
if( !buffer[0] ) {
char s[256];
strncpy(s, APP_NAME, 256);
char *a = strrchr(s, '/');
char *b = strrchr(s, '\\');
strncpy(buffer, a > b ? a+1 : b > a ? b+1 : s, 256);
if(strendi(buffer, ".exe")) buffer[strlen(buffer) - 4] = 0;
}
return buffer;
}
const char *app_cmdline() {
static char *cmdline = 0;
if( !cmdline ) {
if( argc() <= 1 ) strcatf(&cmdline, "%s", " ");
for( int i = 1; i < argc(); ++i ) strcatf(&cmdline, " %s", argv(i));
}
return cmdline+1;
}
const char *app_cache() {
static char buffer[256] = {0};
if( !buffer[0] ) {
#if is(osx)
snprintf(buffer, 256, "~/Library/Caches/%s/", app_name()); // getenv("user.home")
#elif is(win32) // APPDATA for roaming?
snprintf(buffer, 256, "%s\\%s\\", getenv("LOCALAPPDATA"), app_name()); // getenv("LOCALAPPDATA")
#else // linux
snprintf(buffer, 256, "~/.cache/%s/", app_name()); // getenv("user.home")
#endif
mkdir(buffer, 0777);
for( int i = 0; buffer[i]; ++i ) if( buffer[i] == '\\' ) buffer[i] = '/';
}
return buffer;
}
const char * app_exec( const char *cmd ) {
static __thread char output[4096+16] = {0};
char *buf = output + 16; buf[0] = 0; // memset(buf, 0, 4096);
if( !cmd[0] ) return "0 ";
cmd = file_normalize(cmd);
int rc = -1;
// pick the fastest code path per platform
#if is(osx)
rc = system(cmd);
#elif is(win32)
STARTUPINFOA si = {0}; si.cb = sizeof(si);
PROCESS_INFORMATION pi = {0};
snprintf(output+16, 4096, "cmd /c \"%s\"", cmd);
int prio = //strstr(cmd, "ffmpeg") || strstr(cmd, "furnace") || strstr(cmd, "ass2iqe") ?
REALTIME_PRIORITY_CLASS; //: 0;
//prio |= DETACHED_PROCESS;
//si.dwFlags = STARTF_USESTDHANDLES;
if( CreateProcessA(
NULL, output+16, // cmdline
NULL,
NULL,
FALSE, // FALSE: dont inherit handles
prio /*CREATE_DEFAULT_ERROR_MODE|CREATE_NO_WINDOW*/, // 0|HIGH_PRIORITY_CLASS
NULL, // "", // NULL would inherit env
NULL, // current dir
&si, &pi) )
{
// Wait for process
DWORD dwExitCode2 = WaitForSingleObject(pi.hProcess, INFINITE);
DWORD dwExitCode; GetExitCodeProcess(pi.hProcess, &dwExitCode);
rc = dwExitCode;
}
else
{
// CreateProcess() failed
rc = GetLastError();
}
#else
rc = system(cmd);
#endif
return snprintf(output, 16, "%-15d", rc), buf[-1] = ' ', output;
}
int app_spawn( const char *cmd ) {
if( !cmd[0] ) return false;
cmd = file_normalize(cmd);
#if _WIN32
bool ok = WinExec(va("cmd /c \"%s\"", cmd), SW_HIDE) > 31;
#else
bool ok = system(va("%s &", cmd)) == 0;
#endif
return ok;
}
#if is(osx)
#include <execinfo.h> // backtrace, backtrace_symbols
#include <dlfcn.h> // dladdr, Dl_info
#elif is(gcc) && !is(ems) && !is(mingw) // maybe is(linux) is enough?
#include <execinfo.h> // backtrace, backtrace_symbols
#elif is(win32) // && !defined __TINYC__
#include <winsock2.h> // windows.h alternative
#include <dbghelp.h>
#pragma comment(lib, "DbgHelp")
#pragma comment(lib, "Kernel32")
static int backtrace( void **addr, int maxtraces ) {
static bool init = 0;
do_once SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_INCLUDE_32BIT_MODULES);
do_once init = SymInitialize(GetCurrentProcess(), NULL, TRUE);
if(!init) return 0; // error: cannot initialize DbgHelp.lib
//typedef USHORT (WINAPI *pFN)(__in ULONG, __in ULONG, __out PVOID*, __out_opt PULONG); // _MSC_VER
typedef USHORT (WINAPI *pFN)(); // TINYC
static pFN rtlCaptureStackBackTrace = 0;
if( !rtlCaptureStackBackTrace ) {
rtlCaptureStackBackTrace = (pFN)GetProcAddress(LoadLibraryA("kernel32.dll"), "RtlCaptureStackBackTrace");
}
if( !rtlCaptureStackBackTrace ) {
return 0;
}
return rtlCaptureStackBackTrace(1, maxtraces, (PVOID *)addr, (DWORD *) 0);
}
static char **backtrace_symbols(void *const *list,int size) {
HANDLE process = GetCurrentProcess();
struct symbol_t {
SYMBOL_INFO info;
TCHAR symbolname[256], terminator;
} si = { {0} };
si.info.SizeOfStruct = sizeof(SYMBOL_INFO);
si.info.MaxNameLen = sizeof(si.symbolname) / sizeof(TCHAR); // number of chars, not bytes
IMAGEHLP_LINE l64 = { 0 };
l64.SizeOfStruct = sizeof(IMAGEHLP_LINE);
static __thread char **symbols = 0; //[32][64] = {0};
if( !symbols ) {
symbols = SYS_MEM_REALLOC(0, 128 * sizeof(char*));
for( int i = 0; i < 128; ++i) symbols[i] = SYS_MEM_REALLOC(0, 128 * sizeof(char));
}
if(size > 128) size = 128;
for( int i = 0; i < size; ++i ) {
char *ptr = symbols[i];
*ptr = '\0';
if (SymFromAddr(process, (DWORD64)(uintptr_t)list[i], 0, &si.info)) {
//char undecorated[1024];
//UnDecorateSymbolName(si.info.Name, undecorated, sizeof(undecorated)-1, UNDNAME_COMPLETE);
char* undecorated = (char*)si.info.Name;
ptr += snprintf(ptr, 128, "%s", undecorated);
} else {
ptr += snprintf(ptr, 128, "%s", "(?""?)");
}
DWORD dw = 0;
if (SymGetLineFromAddr(process, (DWORD64)(uintptr_t)list[i], &dw, &l64)) {
ptr += snprintf(ptr, 128 - (ptr - symbols[i]), " (%s:%u)", l64.FileName, (unsigned)l64.LineNumber);
}
}
return symbols;
}
#else
static int backtrace(void **heap, int num) { return 0; }
static char **backtrace_symbols(void *const *sym,int num) { return 0; }
#endif
char *callstack( int traces ) {
#if is(tcc) && is(linux)
return "";
#endif
#if is(ems) // there is a stack overflow failure somewhere in the impl below
static char empty[1]; return empty[0] = '\0', empty;
#endif
enum { skip = 1 }; /* exclude 1 trace from stack (this function) */
enum { maxtraces = 96 };
static __thread char *output = 0;
if(!output ) output = SYS_MEM_REALLOC( 0, maxtraces * (128+2) );
if( output ) output[0] = '\0';
char *ptr = output;
int inc = 1;
if( traces < 0 ) traces = -traces, inc = -1;
if( traces == 0 ) return "";
if( traces > maxtraces ) traces = maxtraces;
void* stacks[maxtraces + 1]; stacks[maxtraces] = NULL; // = { 0 };
traces = backtrace( stacks, traces );
char **symbols = backtrace_symbols( stacks, traces ); // @todo: optimization: map(void*,char*) cache; and retrieve only symbols not in cache
char demangled[1024] = "??";
int L = 0, B = inc>0 ? skip - 1 : traces, E = inc>0 ? traces : skip - 1;
for( int i = B; ( i += inc ) != E; ) {
#if is(linux)
#if ENABLE_LINUX_CALLSTACKS
// @fixme: following snippet works if compiled with '-g', albeit terribly slow
// should concat addresses into a multi-address line
char *binary = symbols[i];
char *address = strchr( symbols[i], '(' ) + 1;
*strrchr( address, ')') = '\0'; *(address - 1) = '\0';
for( FILE *fp = popen(va("addr2line -e %s %s", binary, address), "r" ); fp ; pclose(fp), fp = 0 ) { //addr2line -e binary -f -C address
fgets(demangled, sizeof(demangled), fp);
int len = strlen(demangled); while( len > 0 && demangled[len-1] < 32 ) demangled[--len] = 0;
}
symbols[i] = demangled;
#else
// make it shorter. ie, `0x00558997ccc87e ./a.out(+0x20187e) [0x00558997ccc87e]`
strchr(symbols[i], ')')[1] = '\0';
#endif
#elif is(osx)
/*struct*/ Dl_info info;
if( dladdr(stacks[i], &info) && info.dli_sname ) {
const char *dmgbuf = info.dli_sname[0] != '_' ? NULL :
ifdef(cpp, __cxa_demangle(info.dli_sname, NULL, 0, NULL), info.dli_sname);
strcpy( demangled, dmgbuf ? dmgbuf : info.dli_sname );
symbols[i] = demangled;
ifdef(cpp, dmgbuf && free( (void*)dmgbuf ) );
}
#endif
if( symbols[i] )
ptr += sprintf(ptr, "%03d: %p %s\n", ++L, (void*)(uintptr_t)stacks[i], symbols[i]); // format gymnastics because %p is not standard when printing pointers
}
#if is(linux) || is(osx)
if(symbols) free(symbols);
#endif
return output ? output : "";
}
int callstackf( FILE *fp, int traces ) {
char *buf = callstack(traces);
fputs(buf, fp);
return 0;
}
// trap signals ---------------------------------------------------------------
const char *trap_name(int signal) {
if(signal == SIGABRT) return "SIGABRT - \"abort\", abnormal termination";
if(signal == SIGFPE) return "SIGFPE - floating point exception";
if(signal == SIGILL) return "SIGILL - \"illegal\", invalid instruction";
if(signal == SIGSEGV) return "SIGSEGV - \"segmentation violation\", invalid memory access";
if(signal == SIGINT) return "SIGINT - \"interrupt\", interactive attention request sent to the program";
if(signal == SIGTERM) return "SIGTERM - \"terminate\", termination request sent to the program";
ifndef(win32, if(signal == SIGBUS) return "SIGBUS");
ifdef(linux, if(signal == SIGSTKFLT) return "SIGSTKFLT");
ifndef(win32, if(signal == SIGQUIT) return "SIGQUIT");
return "??";
}
void trap_on_ignore(int sgn) {
signal(sgn, trap_on_ignore);
}
void trap_on_quit(int sgn) {
signal(sgn, trap_on_quit);
exit(0);
}
void trap_on_abort(int sgn) {
char *cs = va("Error: unexpected signal %s (%d)\n%s", trap_name(sgn), sgn, callstack(+16));
fprintf(stderr, "%s\n", cs), alert(cs), breakpoint();
signal(sgn, trap_on_abort);
exit(-sgn);
}
void trap_on_debug(int sgn) { // @todo: rename to trap_on_choice() and ask the developer what to do next? abort, continue, debug
char *cs = va("Error: unexpected signal %s (%d)\n%s", trap_name(sgn), sgn, callstack(+16));
fprintf(stderr, "%s\n", cs), alert(cs), breakpoint();
signal(sgn, trap_on_debug);
}
#if is(win32)
LONG WINAPI trap_on_SEH(PEXCEPTION_POINTERS pExceptionPtrs) {
char *cs = va("Error: unexpected SEH exception\n%s", callstack(+16));
fprintf(stderr, "%s\n", cs), alert(cs), breakpoint();
return EXCEPTION_EXECUTE_HANDLER; // Execute default exception handler next
}
#endif
void trap_install(void) {
// expected signals
signal(SIGINT, trap_on_quit);
signal(SIGTERM, trap_on_quit);
ifndef(win32, signal(SIGQUIT, trap_on_quit));
// unexpected signals
signal(SIGABRT, trap_on_abort);
signal(SIGFPE, trap_on_abort);
signal(SIGILL, trap_on_abort);
signal(SIGSEGV, trap_on_abort);
ifndef(win32, signal(SIGBUS, trap_on_abort));
ifdef(linux, signal(SIGSTKFLT, trap_on_abort));
// others
ifdef(win32,SetUnhandledExceptionFilter(trap_on_SEH));
}
#ifdef TRAP_DEMO
AUTORUN {
trap_install();
app_crash(); // app_hang();
}
#endif
// cpu -------------------------------------------------------------------------
#if is(linux)
#include <sched.h>
#endif
int app_cores() {
#if is(win32)
DWORD_PTR pm, sm;
if( GetProcessAffinityMask(GetCurrentProcess(), &pm, &sm) ) if( pm ) {
int count = 0;
while( pm ) {
++count;
pm &= pm - 1;
}
return count;
}
{ SYSTEM_INFO si; GetSystemInfo(&si); return (int)si.dwNumberOfProcessors; }
#else // unix
int count = sysconf(_SC_NPROCESSORS_ONLN);
return count > 0 ? count : 1;
#endif
#if 0
#elif is(linux)
cpu_set_t prevmask, testmask;
CPU_ZERO(&prevmask);
CPU_ZERO(&testmask);
sched_getaffinity(0, sizeof(prevmask), &prevmask); //Get current mask
sched_setaffinity(0, sizeof(testmask), &testmask); //Set zero mask
sched_getaffinity(0, sizeof(testmask), &testmask); //Get mask for all CPUs
sched_setaffinity(0, sizeof(prevmask), &prevmask); //Reset current mask
int num = CPU_COUNT(&testmask);
return (num > 1 ? num : 1);
#elif is(cpp)
return (int)std::thread::hardware_concurrency();
#elif defined(_OPENMP)
// omp
int cores = 0;
#pragma omp parallel
{
#pragma omp atomic
++cores;
}
return cores;
#endif
}
// -----------------------------------------------------------------------------
// Battery API. Based on code by Rabia Alhaffar (UNLICENSE)
// - rlyeh, public domain.
#if is(win32)
#include <winsock2.h>
int app_battery() {
SYSTEM_POWER_STATUS ibstatus;
if (GetSystemPowerStatus(&ibstatus) == FALSE) {
return 0;
}
int level = (ibstatus.BatteryLifePercent != 255) ? ibstatus.BatteryLifePercent : 0;
int charging = (ibstatus.BatteryFlag & 8) > 0;
return charging ? +level : -level;
}
#elif defined __linux__ // is(linux)
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int app_battery() {
static int battery_status_handle;
static int battery_capacity_handle;
do_once {
battery_status_handle = open("/sys/class/power_supply/BAT0/status", O_RDONLY);
battery_capacity_handle = open("/sys/class/power_supply/BAT0/capacity", O_RDONLY);
}
if (battery_status_handle == -1 || battery_capacity_handle == -1) {
return 0;
}
char buffer[512];
// level
lseek(battery_capacity_handle, 0, SEEK_SET);
int readlen = read(battery_capacity_handle, buffer, 511); buffer[readlen < 0 ? 0 : readlen] = '\0';
int level = atoi(buffer);
// charging
lseek(battery_status_handle, 0, SEEK_SET);
readlen = read(battery_status_handle, buffer, 511); buffer[readlen < 0 ? 0 : readlen] = '\0';
int charging = strstr(buffer, "Discharging") ? 0 : 1;
return charging ? +level : -level;
}
#elif is(osx)
#import <Foundation/Foundation.h>
#include <CoreFoundation/CoreFoundation.h>
#import <IOKit/ps/IOPowerSources.h>
#import <IOKit/ps/IOPSKeys.h>
int app_battery() {
static CFDictionaryRef psrc;
do_once {
CFTypeRef blob = IOPSCopyPowerSourcesInfo();
CFArrayRef sources = IOPSCopyPowerSourcesList(blob);
int sourcesCount = CFArrayGetCount(sources);
if (sourcesCount > 0) {
psrc = IOPSGetPowerSourceDescription(blob, CFArrayGetValueAtIndex(sources, 0));
}
}
if(psrc == NULL) return 0;
int cur_cap = 0;
CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(psrc, CFSTR(kIOPSCurrentCapacityKey)), kCFNumberSInt32Type, &cur_cap);
int max_cap = 0;
CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(psrc, CFSTR(kIOPSMaxCapacityKey)), kCFNumberSInt32Type, &max_cap);
int level = (int)(cur_cap * 100.f / max_cap);
int charging = CFDictionaryGetValue(psrc, CFSTR(kIOPSIsChargingKey)) == kCFBooleanTrue;
return charging ? +level : -level;
}
#else
int app_battery() {
return 0;
}
#endif
// ----------------------------------------------------------------------------
// argc/v
static void argc_init() {
#if is(tcc) && is(linux)
do_once {
char buffer[128], arg0[128] = {0};
for( FILE *fp = fopen("/proc/self/status", "rb"); fp; fclose(fp), fp = 0) {
while( fgets(buffer, 128, fp) ) {
if( strbeg(buffer, "Name:") ) {
sscanf(buffer + 5, "%s", arg0 );
break;
}
}
}
extern char **environ;
__argv = environ - 2; // last argv, as stack is [argc][argv0][argv1][...][NULL][envp]
while( !strend(*__argv,arg0) ) --__argv;
__argc = *(int*)(__argv-1);
}
#endif
}
int argc() {
do_once argc_init();
return __argc;
}
char* argv(int arg) {
do_once argc_init();
static __thread char empty[1];
return (unsigned)arg < __argc ? __argv[arg] : (empty[0] = '\0', empty);
}
// ----------------------------------------------------------------------------
// options
int flag(const char *commalist) {
while( commalist[0] ) {
const char *begin = commalist;
while(*commalist != ',' && *commalist != '\0') ++commalist;
const char *end = commalist;
char token[128];
snprintf(token, 128, "%.*s", (int)(end - begin), begin);
for( int i = 1; i < argc(); ++i ) {
char *arg = argv(i);
if( !strcmpi( arg, token ) ) { // --arg
return 1;
}
}
commalist = end + !!end[0];
}
return 0;
}
const char *option(const char *commalist, const char *defaults) {
while( commalist[0] ) {
const char *begin = commalist;
while(*commalist != ',' && *commalist != '\0') ++commalist;
const char *end = commalist;
char token[128], tokeneq[128];
snprintf(token, 128, "%.*s", (int)(end - begin), begin);
snprintf(tokeneq, 128, "%.*s=", (int)(end - begin), begin);
for( int i = 1; i < argc(); ++i ) {
char *arg = argv(i);
if( strbegi( arg, tokeneq ) ) { // --arg=value
return argv(i) + strlen(tokeneq);
}
if( !strcmpi( arg, token ) ) { // --arg value
if( (i+1) < argc() ) {
return argv(i+1);
}
}
}
commalist = end + !!end[0];
}
return defaults;
}
int optioni(const char *commalist, int defaults) {
const char *rc = option(commalist, 0);
return rc ? atoi(rc) : defaults;
}
float optionf(const char *commalist, float defaults) {
const char *rc = option(commalist, 0);
return rc ? atof(rc) : defaults;
}
// ----------------------------------------------------------------------------
// tty
void tty_color(unsigned color) {
#if is(win32)
do_once {
DWORD mode = 0; SetConsoleMode(GetStdHandle(-11), (GetConsoleMode(GetStdHandle(-11), &mode), mode|4));
}
#endif
if( color ) {
// if( color == RED ) alert("break on error message (RED)"), breakpoint(); // debug
unsigned r = (color >> 0) & 255;
unsigned g = (color >> 8) & 255;
unsigned b = (color >> 16) & 255;
// 24-bit console ESC[ … 38;2;<r>;<g>;<b> … m Select RGB foreground color
// 256-color console ESC[38;5;<fgcode>m
// 0x00-0x07: standard colors (as in ESC [ 30..37 m)
// 0x08-0x0F: high intensity colors (as in ESC [ 90..97 m)
// 0x10-0xE7: 6*6*6=216 colors: 16 + 36*r + 6*g + b (0≤r,g,b≤5)
// 0xE8-0xFF: grayscale from black to white in 24 steps
r /= 51, g /= 51, b /= 51; // [0..5]
printf("\033[38;5;%dm", r*36+g*6+b+16); // "\033[0;3%sm", color_code);
} else {
printf("%s", "\x1B[39;49m"); // reset
}
}
void tty_puts(unsigned color, const char *text) {
tty_color(color); puts(text);
}
void tty_init() {
tty_color(0);
}
int tty_cols() {
#if is(win32)
CONSOLE_SCREEN_BUFFER_INFO c;
if( GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &c) ) {
int w = c.srWindow.Right-c.srWindow.Left-c.dwCursorPosition.X;
return w > 2 ? w - 1 : w; // w-1 to allow window resizing to a larger dimension (already printed text would break otherwise)
}
#endif
#ifdef TIOCGWINSZ
struct winsize ws;
ioctl(STDIN_FILENO, TIOCGWINSZ, &ws);
return ws.ws_col - 1;
#endif
#ifdef TIOCGSIZE
struct ttysize ts;
ioctl(STDIN_FILENO, TIOCGSIZE, &ts);
return ts.ts_cols - 1;
#endif
return 80;
}
void tty_detach() {
ifdef(win32, FreeConsole());
}
void tty_attach() {
#if is(win32)
// in order to have a Windows gui application with console:
// - use WinMain() then AllocConsole(), but that may require supporintg different entry points for different platforms.
// - /link /SUBSYSTEM:CONSOLE and then call FreeConsole() if no console is needed, but feels naive to flash the terminal for a second.
// - /link /SUBSYSTEM:WINDOWS /entry:mainCRTStartup, then AllocConsole() as follows. Quoting @pmttavara:
// "following calls are the closest i'm aware you can get to /SUBSYSTEM:CONSOLE in a gui program
// while cleanly handling existing consoles (cmd.exe), pipes (ninja) and no console (VS/RemedyBG; double-clicking the game)"
do_once {
if( !AttachConsole(ATTACH_PARENT_PROCESS) && GetLastError() != ERROR_ACCESS_DENIED ) { bool ok = !!AllocConsole(); ASSERT( ok ); }
printf("\n"); // print >= 1 byte to distinguish empty stdout from a redirected stdout (fgetpos() position <= 0)
fpos_t pos = 0;
if( fgetpos(stdout, &pos) != 0 || pos <= 0 ) {
bool ok1 = !!freopen("CONIN$" , "r", stdin ); ASSERT( ok1 );
bool ok2 = !!freopen("CONOUT$", "w", stderr); ASSERT( ok2 );
bool ok3 = !!freopen("CONOUT$", "w", stdout); ASSERT( ok3 );
}
}
#endif
}
// -----------------------------------------------------------------------------
// debugger
#include <stdio.h>
void hexdumpf( FILE *fp, const void *ptr, unsigned len, int width ) {
unsigned char *data = (unsigned char*)ptr;
for( unsigned jt = 0; jt <= len; jt += width ) {
fprintf( fp, "; %05d%s", jt, jt == len ? "\n" : " " );
for( unsigned it = jt, next = it + width; it < len && it < next; ++it ) {
fprintf( fp, "%02x %s", (unsigned char)data[it], &" \n\0...\n"[ (1+it) < len ? 2 * !!((1+it) % width) : 3 ] );
}
fprintf( fp, "; %05d%s", jt, jt == len ? "\n" : " " );
for( unsigned it = jt, next = it + width; it < len && it < next; ++it ) {
fprintf( fp, " %c %s", (signed char)data[it] >= 32 ? (signed char)data[it] : (signed char)'.', &" \n\0..."[ (1+it) < len ? 2 * !!((1+it) % width) : 3 ] );
}
}
fprintf(fp, " %d bytes\n", len);
}
void hexdump( const void *ptr, unsigned len ) {
hexdumpf( stdout, ptr, len, 16 );
}
#if 0 // is(cl) only
static void debugbreak(void) {
do { \
__try { DebugBreak(); } \
__except (GetExceptionCode() == EXCEPTION_BREAKPOINT ? \
EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {} \
} while(0);
}
#endif
#if is(win32)
static void debugbreak(void) { if(IsDebuggerPresent()) DebugBreak(); }
#else // is(unix)
static int is_debugger_present = -1;
static void _sigtrap_handler(int signum) {
is_debugger_present = 0;
signal(SIGTRAP, SIG_DFL);
}
static void debugbreak(void) { // break if debugger present
// __builtin_trap(); //
//raise(SIGABRT); // SIGTRAP);
//__asm__ volatile("int $0x03");
if( is_debugger_present < 0 ) {
is_debugger_present = 1;
signal(SIGTRAP, _sigtrap_handler);
raise(SIGTRAP);
}
}
#endif
void alert(const char *message) { // @todo: move to app_, besides die()
window_visible(false);
message = message[0] == '!' ? (const char*)va("%s\n%s", message+1, callstack(+48)) : message;
#if is(win32)
MessageBoxA(0, message, 0,0);
#elif is(ems)
emscripten_run_script(va("alert('%s')", message));
#elif is(linux)
for(FILE *fp = fopen("/tmp/v4k.warning","wb");fp;fp=0)
fputs(message,fp), fclose(fp), system("xmessage -center -file /tmp/v4k.warning");
#elif is(osx)
system(va("osascript -e 'display alert \"Alert\" message \"%s\"'", message));
#endif
window_visible(true);
}
void breakpoint() {
debugbreak();
}
bool has_debugger() {
#if is(win32)
return IsDebuggerPresent(); // SetLastError(123); OutputDebugStringA("\1"); enabled = GetLastError() != 123;
#else
return false;
#endif
}
void die(const char *message) {
fprintf(stderr, "%s\n", message);
fflush(stderr);
alert(message);
exit(-1);
}
// ----------------------------------------------------------------------------
// logger
//static int __thread _thread_id;
//#define PRINTF(...) (printf("%03d %07.3fs|%-16s|", (((unsigned)(uintptr_t)&_thread_id)>>8) % 1000, time_ss(), __FUNCTION__), printf(__VA_ARGS__), printf("%s", 1[#__VA_ARGS__] == '!' ? callstack(+48) : "")) // verbose logger
int (PRINTF)(const char *text, const char *stack, const char *file, int line, const char *function) {
double secs = time_ss();
uint32_t color = 0;
/**/ if( strstri(text, "fail") || strstri(text, "error") ) color = RED;
else if( strstri(text, "warn") || strstri(text, "not found") ) color = YELLOW;
#if is(cl)
char *slash = strrchr(file, '\\'); if(slash) file = slash + 1;
#endif
char *location = va("|%s|%s:%d", /*errno?strerror(errno):*/function, file, line);
int cols = tty_cols() + 1 - (int)strlen(location);
flockfile(stdout);
tty_color(color);
printf("\r%*.s%s", cols, "", location);
printf("\r%07.3fs|%s%s", secs, text, stack);
tty_color(0);
funlockfile(stdout);
return 1;
}
// ----------------------------------------------------------------------------
// panic
static void *panic_oom_reserve; // for out-of-memory recovery
int (PANIC)(const char *error, const char *file, int line) {
panic_oom_reserve = SYS_MEM_REALLOC(panic_oom_reserve, 0);
tty_color(RED);
error += error[0] == '!';
fprintf(stderr, "Error: %s (%s:%d) (errno:%s)\n", error, file, line, strerror(errno));
fprintf(stderr, "%s", callstack(+16)); // no \n
fflush(0); // fflush(stderr);
tty_color(0);
alert(error);
breakpoint();
exit(-line);
return 1;
}
// ----------------------------------------------------------------------------
// threads
struct thread_wrapper {
int (*func)(void *user_data);
void *user_data;
};
static
int thread_proc( void* user_data ) {
struct thread_wrapper *w = (struct thread_wrapper*)user_data;
int return_code = w->func( w->user_data );
thread_exit( return_code );
FREE(w);
return 0;
}
void* thread( int (*thread_func)(void* user_data), void* user_data ) {
struct thread_wrapper *w = MALLOC(sizeof(struct thread_wrapper));
w->func = thread_func;
w->user_data = user_data;
int thread_stack_size = 0;
const char *thread_name = "";
thread_ptr_t thd = thread_init( thread_proc, w, thread_name, thread_stack_size );
return thd;
}
void thread_destroy( void *thd ) {
int rc = thread_join(thd);
thread_term(thd);
}
void app_hang() {
for(;;);
}
void app_crash() {
volatile int *p = 0;
*p = 42;
}
void app_beep() {
ifdef(win32, app_spawn("rundll32 user32.dll,MessageBeep"); return; );
ifdef(linux, app_spawn("paplay /usr/share/sounds/freedesktop/stereo/message.oga"); return; );
ifdef(osx, app_spawn("tput bel"); return; );
//fallback:
fputc('\x7', stdout);
// win32:
// rundll32 user32.dll,MessageBeep ; ok
// rundll32 cmdext.dll,MessageBeepStub ; ok
// osx:
// tput bel
// say "beep"
// osascript -e 'beep'
// osascript -e "beep 1"
// afplay /System/Library/Sounds/Ping.aiff
// /usr/bin/printf "\a"
// linux:
// paplay /usr/share/sounds/freedesktop/stereo/message.oga ; ok
// paplay /usr/share/sounds/freedesktop/stereo/complete.oga ; ok
// paplay /usr/share/sounds/freedesktop/stereo/bell.oga ; ok
// beep ; apt-get
// echo -e '\007' ; mute
// echo -e "\007" >/dev/tty10 ; sudo
// tput bel ; mute
}
void app_singleton(const char *guid) {
#ifdef _WIN32
do_once {
char buffer[128];
snprintf(buffer, 128, "Global\\{%s}", guid);
static HANDLE app_mutex = 0;
app_mutex = CreateMutexA(NULL, FALSE, buffer);
if( ERROR_ALREADY_EXISTS == GetLastError() ) {
exit(-1);
}
}
#endif
}
#ifdef APP_SINGLETON_GUID
AUTORUN { app_singleton(APP_SINGLETON_GUID); }
#endif
static
bool app_open_folder(const char *file) {
char buf[1024];
#ifdef _WIN32
snprintf(buf, sizeof(buf), "start \"\" \"%s\"", file);
#elif __APPLE__
snprintf(buf, sizeof(buf), "%s \"%s\"", file_directory(file) ? "open" : "open --reveal", file);
#else
snprintf(buf, sizeof(buf), "xdg-open \"%s\"", file);
#endif
return app_spawn(buf);
}
static
bool app_open_file(const char *file) {
char buf[1024];
#ifdef _WIN32
snprintf(buf, sizeof(buf), "start \"\" \"%s\"", file);
#elif __APPLE__
snprintf(buf, sizeof(buf), "open \"%s\"", file);
#else
snprintf(buf, sizeof(buf), "xdg-open \"%s\"", file);
#endif
return app_spawn(buf);
}
static
bool app_open_url(const char *url) {
return app_open_file(url);
}
bool app_open(const char *link) {
if( file_directory(link) ) return app_open_folder(link);
if( file_exist(link) ) return app_open_file(link);
return app_open_url(link);
}
const char* app_loadfile() {
const char *windowTitle = NULL;
const char *defaultPathFile = NULL;
const char *filterHints = NULL; // "image files"
const char *filters[] = { "*.*" };
int allowMultipleSelections = 0;
tinyfd_assumeGraphicDisplay = 1;
return tinyfd_openFileDialog( windowTitle, defaultPathFile, countof(filters), filters, filterHints, allowMultipleSelections );
}
const char* app_savefile() {
const char *windowTitle = NULL;
const char *defaultPathFile = NULL;
const char *filterHints = NULL; // "image files"
const char *filters[] = { "*.*" };
tinyfd_assumeGraphicDisplay = 1;
return tinyfd_saveFileDialog( windowTitle, defaultPathFile, countof(filters), filters, filterHints );
}
// ----------------------------------------------------------------------------
// tests
static __thread int test_oks, test_errors, test_once;
static void test_exit(void) { fprintf(stderr, "%d/%d tests passed\n", test_oks, test_oks+test_errors); }
int (test)(const char *file, int line, const char *expr, bool result) {
static int breakon = -1; if(breakon<0) breakon = optioni("--test-break", 0);
if( breakon == (test_oks+test_errors+1) ) alert("user requested to break on this test"), breakpoint();
test_once = test_once || !(atexit)(test_exit);
test_oks += result, test_errors += !result;
return (result || (tty_color(RED), fprintf(stderr, "(Test `%s` failed %s:%d)\n", expr, file, line), tty_color(0), 0) );
}
#line 0
#line 1 "v4k_time.c"
// ----------------------------------------------------------------------------
// time
#if 0
uint64_t time_gpu() {
GLint64 t = 123456789;
glGetInteger64v(GL_TIMESTAMP, &t);
return (uint64_t)t;
}
#endif
uint64_t date() {
time_t epoch = time(0);
struct tm *ti = localtime(&epoch);
return atoi64(va("%04d%02d%02d%02d%02d%02d",ti->tm_year+1900,ti->tm_mon+1,ti->tm_mday,ti->tm_hour,ti->tm_min,ti->tm_sec));
}
char *date_string() {
time_t epoch = time(0);
struct tm *ti = localtime(&epoch);
return va("%04d-%02d-%02d %02d:%02d:%02d",ti->tm_year+1900,ti->tm_mon+1,ti->tm_mday,ti->tm_hour,ti->tm_min,ti->tm_sec);
}
uint64_t date_epoch() {
time_t epoch = time(0);
return epoch;
}
#if 0
double time_ss() {
return glfwGetTime();
}
double time_ms() {
return glfwGetTime() * 1000.0;
}
uint64_t time_us() {
return (uint64_t)(glfwGetTime() * 1000000.0); // @fixme: use a high resolution timer instead, or time_gpu below
}
uint64_t sleep_us(uint64_t us) { // @fixme: use a high resolution sleeper instead
return sleep_ms( us / 1000.0 );
}
double sleep_ms(double ms) {
double now = time_ms();
if( ms <= 0 ) {
#if is(win32)
Sleep(0); // yield
#else
usleep(0);
#endif
} else {
#if is(win32)
Sleep(ms);
#else
usleep(ms * 1000);
#endif
}
return time_ms() - now;
}
double sleep_ss(double ss) {
return sleep_ms( ss * 1000 ) / 1000.0;
}
#endif
// high-perf functions
#define TIMER_E3 1000ULL
#define TIMER_E6 1000000ULL
#define TIMER_E9 1000000000ULL
#ifdef CLOCK_MONOTONIC_RAW
#define TIME_MONOTONIC CLOCK_MONOTONIC_RAW
#elif defined CLOCK_MONOTONIC
#define TIME_MONOTONIC CLOCK_MONOTONIC
#else
// #define TIME_MONOTONIC CLOCK_REALTIME // untested
#endif
static uint64_t nanotimer(uint64_t *out_freq) {
if( out_freq ) {
#if is(win32)
LARGE_INTEGER li;
QueryPerformanceFrequency(&li);
*out_freq = li.QuadPart;
//#elif is(ANDROID)
// *out_freq = CLOCKS_PER_SEC;
#elif defined TIME_MONOTONIC
*out_freq = TIMER_E9;
#else
*out_freq = TIMER_E6;
#endif
}
#if is(win32)
LARGE_INTEGER li;
QueryPerformanceCounter(&li);
return (uint64_t)li.QuadPart;
//#elif is(ANDROID)
// return (uint64_t)clock();
#elif defined TIME_MONOTONIC
struct timespec ts;
clock_gettime(TIME_MONOTONIC, &ts);
return (TIMER_E9 * (uint64_t)ts.tv_sec) + ts.tv_nsec;
#else
struct timeval tv;
gettimeofday(&tv, NULL);
return (TIMER_E6 * (uint64_t)tv.tv_sec) + tv.tv_usec;
#endif
}
uint64_t time_ns() {
static __thread uint64_t epoch = 0;
static __thread uint64_t freq = 0;
if( !freq ) {
epoch = nanotimer(&freq);
}
uint64_t a = nanotimer(NULL) - epoch;
uint64_t b = TIMER_E9;
uint64_t c = freq;
// Computes (a*b)/c without overflow, as long as both (a*b) and the overall result fit into 64-bits.
// [ref] https://github.com/rust-lang/rust/blob/3809bbf47c8557bd149b3e52ceb47434ca8378d5/src/libstd/sys_common/mod.rs#L124
uint64_t q = a / c;
uint64_t r = a % c;
return q * b + r * b / c;
}
uint64_t time_us() {
return time_ns() / TIMER_E3;
}
uint64_t time_ms() {
return time_ns() / TIMER_E6;
}
double time_ss() {
return time_ns() / 1e9; // TIMER_E9;
}
double time_mm() {
return time_ss() / 60;
}
double time_hh() {
return time_mm() / 60;
}
void sleep_ns( double ns ) {
#if is(win32)
if( ns >= 100 ) {
LARGE_INTEGER li; // Windows sleep in 100ns units
HANDLE timer = CreateWaitableTimer(NULL, TRUE, NULL);
li.QuadPart = (LONGLONG)(__int64)(-ns/100); // Negative for relative time
SetWaitableTimer(timer, &li, 0, NULL, NULL, FALSE);
WaitForSingleObject(timer, INFINITE);
CloseHandle(timer);
#else
if( ns > 0 ) {
struct timespec wait = {0};
wait.tv_sec = ns / 1e9;
wait.tv_nsec = ns - wait.tv_sec * 1e9;
nanosleep(&wait, NULL);
#endif
} else {
#if is(win32)
Sleep(0); // yield, Sleep(0), SwitchToThread
#else
usleep(0);
#endif
}
}
void sleep_us( double us ) {
sleep_ns(us * 1e3);
}
void sleep_ms( double ms ) {
sleep_ns(ms * 1e6);
}
void sleep_ss( double ss ) {
sleep_ns(ss * 1e9);
}
// ----------------------------------------------------------------------------
// timer
struct timer_internal_t {
unsigned ms;
unsigned (*callback)(unsigned interval, void *arg);
void *arg;
thread_ptr_t thd;
};
static int timer_func(void *arg) {
struct timer_internal_t *p = (struct timer_internal_t*)arg;
sleep_ms( p->ms );
for( ;; ) {
unsigned then = time_ms();
p->ms = p->callback(p->ms, p->arg);
if( !p->ms ) break;
unsigned now = time_ms();
unsigned lapse = now - then;
int diff = p->ms - lapse;
sleep_ms( diff <= 0 ? 0 : diff );
}
thread_exit(0);
return 0;
}
static __thread array(struct timer_internal_t *) timers;
unsigned timer(unsigned ms, unsigned (*callback)(unsigned ms, void *arg), void *arg) {
struct timer_internal_t *p = MALLOC( sizeof(struct timer_internal_t) );
p->ms = ms;
p->callback = callback;
p->arg = arg;
p->thd = thread_init( timer_func, p, "", 0 );
array_push(timers, p);
return array_count(timers);
}
void timer_destroy(unsigned i) {
if( i-- ) {
thread_join(timers[i]->thd);
thread_term(timers[i]->thd);
FREE(timers[i]);
timers[i] = 0;
}
}
// ----------------------------------------------------------------------------
// guid
//typedef vec3i guid;
guid guid_create() {
static __thread unsigned counter = 0;
static uint64_t appid = 0; do_once appid = hash_str(app_name());
union conv {
struct {
unsigned timestamp : 32;
unsigned threadid : 16; // inverted order in LE
unsigned appid : 16; //
unsigned counter : 32;
};
vec3i v3;
} c;
c.timestamp = date_epoch() - 0x65000000;
c.appid = (unsigned)appid;
c.threadid = (unsigned)(uintptr_t)thread_current_thread_id();
c.counter = ++counter;
return c.v3;
}
// ----------------------------------------------------------------------------
// ease
float ease_zero(float t) { return 0; }
float ease_one(float t) { return 1; }
float ease_linear(float t) { return t; }
float ease_out_sine(float t) { return sinf(t*(C_PI*0.5f)); }
float ease_out_quad(float t) { return -(t*(t-2)); }
float ease_out_cubic(float t) { float f=t-1; return f*f*f+1; }
float ease_out_quart(float t) { float f=t-1; return f*f*f*(1-t)+1; }
float ease_out_quint(float t) { float f=(t-1); return f*f*f*f*f+1; }
float ease_out_expo(float t) { return (t >= 1) ? t : 1-powf(2,-10*t); }
float ease_out_circ(float t) { return sqrtf((2-t)*t); }
float ease_out_back(float t) { float f=1-t; return 1-(f*f*f-f*sinf(f*C_PI)); }
float ease_out_elastic(float t) { return sinf(-13*(C_PI*0.5f)*(t+1))*powf(2,-10*t)+1; }
float ease_out_bounce(float t) { return (t < 4.f/11) ? (121.f*t*t)/16 : (t < 8.f/11) ? (363.f/40*t*t)-(99.f/10*t)+17.f/5 : (t < 9.f/10) ? (4356.f/361*t*t)-(35442.f/1805*t)+16061.f/1805 : (54.f/5*t*t)-(513.f/25*t)+268.f/25; }
float ease_in_sine(float t) { return 1+sinf((t-1)*(C_PI*0.5f)); }
float ease_in_quad(float t) { return t*t; }
float ease_in_cubic(float t) { return t*t*t; }
float ease_in_quart(float t) { return t*t*t*t; }
float ease_in_quint(float t) { return t*t*t*t*t; }
float ease_in_expo(float t) { return (t <= 0) ? t : powf(2,10*(t-1)); }
float ease_in_circ(float t) { return 1-sqrtf(1-(t*t)); }
float ease_in_back(float t) { return t*t*t-t*sinf(t*C_PI); }
float ease_in_elastic(float t) { return sinf(13*(C_PI*0.5f)*t)*powf(2,10*(t-1)); }
float ease_in_bounce(float t) { return 1-ease_out_bounce(1-t); }
float ease_inout_sine(float t) { return 0.5f*(1-cosf(t*C_PI)); }
float ease_inout_quad(float t) { return (t < 0.5f) ? 2*t*t : (-2*t*t)+(4*t)-1; }
float ease_inout_cubic(float t) { float f; return (t < 0.5f) ? 4*t*t*t : (f=(2*t)-2,0.5f*f*f*f+1); }
float ease_inout_quart(float t) { float f; return (t < 0.5f) ? 8*t*t*t*t : (f=(t-1),-8*f*f*f*f+1); }
float ease_inout_quint(float t) { float f; return (t < 0.5f) ? 16*t*t*t*t*t : (f=((2*t)-2),0.5f*f*f*f*f*f+1); }
float ease_inout_expo(float t) { return (t <= 0 || t >= 1) ? t : t < 0.5f ? 0.5f*powf(2,(20*t)-10) : -0.5f*powf(2,(-20*t)+10)+1; }
float ease_inout_circ(float t) { return t < 0.5f ? 0.5f*(1-sqrtf(1-4*(t*t))) : 0.5f*(sqrtf(-((2*t)-3)*((2*t)-1))+1); }
float ease_inout_back(float t) { float f; return t < 0.5f ? (f=2*t,0.5f*(f*f*f-f*sinf(f*C_PI))) : (f=(1-(2*t-1)),0.5f*(1-(f*f*f-f*sinf(f*C_PI)))+0.5f); }
float ease_inout_elastic(float t) { return t < 0.5f ? 0.5f*sinf(13*(C_PI*0.5f)*(2*t))*powf(2,10*((2*t)-1)) : 0.5f*(sinf(-13*(C_PI*0.5f)*((2*t-1)+1))*powf(2,-10*(2*t-1))+2); }
float ease_inout_bounce(float t) { return t < 0.5f ? 0.5f*ease_in_bounce(t*2) : 0.5f*ease_out_bounce(t*2-1)+0.5f; }
float ease_inout_perlin(float t) { float t3=t*t*t,t4=t3*t,t5=t4*t; return 6*t5-15*t4+10*t3; }
float ease(float t01, unsigned mode) {
typedef float (*easing)(float);
easing modes[] = {
ease_out_sine,
ease_out_quad,
ease_out_cubic,
ease_out_quart,
ease_out_quint,
ease_out_expo,
ease_out_circ,
ease_out_back,
ease_out_elastic,
ease_out_bounce,
ease_in_sine,
ease_in_quad,
ease_in_cubic,
ease_in_quart,
ease_in_quint,
ease_in_expo,
ease_in_circ,
ease_in_back,
ease_in_elastic,
ease_in_bounce,
ease_inout_sine,
ease_inout_quad,
ease_inout_cubic,
ease_inout_quart,
ease_inout_quint,
ease_inout_expo,
ease_inout_circ,
ease_inout_back,
ease_inout_elastic,
ease_inout_bounce,
ease_zero,
ease_one,
ease_linear,
ease_inout_perlin,
};
return modes[clampi(mode, 0, countof(modes))](clampf(t01,0,1));
}
float ease_pong(float t, unsigned fn) { return 1 - ease(t, fn); }
float ease_ping_pong(float t, unsigned fn1, unsigned fn2) { return t < 0.5 ? ease(t*2,fn1) : ease(1-(t-0.5)*2,fn2); }
float ease_pong_ping(float t, unsigned fn1, unsigned fn2) { return 1 - ease_ping_pong(t,fn1,fn2); }
const char **ease_enums() {
static const char *list[] = {
"ease_out_sine",
"ease_out_quad",
"ease_out_cubic",
"ease_out_quart",
"ease_out_quint",
"ease_out_expo",
"ease_out_circ",
"ease_out_back",
"ease_out_elastic",
"ease_out_bounce",
"ease_in_sine",
"ease_in_quad",
"ease_in_cubic",
"ease_in_quart",
"ease_in_quint",
"ease_in_expo",
"ease_in_circ",
"ease_in_back",
"ease_in_elastic",
"ease_in_bounce",
"ease_inout_sine",
"ease_inout_quad",
"ease_inout_cubic",
"ease_inout_quart",
"ease_inout_quint",
"ease_inout_expo",
"ease_inout_circ",
"ease_inout_back",
"ease_inout_elastic",
"ease_inout_bounce",
"ease_zero",
"ease_one",
"ease_linear",
"ease_inout_perlin",
0
};
return list;
}
const char *ease_enum(unsigned mode) {
return mode[ ease_enums() ];
}
/*AUTORUN {
ENUM(EASE_LINEAR|EASE_OUT);
ENUM(EASE_SINE|EASE_OUT);
ENUM(EASE_QUAD|EASE_OUT);
ENUM(EASE_CUBIC|EASE_OUT);
ENUM(EASE_QUART|EASE_OUT);
ENUM(EASE_QUINT|EASE_OUT);
ENUM(EASE_EXPO|EASE_OUT);
ENUM(EASE_CIRC|EASE_OUT);
ENUM(EASE_BACK|EASE_OUT);
ENUM(EASE_ELASTIC|EASE_OUT);
ENUM(EASE_BOUNCE|EASE_OUT);
ENUM(EASE_SINE|EASE_IN);
ENUM(EASE_QUAD|EASE_IN);
ENUM(EASE_CUBIC|EASE_IN);
ENUM(EASE_QUART|EASE_IN);
ENUM(EASE_QUINT|EASE_IN);
ENUM(EASE_EXPO|EASE_IN);
ENUM(EASE_CIRC|EASE_IN);
ENUM(EASE_BACK|EASE_IN);
ENUM(EASE_ELASTIC|EASE_IN);
ENUM(EASE_BOUNCE|EASE_IN);
ENUM(EASE_SINE|EASE_INOUT);
ENUM(EASE_QUAD|EASE_INOUT);
ENUM(EASE_CUBIC|EASE_INOUT);
ENUM(EASE_QUART|EASE_INOUT);
ENUM(EASE_QUINT|EASE_INOUT);
ENUM(EASE_EXPO|EASE_INOUT);
ENUM(EASE_CIRC|EASE_INOUT);
ENUM(EASE_BACK|EASE_INOUT);
ENUM(EASE_ELASTIC|EASE_INOUT);
ENUM(EASE_BOUNCE|EASE_INOUT);
ENUM(EASE_ZERO);
ENUM(EASE_ONE);
ENUM(EASE_LINEAR);
ENUM(EASE_INOUT_PERLIN);
};*/
// ----------------------------------------------------------------------------
// tween
tween_t tween() {
tween_t tw = {0};
return tw;
}
float tween_update(tween_t *tw, float dt) {
if( !array_count(tw->keyframes) ) return 0.0f;
for( int i = 0, end = array_count(tw->keyframes) - 1; i < end; ++i ) {
tween_keyframe_t *kf1 = &tw->keyframes[i];
tween_keyframe_t *kf2 = &tw->keyframes[i + 1];
if (tw->time >= kf1->t && tw->time <= kf2->t) {
float localT = (tw->time - kf1->t) / (kf2->t - kf1->t);
float easedT = ease(localT, kf1->ease);
tw->result = mix3(kf1->v, kf2->v, easedT);
break;
}
}
float done = (tw->time / tw->duration);
tw->time += dt;
return clampf(done, 0.0f, 1.0f);
}
void tween_reset(tween_t *tw) {
tw->time = 0.0f;
}
void tween_destroy(tween_t *tw) {
tween_t tw_ = {0};
array_free(tw->keyframes);
*tw = tw_;
}
static INLINE
int tween_comp_keyframes(const void *a, const void *b) {
float t1 = ((const tween_keyframe_t*)a)->t;
float t2 = ((const tween_keyframe_t*)b)->t;
return (t1 > t2) - (t1 < t2);
}
void tween_setkey(tween_t *tw, float t, vec3 v, unsigned mode) {
tween_keyframe_t keyframe = { t, v, mode };
array_push(tw->keyframes, keyframe);
array_sort(tw->keyframes, tween_comp_keyframes);
tw->duration = array_back(tw->keyframes)->t;
}
void tween_delkey(tween_t *tw, float t) { // @todo: untested
for( int i = 0, end = array_count(tw->keyframes); i < end; i++ ) {
if( tw->keyframes[i].t == t ) {
array_erase_slow(tw->keyframes, i);
tw->duration = array_back(tw->keyframes)->t;
return;
}
}
}
// ----------------------------------------------------------------------------
// curve
curve_t curve() {
curve_t c = {0};
return c;
}
static INLINE
vec3 catmull( vec3 p0, vec3 p1, vec3 p2, vec3 p3, float t ) {
float t2 = t*t;
float t3 = t*t*t;
vec3 c;
c.x = 0.5 * ((2 * p1.x) + (-p0.x + p2.x) * t + (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t2 + (-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t3);
c.y = 0.5 * ((2 * p1.y) + (-p0.y + p2.y) * t + (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 + (-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3);
c.z = 0.5 * ((2 * p1.z) + (-p0.z + p2.z) * t + (2 * p0.z - 5 * p1.z + 4 * p2.z - p3.z) * t2 + (-p0.z + 3 * p1.z - 3 * p2.z + p3.z) * t3);
return c;
}
void curve_add(curve_t *c, vec3 p) {
array_push(c->points, p);
}
void curve_end( curve_t *c, int k ) {
ASSERT( k > 0 );
array_free(c->lengths);
array_free(c->samples);
array_free(c->indices);
array_free(c->colors);
// refit points[N] to samples[K]
int N = array_count(c->points);
if( k < N ) {
// truncate: expected k-points lesser or equal than existing N points
for( int i = 0; i <= k; ++i ) {
float s = (float)i / k;
int t = s * (N-1);
array_push(c->samples, c->points[t]);
float p = fmod(i, N-1) / (N-1); // [0..1)
int is_control_point = p <= 0 || p >= 1;
array_push(c->colors, is_control_point ? ORANGE: BLUE);
}
} else {
// interpolate: expected k-points greater than existing N-points
--N;
int upper = N - (k%N);
int lower = (k%N);
if(upper < lower)
k += upper;
else
k -= lower;
int points_per_segment = (k / N);
++N;
int looped = len3sq(sub3(c->points[0], *array_back(c->points))) < 0.1;
for( int i = 0; i <= k; ++i ) {
int point = i % points_per_segment;
float p = point / (float)points_per_segment; // [0..1)
int t = i / points_per_segment;
// linear
vec3 l = mix3(c->points[t], c->points[t+(i!=k)], p);
// printf("%d) %d>%d %f\n", i, t, t+(i!=k), p);
ASSERT(p <= 1);
// catmull
int p0 = t - 1;
int p1 = t + 0;
int p2 = t + 1;
int p3 = t + 2;
if( looped )
{
int M = N-1;
if(p0<0) p0+=M; else if(p0>=M) p0-=M;
if(p1<0) p1+=M; else if(p1>=M) p1-=M;
if(p2<0) p2+=M; else if(p2>=M) p2-=M;
if(p3<0) p3+=M; else if(p3>=M) p3-=M;
}
else
{
int M = N-1;
if(p0<0) p0=0; else if(p0>=M) p0=M;
if(p1<0) p1=0; else if(p1>=M) p1=M;
if(p2<0) p2=0; else if(p2>=M) p2=M;
if(p3<0) p3=0; else if(p3>=M) p3=M;
}
vec3 m = catmull(c->points[p0],c->points[p1],c->points[p2],c->points[p3],p);
l = m;
array_push(c->samples, l);
int is_control_point = p <= 0 || p >= 1;
array_push(c->colors, is_control_point ? ORANGE: BLUE);
}
}
array_push(c->lengths, 0 );
for( int i = 1; i <= k; ++i ) {
// approximate curve length at every sample point
array_push(c->lengths, len3(sub3(c->samples[i], c->samples[i-1])) + c->lengths[i-1] );
}
// normalize lengths to be between 0 and 1
float maxv = c->lengths[k];
for( int i = 1; i <= k; ++i ) c->lengths[i] /= maxv;
array_push(c->indices, 0 );
for( int i = 0/*1*/; i </*=*/ k; ++i ) {
float s = (float)i / (k-1); //k;
int j; // j = so that lengths[j] <= s < lengths[j+1];
// j = Index of the highest length that is less or equal to s
// Can be optimized with a binary search instead
for( j = *array_back(c->indices) + 1; j </*=*/ k; ++j ) {
if( c->lengths[j] </*=*/ s ) continue;
break;
}
if (c->lengths[j] > 0.01)
array_push(c->indices, j );
}
}
vec3 curve_eval(curve_t *c, float dt, unsigned *color) {
dt = clampf(dt, 0.0f, 1.0f);
int l = (int)(array_count(c->indices) - 1);
int p = (int)(dt * l);
int t = c->indices[p];
t %= (array_count(c->indices)-1);
vec3 pos = mix3(c->samples[t], c->samples[t+1], dt * l - p);
if(color) *color = c->colors[t];
return pos;
}
void curve_destroy(curve_t *c) {
array_free(c->lengths);
array_free(c->colors);
array_free(c->samples);
array_free(c->points);
array_free(c->indices);
}
#line 0
#line 1 "v4k_profile.c"
#if ENABLE_PROFILER
profiler_t profiler;
int profiler_enabled = 1;
void (profiler_init)() { map_init(profiler, less_str, hash_str); profiler_enabled &= !!profiler; }
int (profiler_enable)(bool on) { return profiler_enabled = on; }
void (ui_profiler)() {
// @todo: ui_plot()
double fps = window_fps();
profile_setstat("Render.num_fps", fps);
enum { COUNT = 300 };
static float values[COUNT] = {0}; static int offset = 0;
values[offset=(offset+1)%COUNT] = fps;
// draw fps-meter: 300 samples, [0..70] range each, 70px height plot ...
// ... unless filtering is enabled
if( !(ui_filter && ui_filter[0]) ) {
nk_layout_row_dynamic(ui_ctx, 70, 1);
int index = -1;
if( nk_chart_begin(ui_ctx, NK_CHART_LINES, COUNT, 0.f, 70.f) ) {
for( int i = 0; i < COUNT; ++i ) {
nk_flags res = nk_chart_push(ui_ctx, (float)values[i]);
if( res & NK_CHART_HOVERING ) index = i;
if( res & NK_CHART_CLICKED ) index = i;
}
nk_chart_end(ui_ctx);
}
// hightlight 60fps, 36fps and 12fps
struct nk_rect space; nk_layout_peek(&space, ui_ctx);
struct nk_command_buffer *canvas = nk_window_get_canvas(ui_ctx);
nk_stroke_line(canvas, space.x+0,space.y-60,space.x+space.w,space.y-60, 1.0, nk_rgba(0,255,0,128));
nk_stroke_line(canvas, space.x+0,space.y-36,space.x+space.w,space.y-36, 1.0, nk_rgba(255,255,0,128));
nk_stroke_line(canvas, space.x+0,space.y-12,space.x+space.w,space.y-12, 1.0, nk_rgba(255,0,0,128));
if( index >= 0 ) {
nk_tooltipf(ui_ctx, "%.2f fps", (float)values[index]);
}
}
for each_map_ptr_sorted(profiler, const char *, key, struct profile_t, val ) {
if( isnan(val->stat) ) {
float v = val->avg/1000.0;
ui_slider2(*key, &v, va("%.2f ms", val->avg/1000.0));
} else {
float v = val->stat;
ui_slider2(*key, &v, va("%.2f", val->stat));
val->stat = 0;
}
}
}
#endif
#line 0
#line 1 "v4k_video.c"
// tip: convert video to x265/mp4. note: higher crf to increase compression (default crf is 28)
// ffmpeg -i {{infile}} -c:v libx265 -crf 24 -c:a copy {{outfile}}
struct video_t {
// mpeg player
plm_t *plm;
double previous_time;
bool paused;
bool has_ycbcr;
bool has_audio;
// yCbCr
union {
struct {
texture_t textureY;
texture_t textureCb;
texture_t textureCr;
};
texture_t textures[3];
};
// rgb
void *surface;
texture_t texture;
};
static void mpeg_update_texture(GLuint unit, GLuint texture, plm_plane_t *plane) {
glActiveTexture(unit);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(
GL_TEXTURE_2D, 0, GL_RED, plane->width, plane->height, 0,
GL_RED, GL_UNSIGNED_BYTE, plane->data
);
}
static void mpeg_video_callback( plm_t* plm, plm_frame_t* frame, void* user ) {
video_t *v = (video_t*)user;
if(v->paused) return;
if(v->has_ycbcr) {
mpeg_update_texture(GL_TEXTURE0, v->textureY.id, &frame->y);
mpeg_update_texture(GL_TEXTURE1, v->textureCb.id, &frame->cb);
mpeg_update_texture(GL_TEXTURE2, v->textureCr.id, &frame->cr);
} else {
plm_frame_to_rgb( frame, v->surface, v->texture.w * 3 );
texture_update( &v->texture, v->texture.w, v->texture.h, v->texture.n, v->surface, v->texture.flags );
}
(void)plm;
}
static void mpeg_audio_callback(plm_t *plm, plm_samples_t *samples, void *user) {
video_t *v = (video_t*)user;
audio_queue(v->paused ? NULL : samples->interleaved, samples->count, AUDIO_FLOAT | AUDIO_2CH | AUDIO_44KHZ );
(void)plm;
}
video_t* video( const char *filename, int flags ) {
plm_t* plm = plm_create_with_file( vfs_handle(filename), 1 );
if ( !plm ) {
PANIC( "!Cannot open '%s' file for reading\n", filename );
return 0;
}
int w = plm_get_width( plm );
int h = plm_get_height( plm );
float fps = plm_get_framerate( plm );
float rate = plm_get_samplerate( plm );
video_t *v = MALLOC(sizeof(video_t)), zero = {0};
*v = zero;
v->has_ycbcr = flags & VIDEO_RGB ? 0 : 1;
if( v->has_ycbcr ) {
v->textureY = texture_create( w, h, 1, NULL, TEXTURE_R );
v->textureCb = texture_create( w, h, 1, NULL, TEXTURE_R );
v->textureCr = texture_create( w, h, 1, NULL, TEXTURE_R );
} else {
int w16 = (w+15) & ~15;
int h16 = (h+15) & ~15;
v->texture = texture_create( w16, h16, 3, NULL, 0 );
v->surface = REALLOC( v->surface, w16 * h16 * 3 );
}
v->plm = plm;
v->has_audio = flags & VIDEO_NO_AUDIO ? 0 : 1;
plm_set_loop(plm, flags & VIDEO_LOOP);
plm_set_video_decode_callback(plm, mpeg_video_callback, v);
if( v->has_audio ) {
plm_set_audio_enabled(plm, true);
plm_set_audio_stream(plm, 0);
plm_set_audio_decode_callback(plm, mpeg_audio_callback, v);
}
PRINTF( "Video texture: %s (%dx%dx%d %.0ffps %.1fKHz)\n", file_name(filename), w, h, v->has_ycbcr ? 3:1, fps, rate / 1000 );
return v;
}
texture_t* video_decode( video_t *v ) { // decodes next frame, returns associated texture(s)
double current_time = time_ss();
double elapsed_time = current_time - v->previous_time;
if (elapsed_time > 1.0 / 30.0) {
elapsed_time = 1.0 / 30.0;
}
v->previous_time = current_time;
if(!v->paused)
plm_decode(v->plm, elapsed_time);
return v->has_ycbcr ? &v->textureY : &v->texture;
}
void video_destroy(video_t *v) {
plm_destroy( v->plm );
if( v->has_ycbcr ) {
texture_destroy(&v->textureY);
texture_destroy(&v->textureCr);
texture_destroy(&v->textureCb);
} else {
texture_destroy(&v->texture);
v->surface = REALLOC(v->surface, 0);
}
video_t zero = {0};
*v = zero;
FREE(v);
}
int video_has_finished(video_t *v) {
return !!plm_has_ended(v->plm);
}
double video_duration(video_t *v) {
return plm_get_duration(v->plm);
}
int video_seek(video_t *v, double seek_to) {
plm_seek(v->plm, clampf(seek_to, 0, video_duration(v)), FALSE);
if( v->has_audio ) audio_queue_clear();
return 1;
}
double video_position(video_t *v) {
return plm_get_time(v->plm);
}
void video_pause(video_t *v, bool paused) {
v->paused = paused;
}
bool video_is_paused(video_t *v) {
return v->paused;
}
bool video_is_rgb(video_t *v) {
return !v->has_ycbcr;
}
texture_t* video_textures( video_t *v ) {
return v->has_ycbcr ? &v->textureY : &v->texture;
}
// -----------------------------------------------------------------------------
// ffmpeg video recording
// [src] http://blog.mmacklin.com/2013/06/11/real-time-video-capture-with-ffmpeg/
static FILE* rec_ffmpeg;
static FILE* rec_mpeg1;
void record_stop(void) {
if(rec_ffmpeg) ifdef(win32, _pclose, pclose)(rec_ffmpeg);
rec_ffmpeg = 0;
if(rec_mpeg1) fclose(rec_mpeg1);
rec_mpeg1 = 0;
}
bool record_active() {
return rec_ffmpeg || rec_mpeg1;
}
bool record_start(const char *outfile_mp4) {
do_once atexit(record_stop);
record_stop();
// first choice: external ffmpeg encoder
if( !rec_ffmpeg ) {
extern const char *TOOLS;
char *tools_native_path = strswap( va("%s/", TOOLS), ifdef(win32, "/", "\\"), ifdef(win32, "\\", "/") );
char *cmd = va("%sffmpeg%s "
"-hide_banner -loglevel error " // less verbose
"-r %d -f rawvideo -pix_fmt bgr24 -s %dx%d " // raw BGR WxH-60Hz frames
// "-framerate 30 " // interpolating new video output frames from the source frames
"-i - " // read frames from stdin
//"-draw_mouse 1 "
"-threads 0 "
//"-vsync vfr "
"-preset ultrafast " // collection of options that will provide a certain encoding speed [fast,ultrafast]
// "-tune zerolatency " // change settings based upon the specifics of your input
//"-crf 21 " // range of the CRF scale [0(lossless)..23(default)..51(worst quality)]
"-pix_fmt yuv420p " // compatible with Windows Media Player and Quicktime
"-vf vflip " // flip Y
// "-vf \"pad=ceil(iw/2)*2:ceil(ih/2)*2\" "
"-y \"%s\"", tools_native_path, ifdef(win32, ".exe", ifdef(osx, ".osx",".linux")),
(int)window_fps(), window_width(), window_height(), outfile_mp4); // overwrite output file
// -rtbufsize 100M (https://trac.ffmpeg.org/wiki/DirectShow#BufferingLatency) Prevent some frames in the buffer from being dropped.
// -probesize 10M (https://www.ffmpeg.org/ffmpeg-formats.html#Format-Options) Set probing size in bytes, i.e. the size of the data to analyze to get stream information. A higher value will enable detecting more information in case it is dispersed into the stream, but will increase latency. Must be an integer not lesser than 32. It is 5000000 by default.
// -c:v libx264 (https://www.ffmpeg.org/ffmpeg.html#Main-options) Select an encoder (when used before an output file) or a decoder (when used before an input file) for one or more streams. codec is the name of a decoder/encoder or a special value copy (output only) to indicate that the stream is not to be re-encoded.
// open pipe to ffmpeg's stdin in binary write mode
rec_ffmpeg = ifdef(win32, _popen(cmd, "wb"), popen(cmd, "w"));
}
// fallback: built-in mpeg1 encoder
if( !rec_ffmpeg ) {
rec_mpeg1 = fopen(outfile_mp4, "wb"); // "a+b"
}
return record_active();
}
void record_frame() {
if( record_active() ) {
void* pixels = screenshot_async(-3); // 3 RGB, 4 RGBA, -3 BGR, -4 BGRA. ps: BGR is fastest on my intel discrete gpu
if( rec_ffmpeg ) {
fwrite(pixels, 3 * window_width() * window_height(), 1, rec_ffmpeg);
}
if( rec_mpeg1 ) {
jo_write_mpeg(rec_mpeg1, pixels, window_width(), window_height(), 24); // 24fps
}
}
}
#line 0
#line 1 "v4k_window.c"
//-----------------------------------------------------------------------------
// capture tests
static
uint64_t tests_captureframes() {
static uint64_t capture_target; do_once capture_target = optioni("--capture", 0);
return capture_target;
}
//-----------------------------------------------------------------------------
// fps locking
static volatile float framerate = 0;
static volatile unsigned fps_active, timer_counter, loop_counter;
static
int fps__timing_thread(void *arg) {
int64_t ns_excess = 0;
while( fps_active ) {
if( framerate <= 0 ) {
loop_counter = timer_counter = 0;
sleep_ms(250);
} else {
timer_counter++;
int64_t tt = (int64_t)(1e9/(float)framerate) - ns_excess;
uint64_t took = -time_ns();
#if is(win32)
timeBeginPeriod(1);
#endif
sleep_ns( tt > 0 ? (float)tt : 0.f );
took += time_ns();
ns_excess = took - tt;
if( ns_excess < 0 ) ns_excess = 0;
//puts( strf("%lld", ns_excess) );
}
}
fps_active = 1;
(void)arg;
return thread_exit(0), 0;
}
static
void fps_locker( int on ) {
if( on ) {
// private threaded timer
fps_active = 1, timer_counter = loop_counter = 0;
thread_init( fps__timing_thread, 0, "fps__timing_thread()", 0 );
} else {
fps_active = 0;
}
}
// function that locks render to desired `framerate` framerate (in FPS).
// - assumes fps_locker() was enabled beforehand.
// - returns true if must render, else 0.
static
int fps_wait() {
if( framerate <= 0 ) return 1;
if( !fps_active ) return 1;
// if we throttled too much, cpu idle wait
while( fps_active && (loop_counter > timer_counter) ) {
//thread_yield();
sleep_ns(100);
}
// max auto frameskip is 10: ie, even if speed is low paint at least one frame every 10
enum { maxframeskip = 10 };
if( timer_counter > loop_counter + maxframeskip ) {
loop_counter = timer_counter;
}
loop_counter++;
// only draw if we are fast enough, otherwise skip the frame
return loop_counter >= timer_counter;
}
static
void window_vsync(float hz) {
if( tests_captureframes() ) return;
if( hz <= 0 ) return;
do_once fps_locker(1);
framerate = hz;
fps_wait();
}
//-----------------------------------------------------------------------------
#if 0 // deprecated
static void (*hooks[64])() = {0};
static void *userdatas[64] = {0};
bool window_hook(void (*func)(), void* user) {
window_unhook( func );
for( int i = 0; i < 64; ++i ) {
if( !hooks[i] ) {
hooks[i] = func;
userdatas[i] = user;
return true;
}
}
return false;
}
void window_unhook(void (*func)()) {
for( int i = 0; i < 64; ++i ) {
if(hooks[i] == func) {
hooks[i] = 0;
userdatas[i] = 0;
}
}
}
#endif
static GLFWwindow *window;
static int w, h, xpos, ypos, paused;
static int fullscreen, xprev, yprev, wprev, hprev;
static uint64_t frame_count;
static double t, dt, fps, hz = 0.00;
static char title[128] = {0};
static char screenshot_file[DIR_MAX];
static int locked_aspect_ratio = 0;
static vec4 winbgcolor = {0,0,0,1};
vec4 window_getcolor_() { return winbgcolor; } // internal
// -----------------------------------------------------------------------------
// glfw
struct app {
GLFWwindow *window;
int width, height, keep_running;
unsigned flags;
struct nk_context *ctx;
struct nk_glfw *nk_glfw;
} appHandle = {0}, *g;
static void glfw_error_callback(int error, const char *description) {
if( is(osx) && error == 65544 ) return; // whitelisted
PANIC("%s (error %x)", description, error);
}
void glfw_quit(void) {
do_once {
glfwTerminate();
}
}
void glfw_init() {
do_once {
g = &appHandle;
glfwSetErrorCallback(glfw_error_callback);
int ok = !!glfwInit();
assert(ok); // if(!ok) PANIC("cannot initialize glfw");
atexit(glfw_quit); //glfwTerminate);
}
}
void window_drop_callback(GLFWwindow* window, int count, const char** paths) {
// @fixme: win: convert from utf8 to window16 before processing
char pathdir[DIR_MAX]; snprintf(pathdir, DIR_MAX, "%s/import/%llu_%s/", ART, (unsigned long long)date(), ifdef(linux, getlogin(), getenv("USERNAME")));
mkdir( pathdir, 0777 );
int errors = 0;
for( int i = 0; i < count; ++i ) {
const char *src = paths[i];
const char *dst = va("%s%s", pathdir, file_name(src));
errors += file_copy(src, dst) ? 0 : 1;
}
if( errors ) PANIC("%d errors found during file dropping", errors);
else window_reload();
(void)window;
}
void window_hints(unsigned flags) {
#ifdef __APPLE__
//glfwInitHint( GLFW_COCOA_CHDIR_RESOURCES, GLFW_FALSE );
glfwWindowHint( GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE ); // @todo: remove silicon mac M1 hack
//glfwWindowHint( GLFW_COCOA_GRAPHICS_SWITCHING, GLFW_FALSE );
//glfwWindowHint( GLFW_COCOA_MENUBAR, GLFW_FALSE );
#endif
#ifdef __APPLE__
/* We need to explicitly ask for a 3.2 context on OS X */
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // osx
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); // osx, 2:#version150,3:330
#else
// Compute shaders need 4.5 otherwise. But...
// According to the GLFW docs, the context version hint acts as a minimum version.
// i.e, the context you actually get may be a higher or highest version (which is usually the case)
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
#endif
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); //osx
#endif
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //osx+ems
glfwWindowHint(GLFW_STENCIL_BITS, 8); //osx
#if DEBUG
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
#endif
//glfwWindowHint( GLFW_RED_BITS, 8 );
//glfwWindowHint( GLFW_GREEN_BITS, 8 );
//glfwWindowHint( GLFW_BLUE_BITS, 8 );
//glfwWindowHint( GLFW_ALPHA_BITS, 8 );
//glfwWindowHint( GLFW_DEPTH_BITS, 24 );
//glfwWindowHint(GLFW_AUX_BUFFERS, Nth);
//glfwWindowHint(GLFW_STEREO, GL_TRUE);
glfwWindowHint(GLFW_DOUBLEBUFFER, GL_TRUE);
// Prevent fullscreen window minimize on focus loss
glfwWindowHint( GLFW_AUTO_ICONIFY, GL_FALSE );
// Fix SRGB on intels
glfwWindowHint(GLFW_SRGB_CAPABLE, GLFW_TRUE);
glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
// glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
// glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); // makes it non-resizable
if(flags & WINDOW_MSAA2) glfwWindowHint(GLFW_SAMPLES, 2); // x2 AA
if(flags & WINDOW_MSAA4) glfwWindowHint(GLFW_SAMPLES, 4); // x4 AA
if(flags & WINDOW_MSAA8) glfwWindowHint(GLFW_SAMPLES, 8); // x8 AA
g->flags = flags;
}
struct nk_glfw *window_handle_nkglfw() {
return g->nk_glfw;
}
static renderstate_t window_rs;
void glNewFrame() {
do_once {
window_rs = renderstate();
window_rs.blend_enabled = 1;
window_rs.depth_test_enabled = 1;
}
window_rs.clear_color[0] = winbgcolor.r;
window_rs.clear_color[1] = winbgcolor.g;
window_rs.clear_color[2] = winbgcolor.b;
window_rs.clear_color[3] = window_has_transparent() ? 0 : winbgcolor.a;
// @transparent debug
// if( input_down(KEY_F1) ) window_transparent(window_has_transparent()^1);
// if( input_down(KEY_F2) ) window_maximize(window_has_maximize()^1);
// @transparent debug
#if 0 // is(ems)
int canvasWidth, canvasHeight;
emscripten_get_canvas_element_size("#canvas", &canvasWidth, &canvasHeight);
w = canvasWidth;
h = canvasHeight;
//printf("%dx%d\n", w, h);
#else
//glfwGetWindowSize(window, &w, &h);
glfwGetFramebufferSize(window, &w, &h);
//printf("%dx%d\n", w, h);
#endif
g->width = w;
g->height = h;
renderstate_apply(&window_rs);
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
}
static bool cook_done = false;
bool window_create_from_handle(void *handle, float scale, unsigned flags) {
// abort run if any test suite failed in unit-test mode
ifdef(debug, if( flag("--test") ) exit( test_errors ? -test_errors : 0 ));
glfw_init();
v4k_init();
if(!t) t = glfwGetTime();
#if is(ems)
scale = 100.f;
#endif
if( flag("--fullscreen") ) scale = 100;
scale = (scale < 1 ? scale * 100 : scale);
bool FLAGS_FULLSCREEN = scale > 100;
bool FLAGS_FULLSCREEN_DESKTOP = scale == 100;
bool FLAGS_WINDOWED = scale < 100;
bool FLAGS_TRUE_BORDERLESS = flags & WINDOW_TRUE_BORDERLESS;
bool FLAGS_TRANSPARENT = flag("--transparent") || (flags & WINDOW_TRANSPARENT);
if( FLAGS_TRANSPARENT ) FLAGS_FULLSCREEN = 0, FLAGS_FULLSCREEN_DESKTOP = 0, FLAGS_WINDOWED = 1;
scale = (scale > 100 ? 100 : scale) / 100.f;
int winWidth = window_canvas().w * scale;
int winHeight = window_canvas().h * scale;
if (FLAGS_TRUE_BORDERLESS) {
FLAGS_FULLSCREEN = FLAGS_FULLSCREEN_DESKTOP = 0;
FLAGS_WINDOWED = 1;
flags |= WINDOW_BORDERLESS;
}
if (tests_captureframes()) {
winWidth = 1280;
winHeight = 720;
}
window_hints(flags);
GLFWmonitor* monitor = NULL;
#ifndef __EMSCRIPTEN__
if( FLAGS_FULLSCREEN || FLAGS_FULLSCREEN_DESKTOP ) {
monitor = glfwGetPrimaryMonitor();
}
if( FLAGS_FULLSCREEN_DESKTOP ) {
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
glfwWindowHint(GLFW_RED_BITS, mode->redBits);
glfwWindowHint(GLFW_GREEN_BITS, mode->greenBits);
glfwWindowHint(GLFW_BLUE_BITS, mode->blueBits);
glfwWindowHint(GLFW_REFRESH_RATE, mode->refreshRate);
winWidth = mode->width;
winHeight = mode->height;
}
if( FLAGS_WINDOWED ) {
#if !is(ems)
if( FLAGS_TRANSPARENT ) { // @transparent
//glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, GLFW_TRUE); // see through. requires undecorated
//glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); // always on top
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE);
}
if( flags & WINDOW_BORDERLESS ) {
// glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
}
#endif
// windowed
float ratio = (float)winWidth / (winHeight + !winHeight);
if( flags & WINDOW_SQUARE ) winWidth = winHeight = winWidth > winHeight ? winHeight : winWidth;
//if( flags & WINDOW_LANDSCAPE ) if( winWidth < winHeight ) winHeight = winWidth * ratio;
if( flags & WINDOW_PORTRAIT ) if( winWidth > winHeight ) winWidth = winHeight * (1.f / ratio);
}
#endif
window = handle ? handle : glfwCreateWindow(winWidth, winHeight, "", monitor, NULL);
if( !window ) return PANIC("GLFW Window creation failed"), false;
glfwGetFramebufferSize(window, &w, &h); //glfwGetWindowSize(window, &w, &h);
if( flags & WINDOW_FIXED ) { // disable resizing
glfwSetWindowSizeLimits(window, w, h, w, h);
}
if( flags & (WINDOW_SQUARE | WINDOW_PORTRAIT | WINDOW_LANDSCAPE | WINDOW_ASPECT) ) { // keep aspect ratio
window_aspect_lock(w, h);
}
#ifndef __EMSCRIPTEN__
if( FLAGS_WINDOWED ) {
// center window
monitor = monitor ? monitor : glfwGetPrimaryMonitor();
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
int area_width = mode->width, area_height = mode->height;
glfwGetMonitorWorkarea(monitor, &xpos, &ypos, &area_width, &area_height);
glfwSetWindowPos(window, xpos = xpos + (area_width - winWidth) / 2, ypos = ypos + (area_height - winHeight) / 2);
//printf("%dx%d @(%d,%d) [res:%dx%d]\n", winWidth, winHeight, xpos,ypos, area_width, area_height );
wprev = w, hprev = h;
xprev = xpos, yprev = ypos;
}
#endif
glfwMakeContextCurrent(window);
#if is(ems)
if( FLAGS_FULLSCREEN ) window_fullscreen(1);
#else
int gl_version = gladLoadGL(glfwGetProcAddress);
#endif
// set black screen
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glfwSwapBuffers(window);
glDebugEnable();
// setup nuklear ui
ui_ctx = nk_glfw3_init(&nk_glfw, window, NK_GLFW3_INSTALL_CALLBACKS);
//glEnable(GL_TEXTURE_2D);
// 0:disable vsync, 1:enable vsync, <0:adaptive (allow vsync when framerate is higher than syncrate and disable vsync when framerate drops below syncrate)
flags |= optioni("--vsync", 0) || flag("--vsync") ? WINDOW_VSYNC : WINDOW_VSYNC_DISABLED;
flags |= optioni("--vsync-adaptive", 0) || flag("--vsync-adaptive") ? WINDOW_VSYNC_ADAPTIVE : 0;
int has_adaptive_vsync = glfwExtensionSupported("WGL_EXT_swap_control_tear") || glfwExtensionSupported("GLX_EXT_swap_control_tear") || glfwExtensionSupported("EXT_swap_control_tear");
int wants_adaptive_vsync = (flags & WINDOW_VSYNC_ADAPTIVE);
int interval = has_adaptive_vsync && wants_adaptive_vsync ? -1 : (flags & WINDOW_VSYNC ? 1 : 0);
glfwSwapInterval(interval);
const GLFWvidmode *mode = glfwGetVideoMode(monitor ? monitor : glfwGetPrimaryMonitor());
PRINTF("Build version: %s\n", BUILD_VERSION);
PRINTF("Monitor: %s (%dHz, vsync=%d)\n", glfwGetMonitorName(monitor ? monitor : glfwGetPrimaryMonitor()), mode->refreshRate, interval);
PRINTF("GPU device: %s\n", glGetString(GL_RENDERER));
PRINTF("GPU driver: %s\n", glGetString(GL_VERSION));
#if !is(ems)
PRINTF("GPU OpenGL: %d.%d\n", GLAD_VERSION_MAJOR(gl_version), GLAD_VERSION_MINOR(gl_version));
if( FLAGS_TRANSPARENT ) { // @transparent
glfwSetWindowAttrib(window, GLFW_DECORATED, GLFW_FALSE); // @todo: is decorated an attrib or a hint?
if( scale >= 1 ) glfwMaximizeWindow(window);
}
if ( FLAGS_TRUE_BORDERLESS ) {
if( scale >= 1 ) glfwMaximizeWindow(window);
glfwSetWindowSize(window, w, h);
}
#endif
g->ctx = ui_ctx;
g->nk_glfw = &nk_glfw;
g->window = window;
g->width = window_width();
g->height = window_height();
PRINTF("Window: %dx%d\n", g->width, g->height);
if (glfwRawMouseMotionSupported()) {
glfwSetInputMode(window, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
}
// window_cursor(flags & WINDOW_NO_MOUSE ? false : true);
glfwSetDropCallback(window, window_drop_callback);
// camera inits for v4k_pre_init() -> ddraw_flush() -> get_active_camera()
// static camera_t cam = {0}; id44(cam.view); id44(cam.proj); extern camera_t *last_camera; last_camera = &cam;
v4k_pre_init();
// display a progress bar meanwhile cook is working in the background
// Sleep(500);
if( !COOK_ON_DEMAND )
if( have_tools() && cook_jobs() )
while( cook_progress() < 100 ) {
for( int frames = 0; frames < 2/*10*/ && window_swap(); frames += cook_progress() >= 100 ) {
window_title(va("%s %.2d%%", cook_cancelling ? "Aborting" : "Cooking assets", cook_progress()));
if( input(KEY_ESC) ) cook_cancel();
glNewFrame();
static float previous[JOBS_MAX] = {0};
#define ddraw_progress_bar(JOB_ID, JOB_MAX, PERCENT) do { \
/* NDC coordinates (2d): bottom-left(-1,-1), center(0,0), top-right(+1,+1) */ \
float progress = (PERCENT+1) / 100.f; if(progress > 1) progress = 1; \
float speed = progress < 1 ? 0.05f : 0.75f; \
float smooth = previous[JOB_ID] = progress * speed + previous[JOB_ID] * (1-speed); \
\
float pixel = 2.f / window_height(), dist = smooth*2-1, y = pixel*3*JOB_ID; \
if(JOB_ID==0)ddraw_line(vec3(-1,y-pixel*2,0), vec3(1, y-pixel*2,0)); /* full line */ \
ddraw_line(vec3(-1,y-pixel ,0), vec3(dist,y-pixel ,0)); /* progress line */ \
ddraw_line(vec3(-1,y+0 ,0), vec3(dist,y+0 ,0)); /* progress line */ \
ddraw_line(vec3(-1,y+pixel ,0), vec3(dist,y+pixel ,0)); /* progress line */ \
if(JOB_ID==JOB_MAX-1)ddraw_line(vec3(-1,y+pixel*2,0), vec3(1, y+pixel*2,0)); /* full line */ \
} while(0)
if( FLAGS_TRANSPARENT ) {} else // @transparent
for(int i = 0; i < cook_jobs(); ++i) ddraw_progress_bar(i, cook_jobs(), jobs[i].progress);
// ddraw_progress_bar(0, 1, cook_progress());
ddraw_flush();
do_once window_visible(1);
// render progress bar at 30Hz + give the cook threads more time to actually cook the assets.
// no big deal since progress bar is usually quiet when cooking assets most of the time.
// also, make the delay even larger when window is minimized or hidden.
// shaved cook times: 88s -> 57s (tcc), 50s -> 43s (vc)
sleep_ms( window_has_visible() && window_has_focus() ? 8 : 16 );
}
// set black screen
glNewFrame();
window_swap();
#if !ENABLE_RETAIL
window_title("");
#endif
}
if(cook_cancelling) cook_stop(), exit(-1);
cook_done = true;
v4k_post_init(mode->refreshRate);
return true;
}
bool window_create(float scale, unsigned flags) {
return window_create_from_handle(NULL, scale, flags);
}
void window_destroy() {
if( !window ) return;
glfwSetWindowShouldClose(window, GL_TRUE);
}
static double boot_time = 0;
char* window_stats() {
static double num_frames = 0, begin = FLT_MAX, prev_frame = 0;
double now = time_ss();
if( boot_time < 0 ) boot_time = now;
if( begin > now ) {
begin = now;
num_frames = 0;
}
if( (now - begin) >= 0.25f ) {
fps = num_frames * (1.f / (now - begin));
}
if( (now - begin) > 1 ) {
begin = now + ((now - begin) - 1);
num_frames = 0;
}
const char *cmdline = app_cmdline();
// @todo: print %used/%avail kib mem, %used/%avail objs as well
static char buf[256];
snprintf(buf, 256, "%s%s%s%s | boot %.2fs | %5.2ffps (%.2fms)%s%s",
title, BUILD_VERSION[0] ? " (":"", BUILD_VERSION[0] ? BUILD_VERSION:"", BUILD_VERSION[0] ? ")":"",
!boot_time ? now : boot_time,
fps, (now - prev_frame) * 1000.f,
cmdline[0] ? " | ":"", cmdline[0] ? cmdline:"");
prev_frame = now;
++num_frames;
return buf + strspn(buf, " ");
}
int window_frame_begin() {
glfwPollEvents();
// we cannot simply terminate threads on some OSes. also, aborted cook jobs could leave temporary files on disc.
// so let's try to be polite: we will be disabling any window closing briefly until all cook is either done or canceled.
static bool has_cook; do_once has_cook = !COOK_ON_DEMAND && have_tools() && cook_jobs();
if( has_cook ) {
has_cook = cook_progress() < 100;
if( glfwWindowShouldClose(g->window) ) cook_cancel();
glfwSetWindowShouldClose(g->window, GLFW_FALSE);
}
if( glfwWindowShouldClose(g->window) ) {
return 0;
}
glNewFrame();
ui_create();
#if !ENABLE_RETAIL
bool has_menu = ui_has_menubar();
bool may_render_debug_panel = 1;
if( have_tools() ) {
static int cook_has_progressbar; do_once cook_has_progressbar = !COOK_ON_DEMAND;
if( cook_has_progressbar) {
// render profiler, unless we are in the cook progress screen
static unsigned frames = 0; if(frames <= 0) frames += cook_progress() >= 100;
may_render_debug_panel = (frames > 0);
}
}
if (!win_debug_visible)
may_render_debug_panel = 0;
// generate Debug panel contents
if( may_render_debug_panel ) {
if( has_menu ? ui_window("Debug " ICON_MD_SETTINGS, 0) : ui_panel("Debug " ICON_MD_SETTINGS, 0) ) {
ui_engine();
(has_menu ? ui_window_end : ui_panel_end)();
}
API int engine_tick();
engine_tick();
}
#endif // ENABLE_RETAIL
#if 0 // deprecated
// run user-defined hooks
for(int i = 0; i < 64; ++i) {
if( hooks[i] ) hooks[i]( userdatas[i] );
}
#endif
double now = paused ? t : glfwGetTime();
dt = now - t;
t = now;
#if !ENABLE_RETAIL
char *st = window_stats();
static double timer = 0;
timer += window_delta();
if( timer >= 0.25 ) {
glfwSetWindowTitle(window, st);
timer = 0;
}
#else
glfwSetWindowTitle(window, title);
#endif
void input_update();
input_update();
return 1;
}
void window_frame_end() {
// flush batching systems that need to be rendered before frame swapping. order matters.
{
font_goto(0,0);
touch_flush();
sprite_flush();
// flush all debugdraw calls before swap
dd_ontop = 0;
ddraw_flush();
glClear(GL_DEPTH_BUFFER_BIT);
dd_ontop = 1;
ddraw_flush();
ui_render();
}
#if !is(ems)
// save screenshot if queued
if( screenshot_file[0] ) {
int n = 3;
void *rgb = screenshot(n);
stbi_flip_vertically_on_write(true);
if(!stbi_write_png(screenshot_file, w, h, n, rgb, n * w) ) {
PANIC("!could not write screenshot file `%s`\n", screenshot_file);
}
screenshot_file[0] = 0;
}
if( record_active() ) {
void record_frame();
record_frame();
}
#endif
}
void window_frame_swap() {
// glFinish();
#if !is(ems)
window_vsync(hz);
#endif
glfwSwapBuffers(window);
// emscripten_webgl_commit_frame();
static int delay = 0; do_once delay = optioni("--delay", 0);
if( delay && !COOK_ON_DEMAND && cook_progress() >= 100 ) sleep_ms( delay );
}
static
void window_shutdown() {
do_once {
#if ENABLE_SELFIES
snprintf(screenshot_file, DIR_MAX, "%s.png", app_name());
int n = 3;
void *rgb = screenshot(n);
stbi_flip_vertically_on_write(true);
if(!stbi_write_png(screenshot_file, w, h, n, rgb, n * w) ) {
PANIC("!could not write screenshot file `%s`\n", screenshot_file);
}
screenshot_file[0] = 0;
#endif
window_loop_exit(); // finish emscripten loop automatically
glfwTerminate();
}
}
int window_swap() {
// end frame
if( frame_count > 0 ) {
window_frame_end();
window_frame_swap();
}
++frame_count;
// begin frame
int ready = window_frame_begin();
if( !ready ) {
window_shutdown();
return 0;
}
static uint64_t capture_frame = 0;
if( cook_done && ++capture_frame == tests_captureframes() ) {
mkdir( "tests/out", 0777 );
const char *screenshot_file = va("tests/out/%s.png", app_name());
int n = 3;
void *rgb = screenshot(n);
stbi_flip_vertically_on_write(true);
if(!stbi_write_png(screenshot_file, w, h, n, rgb, n * w) ) {
PANIC("!could not write screenshot file `%s`\n", screenshot_file);
}
return 0;
}
return 1;
}
static
void (*window_render_callback)(void* loopArg);
static vec2 last_canvas_size = {0};
static
void window_resize() {
#if is(ems)
EM_ASM(canvas.canResize = 0);
if (g->flags&WINDOW_FIXED) return;
EM_ASM(canvas.canResize = 1);
vec2 size = window_canvas();
if (size.x != last_canvas_size.x || size.y != last_canvas_size.y) {
w = size.x;
h = size.y;
g->width = w;
g->height = h;
last_canvas_size = vec2(w,h);
emscripten_set_canvas_size(w, h);
}
#endif /* __EMSCRIPTEN__ */
}
static
void window_loop_wrapper( void *loopArg ) {
if( window_frame_begin() ) {
window_resize();
window_render_callback(loopArg);
window_frame_end();
window_frame_swap();
} else {
do_once window_shutdown();
}
}
void window_loop(void (*user_function)(void* loopArg), void* loopArg ) {
#if is(ems)
window_render_callback = user_function;
emscripten_set_main_loop_arg(window_loop_wrapper, loopArg, 0, 1);
#else
g->keep_running = true;
while (g->keep_running)
user_function(loopArg);
#endif /* __EMSCRIPTEN__ */
}
void window_loop_exit() {
#if is(ems)
emscripten_cancel_main_loop();
#else
g->keep_running = false;
#endif /* __EMSCRIPTEN__ */
}
vec2 window_canvas() {
#if is(ems)
int width = EM_ASM_INT_V(return canvas.width);
int height = EM_ASM_INT_V(return canvas.height);
return vec2(width, height);
#else
glfw_init();
const GLFWvidmode* mode = glfwGetVideoMode( glfwGetPrimaryMonitor() );
assert( mode );
return vec2(mode->width, mode->height);
#endif /* __EMSCRIPTEN__ */
}
int window_width() {
return w;
}
int window_height() {
return h;
}
double window_time() {
return t;
}
double window_delta() {
return dt;
}
void window_debug(int visible) {
win_debug_visible = visible;
}
int window_has_debug() {
return win_debug_visible;
}
double window_fps() {
return fps;
}
void window_fps_lock(float fps) {
hz = fps;
}
void window_fps_unlock() {
hz = 0;
}
double window_fps_target() {
return hz;
}
void window_fps_vsync(int vsync) {
glfwSwapInterval(vsync);
}
uint64_t window_frame() {
return frame_count;
}
void window_title(const char *title_) {
snprintf(title, 128, "%s", title_);
if( !title[0] ) glfwSetWindowTitle(window, title);
}
void window_color(unsigned color) {
unsigned r = (color >> 0) & 255;
unsigned g = (color >> 8) & 255;
unsigned b = (color >> 16) & 255;
unsigned a = (color >> 24) & 255;
winbgcolor = vec4(r / 255.0, g / 255.0, b / 255.0, a / 255.0);
}
static int has_icon;
int window_has_icon() {
return has_icon;
}
void window_icon(const char *file_icon) {
int len = 0;
void *data = vfs_load(file_icon, &len);
if( !data ) data = file_read(file_icon), len = file_size(file_icon);
if( data && len ) {
image_t img = image_from_mem(data, len, IMAGE_RGBA);
if( img.w && img.h && img.pixels ) {
GLFWimage images[1];
images[0].width = img.w;
images[0].height = img.h;
images[0].pixels = img.pixels;
glfwSetWindowIcon(window, 1, images);
has_icon = 1;
return;
}
}
#if 0 // is(win32)
HANDLE hIcon = LoadImageA(0, file_icon, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE);
if( hIcon ) {
HWND hWnd = glfwGetWin32Window(window);
SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
SendMessage(GetWindow(hWnd, GW_OWNER), WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
SendMessage(GetWindow(hWnd, GW_OWNER), WM_SETICON, ICON_BIG, (LPARAM)hIcon);
has_icon = 1;
return;
}
#endif
}
void* window_handle() {
return window;
}
void window_reload() {
// @todo: save_on_exit();
fflush(0);
// chdir(app_path());
execv(__argv[0], __argv);
exit(0);
}
int window_record(const char *outfile_mp4) {
record_start(outfile_mp4);
// @todo: if( flags & RECORD_MOUSE )
if( record_active() ) window_cursor_shape(CURSOR_SW_AUTO); else window_cursor_shape(CURSOR_HW_ARROW);
return record_active();
}
vec2 window_dpi() {
vec2 dpi = vec2(1,1);
#if !is(ems) && !is(osx) // @todo: remove silicon mac M1 hack
glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &dpi.x, &dpi.y);
#endif
return dpi;
}
// -----------------------------------------------------------------------------
// fullscreen
static
GLFWmonitor *window_find_monitor(int wx, int wy) {
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
// find best monitor given current window coordinates. @todo: select by ocuppied window area inside each monitor instead.
int num_monitors = 0;
GLFWmonitor** monitors = glfwGetMonitors(&num_monitors);
#if is(ems)
return *monitors;
#else
for( int i = 0; i < num_monitors; ++i) {
int mx = 0, my = 0, mw = 0, mh = 0;
glfwGetMonitorWorkarea(monitors[i], &mx, &my, &mw, &mh);
monitor = wx >= mx && wx <= (mx+mw) && wy >= my && wy <= (my+mh) ? monitors[i] : monitor;
}
return monitor;
#endif
}
#if 0 // to deprecate
void window_fullscreen(int enabled) {
fullscreen = !!enabled;
#ifndef __EMSCRIPTEN__
if( fullscreen ) {
int wx = 0, wy = 0; glfwGetWindowPos(window, &wx, &wy);
GLFWmonitor *monitor = window_find_monitor(wx, wy);
wprev = w, hprev = h, xprev = wx, yprev = wy; // save window context for further restoring
int width, height;
glfwGetMonitorWorkarea(monitor, NULL, NULL, &width, &height);
glfwSetWindowMonitor(window, monitor, 0, 0, width, height, GLFW_DONT_CARE);
} else {
glfwSetWindowMonitor(window, NULL, xpos, ypos, wprev, hprev, GLFW_DONT_CARE);
glfwSetWindowPos(window, xprev, yprev);
}
#endif
}
int window_has_fullscreen() {
return fullscreen;
}
#else
int window_has_fullscreen() {
#if is(ems)
EmscriptenFullscreenChangeEvent fsce;
emscripten_get_fullscreen_status(&fsce);
return !!fsce.isFullscreen;
#else
return !!glfwGetWindowMonitor(g->window);
#endif /* __EMSCRIPTEN__ */
}
void window_fullscreen(int enabled) {
if( window_has_fullscreen() == !!enabled ) return;
#if is(ems)
#if 0 // deprecated: crash
if( enabled ) {
emscripten_exit_soft_fullscreen();
/* Workaround https://github.com/kripken/emscripten/issues/5124#issuecomment-292849872 */
EM_ASM(JSEvents.inEventHandler = true);
EM_ASM(JSEvents.currentEventHandler = {allowsDeferredCalls:true});
EmscriptenFullscreenStrategy strategy = {0};
strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; // _ASPECT
strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF; // _NONE _HIDEF
strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; // _NEAREST
emscripten_request_fullscreen_strategy(NULL, EM_FALSE/*EM_TRUE*/, &strategy);
//emscripten_enter_soft_fullscreen(NULL, &strategy);
} else {
emscripten_exit_fullscreen();
}
#else
if( enabled )
EM_ASM(Module.requestFullscreen(1, 1));
else
EM_ASM(Module.exitFullscreen());
#endif
#else
#if 0
if( enabled ) {
/*glfwGetWindowPos(g->window, &g->window_xpos, &g->window_ypos);*/
glfwGetWindowSize(g->window, &g->width, &g->height);
glfwSetWindowMonitor(g->window, glfwGetPrimaryMonitor(), 0, 0, g->width, g->height, GLFW_DONT_CARE);
} else {
glfwSetWindowMonitor(g->window, NULL, 0, 0, g->width, g->height, GLFW_DONT_CARE);
}
#else
if( enabled ) {
int wx = 0, wy = 0; glfwGetWindowPos(window, &wx, &wy);
GLFWmonitor *monitor = window_find_monitor(wx, wy);
wprev = w, hprev = h, xprev = wx, yprev = wy; // save window context for further restoring
int width, height;
glfwGetMonitorWorkarea(monitor, NULL, NULL, &width, &height);
glfwSetWindowMonitor(window, monitor, 0, 0, width, height, GLFW_DONT_CARE);
} else {
glfwSetWindowMonitor(window, NULL, xpos, ypos, wprev, hprev, GLFW_DONT_CARE);
glfwSetWindowPos(window, xprev, yprev);
}
#endif
#endif
}
#endif
void window_pause(int enabled) {
paused = enabled;
}
int window_has_pause() {
return paused;
}
void window_focus() {
glfwFocusWindow(window);
}
int window_has_focus() {
return !!glfwGetWindowAttrib(window, GLFW_FOCUSED);
}
static int cursorshape = 1;
void window_cursor_shape(unsigned mode) {
cursorshape = (mode &= 7);
static GLFWcursor* cursors[7] = { 0 };
static unsigned enums[7] = {
0,
GLFW_ARROW_CURSOR,
GLFW_IBEAM_CURSOR,
GLFW_HRESIZE_CURSOR,
GLFW_VRESIZE_CURSOR,
GLFW_HAND_CURSOR,
GLFW_CROSSHAIR_CURSOR,
};
do_once {
static unsigned pixels[16 * 16] = { 0x01000000 }; // ABGR(le) glfw3 note: A(0x00) means 0xFF for some reason
static GLFWimage image = { 16, 16, (void*)pixels };
static GLFWcursor* empty;
for( int x = 0; x < 16 * 16; ++x ) pixels[x] = pixels[0];
empty = glfwCreateCursor(&image, 0, 0);
for(int i = 0; i < countof(enums); ++i) cursors[i] = i ? glfwCreateStandardCursor( enums[i] ) : empty;
}
if( mode == CURSOR_SW_AUTO ) { // UI (nuklear) driven cursor
nk_style_show_cursor(ui_handle());
glfwSetCursor(window, cursors[0] );
} else {
nk_style_hide_cursor(ui_handle());
glfwSetCursor(window, mode < countof(enums) ? cursors[mode] : NULL);
}
}
void window_cursor(int visible) {
(cursorshape == CURSOR_SW_AUTO && visible ? nk_style_show_cursor : nk_style_hide_cursor)(ui_handle());
glfwSetInputMode(window, GLFW_CURSOR, visible ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED);
}
int window_has_cursor() {
return glfwGetInputMode(window, GLFW_CURSOR) == GLFW_CURSOR_NORMAL;
}
void window_visible(int visible) {
if(!window) return;
//if(window) (visible ? glfwRestoreWindow : glfwIconifyWindow)(window);
(visible ? glfwShowWindow : glfwHideWindow)(window);
// call glfwpollevents in linux to flush visiblity changes that would happen in next frame otherwise
#if is(linux) || is(osx)
glfwPollEvents();
#endif
}
int window_has_visible() {
return glfwGetWindowAttrib(window, GLFW_VISIBLE);
}
void window_screenshot(const char* outfile_png) {
snprintf(screenshot_file, DIR_MAX, "%s", outfile_png ? outfile_png : "");
}
double window_aspect() {
return (double)w / (h + !h);
}
void window_aspect_lock(unsigned numer, unsigned denom) {
if(!window) return;
if( numer * denom ) {
glfwSetWindowAspectRatio(window, numer, denom);
} else {
glfwSetWindowAspectRatio(window, GLFW_DONT_CARE, GLFW_DONT_CARE);
}
}
void window_aspect_unlock() {
if(!window) return;
window_aspect_lock(0, 0);
}
void window_transparent(int enabled) {
#if !is(ems)
if( !window_has_fullscreen() ) {
if( enabled ) {
glfwSetWindowAttrib(window, GLFW_DECORATED, GLFW_FALSE);
//glfwSetWindowAttrib(window, GLFW_MOUSE_PASSTHROUGH , GLFW_TRUE);
//glfwMaximizeWindow(window);
} else {
//glfwRestoreWindow(window);
//glfwSetWindowAttrib(window, GLFW_MOUSE_PASSTHROUGH , GLFW_FALSE);
glfwSetWindowAttrib(window, GLFW_DECORATED, GLFW_TRUE);
}
}
#endif
}
int window_has_transparent() {
return ifdef(ems, 0, glfwGetWindowAttrib(window, GLFW_DECORATED) != GLFW_TRUE);
}
void window_maximize(int enabled) {
ifdef(ems, return);
if( !window_has_fullscreen() ) {
if( enabled ) {
glfwMaximizeWindow(window);
} else {
glfwRestoreWindow(window);
}
}
}
int window_has_maximize() {
return ifdef(ems, 0, glfwGetWindowAttrib(window, GLFW_MAXIMIZED) == GLFW_TRUE);
}
const char *window_clipboard() {
return glfwGetClipboardString(window);
}
void window_setclipboard(const char *text) {
glfwSetClipboardString(window, text);
}
static
double window_scale() { // ok? @testme
float xscale = 1, yscale = 1;
#if !is(ems) && !is(osx) // @todo: remove silicon mac M1 hack
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
glfwGetMonitorContentScale(monitor, &xscale, &yscale);
#endif
return maxi(xscale, yscale);
}
#line 0
#line 1 "v4k_obj.c"
// -----------------------------------------------------------------------------
// factory of handle ids, based on code by randy gaul (PD/Zlib licensed)
// - rlyeh, public domain
//
// [src] http://www.randygaul.net/wp-content/uploads/2021/04/handle_table.cpp
// [ref] http://bitsquid.blogspot.com.es/2011/09/managing-decoupling-part-4-id-lookup.html
// [ref] http://glampert.com/2016/05-04/dissecting-idhashindex/
// [ref] https://github.com/nlguillemot/dof/blob/master/viewer/packed_freelist.h
// [ref] https://gist.github.com/pervognsen/ffd89e45b5750e9ce4c6c8589fc7f253
// you cannot change this one: the number of ID_DATA_BITS you can store in a handle depends on ID_COUNT_BITS
#define ID_DATA_BITS (64-ID_COUNT_BITS)
typedef union id64 {
uint64_t h;
struct {
#if (ID_INDEX_BITS+ID_COUNT_BITS) != 64
uint64_t padding : 64-ID_INDEX_BITS-ID_COUNT_BITS;
#endif
uint64_t index : ID_INDEX_BITS;
uint64_t count : ID_COUNT_BITS;
};
} id64;
typedef struct id_factory id_factory;
id_factory id_factory_create(uint64_t capacity /*= 256*/);
id64 id_factory_insert(id_factory *f, uint64_t data);
uint64_t id_factory_getvalue(id_factory *f, id64 handle);
void id_factory_setvalue(id_factory *f, id64 handle, uint64_t data);
void id_factory_erase(id_factory *f, id64 handle);
bool id_factory_isvalid(id_factory *f, id64 handle);
void id_factory_destroy(id_factory *f);
// ---
typedef struct id_factory {
uint64_t freelist;
uint64_t capacity;
uint64_t canary;
union entry* entries;
} id_factory;
typedef union entry {
struct {
uint64_t data : ID_DATA_BITS;
uint64_t count : ID_COUNT_BITS;
};
uint64_t h;
} entry;
id_factory id_factory_create(uint64_t capacity) {
if(!capacity) capacity = 1ULL << ID_INDEX_BITS;
id_factory f = {0};
f.entries = CALLOC(1, sizeof(entry) * capacity);
f.capacity = capacity;
for (int i = 0; i < capacity - 1; ++i) {
f.entries[i].data = i + 1;
f.entries[i].count = 0;
}
f.entries[capacity - 1].h = 0;
f.entries[capacity - 1].data = ~0;
f.entries[capacity - 1].count = ~0;
f.canary = f.entries[capacity - 1].data;
return f;
}
void id_factory_destroy(id_factory *f) {
FREE(f->entries);
}
id64 id_factory_insert(id_factory *f, uint64_t data) {
// pop element off the free list
assert(f->freelist != f->canary && "max alive capacity reached");
uint64_t index = f->freelist;
f->freelist = f->entries[f->freelist].data;
// create new id64
f->entries[index].data = data;
id64 handle = {0};
handle.index = index;
handle.count = f->entries[index].count;
return handle;
}
void id_factory_erase(id_factory *f, id64 handle) {
// push id64 onto the freelist
uint64_t index = handle.index;
f->entries[index].data = f->freelist;
f->freelist = index;
// increment the count. this signifies a change in lifetime (this particular id64 is now dead)
// the next time this particular index is used in alloc, a new `count` will be used to uniquely identify
f->entries[index].count++;
}
uint64_t id_factory_getvalue(id_factory *f, id64 handle) {
uint64_t index = handle.index;
uint64_t count = handle.count;
assert(f->entries[index].count == count);
return f->entries[index].data;
}
void id_factory_setvalue(id_factory *f, id64 handle, uint64_t data) {
uint64_t index = handle.index;
uint64_t count = handle.count;
assert(f->entries[index].count == count);
f->entries[index].data = data;
}
bool id_factory_isvalid(id_factory *f, id64 handle) {
uint64_t index = handle.index;
uint64_t count = handle.count;
if (index >= f->capacity) return false;
return f->entries[index].count == count;
}
#if 0
// monitor history of a single entity by running `id_factory | find " 123."`
AUTORUN {
trap_install();
id_factory f = id_factory_create(optioni("--NUM",256));
array(id64) ids = 0;
for( int i = 0 ; ; ++i, i &= ((1ULL << ID_INDEX_BITS) - 1) ) { // infinite wrap
printf("count_ids(%d) ", array_count(ids));
bool insert = randf() < 0.49; // biased against deletion
if( insert ) {
id64 h = id_factory_insert(&f, i);
array_push(ids, h);
printf("add %llu.%llu\n", h.index, h.count);
} else {
int count = array_count(ids);
if( count ) {
int chosen = randi(0,count);
printf("del %d.\n", chosen);
id64 h = ids[chosen];
id_factory_erase(&f, h);
array_erase(ids, chosen);
}
}
}
}
#endif
// ----------------------------------------------------------------------
// public api
static id_factory fid; // @fixme: threadsafe
uintptr_t id_make(void *ptr) {
do_once fid = id_factory_create(0), id_factory_insert(&fid, 0); // init and reserve id(0)
id64 newid = id_factory_insert(&fid, (uint64_t)(uintptr_t)ptr ); // 48-bit effective addr
return newid.h;
}
void *id_handle(uintptr_t id) {
return (void *)(uintptr_t)id_factory_getvalue(&fid, ((id64){ (uint64_t)id }) );
}
void id_dispose(uintptr_t id) {
id_factory_erase(&fid, ((id64){ (uint64_t)id }) );
}
bool id_valid(uintptr_t id) {
return id_factory_isvalid(&fid, ((id64){ (uint64_t)id }) );
}
// ----------------------------------------------------------------------
// C objects framework
// - rlyeh, public domain.
// --- implement new vtables
obj_vtable(ctor, void, {} );
obj_vtable(dtor, void, {} );
obj_vtable_null(save, char* );
obj_vtable_null(load, bool );
obj_vtable_null(test, int );
obj_vtable_null(init, int );
obj_vtable_null(quit, int );
obj_vtable_null(tick, int );
obj_vtable_null(draw, int );
obj_vtable_null(lerp, int );
obj_vtable_null(edit, int ); // OSC cmds: argc,argv "undo","redo","cut","copy","paste","edit","view","menu"
obj_vtable_null(menu, int );
obj_vtable_null(aabb, int );
obj_vtable_null(icon, char* );
// ----------------------------------------------------------------------------
const char *OBJTYPES[256] = { 0 }; // = { REPEAT256("") };
// ----------------------------------------------------------------------------
// heap/stack ctor/dtor
void *obj_malloc(unsigned sz) {
//sz = sizeof(obj) + sz + sizeof(array(obj*))); // useful?
obj *ptr = CALLOC(1, sz);
OBJ_CTOR_HDR(ptr,1,sz,OBJTYPE_obj);
return ptr;
}
void *obj_free(void *o) {
if( !((obj*)o)->objrefs ) {
obj_detach(o);
obj_dtor(o);
//obj_zero(o);
if( ((obj*)o)->objheap ) {
FREE(o);
}
return 0;
}
return o; // cannot destroy: object is still referenced
}
// ----------------------------------------------------------------------------
// core
uintptr_t obj_header(const void *o) {
return ((obj*)o)->objheader;
}
uintptr_t obj_id(const void *o) {
return ((obj*)o)->objid;
}
unsigned obj_typeid(const void *o) {
return ((obj*)o)->objtype;
}
const char *obj_type(const void *o) {
return OBJTYPES[ (((obj*)o)->objtype) ];
}
//const char *obj_name(const void *o) {
// return quark(((obj*)o)->objnameid);
//}
int obj_sizeof(const void *o) {
return (int)( ((const obj*)o)->objsizew << OBJ_MIN_PRAGMAPACK_BITS );
}
int obj_size(const void *o) { // size of all members together in struct. may include padding bytes.
static int obj_sizes[256] = {0};
unsigned objtypeid = ((obj*)o)->objtype;
if( objtypeid > 1 && !obj_sizes[objtypeid] ) { // check reflection for a more accurate objsize (without padding bits)
reflect_init();
array(reflect_t) *found = map_find(members, intern(obj_type(o)));
if(!found)
obj_sizes[objtypeid] = obj_sizeof(o) - sizeof(obj); // @fixme: -= sizeof(entity);
else
for each_array_ptr(*found, reflect_t, it)
obj_sizes[objtypeid] += it->bytes;
}
return obj_sizes[objtypeid];
}
char *obj_data(void *o) { // pointer to the first member in struct
return (char*)o + sizeof(obj);
}
const char *obj_datac(const void *o) { // const pointer to the first struct member
return (const char*)o + sizeof(obj);
}
void* obj_payload(const void *o) { // pointer right after last member in struct
return (char*)o + (((obj*)o)->objsizew<<OBJ_MIN_PRAGMAPACK_BITS);
}
void *obj_zero(void *o) { // reset all object members
return memset(obj_data(o), 0, obj_size(o)), o;
}
static
void test_obj_core() {
obj *r = obj_new_ext(obj, "root");
obj *s = obj_new_ext(obj, "root");
test(r);
test( 0 == strcmp(obj_type(r), "obj") );
test( 0 == strcmp(obj_name(r), "root") );
test( OBJTYPE_obj == obj_typeid(r) );
test(s);
test( 0 == strcmp(obj_type(s), "obj") );
test( 0 == strcmp(obj_name(s), "root") );
test( OBJTYPE_obj == obj_typeid(s) );
test( obj_id(r) != 0 );
test( obj_id(s) != 0 );
test( obj_id(r) != obj_id(s) );
obj t = obj(obj); obj_setname(&t, "root");
obj u = obj(obj); obj_setname(&u, "root");
test(&t);
test( 0 == strcmp(obj_type(&t), "obj") );
test( 0 == strcmp(obj_name(&t), "root") );
test( OBJTYPE_obj == obj_typeid(&t) );
test(&u);
test( 0 == strcmp(obj_type(&u), "obj") );
test( 0 == strcmp(obj_name(&u), "root") );
test( OBJTYPE_obj == obj_typeid(&u) );
test( obj_id(&t) == 0 );
test( obj_id(&u) == 0 );
test( obj_id(&t) == obj_id(&u) );
}
// ----------------------------------------------------------------------------
// refcounting
// static int __thread global_ref_count; // @fixme: make it atomic
// static void objref_check_atexit(void) {
// if(global_ref_count) tty_color(YELLOW), fprintf(stderr, "Warn! obj_refs not zero (%d)\n", global_ref_count), tty_color(0);
// }
// AUTORUN { (atexit)(objref_check_atexit); }
void *obj_ref(void *oo) {
obj* o = (obj*)oo;
int num = o->objrefs;
++o->objrefs;
assert( num < o->objrefs && "Object referenced too many times");
//++global_ref_count;
return o;
}
void *obj_unref(void *oo) {
obj* o = (obj*)oo;
if( o->objrefs ) --o->objrefs;
if( o->objrefs ) return o;
obj_free(o);
//--global_ref_count;
return 0;
}
// ----------------------------------------------------------------------------
// scene tree
array(obj*)* obj_children(const void *o) {
array(obj*) *c = &((obj*)o)->objchildren;
if(!(*c)) array_push((*c), NULL); // default parenting: none. @todo: optimize & move this at construction time
return c;
}
obj* obj_parent(const void *o) {
array(obj*) *c = obj_children(o);
return 0[*c]; // (*c) ? 0[*c] : NULL;
}
obj* obj_root(const void *o) {
while( obj_parent(o) ) o = obj_parent(o);
return (obj*)o;
}
array(obj*)* obj_siblings(const void *o) {
return obj_children(obj_parent(o));
}
static
obj* obj_reparent(obj *o, const void *p) {
array(obj*) *c = obj_children(o);
0[*c] = (void*)p;
return o;
}
obj* obj_detach(void *c) {
obj *p = obj_parent(c);
if( p ) {
uintptr_t id = obj_id(c);
array(obj*) *oo = obj_children(p);
for( int i = 1, end = array_count(*oo); i < end; ++i) {
obj *v = (*oo)[i];
{
if( obj_id(v) == id ) {
obj_reparent(c, 0);
array_erase_slow(*oo, i);
return c;
}
}
}
}
return 0;
}
obj* obj_attach(void *o, void *c) {
// reattach
obj_detach(c);
obj_reparent(c, o);
// insert into children
array(obj*) *p = obj_children(o);
array_push(*p, c);
return o;
}
int obj_dumptree(const void *o) {
static int tabs = 0;
printf("%*s" "+- %s\n", tabs++, "", obj_name(o));
for each_objchild(o, obj*, v) {
obj_dumptree(v);
}
--tabs;
return 0;
}
static
void test_obj_scene() {
obj *r = obj_new_ext(obj, "root"); // root
obj *c1 = obj_new_ext(obj, "child1"); // child1
obj *c2 = obj_new_ext(obj, "child2"); // child2
obj *gc1 = obj_new_ext(obj, "grandchild1"); // grandchild1
obj *gc2 = obj_new_ext(obj, "grandchild2"); // grandchild2
obj *gc3 = obj_new_ext(obj, "grandchild3"); // grandchild3
test( !obj_parent(r) );
test( !obj_parent(c1) );
test( !obj_parent(c2) );
test( !obj_parent(gc1) );
test( !obj_parent(gc2) );
test( !obj_parent(gc3) );
test( obj_root(r) == r );
test( obj_root(c1) == c1 );
test( obj_root(c2) == c2 );
test( obj_root(gc1) == gc1 );
test( obj_root(gc2) == gc2 );
test( obj_root(gc3) == gc3 );
// r
obj_attach(r, c1); // +- c1
obj_attach(c1, gc1); // +- gc1
obj_attach(r, c2); // +- c2
obj_attach(c2, gc2); // +- gc2
obj_attach(c2, gc3); // +- gc3
obj_dumptree(r);
// puts("---");
test( obj_parent(r) == 0 );
test( obj_parent(c1) == r );
test( obj_parent(c2) == r );
test( obj_parent(gc1) == c1 );
test( obj_parent(gc2) == c2 );
test( obj_parent(gc3) == c2 );
test( obj_root(r) == r );
test( obj_root(c1) == r );
test( obj_root(c2) == r );
test( obj_root(gc1) == r );
test( obj_root(gc2) == r );
test( obj_root(gc3) == r );
for each_objchild(r, obj*, o) test( o == c1 || o == c2 );
for each_objchild(c1, obj*, o) test( o == gc1 );
for each_objchild(c2, obj*, o) test( o == gc2 || o == gc3 );
obj_detach(c1);
test( !obj_parent(c1) );
for each_objchild(r, obj*, o) test( o != c1 );
for each_objchild(c1, obj*, o) test( o == gc1 );
obj_detach(c2);
test( !obj_parent(c2) );
for each_objchild(r, obj*, o) test( o != c2 );
for each_objchild(c2, obj*, o) test( o == gc2 || o == gc3 );
}
// ----------------------------------------------------------------------------
// metadata
static map(int,int) oms;
static thread_mutex_t *oms_lock;
void *obj_setmeta(void *o, const char *key, const char *value) {
void *ret = 0;
do_threadlock(oms_lock) {
if(!oms) map_init_int(oms);
int *q = map_find_or_add(oms, intern(va("%p-%s",(void*)obj_id((obj*)o),key)), 0);
if(!*q && !value[0]) {} else *q = intern(value);
quark(*q), ret = o;
}
return ret;
}
const char* obj_meta(const void *o, const char *key) {
const char *ret = 0;
do_threadlock(oms_lock) {
if(!oms) map_init_int(oms);
int *q = map_find_or_add(oms, intern(va("%p-%s",(void*)obj_id((obj*)o),key)), 0);
ret = quark(*q);
}
return ret;
}
void *obj_setname(void *o, const char *name) {
ifdef(debug,((obj*)o)->objname = name);
return obj_setmeta(o, "name", name);
}
const char *obj_name(const void *o) {
const char *objname = obj_meta(o, "name");
return objname[0] ? objname : "obj";
}
static
void test_obj_metadatas( void *o1 ) {
obj *o = (obj *)o1;
test( !strcmp("", obj_meta(o, "has_passed_test")) );
test( obj_setmeta(o, "has_passed_test", "yes") );
test( !strcmp("yes", obj_meta(o, "has_passed_test")) );
}
// ----------------------------------------------------------------------------
// stl
void* obj_swap(void *dst, void *src) { // @testme
int len = obj_size(dst);
char *buffer = ALLOCA(len);
memcpy(buffer, obj_datac(dst), len);
memcpy(obj_data(dst), obj_datac(src), len);
memcpy(obj_data(src), buffer, len);
return dst;
}
void* obj_copy_fast(void *dst, const void *src) {
// note: prefer obj_copy() as it should handle pointers and guids as well
return memcpy(obj_data(dst), obj_datac(src), obj_size(dst));
}
void* obj_copy(void *dst, const void *src) { // @testme
// @todo: use obj_copy_fast() silently if the object does not contain any pointers/guids
return obj_loadini(dst, obj_saveini(src));
// return obj_load(dst, obj_save(src));
// return obj_loadbin(dst, obj_savebin(src));
// return obj_loadini(dst, obj_saveini(src));
// return obj_loadjson(dst, obj_savejson(src));
// return obj_loadmpack(dst, obj_savempack(src));
}
int obj_comp_fast(const void *a, const void *b) {
// note: prefer obj_comp() as it should handle pointers and guids as well
return memcmp(obj_datac(a), obj_datac(b), obj_size(a));
}
int obj_comp(const void *a, const void *b) {
// @todo: use obj_comp_fast() silently if the object does not contain any pointers/guids
return strcmp(obj_saveini(a),obj_saveini(b));
}
int obj_lesser(const void *a, const void *b) {
return obj_comp(a,b) < 0;
}
int obj_greater(const void *a, const void *b) {
return obj_comp(a,b) > 0;
}
int obj_equal(const void *a, const void *b) {
return obj_comp(a,b) == 0;
}
uint64_t obj_hash(const void *o) {
return hash_bin(obj_datac(o), obj_size(o));
}
static
void test_obj_similarity(void *o1, void *o2) {
obj *b = (obj*)o1;
obj *c = (obj*)o2;
test( 0 == strcmp(obj_name(b),obj_name(c)) );
test( 0 == strcmp(obj_type(b),obj_type(c)) );
}
static
void test_obj_equality(void *o1, void *o2) {
obj *b = (obj*)o1;
obj *c = (obj*)o2;
test_obj_similarity(b, c);
test( obj_size(b) == obj_size(c) );
test( obj_hash(b) == obj_hash(c) );
test( 0 == obj_comp(b,c) );
test( obj_equal(b,c) );
test( !obj_lesser(b,c) );
test( !obj_greater(b,c) );
}
static
void test_obj_exact(void *o1, void *o2) {
obj *b = (obj*)o1;
obj *c = (obj*)o2;
test_obj_equality(b, c);
test( obj_header(b) == obj_header(c) );
test( 0 == memcmp(b, c, obj_sizeof(b)) );
}
// ----------------------------------------------------------------------------
// debug
bool obj_hexdump(const void *oo) {
const obj *o = (const obj *)oo;
int header = 1 * sizeof(obj);
printf("; name[%s] type[%s] id[%d..%d] unused[%08x] sizeof[%02d] %p\n",
obj_name(o), obj_type(o),
(int)o->objid>>16, (int)o->objid&0xffff, (int)o->objunused,
obj_sizeof(o), (void*)o->objheader);
return hexdump(obj_datac(o) - header, obj_size(o) + header), 1;
}
int obj_print(const void *o) {
char *sav = obj_saveini(o); // obj_savejson(o)
return puts(sav);
}
static char *obj_tempname = 0;
static FILE *obj_filelog = 0;
int (obj_printf)(const void *o, const char *text) {
if( !obj_tempname ) {
obj_tempname = stringf("%s.log", app_name());
unlink(obj_tempname);
obj_filelog = fopen(obj_tempname, "w+b");
if( obj_filelog ) fseek(obj_filelog, 0L, SEEK_SET);
}
int rc = 0;
for( char *end; (end = strchr(text, '\n')) != NULL; ) {
rc |= fprintf(obj_filelog, "[%p] %.*s\n", o, (int)(end - text), text );
text = end + 1;
}
if( text[0] ) rc |= fprintf(obj_filelog, "[%p] %s\n", o, text);
return rc;
}
int obj_console(const void *o) { // obj_output() ?
if( obj_filelog ) fflush(obj_filelog);
return obj_tempname && !system(va(ifdef(win32,"type \"%s\" | find \"[%p]\"", "cat %s | grep \"[%p]\""), obj_tempname, o));
}
static
void test_obj_console(void *o1) {
obj *o = (obj *)o1;
obj_printf(o, "this is [%s], line 1\n", obj_name(o));
obj_printf(NULL, "this line does not belong to any object\n");
obj_printf(o, "this is [%s], line 2\n", obj_name(o));
obj_console(o);
}
// ----------------------------------------------------------------------------
// serialization
const char *p2s(const char *type, void *p) {
// @todo: p2s(int interned_type, void *p)
/**/ if( !strcmp(type, "int") ) return itoa1(*(int*)p);
else if( !strcmp(type, "unsigned") ) return itoa1(*(unsigned*)p);
else if( !strcmp(type, "float") ) return ftoa1(*(float*)p);
else if( !strcmp(type, "double") ) return ftoa1(*(double*)p);
else if( !strcmp(type, "uintptr_t") ) return va("%p", (void*)*(uintptr_t*)p);
else if( !strcmp(type, "vec2i") ) return itoa2(*(vec2i*)p);
else if( !strcmp(type, "vec3i") ) return itoa3(*(vec3i*)p);
else if( !strcmp(type, "vec2") ) return ftoa2(*(vec2*)p);
else if( !strcmp(type, "vec3") ) return ftoa3(*(vec3*)p);
else if( !strcmp(type, "vec4") ) return ftoa4(*(vec4*)p);
else if( !strcmp(type, "rgb") ) return rgbatoa(*(unsigned*)p);
else if( !strcmp(type, "rgba") ) return rgbatoa(*(unsigned*)p);
else if( !strcmp(type, "char*") || !strcmp(type, "string") ) return va("%s", *(char**)p);
// @todo: if strchr('*') assume obj, if reflected save guid: obj_id();
return tty_color(YELLOW), printf("p2s: cannot serialize `%s` type\n", type), tty_color(0), "";
}
bool s2p(void *P, const char *type, const char *str) {
int i; unsigned u; float f; double g; char *s = 0; uintptr_t p;
vec2 v2; vec3 v3; vec4 v4; vec2i v2i; vec3i v3i;
/**/ if( !strcmp(type, "int") ) return !!memcpy(P, (i = atoi1(str), &i), sizeof(i));
else if( !strcmp(type, "unsigned") ) return !!memcpy(P, (u = atoi1(str), &u), sizeof(u));
else if( !strcmp(type, "vec2i") ) return !!memcpy(P, (v2i = atoi2(str), &v2i), sizeof(v2i));
else if( !strcmp(type, "vec3i") ) return !!memcpy(P, (v3i = atoi3(str), &v3i), sizeof(v3i));
else if( !strcmp(type, "float") ) return !!memcpy(P, (f = atof1(str), &f), sizeof(f));
else if( !strcmp(type, "double") ) return !!memcpy(P, (g = atof1(str), &g), sizeof(g));
else if( !strcmp(type, "vec2") ) return !!memcpy(P, (v2 = atof2(str), &v2), sizeof(v2));
else if( !strcmp(type, "vec3") ) return !!memcpy(P, (v3 = atof3(str), &v3), sizeof(v3));
else if( !strcmp(type, "vec4") ) return !!memcpy(P, (v4 = atof4(str), &v4), sizeof(v4));
else if( !strcmp(type, "rgb") ) return !!memcpy(P, (u = atorgba(str), &u), sizeof(u));
else if( !strcmp(type, "rgba") ) return !!memcpy(P, (u = atorgba(str), &u), sizeof(u));
else if( !strcmp(type, "uintptr_t") ) return !!memcpy(P, (p = strtol(str, NULL, 16), &p), sizeof(p));
else if( !strcmp(type, "char*") || !strcmp(type, "string") ) {
char substring[128] = {0};
sscanf(str, "%[^\r\n]", substring);
strcatf(&s, "%s", substring);
*(uintptr_t*)(P) = (uintptr_t)s;
return 1;
}
// @todo: if strchr('*') assume obj, if reflected load guid: obj_id();
return tty_color(YELLOW), printf("s2p: cannot deserialize `%s` type\n", type), tty_color(0), 0;
}
char *obj_saveini(const void *o) { // @testme
char *out = 0;
const char *T = obj_type(o);
strcatf(&out, "[%s] ; v100\n", T);
for each_member(T,R) {
const char *sav = p2s(R->type,(char*)(o)+R->sz);
if(!sav) return FREE(out), NULL;
strcatf(&out,"%s.%s=%s\n", R->type,R->name,sav );
}
char *cpy = va("%s", out);
FREE(out);
return cpy;
}
obj *obj_mergeini(void *o, const char *ini) { // @testme
const char *sqr = strchr(ini, '[');
if( !sqr ) return 0;
ini = sqr+1;
char T[64] = {0};
if( sscanf(ini, "%63[^]]", T) != 1 ) return 0; // @todo: parse version as well
ini += strlen(T);
for each_member(T,R) {
char *lookup = va("\n%s.%s=", R->type,R->name), *found = 0;
// type needed? /*
if(!found) { found = strstr(ini, lookup); if (found) found += strlen(lookup); }
if(!found) { *lookup = '\r'; }
if(!found) { found = strstr(ini, lookup); if (found) found += strlen(lookup); }
// */
if(!found) lookup = va("\n%s=", R->name);
if(!found) { found = strstr(ini, lookup); if (found) found += strlen(lookup); }
if(!found) { *lookup = '\r'; }
if(!found) { found = strstr(ini, lookup); if (found) found += strlen(lookup); }
if( found) {
if(!s2p((char*)(o)+R->sz, R->type, found))
return 0;
}
}
return o;
}
obj *obj_loadini(void *o, const char *ini) { // @testme
return obj_mergeini(obj_zero(o), ini);
}
char *obj_savejson(const void *o) {
char *j = 0;
const char *T = obj_type(o);
for each_member(T,R) {
const char *sav = p2s(R->type,(char*)(o)+R->sz);
if(!sav) return FREE(j), NULL;
char is_string = !strcmp(R->type,"char*") || !strcmp(R->type,"string");
strcatf(&j," %s: %s%s%s,\n", R->name,is_string?"\"":"",sav,is_string?"\"":"" );
}
char *out = va("%s: { // v100\n%s}\n", T,j);
FREE(j);
#if is(debug)
json5 root = { 0 };
char *error = json5_parse(&root, va("%s", out), 0);
assert( !error );
json5_free(&root);
#endif
return out;
}
obj *obj_mergejson(void *o, const char *json) {
// @fixme: va() call below could be optimized out since we could figure it out if json was internally provided (via va or strdup), or user-provided
json5 root = { 0 };
char *error = json5_parse(&root, va("%s", json), 0); // @todo: parse version comment
if( !error && root.type == JSON5_OBJECT && root.count == 1 ) {
json5 *n = &root.nodes[0];
char *T = n->name;
for each_member(T,R) {
for( int i = 0; i < n->count; ++i ) {
if( !strcmp(R->name, n->nodes[i].name) ) {
void *p = (char*)o + R->sz;
/**/ if( n->nodes[i].type == JSON5_UNDEFINED ) {}
else if( n->nodes[i].type == JSON5_NULL ) {
*(uintptr_t*)(p) = (uintptr_t)0;
}
else if( n->nodes[i].type == JSON5_BOOL ) {
*(bool*)p = n->nodes[i].boolean;
}
else if( n->nodes[i].type == JSON5_INTEGER ) {
if( strstr(R->type, "64" ) )
*(int64_t*)p = n->nodes[i].integer;
else
*(int*)p = n->nodes[i].integer;
}
else if( n->nodes[i].type == JSON5_STRING ) {
char *s = 0;
strcatf(&s, "%s", n->nodes[i].string);
*(uintptr_t*)(p) = (uintptr_t)s;
}
else if( n->nodes[i].type == JSON5_REAL ) {
if( R->type[0] == 'f' )
*(float*)(p) = n->nodes[i].real;
else
*(double*)(p) = n->nodes[i].real;
}
else if( n->nodes[i].type == JSON5_OBJECT ) {}
else if( n->nodes[i].type == JSON5_ARRAY ) {}
break;
}
}
}
}
json5_free(&root);
return error ? 0 : o;
}
obj *obj_loadjson(void *o, const char *json) { // @testme
return obj_mergejson(obj_zero(o), json);
}
char *obj_savebin(const void *o) { // PACKMSG("ss", "entity_v1", quark(self->objnameid)); // = PACKMSG("p", obj_data(&b), (uint64_t)obj_size(&b));
int len = cobs_bounds(obj_size(o));
char *sav = va("%*.s", len, "");
len = cobs_encode(obj_datac(o), obj_size(o), sav, len);
sav[len] = '\0';
return sav;
}
obj *obj_mergebin(void *o, const char *sav) { // UNPACKMSG(sav, "p", obj_data(c), (uint64_t)obj_size(c));
int outlen = cobs_decode(sav, strlen(sav), obj_data(o), obj_size(o));
return outlen != obj_size(o) ? NULL : o;
}
obj *obj_loadbin(void *o, const char *sav) {
return obj_mergebin(obj_zero(o), sav);
}
char *obj_savempack(const void *o) { // @todo
return "";
}
obj *obj_mergempack(void *o, const char *sav) { // @todo
return 0;
}
obj *obj_loadmpack(void *o, const char *sav) { // @todo
return obj_mergempack(obj_zero(o), sav);
}
static __thread map(void*,array(char*)) obj_stack;
int obj_push(const void *o) {
if(!obj_stack) map_init_ptr(obj_stack);
array(char*) *found = map_find_or_add(obj_stack,(void*)o,0);
char *bin = STRDUP(obj_saveini(o)); // @todo: savebin
array_push(*found, bin);
return 1;
}
int obj_pop(void *o) {
if(!obj_stack) map_init_ptr(obj_stack);
array(char*) *found = map_find_or_add(obj_stack,(void*)o,0);
char **bin = array_back(*found);
if( bin ) {
int rc = !!obj_loadini(o, *bin); // @todo: loadbin
if( array_count(*found) > 1 ) {
FREE(*bin);
array_pop(*found);
}
return rc;
}
return 0;
}
static
void test_obj_serialization(void *o1, void *o2) {
obj* b = (obj*)o1;
obj* c = (obj*)o2;
char *json = obj_savejson(b); // puts(json);
test( json[0] );
char *ini = obj_saveini(b); // puts(ini);
test( ini[0] );
char *bin = obj_savebin(b); // puts(bin);
test( bin[0] );
obj_push(c);
test( obj_copy(c,b) );
test( obj_comp(b,c) == 0 ) || obj_hexdump(b) & obj_hexdump(c);
test( obj_zero(c) );
test( obj_comp(c,b) != 0 ) || obj_hexdump(c);
test( obj_loadbin(c, bin) );
test( obj_comp(c,b) == 0 ) || obj_hexdump(c) & obj_hexdump(b);
test( obj_zero(c) );
test( obj_comp(c,b) != 0 ) || obj_hexdump(c);
test( obj_loadini(c, ini) );
test( obj_comp(c,b) == 0 ) || obj_hexdump(c) & obj_hexdump(b);
test( obj_zero(c) );
test( obj_comp(c,b) != 0 ) || obj_hexdump(c);
test( obj_loadjson(c, json) );
test( obj_comp(c,b) == 0 ) || obj_hexdump(c) & obj_hexdump(b);
obj_pop(c);
obj_hexdump(c);
}
// ----------------------------------------------------------------------------
// components
bool obj_addcomponent(entity *e, unsigned c, void *ptr) {
e->cflags |= (3ULL << c);
e->c[c & (OBJCOMPONENTS_MAX-1)] = ptr;
return 1;
}
bool obj_hascomponent(entity *e, unsigned c) {
return !!(e->cflags & (3ULL << c));
}
void* obj_getcomponent(entity *e, unsigned c) {
return e->c[c & (OBJCOMPONENTS_MAX-1)];
}
bool obj_delcomponent(entity *e, unsigned c) {
e->cflags &= ~(3ULL << c);
e->c[c & (OBJCOMPONENTS_MAX-1)] = NULL;
return 1;
}
bool obj_usecomponent(entity *e, unsigned c) {
e->cflags |= (1ULL << c);
return 1;
}
bool obj_offcomponent(entity *e, unsigned c) {
e->cflags &= ~(1ULL << c);
return 0;
}
char *entity_save(entity *self) {
char *sav = obj_saveini(self);
return sav;
}
static
void entity_register() {
do_once {
STRUCT(entity, uintptr_t, cflags);
obj_extend(entity, save);
}
}
AUTORUN{
entity_register();
}
static
void test_obj_ecs() {
entity_register(); // why is this required here? autorun init fiasco?
entity *e = entity_new(entity);
puts(obj_save(e));
for( int i = 0; i < 32; ++i) test(0 == obj_hascomponent(e, i));
for( int i = 0; i < 32; ++i) test(1 == obj_addcomponent(e, i, NULL));
for( int i = 0; i < 32; ++i) test(1 == obj_hascomponent(e, i));
for( int i = 0; i < 32; ++i) test(1 == obj_delcomponent(e, i));
for( int i = 0; i < 32; ++i) test(0 == obj_hascomponent(e, i));
}
// ----------------------------------------------------------------------------
// reflection
void* obj_mutate(void *dst, const void *src) {
((obj*)dst)->objheader = ((const obj *)src)->objheader;
#if 0
// mutate a class. ie, convert a given object class into a different one,
// while preserving the original metas, components and references as much as possible.
// @todo iterate per field
dtor(dst);
unsigned src_sz = obj_sizeof(src);
unsigned src_id = obj_id(src);
void *dst_ptr = *((void**)dst - 1);
unsigned payload = (OBJPAYLOAD16(dst_ptr) & 255) | src_id << 8;
FREE( OBJUNBOX(dst_ptr) );
*((void**)dst - 1) = OBJBOX( STRDUP( OBJUNBOX(*((void**)src - 1)) ), payload);
void *base = (void*)((void**)dst - 1);
base = REALLOC(base, src_sz + sizeof(void*));
*dst_ = (char*)base + sizeof(void*);
dst = (char*)base + sizeof(void*);
memcpy(dst, src, src_sz);
ctor(dst);
#endif
return dst;
}
void *obj_clone(const void *src) {
int sz = sizeof(obj) + obj_size(src) + sizeof(array(obj*));
enum { N = 8 }; sz = ((sz + (N - 1)) & -N); // Round up to N-byte boundary
obj *ptr = obj_malloc( sz );
obj_mutate(ptr, src); // ptr->objheader = ((const obj *)src)->objheader;
obj_loadini(ptr, obj_saveini(src));
return ptr;
}
void* obj_merge(void *dst, const void *src) { // @testme
char *bin = obj_savebin(src);
return obj_mergebin(dst, bin);
}
void *obj_make(const char *str) {
const char *T;
const char *I = strchr(str, '['); // is_ini
const char *J = strchr(str, '{'); // is_json
if( !I && !J ) return 0;
else if( I && !J ) T = I;
else if( !I && J ) T = J;
else T = I < J ? I : J;
char name[64] = {0};
if( sscanf(T+1, T == I ? "%63[^]]" : "%63[^:=]", name) != 1 ) return 0;
int has_components = 0; // @todo: support entities too
unsigned Tid = intern(name);
reflect_init();
reflect_t *found = map_find(reflects, Tid);
if(!found) return obj_new(obj);
obj *ptr = CALLOC(1, found->sz + (has_components+1) * sizeof(array(obj*)));
void *ret = (T == I ? obj_mergeini : obj_mergejson)(ptr, str);
OBJTYPES[ found->objtype ] = found->name;
OBJ_CTOR_PTR(ptr,1,/*found->id,*/found->sz,found->objtype);
obj_setname(ptr, name); // found->id);
return ptr; // returns partial construction as well. @todo: just return `ret` for a more strict built/failed policy
}
#line 0
#line 1 "v4k_ai.c"
// AI framework
// - rlyeh, public domain.
//
// [src] original A-star code by @mmozeiko (PD) - https://gist.github.com/mmozeiko/68f0a8459ef2f98bcd879158011cc275
// [src] original swarm/boids code by @Cultrarius (UNLICENSE) - https://github.com/Cultrarius/Swarmz
// boids/swarm -----------------------------------------------------------------
vec3 rnd3() { // random uniform
float theta = randf() * C_PI * 2;
float r = sqrt(randf());
float z = sqrt(1.0f - r * r) * (randf() > 0.5f ? -1.0f : 1.0f);
return vec3(r * cos(theta), r * sin(theta), z);
}
int less3(vec3 *lhs, vec3 *rhs) {
if(lhs->x != rhs->x) return lhs->x - rhs->x;
if(lhs->y != rhs->y) return lhs->y - rhs->y;
if(lhs->z != rhs->z) return lhs->z - rhs->z; // @testme: remove superfluous if check
return 0;
}
uint64_t hash3(vec3 *v) {
uint64_t h1 = hash_flt(v->x);
uint64_t h2 = hash_flt(v->y);
uint64_t h3 = hash_flt(v->z);
return (h1 * 31 + h2) * 31 + h3;
}
vec3 clamplen3(vec3 v, float length) {
return len3(v) <= length ? v : scale3(norm3(v), length);
}
float transform_distance(float distance, SWARM_DISTANCE type) {
float quad;
/**/ if (type == SWARM_DISTANCE_LINEAR) return distance;
else if (type == SWARM_DISTANCE_QUADRATIC) return distance * distance;
else if (type == SWARM_DISTANCE_INVERSE_LINEAR) return distance == 0 ? 0 : 1 / distance;
else if (type == SWARM_DISTANCE_INVERSE_QUADRATIC) return (quad = distance * distance), (quad == 0 ? 0 : 1 / quad);
return distance; // throw exception instead?
}
typedef struct nearby_boid_t {
boid_t *boid;
vec3 direction;
float distance;
} nearby_boid_t;
static
vec3 get_voxel_for_boid(float perception_radius, const boid_t *b) { // quantize position
float r = absf(perception_radius);
return vec3( (int)(b->position.x / r), (int)(b->position.y / r), (int)(b->position.z / r) );
}
static
void check_voxel_for_boids(float perception_radius, float blindspot_angledeg_compare_value, array(boid_t*) voxel_cached, array(nearby_boid_t) *result, const vec3 voxelPos, const boid_t *b) {
for each_array_ptr(voxel_cached, boid_t*, test) {
vec3 p1 = b->position;
vec3 p2 = (*test)->position;
vec3 vec = sub3(p2, p1);
float distance = len3(vec);
float compare_value = 0;
float l1 = len3(vec);
float l2 = len3(b->velocity);
if (l1 != 0 && l2 != 0) {
compare_value = dot3(neg3(b->velocity), vec) / (l1 * l2);
}
if (b != (*test) && distance <= perception_radius && (blindspot_angledeg_compare_value > compare_value || len3(b->velocity) == 0)) {
nearby_boid_t nb;
nb.boid = (boid_t*)*test;
nb.distance = distance;
nb.direction = vec;
array_push(*result, nb);
}
}
}
static
array(nearby_boid_t) get_nearby_boids(const swarm_t *self, const boid_t *b) {
array(nearby_boid_t) result = 0;
array_reserve(result, array_count(self->boids));
vec3 voxelPos = get_voxel_for_boid(self->perception_radius, b);
voxelPos.x -= 1;
voxelPos.y -= 1;
voxelPos.z -= 1;
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 3; y++) {
for (int z = 0; z < 3; z++) {
array(boid_t*) *found = map_find(self->voxel_cache_, &voxelPos);
if( found ) check_voxel_for_boids(self->perception_radius, self->blindspot_angledeg_compare_value_, *found, &result, voxelPos, b);
voxelPos.z++;
}
voxelPos.z -= 3;
voxelPos.y++;
}
voxelPos.y -= 3;
voxelPos.x++;
}
return result;
}
static
void update_boid(swarm_t *self, boid_t *b) {
vec3 separation_sum = {0};
vec3 heading_sum = {0};
vec3 position_sum = {0};
vec3 po = b->position;
array(nearby_boid_t) nearby = get_nearby_boids(self, b); // @leak
for each_array_ptr(nearby, nearby_boid_t, closeboid_t) {
if (closeboid_t->distance == 0) {
separation_sum = add3(separation_sum, scale3(rnd3(), 1000)); //addscale3
}
else {
float separation_factor = transform_distance(closeboid_t->distance, self->separation_type);
separation_sum = add3(separation_sum, scale3(neg3(closeboid_t->direction), separation_factor)); // addscale3
}
heading_sum = add3(heading_sum, closeboid_t->boid->velocity); // inc3
position_sum = add3(position_sum, closeboid_t->boid->position); // inc3
}
vec3 steering_target = b->position;
float target_distance = -1;
for( int i = 0, end = array_count(self->steering_targets); i < end; ++i ) {
vec3 *target = &self->steering_targets[i];
float distance = transform_distance(len3(sub3(*target,b->position)), self->steering_target_type);
if (target_distance < 0 || distance < target_distance) {
steering_target = *target;
target_distance = distance;
}
}
int nearby_size = array_count(nearby);
// Separation: steer to avoid crowding local flockmates
vec3 separation = nearby_size > 0 ? scale3(separation_sum, 1.f / nearby_size) : separation_sum;
// Alignment: steer towards the average heading of local flockmates
vec3 alignment = nearby_size > 0 ? scale3(heading_sum, 1.f / nearby_size) : heading_sum;
// Cohesion: steer to move toward the average position of local flockmates
vec3 avgposition = nearby_size > 0 ? scale3(position_sum, 1.f / nearby_size) : b->position;
vec3 cohesion = sub3(avgposition, b->position);
// Steering: steer towards the nearest target location (like a moth to the light)
vec3 steering = scale3(norm3(sub3(steering_target, b->position)), target_distance);
// calculate boid acceleration
vec3 acceleration;
acceleration = scale3(separation, self->separation_weight);
acceleration = add3(acceleration, scale3(alignment, self->alignment_weight));
acceleration = add3(acceleration, scale3(cohesion, self->cohesion_weight));
acceleration = add3(acceleration, scale3(steering, self->steering_weight));
b->acceleration = clamplen3(acceleration, self->max_acceleration);
}
swarm_t swarm() {
swarm_t self = {0};
self.boids = NULL;
self.perception_radius = 3; // 30
self.separation_weight = 0.1; // 1
self.separation_type = SWARM_DISTANCE_INVERSE_QUADRATIC;
self.alignment_weight = 0.1; // 1
self.cohesion_weight = 0.1; // 1
self.steering_weight = 0.1; // 0.1
// array_push(self.steering_targets, vec3(0,0,0));
self.steering_target_type = SWARM_DISTANCE_LINEAR;
self.blindspot_angledeg = 2; // 20
self.max_acceleration = 1; // 10;
self.max_velocity = 2; // 20;
self.blindspot_angledeg_compare_value_ = 0; // = cos(M_PI * 2 * blindspot_angledeg / 360)
map_init(self.voxel_cache_, less3, hash3);
return self;
}
void swarm_update_acceleration_only(swarm_t *self) {
self->perception_radius += !self->perception_radius; // 0->1
// build voxel cache
map_clear(self->voxel_cache_);
for( int i = 0, end = array_count(self->boids); i < end; ++i ) {
boid_t *b = &(self->boids)[i];
vec3 *key = MALLOC(sizeof(vec3));
*key = get_voxel_for_boid(self->perception_radius, b);
array(boid_t*) *found = map_find_or_add_allocated_key( self->voxel_cache_, key, 0 );
array_push(*found, b);
}
// update all boids
for( int i = 0, end = array_count(self->boids); i < end; ++i ) {
boid_t *b = &(self->boids)[i];
update_boid(self, b);
}
}
void swarm_update_acceleration_and_velocity_only(swarm_t *self, float delta) {
self->blindspot_angledeg_compare_value_ = cosf(C_PI * 2 * self->blindspot_angledeg / 360.0f);
swarm_update_acceleration_only(self);
for( int i = 0, end = array_count(self->boids); i < end; ++i ) {
boid_t *b = &(self->boids)[i];
b->velocity = clamplen3(add3(b->velocity, scale3(b->acceleration, delta)), self->max_velocity);
}
}
void swarm_update(swarm_t *self, float delta) {
swarm_update_acceleration_and_velocity_only(self, delta);
for( int i = 0, end = array_count(self->boids); i < end; ++i ) {
boid_t *b = &(self->boids)[i];
b->prev_position = b->position;
b->position = add3(b->position, scale3(b->velocity, delta));
}
}
int ui_swarm(swarm_t *self) {
const char *distances[] = {
"Linear",
"Inverse Linear",
"Quadratic",
"Inverse Quadratic"
};
int rc = 0;
rc |= ui_float( "Perception Radius", &self->perception_radius);
ui_separator();
rc |= ui_float( "Separation Weight", &self->separation_weight);
rc |= ui_radio( "Separation Type", distances, countof(distances), (int*)&self->separation_type);
ui_separator();
rc |= ui_float( "Alignment Weight", &self->alignment_weight);
rc |= ui_float( "Cohesion Weight", &self->cohesion_weight);
ui_separator();
rc |= ui_float( "Steering Weight", &self->steering_weight);
//array(vec3) steering_targets;
rc |= ui_radio( "Steering Target Type", distances, countof(distances), (int*)&self->steering_target_type);
ui_separator();
rc |= ui_float( "Blindspot Angle", &self->blindspot_angledeg);
rc |= ui_float( "Max Acceleration", &self->max_acceleration);
rc |= ui_float( "Max Velocity", &self->max_velocity);
return rc;
}
// pathfinding -----------------------------------------------------------------
int pathfind_astar(int width, int height, const unsigned* map, vec2i src, vec2i dst, vec2i* path, size_t maxpath) {
#define ALLOW_DIAGONAL_MOVEMENT 1
#if ALLOW_DIAGONAL_MOVEMENT
#define ASTAR_DIR_COUNT 8
#else
#define ASTAR_DIR_COUNT 4
#endif
static const vec2i dir[ASTAR_DIR_COUNT] =
{
{ 1, 0 },
{ 0, 1 },
{ -1, 0 },
{ 0, -1 },
#if ALLOW_DIAGONAL_MOVEMENT
{ 1, 1 },
{ 1, -1 },
{ -1, 1 },
{ -1, -1 },
#endif
};
#define ASTAR_POS_TYPE vec2i
#define ASTAR_POS_START src
#define ASTAR_POS_FINISH dst
#define ASTAR_POS_INDEX(p) ((p).y * width + (p).x)
#define ASTAR_MAX_INDEX (width * height)
#define ASTAR_INDEX_POS(p, i) \
do { \
(p).x = (i) % width; \
(p).y = (i) / width; \
} while (0)
#define ASTAR_POS_EQUAL(a, b) ((a).x == (b).x && (a).y == (b).y)
#define ASTAR_MAP_IS_FREE(p) ((p).y >= 0 && (p).y < height && (p).x >= 0 && (p).x < width && (char)map[(p).y * width + (p).x] == 0)
#define ASTAR_NEXT_POS(p, i) \
do { \
(p).x += dir[i].x; \
(p).y += dir[i].y; \
} while (0)
#define ASTAR_PREV_POS(p, i) \
do { \
(p).x -= dir[i].x; \
(p).y -= dir[i].y; \
} while (0)
#define ASTAR_GET_COST(a, b) (abs((a).x - (b).x) + abs((a).y - (b).y))
#if ALLOW_DIAGONAL_MOVEMENT
#define ASTAR_EXTRA_COST(i) (i < 4 ? 5 : 7) // 7/5 is approx sqrt(2)
#define ASTAR_COST_MUL 5
#endif
size_t path_count = 0;
#define ASTAR_PATH(p) if (path_count < maxpath) path[path_count++] = p
// tempwork memory, not thread-safe.
#define ASTAR_TEMP_SIZE (ASTAR_MAX_INDEX * (sizeof(unsigned)*2) + sizeof(unsigned)*4) // (16<<20)
#define ASTAR_TEMP temp
static array(char) ASTAR_TEMP; do_once array_resize(ASTAR_TEMP, ASTAR_TEMP_SIZE);
// #if 1 "astar.h"
{
// generic A* pathfinding
//
// INTERFACE
//
// mandatory macros
#ifndef ASTAR_POS_TYPE
#error ASTAR_POS_TYPE should specify position type
#endif
#ifndef ASTAR_POS_START
#error ASTAR_POS_START should specify start position
#endif
#ifndef ASTAR_POS_FINISH
#error ASTAR_POS_FINISH should specify finish position
#endif
#ifndef ASTAR_POS_INDEX
#error ASTAR_POS_INDEX(p) should specify macro to map position to index
#endif
#ifndef ASTAR_MAX_INDEX
#error ASTAR_MAX_INDEX should specify max count of indices the position can map to
#endif
#ifndef ASTAR_INDEX_POS
#error ASTAR_INDEX_POS(i) should specify macro to map index to position
#endif
#ifndef ASTAR_POS_EQUAL
#error ASTAR_POS_EQUAL(a, b) should specify macro to compare if two positions are the same
#endif
#ifndef ASTAR_MAP_IS_FREE
#error ASTAR_MAP_IS_FREE(p) should specify macro to check if map at position p is free
#endif
#ifndef ASTAR_NEXT_POS
#error ASTAR_NEXT_POS(p, i) should specify macro to get next position in specific direction
#endif
#ifndef ASTAR_PREV_POS
#error ASTAR_PREV_POS(p, i) should specify macro to get previous position from specific direction
#endif
#ifndef ASTAR_DIR_COUNT
#error ASTAR_DIR_COUNT should specify possible direction count
#endif
#ifndef ASTAR_GET_COST
#error ASTAR_GET_COST(a, b) should specify macro to get get cost between two positions
#endif
#ifndef ASTAR_PATH
#error ASTAR_PATH(p) should specify macro that will be invoked on each position for path (in reverse order), including start/finish positions
#endif
#if !defined(ASTAR_TEMP) || !defined(ASTAR_TEMP_SIZE)
#error ASTAR_TEMP and ASTAR_TEMP_SIZE should specify variable & size for temporary memory (should be at least ASTAR_MAX_INDEX * 4 + extra)
#endif
// optional macros
// adds extra cost for specific direction (useful for increasing cost for diagonal movements)
#ifndef ASTAR_EXTRA_COST
#define ASTAR_EXTRA_COST(i) 1
#endif
// multiplier for adding cost values (current_cost + mul * new_cost) - useful when using extra cost for diagonal movements
#ifndef ASTAR_COST_MUL
#define ASTAR_COST_MUL 1
#endif
//
// IMPLEMENTATION
//
#if ASTAR_DIR_COUNT <= 4
#define ASTAR_DIR_BITS 2
#elif ASTAR_DIR_COUNT <= 8
#define ASTAR_DIR_BITS 3
#elif ASTAR_DIR_COUNT <= 16
#define ASTAR_DIR_BITS 4
#elif ASTAR_DIR_COUNT <= 32
#define ASTAR_DIR_BITS 5
#elif ASTAR_DIR_COUNT <= 64
#define ASTAR_DIR_BITS 6
#else
#error Too many elements for ASTAR_DIR_COUNT, 64 is max
#endif
#define ASTAR_COST_BITS (32 - 1 - ASTAR_DIR_BITS)
#define ASTAR_HEAP_SWAP(a, b) \
do { \
heapnode t = heap[a]; \
heap[a] = heap[b]; \
heap[b] = t; \
} while (0) \
#define ASTAR_HEAP_PUSH(idx, c) \
do { \
heap[heap_count].index = idx; \
heap[heap_count].cost = c; \
int i = heap_count++; \
int p = (i - 1) / 2; \
while (i != 0 && heap[p].cost > heap[i].cost) \
{ \
ASTAR_HEAP_SWAP(i, p); \
i = p; \
p = (i - 1) / 2; \
} \
} while (0)
#define ASTAR_HEAP_POP() \
do { \
heap[0] = heap[--heap_count]; \
int i = 0; \
for (;;) \
{ \
int l = 2 * i + 1; \
int r = 2 * i + 2; \
int s = i; \
if (l < heap_count && heap[l].cost < heap[i].cost) s = l; \
if (r < heap_count && heap[r].cost < heap[s].cost) s = r; \
if (s == i) break; \
ASTAR_HEAP_SWAP(i, s); \
i = s; \
} \
} while (0)
typedef union {
struct {
unsigned int cost : ASTAR_COST_BITS;
unsigned int dir : ASTAR_DIR_BITS;
unsigned int visited : 1;
};
unsigned int all;
} node;
typedef struct {
unsigned int index;
unsigned int cost;
} heapnode;
if (ASTAR_TEMP_SIZE >= sizeof(node) * ASTAR_MAX_INDEX + sizeof(heapnode))
{
node* nodes = (node*)ASTAR_TEMP;
for (unsigned int i = 0; i < ASTAR_MAX_INDEX; i++)
{
nodes[i].all = 0;
}
heapnode* heap = (heapnode*)((char*)ASTAR_TEMP + sizeof(node) * ASTAR_MAX_INDEX);
unsigned int heap_max = (ASTAR_TEMP_SIZE - sizeof(node) * ASTAR_MAX_INDEX) / sizeof(heapnode);
int heap_count = 0;
ASTAR_POS_TYPE p = ASTAR_POS_START;
unsigned int nindex = ASTAR_POS_INDEX(p);
node* n = nodes + nindex;
n->cost = 0;
n->visited = 1;
ASTAR_HEAP_PUSH(nindex, ASTAR_GET_COST(p, ASTAR_POS_FINISH));
int found = 0;
while (heap_count != 0)
{
nindex = heap[0].index;
n = nodes + nindex;
ASTAR_HEAP_POP();
ASTAR_INDEX_POS(p, nindex);
if (ASTAR_POS_EQUAL(p, ASTAR_POS_FINISH))
{
found = 1;
break;
}
n->visited = 1;
for (unsigned int i = 0; i < ASTAR_DIR_COUNT; i++)
{
ASTAR_POS_TYPE next = p;
ASTAR_NEXT_POS(next, i);
if (ASTAR_MAP_IS_FREE(next))
{
unsigned int nnext_index = ASTAR_POS_INDEX(next);
node* nnext = nodes + nnext_index;
unsigned int cost = n->cost + ASTAR_EXTRA_COST(i);
if (nnext->visited == 0 || cost < nnext->cost)
{
nnext->cost = cost;
nnext->dir = i;
nnext->visited = 1;
if (heap_count == heap_max)
{
// out of memory
goto bail;
}
unsigned int new_cost = cost + ASTAR_COST_MUL * ASTAR_GET_COST(next, ASTAR_POS_FINISH);
ASTAR_HEAP_PUSH(nnext_index, new_cost);
}
}
}
}
bail:
if (found)
{
ASTAR_PATH(p);
while (!ASTAR_POS_EQUAL(p, ASTAR_POS_START))
{
ASTAR_PREV_POS(p, n->dir);
n = nodes + ASTAR_POS_INDEX(p);
ASTAR_PATH(p);
}
}
}
else
{
// not enough temp memory
}
#undef ASTAR_POS_TYPE
#undef ASTAR_POS_START
#undef ASTAR_POS_FINISH
#undef ASTAR_POS_INDEX
#undef ASTAR_MAX_INDEX
#undef ASTAR_INDEX_POS
#undef ASTAR_POS_EQUAL
#undef ASTAR_MAP_IS_FREE
#undef ASTAR_NEXT_POS
#undef ASTAR_PREV_POS
#undef ASTAR_DIR_COUNT
#undef ASTAR_GET_COST
#undef ASTAR_EXTRA_COST
#undef ASTAR_COST_MUL
#undef ASTAR_PATH
#undef ASTAR_TEMP
#undef ASTAR_TEMP_SIZE
#undef ASTAR_COST_BITS
#undef ASTAR_DIR_BITS
#undef ASTAR_HEAP_SWAP
#undef ASTAR_HEAP_PUSH
#undef ASTAR_HEAP_POP
}
// #endif "astar.h"
return path_count;
}
// Behavior trees: decision planning and decision making.
// Supersedes finite state-machines (FSM) and hierarchical finite state-machines (HFSM).
// - rlyeh, public domain.
//
// [ref] https://outforafight.wordpress.com/2014/07/15/behaviour-behavior-trees-for-ai-dudes-part-1/
// [ref] https://www.gameaipro.com/GameAIPro/GameAIPro_Chapter06_The_Behavior_Tree_Starter_Kit.pdf
// [ref] https://gitlab.com/NotYetGames/DlgSystem/-/wikis/Dialogue-Manual
// [ref] https://towardsdatascience.com/designing-ai-agents-behaviors-with-behavior-trees-b28aa1c3cf8a
// [ref] https://docs.nvidia.com/isaac/packages/behavior_tree/doc/behavior_trees.html
// [ref] https://docs.unrealengine.com/4.26/en-US/InteractiveExperiences/ArtificialIntelligence/BehaviorTrees/
// [ref] gdc ChampandardDaweHernandezCerpa_BehaviorTrees.pdf @todo debugging
// [ref] https://docs.cocos.com/cocos2d-x/manual/en/actions/
// The nodes in a behavior tree can be broadly categorized into three main types: control nodes, task nodes, and decorator nodes. Here is a brief description of each category:
// Control Nodes: Control nodes are used to control the flow of the behavior tree. They determine the order in which child nodes are executed and how the results of those nodes are combined. They are usually parent nodes.
// Action Nodes: Action nodes are used to perform specific actions or tasks within the behavior tree. They can include actions such as moving, attacking, or interacting with objects in the game world. They are usually leaf nodes.
// Decorator Nodes: Decorator nodes are used to modify the behavior of child nodes in some way. They can be used to repeat child nodes, invert the result of a child node, or add a cooldown period between executions. They are usually located between control and action nodes.
// --- VARIABLES
// Key Prefixes:
// {visiblity:(l)ocal,s(q)uad,(r)ace,(f)action,(g)lobal}
// [persistence:(t)emp,(u)serdata,(s)avegame,(c)loud] + '_' + name.
// [persistence:(tmp),(usr)data,(sav)egame,(net)cloud] + '_' + name.
// Ie, l_health = 123.4, gsav_player = "john"
// --- ACTION NODES
// [ ] * Actions/Behaviors have a common structure:
// [ ] - Entry point (function name) for a C call or Lua script.
// [ ] * Status:
// [ ] - Uninitialized (never run)
// [ ] - Running (in progress)
// [ ] - Suspended (on hold till resumed)
// [ ] - Success (finished and succeeded)
// [ ] - Failure (finished and failed)
// [ ] * Optional callbacks:
// [ ] - on_enter
// [ ] - on_leave
// [ ] - on_success
// [ ] - on_failure
// [ ] - on_suspend
// [ ] - on_resume
// [x] Action Node: This node performs a single action, such as moving to a specific location or attacking a target.
// [ ] Blackboard Node: Node that reads and writes data to a shared memory space known as a blackboard. The blackboard can be used to store information that is relevant to multiple nodes in the behavior tree.
// [ ] SetKey(keyVar,object)
// [ ] HasKey(keyVar)
// [ ] CompareKeys(keyVar1, operator < <= > >= == !=, keyVar2)
// [ ] SetTags(names=blank,cooldownTime=inf,bIsCooldownAdditive=false)
// [ ] HasTags(names=blank,bAllRequired=true)
// [ ] PushToStack(keyVar,itemObj): creates a new stack if one doesnt exist, and stores it in the passed variable name, and then pushes item object onto it.
// [ ] PopFromStack(keyVar,itemVar): pop pops an item off the stack, and stores it in the itemVar variable, failing if the stack is already empty.
// [ ] IsEmptyStack(keyVar): checks if the stack passed is empty and returns success if it is, and failure if its not.
// [ ] Communication Node: This is a type of action node that allows an AI agent to communicate with other agents or entities in the game world. The node takes an input specifying the message to be communicated and the recipient(s) of the message (wildmask,l/p/f/g prefixes). The node then sends the message to the designated recipient(s) and returns success when the communication is completed. This node can be useful for implementing behaviors that require the AI agent to coordinate with other agents or to convey information to the player. It could use a radius argument to specify the maximum allowed distance for the recipients.
// [ ] Condition Node: A leaf node that checks a specific condition, such as the distance to an object, the presence of an enemy, or the status of a health bar.
// [ ] Distance Condition Node: This is a type of condition node that evaluates whether an AI agent is within a specified distance of a target object or location. The node takes two inputs: the current position of the AI agent and the position of the target object or location. If the distance between the two is within a specified range, the node returns success. If the distance is outside of the specified range, the node returns failure. This node can be useful for implementing behaviors that require the AI agent to maintain a certain distance from a target, such as following or avoiding an object. Could use a flag to disambiguate between linear distance and path distance.
// [ ] Query Node: This node checks a condition and returns success or failure based on the result. For example, a query node could check whether an enemy is within range or whether a door is locked.
// [ ] Query Node: A type of decorator node that retrieves information from a database or external system, such as a web service or file system. The Query node can be used to retrieve data that is not available within the game engine, such as weather or traffic conditions.
// [ ] A condition is made of:
// [ ] - Optional [!] negate
// [ ] - Mandatory Value1(Int/Flt/Bool/VarName/FuncName)
// [ ] - Optional operator [< <= > >= == !=] and Value2(Int/Flt/Bool/VarName/FuncName)
// [ ] AllConditions(...) : SUCCESS if ( empty array || all conditions met)
// [ ] AnyConditions(...) : SUCCESS if (!empty array && one conditions met)
// --- DECORATOR NODES
// [ ] Cooldown Node: Decorator node that adds a cooldown period between the execution of a child node, preventing it from being executed again until the cooldown period has elapsed.
// [x] Counter Node: Decorator node that limits the number of times that a child node can execute. For example, if the child node has executed a certain number of times, the Counter node will return a failure.
// [x] Once Node: Decorator node that triggers a specified action when a condition is met for the first time. This can be useful for triggering a one-time event, such as when an AI agent discovers a hidden item or reaches a new area of the game world.
// [x] Inverter Node: Decorator node that inverts the result of a child node, returning success if the child node fails and failure if the child node succeeds. This can be useful for negating the outcome of a particular behavior, such as avoiding a certain area of the game world.
// [x] Repeater Node: Decorator node that repeats the execution of a child node a specified number of times or indefinitely.
// [x] Repeat(times=inf): Runs child node given times. These are often used at the very base of the tree, to make the tree to run continuously.
// [ ] RepeatIf(strong/weak condition): Runs child node as long as the conditions are met.
// [ ] RepeatIfOk(times=inf): Runs child node if it succeedes, max given times.
// [ ] RepeatIfFail(times=inf): Runs child node if it fails, max given times.
// [ ] Branch Node: 2 children [0] for true, [1] for false
// [ ] Resource Node: Decorator node that manages a shared resource, such as a limited supply of ammunition or a limited amount of processing power. The Resource node The node can be used to decide when to use or conserve the resource. For example, if the AI agent is low on ammunition, the node may decide to switch to a melee weapon or to retreat to a safer location. This node can be useful for implementing behaviors that require the AI agent to make strategic decisions about resource use.
// [x] Result Node: Decorator node that tracks the result of a child action node and returns either success or failure depending on the outcome. This can be useful for determining whether an action was successful or not, and then adjusting the AI agent's behavior accordingly.
// [x] Succeeder Node: Decorator node that always returns success, regardless of the result of its child node. This can be useful for ensuring that certain behaviors are always executed, regardless of the overall success or failure of the behavior tree.
// [x] Success(): FAILURE becomes SUCCESS (TRUE).
// [x] Failure(): SUCCESS becomes FAILURE (FALSE).
// [ ] Throttle Node: Decorator node that limits the rate at which a child node can execute. For example, a Throttle node might ensure that an AI agent can only fire its weapon, or using a special ability, only a certain number of times per second.
// [x] Delay Node: Decorator node that adds a delay to the execution of a child node. The delay might be configured to sleep before the execution, after the execution, or both.
// [x] Defer Delay(duration_ss=1): Runs the child node, then sleeps for given seconds.
// [ ] Ease(time,name): Clamps child time to [0,1] range, and applies easing function on it.
// [ ] Dilate(Mul=1,Add=0): Dilates child time
// --- CONTROL NODES
// [x] Root Node: The topmost node in a behavior tree that represents the start of the decision-making process. Returns success if any of its child nodes suceedes.
// [x] Root-Failure Node: Control node that only returns failure if all of its child nodes fail. This can be useful for ensuring that a behavior tree is not prematurely terminated if one or more child nodes fail.
// [ ] Event(name): When name event is raised, it suspends current tree and calls child. "Incoming projectile" -> Evade. Stimulus types: may be disabled by event, or autodisabled.
// [ ] Raise(name): Triggers event name.
// [ ] Checkpoint Node: Control node that saves a state in memory and then continues the tree execution from that point the next time the tree is executed. It can be useful in situations where the behavior tree needs to be interrupted and resumed later.
// [ ] Decision Making Node: Control node that implements a decision-making process for the AI agent. The node takes input specifying the available options for the AI agent and the criteria for evaluating each option. The node then evaluates each option based on the specified criteria and selects the best option. This node can be useful for implementing behaviors that require the AI agent to make strategic decisions, such as choosing a target or selecting a path through the game world.
// [ ] Could be extended with GOAP if dynamically inserting the scores on each update then calling a Probability Selector Node (0.2,0.3,0.5)
// [ ] https://cdn.cloudflare.steamstatic.com/apps/valve/2012/GDC2012_Ruskin_Elan_DynamicDialog.pdf
// [ ] Evaluate Node / Recheck():
// [ ] Actively rechecks all existing sub-conditions on a regular basis after having made decisions about them.
// [ ] Use this feature to dynamically check for risks or opportunities in selected parts of the tree.
// [ ] For example, interrupting a patrol with a search behavior if a disturbance is reported.
// [ ] Interrupt Node: Control node that interrupts the execution of a lower-priority node when a higher-priority node needs to be executed. It can be useful for handling urgent tasks or emergency situations.
// [ ] Monitor Node: Control node that continuously monitors a condition and triggers a specified action when the condition is met. For example, a Monitor node might monitor the distance between an AI agent and a target and trigger a "retreat" behavior when the distance falls below a certain threshold.
// [ ] Observer Node: Control node that monitors the state of the game world and triggers a specified action when certain conditions are met. For example, an Observer node might trigger a "hide" behavior when an enemy is spotted.
// [ ] Parallel Node: Control node that executes multiple child nodes simultaneously. The Parallel node continues to execute even if some of its child nodes fail.
// [ ] Parallel All Node(required_successes=100%): Control node that executes multiple child nodes simultaneously. Returns false when first child fails, and aborts any other running tasks. Returns true if all its children succeed.
// [ ] Parallel One Node(required_successes=1): Control node that executes multiple child nodes simultaneously. Returns true when first child suceedes, and aborts any other running tasks. Returns false if all its children fail.
// [ ] Subsumption Node: Control node that allows multiple behaviors to be executed simultaneously, with higher-priority behaviors taking precedence over lower-priority ones. This can be useful for implementing complex, multi-level behaviors in autonomous systems.
// [ ] Semaphore Node: Control node that blocks the execution of its child nodes until a certain condition is met. For example, a Semaphore node might block the execution of a behavior until a certain object is in range or a certain event occurs.
// [ ] Semaphore Wait Node: Control node that blocks the execution of its child nodes until a resource becomes available. This can be useful for controlling access to shared resources such as a pathfinding system or a communication channel.
// [ ] WaitTags(tags=blank,timeout_ss=inf): Stops execution of child node until the cooldown tag(s) do expire. May return earlier if timed out.
// [ ] WaitEvent(name=blank,timeout_ss=inf): Stops execution of child node until event is raised. May return earlier if timed out.
// [x] Sequence Node(reversed,iterator(From,To)): Control node that executes a series of child nodes in order. If any of the child nodes fail, the Sequence node stops executing and returns a failure.
// [ ] Dynamic Sequence Node: Control node that dynamically changes the order in which its child nodes are executed based on certain conditions. For example, a Dynamic Sequence node might give higher priority to a child node that has recently failed or that is more likely to succeed.
// [ ] Reverse(): iterates children in reversed order (Iterator:(-1,0) equivalent)
// [ ] Iterator(from,to): allows negative indexing. increments if (abs from < abs to), decrements otherwise
// [x] Selector Node: Control node that selects a child node to execute based on a predefined priority or set of conditions. The Selector node stops executing child nodes as soon as one of them succeeds.
// [ ] Priority Selector Node: Control node that executes its child nodes in order of priority. If the first child node fails, it moves on to the next child node in the list until a successful node is found. This can be useful for implementing behaviors that require a specific order of execution, such as a patrol route or a search pattern.
// [ ] Probability Selector Node: Control node that selects a child node to execute based on a probability distribution. For example, if there are three child nodes with probabilities of 0.2, 0.3, and 0.5, the Probability Selector node will execute the third child node 50% of the time.
// [ ] Dynamic Selector Node: Control node that dynamically changes the order in which its child nodes are executed based on certain conditions. For example, a Dynamic Selector node might give higher priority to a child node that has recently succeeded or that is more likely to succeed.
// [ ] Dynamic Selector Node: Control node that dynamically changes the order in which child nodes are executed based on certain conditions. For example, it may prioritize certain nodes when health is low, and other nodes when resources are scarce.
// [ ] Dynamic Priority Node: Control node that dynamically changes the priority of its child nodes based on certain conditions. For example, if a child node is more likely to result in success, the Dynamic Priority node will give it a higher priority.
// [ ] Weighted / Cover Selection Node: This is a type of selector node that evaluates the available cover options in the game world and selects the best cover position for the AI agent. The node takes input specifying the current position of the AI agent and the location of potential cover positions. The node then evaluates the cover positions based on criteria such as distance to enemy positions, line of sight, and available cover points, and selects the best option. This node can be useful for implementing behaviors that require the AI agent to take cover and avoid enemy fire.
// [ ] Random Selector Node: Control node that selects a child node to execute at random. This can be useful for introducing variety and unpredictability into the behavior of an AI agent.
// [ ] Random Weight / Stochastic Node(0.2,0.3,0.5): Control node that introduces randomness into the decision-making process of an AI agent. For example, a Stochastic node might randomly select a child node to execute, with the probability of each child node being proportional to its likelihood of success.
// [ ] Break(bool): breaks parent sequence or selector. may return SUCCESS or FAILURE.
// [ ] Hybrid Node: Control node that combines the functionality of multiple control nodes, such as a sequence node and a selector node. It can be useful for creating complex behavior patterns that require multiple control structures. The Hybrid Node has two modes of operation: strict mode and flexible mode. In strict mode, the child nodes are executed in a fixed order, similar to a Sequence Node. If any child node fails, the entire Hybrid Node fails. In flexible mode, the child nodes can be executed in any order, similar to a Selector Node. If any child node succeeds, the entire Hybrid Node succeeds. The Hybrid Node is often used when you need to create complex behavior patterns that require multiple control structures. For example, you might use a Hybrid Node to implement a search behavior where the AI agent first moves in a fixed direction for a certain distance (strict mode), but then switches to a more exploratory mode where it randomly explores nearby areas (flexible mode).
// [ ] Subtree Node: Control node that calls another behavior tree as a subroutine. This is useful for breaking down complex behaviors into smaller, more manageable parts.
// [ ] Call(name): Calls to the child node with the matching name within behavior tree. Returns FAILURE if name is invalid or if invoked behavior returns FAILURE.
// [ ] Return(boolean): Exits current Call.
// [ ] AttachTree(Name,bDetachAfterUse=true): Attaches subtree to main tree. When a NPC founds the actor, it attaches the behavior to the tree. Ie, Level specific content: Patrols, Initial setups, Story driven events, etc. Ie, DLCs: Behaviors are added to actors in the DLC level (enticers).
// [ ] DetachTree(Name)
// [ ] Switch Node: Control node that evaluates a condition and then selects one of several possible child nodes to execute based on the result of the condition.
// [ ] Switch(name): Jumps to the child node with the matching name within behavior tree. If name is invalid will defer to Fallback child node.
// [ ] Timeout Node: Control node that aborts the execution of its child node after a certain amount of time has elapsed. This can be useful for preventing an AI agent from getting stuck in a loop or waiting indefinitely for a particular event to occur.
// [ ] TimeLimit(timeout_ss=inf): Give the child any amount of time to finish before it gets canceled. The timer is reset every time the node gains focus.
// [ ] Timer Node: Control node which invokes its child node every XX seconds. The timer can repeat the action any given number of times, or indefinitely.
// ## Proposal -----------------------------------------------------------------------------
// BehaviorTrees as case-insensitive INI files. Then,
// - INI -> C INTERPRETER, OR
// - INI -> LUA TRANSPILER -> LUA BYTECODE -> LUA VM.
// ```ini
// [bt]
// recheck
// sequence ;; Approach the player if seen!
// conditions=IsPlayerVisible,
// action=MoveTowardsPlayer
// sequence ;; Attack the player if seen!
// conditions=IsPlayerInRange,
// repeat=3
// action=FireAtPlayer
// sequence ;; Search near last known position
// conditions=HaveWeGotASuspectedLocation,
// action=MoveToPlayersLastKnownPosition
// action=LookAround
// sequence ;; Randomly scanning nearby
// action=MoveToRandomPosition
// action=LookAround
// event=IncomingProjectile
// action=Evade
// ```
map(char*, bt_func) binds;
void bt_addfun(const char *name, int(*func)()){
do_once map_init_str(binds);
map_find_or_add_allocated_key(binds, STRDUP(name), func);
}
bt_func bt_findfun(const char *name) {
bt_func *found = map_find(binds, (char*)name);
return found ? *found : 0;
}
char * bt_funcname(bt_func fn) {
for each_map(binds,char*,k,bt_func,f) {
if( f == fn ) return k;
}
return 0;
}
bt_t bt(const char *ini_file, unsigned flags) {
bt_t z = {0}, root = z;
array(char*) m = strsplit(vfs_read(ini_file), "\r\n");
bt_t *self = &root;
self->type = cc4(r,o,o,t);
//self->parent = self;
for( int i = 0; i < array_count(m); ++i ) {
// parse ini
int level = strspn(m[i], " \t");
char *k = m[i] + level;
if( k[0] == '[' ) {
if( strcmp(k+1, "bt]") ) return z; // we only support [bt]
continue;
}
int sep = strcspn(k, " =:");
char *v = k + sep; if(sep) *v++ = '\0'; else v = k + strlen(k); // v = (char*)"";
array(char*) args = *v ? strsplit(v, " ") : NULL;
// insert node in tree
bt_t *out = self;
while( level-- ) {
out = array_back(out->children);
}
array_push(out->children, ((bt_t){0}));
//array_back(out->children)->parent = out;
out = array_back(out->children);
// config node
out->type = *(uint64_t*)va("%-8s", k);
if( array_count(args) ) out->argf = atof(args[0]);
if( !strcmp(k, "run") ) out->action = bt_findfun(v);
}
return root;
}
int bt_run(bt_t *b) {
int rc = 0;
/**/ if( b->type == cc3( r,u,n) ) { return b->action ? b->action() : 0; }
else if( b->type == cc3( n,o,t) ) { return !bt_run(b->children + 0); }
else if( b->type == cc5( s,l,e,e,p) ) { return sleep_ss(b->argf), bt_run(b->children + 0); }
else if( b->type == cc5( d,e,f,e,r) ) { rc = bt_run(b->children + 0); return sleep_ss(b->argf), rc; }
else if( b->type == cc4( l,o,o,p) ) { int rc; for(int i = 0; i < b->argf; ++i) rc = bt_run(b->children + 0); return rc; }
else if( b->type == cc4( o,n,c,e) ) { return b->argf ? 0 : (b->argf = 1), bt_run(b->children + 0); }
else if( b->type == cc5( c,o,u,n,t) ) { return b->argf <= 0 ? 0 : --b->argf, bt_run(b->children + 0); }
else if( b->type == cc4( p,a,s,s) ) { return bt_run(b->children + 0), 1; }
else if( b->type == cc4( f,a,i,l) ) { return bt_run(b->children + 0), 0; }
else if( b->type == cc6( r,e,s,u,l,t) ) { return bt_run(b->children + 0), !!b->argf; }
else if( b->type == cc3( a,l,l) ) { for( int i = 0; i < array_count(b->children); ++i ) if(!bt_run(b->children+i)) return 0; return 1; }
else if( b->type == cc3( a,n,y) ) { for( int i = 0; i < array_count(b->children); ++i ) if( bt_run(b->children+i)) return 1; return 0; }
else if( b->type == cc4( r,o,o,t) ) { for( int i = 0; i < array_count(b->children); ++i ) rc|=bt_run(b->children+i); return rc; }
else if( b->type == cc8(r,o,o,t,f,a,i,l) ) { rc = 1; for( int i = 0; i < array_count(b->children); ++i ) rc&=~bt_run(b->children+i); return rc; }
return 0;
}
int ui_bt(bt_t *b) {
if( b ) {
char *info = bt_funcname(b->action);
if(!info) info = va("%d", array_count(b->children));
if( ui_collapse(va("%s (%s)", cc8str(b->type), info), va("bt%p",b)) ) {
for( int i = 0; i < array_count(b->children); ++i) {
ui_bt(b->children + i);
}
ui_collapse_end();
}
}
return 0;
}
#line 0
#line 1 "v4k_editor0.c"
// editing:
// nope > functions: add/rem property
#define ICON_PLAY ICON_MD_PLAY_ARROW
#define ICON_PAUSE ICON_MD_PAUSE
#define ICON_STOP ICON_MD_STOP
#define ICON_CANCEL ICON_MD_CLOSE
#define ICON_WARNING ICON_MD_WARNING
#define ICON_BROWSER ICON_MD_FOLDER_SPECIAL
#define ICON_OUTLINER ICON_MD_VIEW_IN_AR
#define ICON_BUILD ICON_MD_BUILD
#define ICON_SCREENSHOT ICON_MD_PHOTO_CAMERA
#define ICON_CAMERA_ON ICON_MD_VIDEOCAM
#define ICON_CAMERA_OFF ICON_MD_VIDEOCAM_OFF
#define ICON_GAMEPAD_ON ICON_MD_VIDEOGAME_ASSET
#define ICON_GAMEPAD_OFF ICON_MD_VIDEOGAME_ASSET_OFF
#define ICON_AUDIO_ON ICON_MD_VOLUME_UP
#define ICON_AUDIO_OFF ICON_MD_VOLUME_OFF
#define ICON_WINDOWED ICON_MD_FULLSCREEN_EXIT
#define ICON_FULLSCREEN ICON_MD_FULLSCREEN
#define ICON_LIGHTS_ON ICON_MD_LIGHTBULB
#define ICON_LIGHTS_OFF ICON_MD_LIGHTBULB_OUTLINE
#define ICON_RENDER_BASIC ICON_MD_IMAGE_SEARCH
#define ICON_RENDER_FULL ICON_MD_INSERT_PHOTO
#define ICON_SIGNAL ICON_MD_SIGNAL_CELLULAR_ALT
#define ICON_DISK ICON_MD_STORAGE
#define ICON_RATE ICON_MD_SPEED
#define ICON_CLOCK ICON_MD_TODAY
#define ICON_CHRONO ICON_MD_TIMELAPSE
#define ICON_SETTINGS ICON_MD_SETTINGS
#define ICON_LANGUAGE ICON_MD_G_TRANSLATE
#define ICON_PERSONA ICON_MD_FACE
#define ICON_SOCIAL ICON_MD_MESSAGE
#define ICON_GAME ICON_MD_ROCKET_LAUNCH
#define ICON_KEYBOARD ICON_MD_KEYBOARD
#define ICON_MOUSE ICON_MD_MOUSE
#define ICON_GAMEPAD ICON_MD_GAMEPAD
#define ICON_MONITOR ICON_MD_MONITOR
#define ICON_WIFI ICON_MD_WIFI
#define ICON_BUDGET ICON_MD_SAVINGS
#define ICON_NEW_FOLDER ICON_MD_CREATE_NEW_FOLDER
#define ICON_PLUGIN ICON_MD_EXTENSION
#define ICON_RESTART ICON_MD_REPLAY
#define ICON_QUIT ICON_MD_CLOSE
#define ICON_POWER ICON_MD_BOLT // ICON_MD_POWER
#define ICON_BATTERY_CHARGING ICON_MD_BATTERY_CHARGING_FULL
#define ICON_BATTERY_LEVELS \
ICON_MD_BATTERY_ALERT, \
ICON_MD_BATTERY_0_BAR,ICON_MD_BATTERY_1_BAR, \
ICON_MD_BATTERY_2_BAR,ICON_MD_BATTERY_3_BAR, \
ICON_MD_BATTERY_4_BAR,ICON_MD_BATTERY_5_BAR, \
ICON_MD_BATTERY_6_BAR,ICON_MD_BATTERY_FULL
char *editor_path(const char *path) {
return va("%s/%s", EDITOR, path);
}
vec3 editor_pick(float mouse_x, float mouse_y) {
#if 0
// unproject 2d coord as 3d coord
camera_t *camera = camera_get_active();
vec3 out, xyd = vec3(mouse_x,window_height()-mouse_y,1); // usually x:mouse_x,y:window_height()-mouse_y,d:0=znear/1=zfar
mat44 mvp, model; identity44(model); multiply44x3(mvp, camera->proj, camera->view, model);
bool ok = unproject44(&out, xyd, vec4(0,0,window_width(),window_height()), mvp);
return out;
#else
// unproject 2d coord as 3d coord
vec2 dpi = ifdef(osx, window_dpi(), vec2(1,1));
camera_t *camera = camera_get_active();
float x = (2.0f * mouse_x) / (dpi.x * window_width()) - 1.0f;
float y = 1.0f - (2.0f * mouse_y) / (dpi.y * window_height());
float z = 1.0f;
vec3 ray_nds = vec3(x, y, z);
vec4 ray_clip = vec4(ray_nds.x, ray_nds.y, -1.0, 1.0);
mat44 inv_proj; invert44(inv_proj, camera->proj);
mat44 inv_view; invert44(inv_view, camera->view);
vec4 p = transform444(inv_proj, ray_clip);
vec4 eye = vec4(p.x, p.y, -1.0, 0.0);
vec4 wld = norm4(transform444(inv_view, eye));
return vec3(wld.x, wld.y, wld.z);
#endif
}
typedef union engine_var {
int i;
float f;
char *s;
} engine_var;
static map(char*,engine_var) engine_vars;
float *engine_getf(const char *key) {
if(!engine_vars) map_init_str(engine_vars);
engine_var *found = map_find_or_add(engine_vars, (char*)key, ((engine_var){0}) );
return &found->f;
}
int *engine_geti(const char *key) {
if(!engine_vars) map_init_str(engine_vars);
engine_var *found = map_find_or_add(engine_vars, (char*)key, ((engine_var){0}) );
return &found->i;
}
char **engine_gets(const char *key) {
if(!engine_vars) map_init_str(engine_vars);
engine_var *found = map_find_or_add(engine_vars, (char*)key, ((engine_var){0}) );
if(!found->s) found->s = stringf("%s","");
return &found->s;
}
int engine_send(const char *cmd, const char *optional_value) {
unsigned *gamepads = engine_geti("gamepads"); // 0 off, mask gamepad1(1), gamepad2(2), gamepad3(4), gamepad4(8)...
unsigned *renders = engine_geti("renders"); // 0 off, mask: 1=lit, 2=ddraw, 3=whiteboxes
float *speed = engine_getf("speed"); // <0 num of frames to advance, 0 paused, [0..1] slomo, 1 play regular speed, >1 fast-forward (x2/x4/x8)
unsigned *powersave = engine_geti("powersave");
char *name;
/**/ if( !strcmp(cmd, "key_quit" )) record_stop(), exit(0);
else if( !strcmp(cmd, "key_stop" )) window_pause(1);
else if( !strcmp(cmd, "key_mute" )) audio_volume_master( 1 ^ !!audio_volume_master(-1) );
else if( !strcmp(cmd, "key_pause" )) window_pause( window_has_pause() ^ 1 );
else if( !strcmp(cmd, "key_reload" )) window_reload();
else if( !strcmp(cmd, "key_battery" )) *powersave = optional_value ? !!atoi(optional_value) : *powersave ^ 1;
else if( !strcmp(cmd, "key_browser" )) ui_show("File Browser", ui_visible("File Browser") ^ true);
else if( !strcmp(cmd, "key_outliner" )) ui_show("Outliner", ui_visible("Outliner") ^ true);
else if( !strcmp(cmd, "key_record" )) if(record_active()) record_stop(), ui_notify(va("Video recorded"), date_string()); else
app_beep(), name = file_counter(va("%s.mp4",app_name())), window_record(name);
else if( !strcmp(cmd, "key_screenshot" )) name = file_counter(va("%s.png",app_name())), window_screenshot(name), ui_notify(va("Screenshot: %s", name), date_string());
else if( !strcmp(cmd, "key_profiler" )) ui_show("Profiler", profiler_enable(ui_visible("Profiler") ^ true));
else if( !strcmp(cmd, "key_fullscreen" )) record_stop(), window_fullscreen( window_has_fullscreen() ^ 1 ); // framebuffer resizing corrupts video stream, so stop any recording beforehand
else if( !strcmp(cmd, "key_gamepad" )) *gamepads = (*gamepads & ~1u) | ((*gamepads & 1) ^ 1);
else if( !strcmp(cmd, "key_lit" )) *renders = (*renders & ~1u) | ((*renders & 1) ^ 1);
else if( !strcmp(cmd, "key_ddraw" )) *renders = (*renders & ~2u) | ((*renders & 2) ^ 2);
else alert(va("editor could not handle `%s` command.", cmd));
return 0;
}
int engine_tick() {
enum { engine_hz_mid = 30 };
enum { engine_hz_low = 10 };
static double old_hz = 0.0;
if( *engine_geti("powersave") ) {
// adaptive framerate
int app_on_background = !window_has_focus();
int hz = app_on_background ? engine_hz_low : engine_hz_mid;
if (!old_hz) old_hz = window_fps_target();
window_fps_lock( hz );
} else if( old_hz && old_hz != window_fps_target() ) {
window_fps_lock( old_hz );
old_hz = 0.0;
}
return 0;
}
int ui_engine() {
static int time_factor = 0;
static int playing = 0;
static int paused = 0;
int advance_frame = 0;
#if 0
static int do_filter = 0;
static int do_profile = 0;
static int do_extra = 0;
char *EDITOR_TOOLBAR_ICONS = va("%s;%s;%s;%s;%s;%s;%s;%s",
do_filter ? ICON_MD_CLOSE : ICON_MD_SEARCH,
ICON_MD_PLAY_ARROW,
paused ? ICON_MD_SKIP_NEXT : ICON_MD_PAUSE,
ICON_MD_FAST_FORWARD,
ICON_MD_STOP,
ICON_MD_REPLAY,
ICON_MD_FACE,
ICON_MD_MENU
);
if( input_down(KEY_F) ) if( input(KEY_LCTRL) || input(KEY_RCTRL) ) do_filter ^= 1;
int choice = ui_toolbar(EDITOR_TOOLBAR_ICONS);
if( choice == 1 ) do_filter ^= 1, do_profile = 0, do_extra = 0;
if( choice == 2 ) playing = 1, paused = 0;
if( choice == 3 ) advance_frame = !!paused, paused = 1;
if( choice == 4 ) paused = 0, time_factor = (++time_factor) % 4;
if( choice == 5 ) playing = 0, paused = 0, advance_frame = 0, time_factor = 0;
if( choice == 6 ) window_reload();
if( choice == 7 ) do_filter = 0, do_profile ^= 1, do_extra = 0;
if( choice == 8 ) do_filter = 0, do_profile = 0, do_extra ^= 1;
if( do_filter ) {
char *bak = ui_filter; ui_filter = 0;
ui_string(ICON_MD_CLOSE " Filter " ICON_MD_SEARCH, &bak);
ui_filter = bak;
if( ui_label_icon_clicked_L.x > 0 && ui_label_icon_clicked_L.x <= 24 ) { // if clicked on CANCEL icon (1st icon)
do_filter = 0;
}
} else {
if( ui_filter ) ui_filter[0] = '\0';
}
char *filter_mask = ui_filter && ui_filter[0] ? va("*%s*", ui_filter) : "*";
static char *username = 0;
static char *userpass = 0;
if( do_profile ) {
ui_string(ICON_MD_FACE " Username", &username);
ui_string(ICON_MD_FACE " Password", &userpass);
}
if( do_extra ) {
int choice2 = ui_label2_toolbar(NULL,
ICON_MD_VIEW_IN_AR
ICON_MD_MESSAGE
ICON_MD_TIPS_AND_UPDATES ICON_MD_LIGHTBULB ICON_MD_LIGHTBULB_OUTLINE
ICON_MD_IMAGE_SEARCH ICON_MD_INSERT_PHOTO
ICON_MD_VIDEOGAME_ASSET ICON_MD_VIDEOGAME_ASSET_OFF
ICON_MD_VOLUME_UP ICON_MD_VOLUME_OFF // audio_volume_master(-1) > 0
ICON_MD_TROUBLESHOOT ICON_MD_SCHEMA ICON_MD_MENU
);
}
#endif
int open = 0, clicked_or_toggled = 0;
#define EDITOR_UI_COLLAPSE(f,...) \
for( int macro(p) = (open = ui_collapse(f,__VA_ARGS__)), macro(dummy) = (clicked_or_toggled = ui_collapse_clicked()); macro(p); ui_collapse_end(), macro(p) = 0)
EDITOR_UI_COLLAPSE(ICON_MD_VIEW_QUILT " Windows", "Debug.Windows") {
int choice = ui_toolbar(ICON_MD_RECYCLING "@Reset layout;" ICON_MD_SAVE_AS "@Save layout");
if( choice == 1 ) ui_layout_all_reset("*");
if( choice == 2 ) file_delete(WINDOWS_INI), ui_layout_all_save_disk("*");
for each_map_ptr_sorted(ui_windows, char*, k, unsigned, v) {
bool visible = ui_visible(*k);
if( ui_bool( *k, &visible ) ) {
ui_show( *k, ui_visible(*k) ^ true );
}
}
}
EDITOR_UI_COLLAPSE(ICON_MD_BUG_REPORT " Bugs 0", "Debug.Bugs") {
// @todo. parse /bugs.ini, includes saved screenshots & videos.
// @todo. screenshot include parseable level, position screen markers (same info as /bugs.ini)
}
// Art and bookmarks
EDITOR_UI_COLLAPSE(ICON_MD_FOLDER_SPECIAL " Art", "Debug.Art") {
bool inlined = true;
const char *file = 0;
if( ui_browse(&file, &inlined) ) {
const char *sep = ifdef(win32, "\"", "'");
app_exec(va("%s %s%s%s", ifdef(win32, "start \"\"", ifdef(osx, "open", "xdg-open")), sep, file, sep));
}
}
EDITOR_UI_COLLAPSE(ICON_MD_BOOKMARK " Bookmarks", "Debug.Bookmarks") { /* @todo */ }
// E,C,S,W
EDITOR_UI_COLLAPSE(ICON_MD_ACCOUNT_TREE " Scene", "Debug.Scene") {
EDITOR_UI_COLLAPSE(ICON_MD_BUBBLE_CHART/*ICON_MD_SCATTER_PLOT*/ " Entities", "Debug.Entities") { /* @todo */ }
EDITOR_UI_COLLAPSE(ICON_MD_TUNE " Components", "Debug.Components") { /* @todo */ }
EDITOR_UI_COLLAPSE(ICON_MD_PRECISION_MANUFACTURING " Systems", "Debug.Systems") { /* @todo */ }
EDITOR_UI_COLLAPSE(ICON_MD_PUBLIC " Levels", "Debug.Levels") {
//node_edit(editor.edit.down,&editor.edit);
}
//EDITOR_UI_COLLAPSE(ICON_MD_ACCOUNT_TREE " Init", "Debug.HierarchyInit") { /* @todo */ }
//EDITOR_UI_COLLAPSE(ICON_MD_ACCOUNT_TREE " Draw", "Debug.HierarchyDraw") { /* @todo */ }
//EDITOR_UI_COLLAPSE(ICON_MD_ACCOUNT_TREE " Tick", "Debug.HierarchyTick") { /* @todo */ }
//EDITOR_UI_COLLAPSE(ICON_MD_ACCOUNT_TREE " Edit", "Debug.HierarchyEdit") { /* @todo */ }
//EDITOR_UI_COLLAPSE(ICON_MD_ACCOUNT_TREE " Quit", "Debug.HierarchyQuit") { /* @todo */ }
// node_edit(&editor.init,&editor.init);
// node_edit(&editor.draw,&editor.draw);
// node_edit(&editor.tick,&editor.tick);
// node_edit(&editor.edit,&editor.edit);
// node_edit(&editor.quit,&editor.quit);
}
EDITOR_UI_COLLAPSE(ICON_MD_ROCKET_LAUNCH " AI", "Debug.AI") {
// @todo
}
EDITOR_UI_COLLAPSE(ICON_MD_VOLUME_UP " Audio", "Debug.Audio") {
ui_audio();
}
EDITOR_UI_COLLAPSE(ICON_MD_VIDEOCAM " Camera", "Debug.Camera") {
ui_camera( camera_get_active() );
}
EDITOR_UI_COLLAPSE(ICON_MD_MONITOR " Display", "Debug.Display") {
// @todo: fps lock, fps target, aspect ratio, fullscreen
char *text = va("%s;%s;%s",
window_has_fullscreen() ? ICON_MD_FULLSCREEN_EXIT : ICON_MD_FULLSCREEN,
ICON_MD_PHOTO_CAMERA,
record_active() ? ICON_MD_VIDEOCAM_OFF : ICON_MD_VIDEOCAM
);
int choice = ui_toolbar(text);
if( choice == 1 ) engine_send("key_fullscreen",0);
if( choice == 2 ) engine_send("key_screenshot",0);
if( choice == 3 ) engine_send("key_record",0);
}
EDITOR_UI_COLLAPSE(ICON_MD_KEYBOARD " Keyboard", "Debug.Keyboard") {
ui_keyboard();
}
EDITOR_UI_COLLAPSE(ICON_MD_MOUSE " Mouse", "Debug.Mouse") {
ui_mouse();
}
EDITOR_UI_COLLAPSE(ICON_MD_GAMEPAD " Gamepads", "Debug.Gamepads") {
for( int q = 0; q < 4; ++q ) {
for( int r = (open = ui_collapse(va("Gamepad #%d",q+1), va("Debug.Gamepads%d",q))), dummy = (clicked_or_toggled = ui_collapse_clicked()); r; ui_collapse_end(), r = 0) {
ui_gamepad(q);
}
}
}
EDITOR_UI_COLLAPSE(ICON_MD_TEXT_FIELDS " Fonts", "Debug.Fonts") {
ui_font();
}
EDITOR_UI_COLLAPSE(ICON_MD_CONTENT_PASTE " Scripts", "Debug.Scripts") {
// @todo
}
EDITOR_UI_COLLAPSE(ICON_MD_STAR_HALF " Shaders", "Debug.Shaders") {
ui_shaders();
}
EDITOR_UI_COLLAPSE(ICON_MD_MOVIE " FXs", "Debug.FXs") {
ui_fxs();
}
EDITOR_UI_COLLAPSE(ICON_MD_SAVINGS " Budgets", "Debug.Budgets") {
// @todo. // mem,fps,gfx,net,hdd,... also logging
}
EDITOR_UI_COLLAPSE(ICON_MD_WIFI/*ICON_MD_SIGNAL_CELLULAR_ALT*/ " Network 0/0 KiB", "Debug.Network") {
// @todo
// SIGNAL_CELLULAR_1_BAR SIGNAL_CELLULAR_2_BAR
}
EDITOR_UI_COLLAPSE(va(ICON_MD_SPEED " Profiler %5.2f/%dfps", window_fps(), (int)window_fps_target()), "Debug.Profiler") {
ui_profiler();
}
EDITOR_UI_COLLAPSE(va(ICON_MD_STORAGE " Storage %s", xstats()), "Debug.Storage") {
// @todo
}
// logic: either plug icon (power saving off) or one of the following ones (power saving on):
// if 0% batt (no batt): battery alert
// if discharging: battery levels [alert,0..6,full]
// if charging: battery charging
int battery_read = app_battery();
int battery_level = abs(battery_read);
int battery_discharging = battery_read < 0 && battery_level < 100;
const char *power_icon_label = ICON_MD_POWER " Power";
if( battery_level ) {
const char *battery_levels[9] = { // @todo: remap [7%..100%] -> [0..1] ?
ICON_MD_BATTERY_ALERT,ICON_MD_BATTERY_0_BAR,ICON_MD_BATTERY_1_BAR,
ICON_MD_BATTERY_2_BAR,ICON_MD_BATTERY_3_BAR,ICON_MD_BATTERY_4_BAR,
ICON_MD_BATTERY_5_BAR,ICON_MD_BATTERY_6_BAR,ICON_MD_BATTERY_FULL,
};
power_icon_label = (const char*)va("%s Power %d%%",
battery_discharging ? battery_levels[(int)((9-1)*clampf(battery_level/100.f,0,1))] : ICON_MD_BATTERY_CHARGING_FULL,
battery_level);
}
EDITOR_UI_COLLAPSE(power_icon_label, "Debug.Power") {
int choice = ui_toolbar( ICON_MD_POWER ";" ICON_MD_BOLT );
if( choice == 1 ) engine_send("key_battery","0");
if( choice == 2 ) engine_send("key_battery","1");
}
EDITOR_UI_COLLAPSE(ICON_MD_WATER " Reflection", "Debug.Reflect") {
ui_reflect("*");
}
EDITOR_UI_COLLAPSE(ICON_MD_EXTENSION " Plugins", "Debug.Plugins") {
// @todo. include VCS
EDITOR_UI_COLLAPSE(ICON_MD_BUILD " Cook", "Debug.Cook") {
// @todo
}
}
return 0;
}
#line 0
#line 1 "v4k_main.c"
// ----------------------------------------------------------------------------
static void v4k_pre_init() {
window_icon(va("%s%s.png", app_path(), app_name()));
glfwPollEvents();
int i;
#pragma omp parallel for
for( i = 0; i <= 3; ++i) {
/**/ if( i == 0 ) ddraw_init();// init this on thread#0 since it will be compiling shaders, and shaders need to be compiled from the very same thread than glfwMakeContextCurrent() was set up
else if( i == 1 ) sprite_init();
else if( i == 2 ) profiler_init();
else if( i == 3 ) storage_mount("save/"), storage_read(), touch_init(); // for ems
}
// window_swap();
}
static void v4k_post_init(float refresh_rate) {
// cook cleanup
cook_stop();
vfs_reload();
// init subsystems that depend on cooked assets now
int i;
#pragma omp parallel for
for( i = 0; i <= 3; ++i ) {
if(i == 0) scene_init(); // init these on thread #0, since both will be compiling shaders, and shaders need to be compiled from the very same thread than glfwMakeContextCurrent() was set up
if(i == 0) ui_init(); // init these on thread #0, since both will be compiling shaders, and shaders need to be compiled from the very same thread than glfwMakeContextCurrent() was set up
if(i == 0) window_icon(va("%s.png", app_name())); // init on thread #0, because of glfw
if(i == 0) input_init(); // init on thread #0, because of glfw
if(i == 1) audio_init(0);
if(i == 2) script_init(), kit_init(), midi_init();
if(i == 3) network_init();
}
// display window
glfwShowWindow(window);
// set black screen
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glfwSwapBuffers(window);
#if !DISABLE_ENGINE_BRANDING
texture_t logo_bg = texture("v4_logo_background.png", 0);
if (logo_bg.id != texture_checker().id) {
texture_t logo_mask = texture("v4_logo_mask.png", 0);
font_face(FONT_FACE3, "fonts/Montserrat-Regular.ttf", 48.0f, FONT_EU | FONT_4096 | FONT_OVERSAMPLE_X | FONT_OVERSAMPLE_Y);
sprite(logo_bg, vec3(w/2.f, h/2.f, 0).array, 0.0f, 0xFFFFFFFF, SPRITE_CENTERED|SPRITE_RESOLUTION_INDEPENDANT);
sprite(logo_mask, vec3(w/2.f, h/2.f, 1).array, 0.0f, 0xFFFFFFFF, SPRITE_CENTERED|SPRITE_RESOLUTION_INDEPENDANT);
sprite_flush();
font_print(
FONT_BOTTOM FONT_CENTER FONT_FACE3 FONT_H3
"Powered by V4K engine\n\n\n\n\n\n\n\n\n\n"
);
int year = 2024;
{
time_t now;
time(&now);
struct tm *local_time = localtime(&now);
year = local_time->tm_year + 1900;
}
char *legalese = va(FONT_CENTER FONT_FACE3 FONT_H4
"© %d v4.games. All rights reserved.\nAll other trademarks and copyrights\n" \
"are properties of their owners.\n\n\n\n", year);
vec2 dims = font_rect(legalese);
font_goto(0, window_height()-dims.y);
font_print(legalese);
glfwSwapBuffers(window);
}
#endif
glfwGetFramebufferSize(window, &w, &h); //glfwGetWindowSize(window, &w, &h);
randset(time_ns() * !tests_captureframes());
boot_time = -time_ss(); // measure boot time, this is continued in window_stats()
// clean any errno setup by cooking stage
errno = 0;
hz = refresh_rate;
// t = glfwGetTime();
// preload brdf LUT early
(void)brdf_lut();
uint64_t fps = optioni("--fps", 0);
if( fps ) {
window_fps_lock(fps);
}
}
// ----------------------------------------------------------------------------
static
void v4k_quit(void) {
storage_flush();
midi_quit();
}
void v4k_init() {
do_once {
// install signal handlers
ifdef(debug, trap_install());
// init panic handler
panic_oom_reserve = SYS_MEM_REALLOC(panic_oom_reserve, 1<<20); // 1MiB
// init glfw
glfw_init();
// enable ansi console
tty_init();
// chdir to root (if invoked as tcc -g -run)
// chdir(app_path());
// skip tcc argvs (if invoked as tcc file.c v4k.c -g -run) (win)
if( __argc > 1 ) if( strstr(__argv[0], "/tcc") || strstr(__argv[0], "\\tcc") ) {
__argc = 0;
}
// create or update cook.zip file
if( /* !COOK_ON_DEMAND && */ have_tools() && cook_jobs() ) {
cook_start(COOK_INI, "**", 0|COOK_ASYNC|COOK_CANCELABLE );
}
atexit(v4k_quit);
}
}
#line 0
// editor is last in place, so it can use all internals from above headers
#line 1 "v4k_editor.c"
// ## Editor long-term plan
// - editor = tree of nodes. levels and objects are nodes, and their widgets are also nodes
// - you can perform actions on nodes, with or without descendants, top-bottom or bottom-top
// - these operations include load/save, undo/redo, reset, play/render, ddraw, etc
// - nodes are saved to disk as a filesystem layout: parents are folders, and leafs are files
// - network replication can be done by external tools by comparing the filesystems and by sending the resulting diff zipped
//
// ## Editor roadmap
// - Gizmosâś±, scene tree, property editorâś±, load/saveâś±, undo/redoâś±, copy/paste, on/off (vis,tick,ddraw,log), vcs.
// - Scenenode pass: node singleton display, node console, node labels, node outlinesâś±.<!-- node == gameobj ? -->
// - Render pass: billboardsâś±, materials, un/lit, cast shadows, wireframe, skyboxâś±/mieâś±, fog/atmosphere
// - Level pass: volumes, triggers, platforms, level streaming, collideâś±, physics
// - Edit pass: Procedural content, brushes, noise and CSG.
// - GUI pass: timeline and data tracks, node graphs. <!-- worthy: will be reused into materials, animgraphs and blueprints -->
// ## Alt plan
// editor is a database + window/tile manager + ui toolkit; all network driven.
// to be precise, editor is a dumb app and ...
// - does not know a thing about what it stores.
// - does not know how to render the game graphics.
// - does not know how to run the game logic.
//
// the editor will create a canvas for your game to render.
// your game will be responsible to tick the logic and render the window inside the editor.
//
// that being said, editor...
// - can store datas hierarchically.
// - can perform diffs and merges, and version the datas into repositories.
// - can be instructed to render UI on top of game and window views.
// - can download new .natvis and plugins quickly.
// - can dump whole project in a filesystem form (zip).
// - editor reflects database contents up-to-date.
// - database can be queried and modified via OSC(UDP) commands.
// editor database uses one table, and stores two kind of payload types:
// - classes: defines typename and dna. class names are prefixed by '@'
// - instances: defines typename and datas. instance names are as-is, not prefixed.
//
// every save contains 5Ws: what, who, when, where, how,
// every save can be diffed/merged.
// ----------------------------------------------------------------------------
array(editor_bind_t) editor_binds;
void editor_addbind(editor_bind_t bind) {
array_push(editor_binds, bind);
}
// ----------------------------------------------------------------------------
typedef void (*editor_no_property)(void *);
array(void*) editor_persist_kv;
array(editor_no_property) editor_no_properties;
#define EDITOR_PROPERTY(T,property_name,defaults) \
editor_##property_name##_map_t *editor_##property_name##_map() { \
static editor_##property_name##_map_t map = 0; do_once map_init_ptr(map); \
return &map; \
} \
T editor_##property_name(const void *obj) { \
return *map_find_or_add(*editor_##property_name##_map(), (void*)obj, ((T) defaults)); \
} \
void editor_set##property_name(const void *obj, T value) { \
*map_find_or_add(*editor_##property_name##_map(), (void*)obj, ((T) value)) = ((T) value); \
} \
void editor_alt##property_name(const void *obj) { \
T* found = map_find_or_add(*editor_##property_name##_map(), (void*)obj, ((T) defaults)); \
*found = (T)(uintptr_t)!(*found); \
} \
void editor_no##property_name(void *obj) { \
T* found = map_find_or_add(*editor_##property_name##_map(), (void*)obj, ((T) defaults)); \
map_erase(*editor_##property_name##_map(), (void*)obj); \
} \
AUTORUN { array_push(editor_persist_kv, #T); array_push(editor_persist_kv, editor_##property_name##_map()); array_push(editor_no_properties, editor_no##property_name); }
EDITOR_PROPERTY(int, open, 0); // whether object is tree opened in tree editor
EDITOR_PROPERTY(int, selected, 0); // whether object is displaying a contextual popup or not
EDITOR_PROPERTY(int, changed, 0); // whether object is displaying a contextual popup or not
EDITOR_PROPERTY(int, popup, 0); // whether object is displaying a contextual popup or not
EDITOR_PROPERTY(int, bookmarked, 0);
EDITOR_PROPERTY(int, visible, 0);
EDITOR_PROPERTY(int, script, 0);
EDITOR_PROPERTY(int, event, 0);
EDITOR_PROPERTY(char*, iconinstance, 0);
EDITOR_PROPERTY(char*, iconclass, 0);
EDITOR_PROPERTY(int, treeoffsety, 0);
// new prop: breakpoint: request to break on any given node
// new prop: persist: objects with this property will be saved on disk
void editor_destroy_properties(void *o) {
for each_array(editor_no_properties,editor_no_property,fn) {
fn(o);
}
}
void editor_load_on_boot(void) {
}
void editor_save_on_quit(void) {
}
AUTORUN {
editor_load_on_boot();
(atexit)(editor_save_on_quit);
}
// ----------------------------------------------------------------------------
typedef int(*subeditor)(int mode);
struct editor_t {
// time
unsigned frame;
double t, dt, slomo;
// controls
int transparent;
int attached;
int active; // focus? does_grabinput instead?
int key;
vec2 mouse; // 2d coord for ray/picking
bool gamepad; // mask instead? |1|2|4|8
int hz_high, hz_medium, hz_low;
int filter;
bool battery; // battery mode: low fps
bool unlit;
bool ddraw;
// event root nodes
obj* root;
obj* on_init;
obj* on_tick;
obj* on_draw;
obj* on_edit;
obj* on_quit;
// all of them (hierarchical)
array(obj*) objs; // @todo:set() world?
// all of them (flat)
set(obj*) world;
//
array(char*) cmds;
// subeditors
array(subeditor) subeditors;
} editor = {
.active = 1,
.gamepad = 1,
.hz_high = 60, .hz_medium = 18, .hz_low = 5,
};
int editor_begin(const char *title, int mode) {
if( mode == 0 ) return ui_panel(title, PANEL_OPEN);
if( mode == 1 ) return ui_window(title, 0);
int ww = window_width(), w = ww * 0.66;
int hh = window_height(), h = hh * 0.66;
struct nk_rect position = { (ww-w)/2,(hh-h)/2, w,h };
nk_flags win_flags = NK_WINDOW_TITLE | NK_WINDOW_BORDER |
NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE |
NK_WINDOW_CLOSABLE | NK_WINDOW_MINIMIZABLE |
// NK_WINDOW_SCALE_LEFT|NK_WINDOW_SCALE_TOP| //< @fixme: move this logic into nuklear
// NK_WINDOW_MAXIMIZABLE | NK_WINDOW_PINNABLE |
0; // NK_WINDOW_SCROLL_AUTO_HIDE;
if( mode == 3 ) {
mode = 2, position.x = input(MOUSE_X), position.w = w/3, win_flags =
NK_WINDOW_TITLE|NK_WINDOW_CLOSABLE|
NK_WINDOW_SCALABLE|NK_WINDOW_MOVABLE| //< nuklear requires these two to `remember` popup rects
0;
}
if( mode == 2 || mode == 3 )
if (nk_begin(ui_ctx, title, position, win_flags))
return 1;
else
return nk_end(ui_ctx), 0;
return 0;
}
int editor_end(int mode) {
if( mode == 0 ) return ui_panel_end();
if( mode == 1 ) return ui_window_end();
if( mode == 2 ) nk_end(ui_ctx);
if( mode == 3 ) nk_end(ui_ctx);
return 0;
}
#if 0 // deprecate
bool editor_active() {
return ui_hover() || ui_active() || gizmo_active() ? editor.active : 0;
}
#endif
int editor_filter() {
if( editor.filter ) {
if (nk_begin(ui_ctx, "Filter", nk_rect(window_width()-window_width()*0.33,32, window_width()*0.33, 40),
NK_WINDOW_NO_SCROLLBAR)) {
char *bak = ui_filter; ui_filter = 0;
ui_string(ICON_MD_CLOSE " Filter " ICON_MD_SEARCH, &bak);
ui_filter = bak;
if( input(KEY_ESC) || ( ui_label_icon_clicked_L.x > 0 && ui_label_icon_clicked_L.x <= 24 )) {
if( ui_filter ) ui_filter[0] = '\0';
editor.filter = 0;
}
}
nk_end(ui_ctx);
}
return editor.filter;
}
static
int editor_select_(void *o, const char *mask) {
int matches = 0;
int off = mask[0] == '!', inv = mask[0] == '~';
int match = strmatchi(obj_type(o), mask+off+inv) || strmatchi(obj_name(o), mask+off+inv);
if( match ) {
editor_setselected(o, inv ? editor_selected(o) ^ 1 : !off);
++matches;
}
for each_objchild(o, obj*, oo) {
matches += editor_select_(oo, mask);
}
return matches;
}
void editor_select(const char *mask) {
for each_array( editor.objs, obj*, o )
editor_select_(o, mask);
}
void editor_unselect() { // same than editor_select("!**");
for each_map_ptr(*editor_selected_map(), void*,o, int, k) {
if( *k ) *k = 0;
}
}
void editor_select_aabb(aabb box) {
int is_inv = input_held(KEY_CTRL);
int is_add = input_held(KEY_SHIFT);
if( !is_inv && !is_add ) editor_unselect();
aabb item = {0};
for each_set_ptr( editor.world, obj*, o ) {
if( obj_hasmethod(*o,aabb) && obj_aabb(*o, &item) ) {
if( aabb_test_aabb(item, box) ) {
if( is_inv )
editor_altselected(*o);
else
editor_setselected(*o, 1);
}
}
}
}
static obj* active_ = 0;
static void editor_selectgroup_(obj *o, obj *first, obj *last) {
// printf("%s (looking for %s in [%s..%s])\n", obj_name(o), active_ ? obj_name(active_) : "", obj_name(first), obj_name(last));
if( !active_ ) if( o == first || o == last ) active_ = o == first ? last : first;
if( active_ ) editor_setselected(o, 1);
if( o == active_ ) active_ = 0;
for each_objchild(o, obj*, oo) {
editor_selectgroup_(oo, first, last);
}
}
void editor_selectgroup(obj *first, obj *last) {
if( last ) {
if( !first ) first = array_count(editor.objs) ? editor.objs[0] : NULL;
if( !first ) editor_setselected(last, 1);
else {
active_ = 0;
for each_array(editor.objs,obj*,o) {
editor_selectgroup_(o, first, last);
}
}
}
}
static obj *find_any_selected_(obj *o) {
if( editor_selected(o) ) return o;
for each_objchild(o,obj*,oo) {
obj *ooo = find_any_selected_(oo);
if( ooo )
return ooo;
}
return 0;
}
void* editor_first_selected() {
for each_array(editor.objs,obj*,o) {
obj *oo = find_any_selected_(o);
// if( oo ) printf("1st found: %s\n", obj_name(oo));
if( oo ) return oo;
}
return 0;
}
static obj *find_last_selected_(obj *o) {
void *last = 0;
if( editor_selected(o) ) last = o;
for each_objchild(o,obj*,oo) {
obj *ooo = find_last_selected_(oo);
if( ooo )
last = ooo;
}
return last;
}
void* editor_last_selected() {
void *last = 0;
for each_array(editor.objs,obj*,o) {
obj *oo = find_last_selected_(o);
// if( oo ) printf("last found: %s\n", obj_name(oo));
if( oo ) last = oo;
}
return last;
}
// ----------------------------------------------------------------------------------------
void editor_addtoworld(obj *o) {
set_find_or_add(editor.world, o);
for each_objchild(o, obj*, oo) {
editor_addtoworld(oo);
}
}
void editor_watch(const void *o) {
array_push(editor.objs, (obj*)o);
obj_push(o); // save state
editor_addtoworld((obj*)o);
}
void* editor_spawn(const char *ini) { // deprecate?
obj *o = obj_make(ini);
editor_watch(o);
return o;
}
void editor_spawn1() {
obj *selected = editor_first_selected();
obj *o = selected ? obj_make(obj_saveini(selected)) : obj_new(obj);
if( selected ) obj_attach(selected, o), editor_setopen(selected, 1);
else
editor_watch(o);
editor_unselect();
editor_setselected(o, 1);
}
typedef set(obj*) set_objp_t;
static
void editor_glob_recurse(set_objp_t*list, obj *o) {
set_find_or_add(*list, o);
for each_objchild(o,obj*,oo) {
editor_glob_recurse(list, oo);
}
}
void editor_destroy_selected() {
set_objp_t list = 0;
set_init_ptr(list);
for each_map_ptr(*editor_selected_map(), obj*,o, int,selected) {
if( *selected ) { editor_glob_recurse(&list, *o); }
}
for each_set(list, obj*, o) {
obj_detach(o);
}
for each_set(list, obj*, o) {
// printf("deleting %p %s\n", o, obj_name(o));
// remove from watched items
for (int i = 0, end = array_count(editor.objs); i < end; ++i) {
if (editor.objs[i] == o) {
editor.objs[i] = 0;
array_erase_slow(editor.objs, i);
--end;
--i;
}
}
// delete from world
set_erase(editor.world, o);
// delete properties + obj
editor_destroy_properties(o);
obj_free(o);
}
set_free(list);
}
void editor_inspect(obj *o) {
ui_section(va("%s (%s)", obj_type(o), obj_name(o)));
if( obj_hasmethod(o, menu) ) {
obj_menu(o);
}
for each_objmember(o,TYPE,NAME,PTR) {
if( !editor_changed(PTR) ) {
obj_push(o);
}
ui_label_icon_highlight = editor_changed(PTR); // @hack: remove ui_label_icon_highlight hack
char *label = va(ICON_MD_UNDO "%s", NAME);
int changed = 0;
/**/ if( !strcmp(TYPE,"float") ) changed = ui_float(label, PTR);
else if( !strcmp(TYPE,"int") ) changed = ui_int(label, PTR);
else if( !strcmp(TYPE,"vec2") ) changed = ui_float2(label, PTR);
else if( !strcmp(TYPE,"vec3") ) changed = ui_float3(label, PTR);
else if( !strcmp(TYPE,"vec4") ) changed = ui_float4(label, PTR);
else if( !strcmp(TYPE,"rgb") ) changed = ui_color3(label, PTR);
else if( !strcmp(TYPE,"rgba") ) changed = ui_color4(label, PTR);
else if( !strcmp(TYPE,"color") ) changed = ui_color4f(label, PTR);
else if( !strcmp(TYPE,"color3f") ) changed = ui_color3f(label, PTR);
else if( !strcmp(TYPE,"color4f") ) changed = ui_color4f(label, PTR);
else if( !strcmp(TYPE,"char*") ) changed = ui_string(label, PTR);
else ui_label2(label, va("(%s)", TYPE)); // INFO instead of (TYPE)?
if( changed ) {
editor_setchanged(PTR, 1);
}
if( ui_label_icon_highlight )
if( ui_label_icon_clicked_L.x >= 6 && ui_label_icon_clicked_L.x <= 26 ) { // @hack: if clicked on UNDO icon (1st icon)
editor_setchanged(PTR, 0);
}
if( !editor_changed(PTR) ) {
obj_pop(o);
}
}
}
// ----------------------------------------------------------------------------------------
// tty
static thread_mutex_t *console_lock;
static array(char*) editor_jobs;
int editor_send(const char *cmd) { // return job-id
int skip = strspn(cmd, " \t\r\n");
char *buf = STRDUP(cmd + skip);
strswap(buf, "\r\n", "");
int jobid;
do_threadlock(console_lock) {
array_push(editor_jobs, buf);
jobid = array_count(editor_jobs) - 1;
}
return jobid;
}
const char* editor_recv(int jobid, double timeout_ss) {
char *answer = 0;
while(!answer && timeout_ss >= 0 ) {
do_threadlock(console_lock) {
if( editor_jobs[jobid][0] == '\0' )
answer = editor_jobs[jobid];
}
timeout_ss -= 0.1;
if( timeout_ss > 0 ) sleep_ms(100); // thread_yield()
}
return answer + 1;
}
// plain and ctrl keys
EDITOR_BIND(play, "down(F5)", { window_pause(0); /* if(!editor.slomo) editor.active = 0; */ editor.slomo = 1; } );
EDITOR_BIND(stop, "down(ESC)", { if(editor.t > 0) { window_pause(1), editor.frame = 0, editor.t = 0, editor.dt = 0, editor.slomo = 0, editor.active = 1; editor_select("**"); editor_destroy_selected(); }} );
EDITOR_BIND(eject, "down(F1)", { /*window_pause(!editor.active); editor.slomo = !!editor.active;*/ editor.active ^= 1; } );
EDITOR_BIND(pause, "(held(CTRL) & down(P)) | down(PAUSE)", { window_pause( window_has_pause() ^ 1 ); } );
EDITOR_BIND(frame, "held(CTRL) & down(LEFT)", { window_pause(1); editor.frame++, editor.t += (editor.dt = 1/60.f); } );
EDITOR_BIND(slomo, "held(CTRL) & down(RIGHT)", { window_pause(0); editor.slomo = maxf(fmod(editor.slomo * 2, 16), 0.125); } );
EDITOR_BIND(reload, "held(CTRL) & down(F5)", { window_reload(); } );
EDITOR_BIND(filter, "held(CTRL) & down(F)", { editor.filter ^= 1; } );
// alt keys
EDITOR_BIND(quit, "held(ALT) & down(F4)", { record_stop(), exit(0); } );
EDITOR_BIND(mute, "held(ALT) & down(M)", { audio_volume_master( 1 ^ !!audio_volume_master(-1) ); } );
EDITOR_BIND(gamepad, "held(ALT) & down(G)", { editor.gamepad ^= 1; } );
EDITOR_BIND(transparent, "held(ALT) & down(T)", { editor.transparent ^= 1; } );
EDITOR_BIND(record, "held(ALT) & down(Z)", { if(record_active()) record_stop(), ui_notify(va("Video recorded"), date_string()); else { char *name = file_counter(va("%s.mp4",app_name())); app_beep(), window_record(name); } } );
EDITOR_BIND(screenshot, "held(ALT) & down(S)", { char *name = file_counter(va("%s.png",app_name())); window_screenshot(name), ui_notify(va("Screenshot: %s", name), date_string()); } );
EDITOR_BIND(battery, "held(ALT) & down(B)", { editor.battery ^= 1; } );
EDITOR_BIND(outliner, "held(ALT) & down(O)", { ui_show("Outliner", ui_visible("Outliner") ^ true); } );
EDITOR_BIND(profiler, "held(ALT) & down(P)", { ui_show("Profiler", profiler_enable(ui_visible("Profiler") ^ true)); } );
EDITOR_BIND(fullscreen, "(held(ALT)&down(ENTER))|down(F11)",{ record_stop(), window_fullscreen( window_has_fullscreen() ^ 1 ); } ); // close any recording before framebuffer resizing, which would corrupt video stream
EDITOR_BIND(unlit, "held(ALT) & down(U)", { editor.unlit ^= 1; } );
EDITOR_BIND(ddraw, "held(ALT) & down(D)", { editor.ddraw ^= 1; } );
void editor_pump() {
for each_array(editor_binds,editor_bind_t,b) {
if( input_eval(b.bindings) ) {
editor_send(b.command);
}
}
do_threadlock(console_lock) {
for each_array_ptr(editor_jobs,char*,cmd) {
if( (*cmd)[0] ) {
int found = 0;
for each_array(editor_binds,editor_bind_t,b) {
if( !strcmpi(b.command, *cmd)) {
b.fn();
found = 1;
break;
}
}
if( !found ) {
// alert(va("Editor: could not handle `%s` command.", *cmd));
(*cmd)[0] = '\0'; strcatf(&(*cmd), "\1%s\n", "Err\n"); (*cmd)[0] = '\0';
}
if( (*cmd)[0] ) {
(*cmd)[0] = '\0'; strcatf(&(*cmd), "\1%s\n", "Ok\n"); (*cmd)[0] = '\0';
}
}
}
}
}
// ----------------------------------------------------------------------------------------
void editor_setmouse(int x, int y) {
glfwSetCursorPos( window_handle(), x, y );
}
vec2 editor_glyph(int x, int y, const char *style, unsigned codepoint) {
do_once {
// style: atlas size, unicode ranges and 3 font faces max
font_face(FONT_FACE10, "B612-Regular.ttf", 12.f, 0);
font_face(FONT_FACE9, "MaterialIconsSharp-Regular.otf", 24.f, FONT_EM|FONT_2048);
font_face(FONT_FACE8, "materialdesignicons-webfont.ttf", 24.f, FONT_EM|FONT_2048); // {0xF68C /*ICON_MDI_MIN*/, 0xF1CC7/*ICON_MDI_MAX*/, 0}},
// style: 5 colors max
font_color(FONT_COLOR1, WHITE);
font_color(FONT_COLOR2, RGBX(0xE8F1FF,128)); // GRAY);
font_color(FONT_COLOR3, YELLOW);
font_color(FONT_COLOR4, ORANGE);
font_color(FONT_COLOR5, CYAN);
}
font_goto(x,y);
vec2 pos = {x,y};
const char *sym = codepoint_to_utf8(codepoint);
return add2(pos, font_print(va("%s%s%s", style ? style : "", codepoint >= ICON_MDI_MIN ? FONT_FACE8 : codepoint >= ICON_MD_MIN ? FONT_FACE9 : FONT_FACE10, sym)));
}
vec2 editor_glyphs(int x, int y, const char *style, const char *utf8) {
vec2 pos = {x,y};
array(unsigned) codepoints = string32(utf8);
for( int i = 0, end = array_count(codepoints); i < end; ++i)
pos = add2(pos, editor_glyph(pos.x,pos.y,style,codepoints[i]));
return pos;
}
void editor_frame( void (*game)(unsigned, float, double) ) {
do_once {
set_init_ptr(editor.world);
//set_init_ptr(editor.selection);
profiler_enable( false );
window_pause( true );
window_cursor_shape(CURSOR_SW_AUTO);
editor.hz_high = window_fps_target();
fx_load("**/editorOutline.fs");
fx_enable(0, 1);
obj_setname(editor.root = obj_new(obj), "Signals");
obj_setname(editor.on_init = obj_new(obj), "onInit");
obj_setname(editor.on_tick = obj_new(obj), "onTick");
obj_setname(editor.on_draw = obj_new(obj), "onDraw");
obj_setname(editor.on_edit = obj_new(obj), "onEdit");
obj_setname(editor.on_quit = obj_new(obj), "onQuit");
obj_attach(editor.root, editor.on_init);
obj_attach(editor.root, editor.on_tick);
obj_attach(editor.root, editor.on_draw);
obj_attach(editor.root, editor.on_edit);
obj_attach(editor.root, editor.on_quit);
editor_seticoninstance(editor.root, ICON_MDI_SIGNAL_VARIANT);
editor_seticoninstance(editor.on_init, ICON_MDI_SIGNAL_VARIANT);
editor_seticoninstance(editor.on_tick, ICON_MDI_SIGNAL_VARIANT);
editor_seticoninstance(editor.on_draw, ICON_MDI_SIGNAL_VARIANT);
editor_seticoninstance(editor.on_edit, ICON_MDI_SIGNAL_VARIANT);
editor_seticoninstance(editor.on_quit, ICON_MDI_SIGNAL_VARIANT);
editor_seticonclass(obj_type(editor.root), ICON_MDI_CUBE_OUTLINE);
}
// game tick
game(editor.frame, editor.dt, editor.t);
// timing
editor.dt = clampf(window_delta(), 0, 1/60.f) * !window_has_pause() * editor.slomo;
editor.t += editor.dt;
editor.frame += !window_has_pause();
editor.frame += !editor.frame;
// process inputs & messages
editor_pump();
// adaptive framerate
int app_on_background = !window_has_focus();
int hz = app_on_background ? editor.hz_low : editor.battery ? editor.hz_medium : editor.hz_high;
window_fps_lock( hz < 5 ? 5 : hz );
// draw menubar
static int stats_mode = 1;
static double last_fps = 0; if(!window_has_pause()) last_fps = window_fps();
const char *STATS = va("x%4.3f %03d.%03dss %02dF %s",
editor.slomo, (int)editor.t, (int)(1000 * (editor.t - (int)editor.t)),
(editor.frame-1) % ((int)window_fps_target() + !(int)window_fps_target()),
stats_mode == 1 ? va("%5.2f/%dfps", last_fps, (int)window_fps_target()) : stats_mode == 0 ? "0/0 KiB" : xstats());
const char *ICON_PL4Y = window_has_pause() ? ICON_MDI_PLAY : ICON_MDI_PAUSE;
const char *ICON_SKIP = window_has_pause() ? ICON_MDI_STEP_FORWARD/*ICON_MDI_SKIP_NEXT*/ : ICON_MDI_FAST_FORWARD;
int is_borderless = !glfwGetWindowAttrib(window, GLFW_DECORATED);
int ingame = !editor.active;
static double clicked_titlebar = 0;
UI_MENU(14+2*is_borderless, \
if(ingame) ui_disable(); \
UI_MENU_POPUP(ICON_MD_SETTINGS, vec2(0.33,1.00), ui_engine()) \
if(ingame) ui_enable(); \
UI_MENU_ITEM(ICON_PL4Y, if(editor.t == 0) editor_send("eject"); editor_send(window_has_pause() ? "play" : "pause")) \
UI_MENU_ITEM(ICON_SKIP, editor_send(window_has_pause() ? "frame" : "slomo")) \
UI_MENU_ITEM(ICON_MDI_STOP, editor_send("stop")) \
UI_MENU_ITEM(ICON_MDI_EJECT, editor_send("eject")) \
UI_MENU_ITEM(STATS, stats_mode = (stats_mode+1) % 3) \
UI_MENU_ALIGN_RIGHT(32+32+32+32+32+32+32 + 32*2*is_borderless + 10, clicked_titlebar = time_ms()) \
if(ingame) ui_disable(); \
UI_MENU_ITEM(ICON_MD_FOLDER_SPECIAL, editor_send("browser")) \
UI_MENU_ITEM(ICON_MDI_FILE_TREE, editor_send("scene")) \
UI_MENU_ITEM(ICON_MDI_SCRIPT_TEXT, editor_send("script")) \
UI_MENU_ITEM(ICON_MDI_CHART_TIMELINE, editor_send("timeline")) \
UI_MENU_ITEM(ICON_MDI_CONSOLE, editor_send("console")) \
UI_MENU_ITEM(ICON_MDI_GRAPH, editor_send("nodes")) \
UI_MENU_ITEM(ICON_MDI_MAGNIFY, editor_send("filter")) /*MD_SEARCH*/ \
if(ingame) ui_enable(); \
UI_MENU_ITEM(window_has_maximize() ? ICON_MDI_WINDOW_MINIMIZE : ICON_MDI_WINDOW_MAXIMIZE, window_maximize(1 ^ window_has_maximize())) \
UI_MENU_ITEM(ICON_MDI_CLOSE, editor_send("quit")) \
);
if( is_borderless ) {
static vec3 drag = {0};
if( clicked_titlebar ) {
static double clicks = 0;
if( input_up(MOUSE_L) ) ++clicks;
if( input_up(MOUSE_L) && clicks == 2 ) window_visible(false), window_maximize( window_has_maximize() ^ 1 ), window_visible(true);
if( (time_ms() - clicked_titlebar) > 400 ) clicks = 0, clicked_titlebar = 0;
if( input_down(MOUSE_L) ) drag = vec3(input(MOUSE_X), input(MOUSE_Y), 1);
}
if( drag.z *= !input_up(MOUSE_L) ) {
int wx = 0, wy = 0;
glfwGetWindowPos(window_handle(), &wx, &wy);
glfwSetWindowPos(window_handle(), wx + input(MOUSE_X) - drag.x, wy + input(MOUSE_Y) - drag.y);
}
}
if( !editor.active ) return;
// draw edit view (gizmos, position markers, etc).
for each_set_ptr(editor.world,obj*,o) {
if( obj_hasmethod(*o,edit) ) {
obj_edit(*o);
}
}
// draw silhouettes
sprite_flush();
fx_begin();
for each_map_ptr(*editor_selected_map(),void*,o,int,selected) {
if( !*selected ) continue;
if( obj_hasmethod(*o,draw) ) {
obj_draw(*o);
}
if( obj_hasmethod(*o,edit) ) {
obj_edit(*o);
}
}
sprite_flush();
fx_end();
// draw box selection
if( !ui_active() && window_has_cursor() && cursorshape ) { //< check that we're not moving a window + not in fps cam
static vec2 from = {0}, to = {0};
if( input_down(MOUSE_L) ) to = vec2(input(MOUSE_X), input(MOUSE_Y)), from = to;
if( input(MOUSE_L) ) to = vec2(input(MOUSE_X), input(MOUSE_Y));
if( len2sq(sub2(from,to)) > 0 ) {
vec2 a = min2(from, to), b = max2(from, to);
ddraw_push_2d();
ddraw_color_push(YELLOW);
ddraw_line( vec3(a.x,a.y,0),vec3(b.x-1,a.y,0) );
ddraw_line( vec3(b.x,a.y,0),vec3(b.x,b.y-1,0) );
ddraw_line( vec3(b.x,b.y,0),vec3(a.x-1,b.y,0) );
ddraw_line( vec3(a.x,b.y,0),vec3(a.x,a.y-1,0) );
ddraw_color_pop();
ddraw_pop_2d();
}
if( input_up(MOUSE_L) ) {
vec2 a = min2(from, to), b = max2(from, to);
from = to = vec2(0,0);
editor_select_aabb(aabb(vec3(a.x,a.y,0),vec3(b.x,b.y,0)));
}
}
// draw mouse aabb
aabb mouse = { vec3(input(MOUSE_X),input(MOUSE_Y),0), vec3(input(MOUSE_X),input(MOUSE_Y),1)};
if( 1 ) {
ddraw_color_push(YELLOW);
ddraw_push_2d();
ddraw_aabb(mouse.min, mouse.max);
ddraw_pop_2d();
ddraw_color_pop();
}
// tick mouse aabb selection and contextual tab (RMB)
aabb box = {0};
for each_set(editor.world,obj*,o) {
if( !obj_hasmethod(o, aabb) ) continue;
if( !obj_aabb(o, &box) ) continue;
// trigger contextual inspector
if( input_down(MOUSE_R) ) {
int is_selected = editor_selected(o);
editor_setpopup(o, is_selected);
}
// draw contextual inspector
if( editor_popup(o) ) {
if( editor_begin(va("%s (%s)", obj_name(o), obj_type(o)),EDITOR_WINDOW_NK_SMALL) ) {
ui_label2(obj_name(o), obj_type(o));
editor_inspect(o);
editor_end(EDITOR_WINDOW_NK_SMALL);
} else {
editor_setpopup(o, 0);
}
}
}
// draw subeditors
static int preferred_window_mode = EDITOR_WINDOW;
static struct nk_color bak, *on = 0; do_once bak = ui_ctx->style.window.fixed_background.data.color; // ui_ctx->style.window.fixed_background.data.color = !!(on = (on ? NULL : &bak)) ? AS_NKCOLOR(0) : bak; };
if( editor.transparent ) ui_ctx->style.window.fixed_background.data.color = AS_NKCOLOR(0);
for each_array(editor.subeditors, subeditor, fn) {
fn(preferred_window_mode);
}
ui_ctx->style.window.fixed_background.data.color = bak;
// draw ui filter (note: render at end-of-frame, so it's hopefully on-top)
editor_filter();
}
void editor_gizmos(int dim) {
// debugdraw
if(dim == 2) ddraw_push_2d();
ddraw_ontop_push(0);
// draw gizmos, aabbs, markers, etc
for each_map_ptr(*editor_selected_map(),void*,o,int,selected) {
if( !*selected ) continue;
void *obj = *o;
// get transform
vec3 *p = NULL;
vec3 *r = NULL;
vec3 *s = NULL;
aabb *a = NULL;
for each_objmember(obj,TYPE,NAME,PTR) {
/**/ if( !strcmp(NAME, "position") ) p = PTR;
else if( !strcmp(NAME, "pos") ) p = PTR;
else if( !strcmp(NAME, "rotation") ) r = PTR;
else if( !strcmp(NAME, "rot") ) r = PTR;
else if( !strcmp(NAME, "scale") ) s = PTR;
else if( !strcmp(NAME, "sca") ) s = PTR;
else if( !strcmp(NAME, "aabb") ) a = PTR;
}
ddraw_ontop(0);
// bounding box 3d
if( 0 ) {
aabb box;
if( obj_hasmethod(*o, aabb) && obj_aabb(*o, &box) ) {
ddraw_color_push(YELLOW);
ddraw_aabb(box.min, box.max);
ddraw_color_pop();
}
}
// position marker
if( p ) {
static map(void*, vec3) prev_dir = 0;
do_once map_init_ptr(prev_dir);
vec3* dir = map_find_or_add(prev_dir, obj, vec3(1,0,0));
static map(void*, vec3) prev_pos = 0;
do_once map_init_ptr(prev_pos);
vec3* found = map_find_or_add(prev_pos, obj, *p), fwd = sub3(*p, *found);
if( (fwd.y = 0, len3sq(fwd)) ) {
*found = *p;
*dir = norm3(fwd);
}
// float diameter = len2( sub2(vec2(box->max.x,box->max.z), vec2(box->min.x,box->min.z) ));
// float radius = diameter * 0.5;
ddraw_position_dir(*p, *dir, 1);
}
ddraw_ontop(1);
// transform gizmo
if( p && r && s ) {
gizmo(p,r,s);
}
}
ddraw_ontop_pop();
if(dim == 2) ddraw_pop_2d();
}
#line 0
#line 1 "v4k_editor_scene.h"
#define SCENE_ICON ICON_MDI_FILE_TREE
#define SCENE_TITLE "Scene " SCENE_ICON
EDITOR_BIND(scene, "held(CTRL)&down(1)", { ui_show(SCENE_TITLE, ui_visible(SCENE_TITLE) ^ true); });
EDITOR_BIND(node_new, "down(INS)", { editor_spawn1(); } );
EDITOR_BIND(node_del, "down(DEL)", { editor_destroy_selected(); } );
EDITOR_BIND(node_save, "held(CTRL)&down(S)", { puts("@todo"); } );
EDITOR_BIND(scene_save, "held(CTRL)&down(S)&held(SHIFT)",{ puts("@todo"); } );
EDITOR_BIND(select_all, "held(CTRL) & down(A)", { editor_select("**"); } );
EDITOR_BIND(select_none, "held(CTRL) & down(D)", { editor_select("!**"); } );
EDITOR_BIND(select_invert, "held(CTRL) & down(I)", { editor_select("~**"); } );
EDITOR_BIND(bookmark, "held(CTRL) & down(B)", { editor_selected_map_t *map = editor_selected_map(); \
int on = 0; \
for each_map_ptr(*map,void*,o,int,selected) if(*selected) on |= !editor_bookmarked(*o); \
for each_map_ptr(*map,void*,o,int,selected) if(*selected) editor_setbookmarked(*o, on); \
} );
enum {
SCENE_RECURSE = 1,
SCENE_SELECTION = 2,
SCENE_CHECKBOX = 4,
SCENE_INDENT = 8,
SCENE_ALL = ~0u
};
static
void editor_scene_(obj *o, unsigned flags) {
static unsigned tabs = ~0u;
++tabs;
if( o ) {
unsigned do_tags = 1;
unsigned do_indent = !!(flags & SCENE_INDENT);
unsigned do_checkbox = !!(flags & SCENE_CHECKBOX);
unsigned do_recurse = !!(flags & SCENE_RECURSE);
unsigned do_selection = !!(flags & SCENE_SELECTION);
nk_layout_row_dynamic(ui_ctx, 25, 1);
const char *objicon = editor_iconinstance(o);
if(!objicon) objicon = editor_iconclass(obj_type(o));
if(!objicon) objicon = ICON_MDI_CUBE_OUTLINE;
const char *objname = va("%s (%s)", obj_type(o), obj_name(o));
const char *objchevron =
!do_recurse || array_count(*obj_children(o)) <= 1 ? ICON_MDI_CIRCLE_SMALL :
editor_open(o) ? ICON_MDI_CHEVRON_DOWN : ICON_MDI_CHEVRON_RIGHT;
char *label = va("%*s%s%s %s", do_indent*(4+2*tabs), "", objchevron, objicon, objname);
const char *iconsL =
//editor_selected(o) ? ICON_MD_CHECK_BOX : ICON_MD_CHECK_BOX_OUTLINE_BLANK;
editor_selected(o) ? ICON_MDI_CHECKBOX_MARKED : ICON_MDI_CHECKBOX_BLANK_OUTLINE;
const char *iconsR = va("%s%s%s",
editor_script(o) ? ICON_MDI_SCRIPT : ICON_MDI_CIRCLE_SMALL,
editor_event(o) ? ICON_MDI_CALENDAR : ICON_MDI_CIRCLE_SMALL,
editor_visible(o) ? ICON_MDI_EYE_OUTLINE : ICON_MDI_EYE_CLOSED );
UI_TOOLBAR_OVERLAY_DECLARE(int choiceL, choiceR);
struct nk_command_buffer *canvas = nk_window_get_canvas(ui_ctx);
struct nk_rect bounds; nk_layout_peek(&bounds, ui_ctx);
int clicked = nk_hovered_text(ui_ctx, label, strlen(label), NK_TEXT_LEFT, editor_selected(o));
if( clicked && nk_input_is_mouse_hovering_rect(&ui_ctx->input, ((struct nk_rect) { bounds.x,bounds.y,bounds.w*0.66,bounds.h })) )
editor_altselected( o );
vec2i offset_in_tree = {0};
if( do_indent ) {
float thickness = 2.f;
struct nk_color color = {255,255,255,64};
int offsx = 30;
int spacx = 10;
int lenx = (tabs+1)*spacx;
int halfy = bounds.h / 2;
int offsy = halfy + 2;
offset_in_tree = vec2i(bounds.x+offsx+lenx-spacx,bounds.y+offsy);
editor_settreeoffsety(o, offset_in_tree.y);
for( obj *p = obj_parent(o); p ; p = 0 )
nk_stroke_line(canvas, offset_in_tree.x-6,offset_in_tree.y, offset_in_tree.x-spacx,offset_in_tree.y, thickness, color),
nk_stroke_line(canvas, offset_in_tree.x-spacx,offset_in_tree.y,offset_in_tree.x-spacx,editor_treeoffsety(p)+4, thickness, color);
}
if( ui_contextual() ) {
int choice = ui_label(ICON_MD_BOOKMARK_ADDED "Toggle bookmarks (CTRL+B)");
if( choice & 1 ) editor_send("bookmark");
ui_contextual_end(!!choice);
}
UI_TOOLBAR_OVERLAY(choiceL,iconsL,nk_rgba_f(1,1,1,do_checkbox*ui_alpha*0.65),NK_TEXT_LEFT);
if( do_tags )
UI_TOOLBAR_OVERLAY(choiceR,iconsR,nk_rgba_f(1,1,1,ui_alpha*0.65),NK_TEXT_RIGHT);
if( choiceR == 3 ) editor_altscript( o );
if( choiceR == 2 ) editor_altevent( o);
if( choiceR == 1 ) editor_altvisible( o );
if( do_recurse && editor_open(o) ) {
for each_objchild(o,obj*,oo) {
editor_scene_(oo,flags);
}
}
if( clicked && !choiceL && !choiceR ) {
int is_picking = input(KEY_CTRL);
if( !is_picking ) {
if( input(KEY_SHIFT) ) {
editor_selectgroup( editor_first_selected(), editor_last_selected() );
} else {
editor_unselect();
editor_setselected(o, 1);
}
}
for( obj *p = obj_parent(o); p; p = obj_parent(p) ) {
editor_setopen(p, 1);
}
if( nk_input_is_mouse_hovering_rect(&ui_ctx->input, ((struct nk_rect) { bounds.x,bounds.y,offset_in_tree.x-bounds.x+UI_ICON_FONTSIZE/2,bounds.h })) ) {
editor_altopen( o );
}
}
}
--tabs;
}
int editor_scene(int window_mode) {
window_mode = EDITOR_WINDOW; // force window
if( editor_begin(SCENE_TITLE, window_mode)) {
// #define HELP ICON_MDI_INFORMATION_OUTLINE "@-A\n-B\n-C\n" ";"
int choice = ui_toolbar(ICON_MDI_PLUS "@New node (CTRL+N);" ICON_MDI_DOWNLOAD "@Save node (CTRL+S);" ICON_MDI_DOWNLOAD "@Save scene (SHIFT+CTRL+S);" ICON_MD_BOOKMARK_ADDED "@Toggle Bookmark (CTRL+B);");
if( choice == 1 ) editor_send("node_new");
if( choice == 2 ) editor_send("node_save");
if( choice == 3 ) editor_send("scene_save");
if( choice == 4 ) editor_send("bookmark");
array(obj*) bookmarks = 0;
for each_map_ptr(*editor_bookmarked_map(), void*,o,int,bookmarked) {
if( *bookmarked ) {
array_push(bookmarks, *o);
}
}
if( ui_collapse("!" ICON_MD_BOOKMARK "Bookmarks", "DEBUG:BOOKMARK")) {
for each_array( bookmarks, obj*, o )
editor_scene_( o, SCENE_ALL & ~(SCENE_RECURSE|SCENE_INDENT|SCENE_CHECKBOX) );
ui_collapse_end();
}
array_free(bookmarks);
editor_scene_( editor.root, SCENE_ALL );
for each_array( editor.objs, obj*, o )
editor_scene_( o, SCENE_ALL );
ui_separator();
// edit selection
for each_map(*editor_selected_map(), void*,o, int, k) {
if( k ) editor_inspect(o);
}
editor_end(window_mode);
}
return 0;
}
AUTORUN {
array_push(editor.subeditors, editor_scene);
}
#line 0
#line 1 "v4k_editor_browser.h"
#define BROWSER_ICON ICON_MD_FOLDER_SPECIAL
#define BROWSER_TITLE "Browser " BROWSER_ICON
EDITOR_BIND(browser, "held(CTRL)&down(2)", { ui_show(BROWSER_TITLE, ui_visible(BROWSER_TITLE) ^ true); });
int editor_browser(int window_mode) {
window_mode = EDITOR_WINDOW; // force window
if( editor_begin(BROWSER_TITLE, window_mode) ) {
const char *file = 0;
if( ui_browse(&file, NULL) ) {
const char *sep = ifdef(win32, "\"", "'");
app_exec(va("%s %s%s%s", ifdef(win32, "start \"\"", ifdef(osx, "open", "xdg-open")), sep, file, sep));
}
editor_end(window_mode);
}
return 0;
}
AUTORUN {
array_push(editor.subeditors, editor_browser);
}
#line 0
#line 1 "v4k_editor_timeline.h"
#define TIMELINE_ICON ICON_MDI_CHART_TIMELINE
#define TIMELINE_TITLE "Timeline " TIMELINE_ICON
EDITOR_BIND(timeline, "held(CTRL)&down(3)", { ui_show(TIMELINE_TITLE, ui_visible(TIMELINE_TITLE) ^ true); });
int ui_tween(const char *label, tween_t *t) {
if( ui_filter && ui_filter[0] ) if( !strstr(label, ui_filter) ) return 0;
int expand_keys = label[0] == '!'; label += expand_keys;
const char *id = label;
if( strchr(id, '@') ) *strchr((char*)(id = (const char*)va("%s", label)), '@') = '\0';
enum { LABEL_SPACING = 250 };
enum { ROUNDING = 0 };
enum { THICKNESS = 1 };
enum { PIXELS_PER_SECOND = 60 };
enum { KEY_WIDTH = 5, KEY_HEIGHT = 5 };
enum { TIMELINE_HEIGHT = 25 };
enum { MARKER1_HEIGHT = 5, MARKER10_HEIGHT = 20, MARKER5_HEIGHT = (MARKER1_HEIGHT + MARKER10_HEIGHT) / 2 };
unsigned base_color = WHITE;
unsigned time_color = YELLOW;
unsigned duration_color = ORANGE;
unsigned key_color = GREEN;
int changed = 0;
#if 0
// two rows with height:30 composed of three widgets
nk_layout_row_template_begin(ui_ctx, 30);
nk_layout_row_template_push_variable(ui_ctx, t->duration * PIXELS_PER_SECOND); // min 80px. can grow
nk_layout_row_template_end(ui_ctx);
#endif
char *sid = va("%s.%d", id, 0);
uint64_t hash = 14695981039346656037ULL, mult = 0x100000001b3ULL;
for(int i = 0; sid[i]; ++i) hash = (hash ^ sid[i]) * mult;
ui_hue = (hash & 0x3F) / (float)0x3F; ui_hue += !ui_hue;
ui_label(label);
struct nk_command_buffer *canvas = nk_window_get_canvas(ui_ctx);
struct nk_rect bounds; nk_layout_peek(&bounds, ui_ctx);
bounds.y -= 30;
struct nk_rect baseline = bounds; baseline.y += 30/2;
baseline.x += LABEL_SPACING;
baseline.w -= LABEL_SPACING;
// tween duration
{
struct nk_rect pos = baseline;
pos.w = pos.x + t->duration * PIXELS_PER_SECOND;
pos.y -= TIMELINE_HEIGHT/2;
pos.h = TIMELINE_HEIGHT;
nk_stroke_rect(canvas, pos, ROUNDING, THICKNESS*2, AS_NKCOLOR(duration_color));
}
// tween ranges
for(int i = 0, end = array_count(t->keyframes) - 1; i < end; ++i) {
tween_keyframe_t *k = t->keyframes + i;
tween_keyframe_t *next = k + 1;
struct nk_rect pos = baseline;
pos.x += k->t * PIXELS_PER_SECOND;
pos.w = (next->t - k->t) * PIXELS_PER_SECOND;
pos.y -= TIMELINE_HEIGHT/2;
pos.h = TIMELINE_HEIGHT;
char *sid = va("%s.%d", id, i);
uint64_t hash = 14695981039346656037ULL, mult = 0x100000001b3ULL;
for(int i = 0; sid[i]; ++i) hash = (hash ^ sid[i]) * mult;
ui_hue = (hash & 0x3F) / (float)0x3F; ui_hue += !ui_hue;
struct nk_color c = nk_hsva_f(ui_hue, 0.75f, 0.8f, ui_alpha);
nk_fill_rect(canvas, pos, ROUNDING, k->ease == EASE_ZERO ? AS_NKCOLOR(0) : c); // AS_NKCOLOR(track_color));
}
// horizontal line
nk_stroke_line(canvas, baseline.x, baseline.y, baseline.x+baseline.w,baseline.y, THICKNESS, AS_NKCOLOR(base_color));
// unit, 5-unit and 10-unit markers
for( float i = 0, j = 0; i < baseline.w; i += PIXELS_PER_SECOND/10, ++j ) {
int len = !((int)j%10) ? MARKER10_HEIGHT : !((int)j%5) ? MARKER5_HEIGHT : MARKER1_HEIGHT;
nk_stroke_line(canvas, baseline.x+i, baseline.y-len, baseline.x+i, baseline.y+len, THICKNESS, AS_NKCOLOR(base_color));
}
// time marker
float px = t->time * PIXELS_PER_SECOND;
nk_stroke_line(canvas, baseline.x+px, bounds.y, baseline.x+px, bounds.y+bounds.h, THICKNESS*2, AS_NKCOLOR(time_color));
nk_draw_symbol(canvas, NK_SYMBOL_TRIANGLE_DOWN, ((struct nk_rect){ baseline.x+px-4,bounds.y-4-8,8,8}), /*bg*/AS_NKCOLOR(0), /*fg*/AS_NKCOLOR(time_color), 0.f/*border_width*/, ui_ctx->style.font);
// key markers
for each_array_ptr(t->keyframes, tween_keyframe_t, k) {
struct nk_rect pos = baseline;
pos.x += k->t * PIXELS_PER_SECOND;
vec2 romboid[] = {
{pos.x-KEY_WIDTH,pos.y}, {pos.x,pos.y-KEY_HEIGHT},
{pos.x+KEY_WIDTH,pos.y}, {pos.x,pos.y+KEY_HEIGHT}
};
nk_fill_polygon(canvas, (float*)romboid, countof(romboid), AS_NKCOLOR(key_color));
}
// keys ui
if( expand_keys )
for(int i = 0, end = array_count(t->keyframes); i < end; ++i) {
tween_keyframe_t *k = t->keyframes + i;
if( ui_collapse(va("Key %d", i), va("%s.%d", id, i))) {
changed |= ui_float("Time", &k->t);
changed |= ui_float3("Value", &k->v.x);
changed |= ui_list("Ease", ease_enums(), EASE_NUM, &k->ease );
ui_collapse_end();
}
}
return changed;
}
tween_t* rand_tween() {
tween_t demo = tween();
int num_keys = randi(2,8);
double t = 0;
for( int i = 0; i < num_keys; ++i) {
tween_setkey(&demo, t, scale3(vec3(randf(),randf(),randf()),randi(-5,5)), randi(0,EASE_NUM) );
t += randi(1,5) / ((float)(1 << randi(0,2)));
}
tween_t *p = CALLOC(1, sizeof(tween_t));
memcpy(p, &demo, sizeof(tween_t));
return p;
}
int editor_timeline(int window_mode) {
static array(tween_t*) tweens = 0;
do_once {
array_push(tweens, rand_tween());
}
if( editor.t == 0 )
for each_array(tweens, tween_t*,t) {
tween_reset(t);
}
else
for each_array(tweens, tween_t*,t) {
tween_update(t, editor.dt);
}
static void *selected = NULL;
if( editor_begin(TIMELINE_TITLE, window_mode) ) {
int choice = ui_toolbar(ICON_MDI_PLUS ";" ICON_MDI_MINUS );
if( choice == 1 ) array_push(tweens, rand_tween());
if( choice == 2 && selected ) {
int target = -1;
for( int i = 0, end = array_count(tweens); i < end; ++i ) if( tweens[i] == selected ) { target = i; break; }
if( target >= 0 ) { array_erase_slow(tweens, target); selected = NULL; }
}
for each_array(tweens, tween_t*,t) {
ui_tween(va("%s%p@%05.2fs Value: %s", t == selected ? "!":"", t, t->time, ftoa3(t->result)), t);
if(ui_label_icon_clicked_L.x) selected = (t != selected) ? t : NULL;
}
editor_end(window_mode);
}
return 0;
}
AUTORUN {
array_push(editor.subeditors, editor_timeline);
}
#line 0
#line 1 "v4k_editor_console.h"
#define CONSOLE_ICON ICON_MDI_CONSOLE
#define CONSOLE_TITLE "Console " CONSOLE_ICON
EDITOR_BIND(console, "held(CTRL)&down(4)", { ui_show(CONSOLE_TITLE, ui_visible(CONSOLE_TITLE) ^ true); });
int editor_console(int window_mode) {
if( editor_begin(CONSOLE_TITLE, window_mode) ) {
// peek complete window space
struct nk_rect bounds = nk_window_get_content_region(ui_ctx);
enum { CONSOLE_LINE_HEIGHT = 20 };
static array(char*) lines = 0;
do_once {
array_push(lines, stringf("> Editor v%s. Type `%s` for help.", EDITOR_VERSION, ""));
}
int max_lines = (bounds.h - UI_ROW_HEIGHT) / (CONSOLE_LINE_HEIGHT * 2);
if( max_lines >= 1 ) {
nk_layout_row_static(ui_ctx, bounds.h - UI_ROW_HEIGHT, bounds.w, 1);
if(nk_group_begin(ui_ctx, "console.group", NK_WINDOW_NO_SCROLLBAR|NK_WINDOW_BORDER)) {
nk_layout_row_static(ui_ctx, CONSOLE_LINE_HEIGHT, bounds.w, 1);
for( int i = array_count(lines); i < max_lines; ++i )
array_push_front(lines, 0);
for( int i = array_count(lines) - max_lines; i < array_count(lines); ++i ) {
if( !lines[i] ) {
#if 0 // debug
nk_label_wrap(ui_ctx, va("%d.A/%d",i+1,max_lines));
nk_label_wrap(ui_ctx, va("%d.B/%d",i+1,max_lines));
#else
nk_label_wrap(ui_ctx, "");
nk_label_wrap(ui_ctx, "");
#endif
} else {
nk_label_wrap(ui_ctx, lines[i]);
const char *answer = isdigit(*lines[i]) ? editor_recv( atoi(lines[i]), 0 ) : NULL;
nk_label_wrap(ui_ctx, answer ? answer : "");
}
}
nk_group_end(ui_ctx);
}
}
static char *cmd = 0;
if( ui_string(NULL, &cmd) ) {
int jobid = editor_send(cmd);
array_push(lines, stringf("%d> %s", jobid, cmd));
cmd[0] = 0;
}
editor_end(window_mode);
}
return 0;
}
AUTORUN {
array_push(editor.subeditors, editor_console);
}
#line 0
#line 1 "v4k_editor_nodes.h"
#define NODES_ICON ICON_MDI_GRAPH
#define NODES_TITLE "Nodes " NODES_ICON
EDITOR_BIND(nodes, "held(CTRL)&down(5)", { ui_show(NODES_TITLE, ui_visible(NODES_TITLE) ^ true); });
/*
A basic node-based UI built with Nuklear.
Builds on the node editor example included in Nuklear v1.00, with the aim of
being used as a prototype for implementing a functioning node editor.
Features:
- Nodes of different types. Currently their implementations are #included in
the main file, but they could easily be turned into eg. a plugin system.
- Pins/pins of different types -- currently float values and colors.
- Adding and removing nodes.
- Linking nodes, with validation (one link per input, only link similar pins).
- Detaching and moving links.
- Evaluation of output values of connected nodes.
- Memory management based on fixed size arrays for links and node pointers
- Multiple node types
- Multiple pin types
- Linking between pins of the same type
- Detaching and reattaching links
- Getting value from linked node if pin is connected
Todo:
- Complete pin types.
- Allow dragging from output to input pin.
- Cut link by CTRL+clicking input pin.
- Cut link by drawing intersect line on a link.
- Group elemnts together with mouse, or LSHIFT+clicking.
- Drag groups.
- DEL elements.
- DEL groups.
- CTRL-C/CTRL-V/CTRL-X elements.
- CTRL-C/CTRL-V/CTRL-X groups.
- CTRL-Z,CTRL-Y.
- CTRL-N.
- CTRL-L,CTRL-S.
- CTRL-F.
- CTRL-Wheel Zooming.
- Allow to extend node types from Lua.
Extra todo:
- Execution Flow (see: nk_stroke_triangle, nk_fill_triangle)
- Complete missing nodes (see: nk_draw_image, nk_draw_text)
- Right-click could visualize node/board diagram as Lua script.
- Once that done, copy/pasting scripts should work within editor.
Sources:
- https://github.com/Immediate-Mode-UI/Nuklear/pull/561
- https://github.com/vurtun/nuklear/blob/master/demo/node_editor.c
*/
typedef enum pin_type_t {
type_flow,
type_int,type_float,
type_block,type_texture,type_image,
type_color,
/*
type_bool,
type_char, type_string,
type_int2, type_int3, type_int4,
type_float2, type_float3, type_float4,
type_array, type_map,
*/
type_total
} pin_type_t;
struct node_pin {
pin_type_t pin_type;
nk_bool is_connected;
struct node* connected_node;
int connected_pin;
};
struct node {
int ID;
char name[32];
struct nk_rect bounds;
int input_count;
int output_count;
struct node_pin *inputs;
struct node_pin *outputs;
struct {
float in_padding_x;
float in_padding_y;
float in_spacing_y;
float out_padding_x;
float out_padding_y;
float out_spacing_y;
} pin_spacing; /* Maybe this should be called "node_layout" and include the bounds? */
struct node *next; /* Z ordering only */
struct node *prev; /* Z ordering only */
void* (*eval_func)(struct node*, int oIndex);
void (*display_func)(struct nk_context*, struct node*);
};
struct node_link {
struct node* input_node;
int input_pin;
struct node* output_node;
int output_pin;
nk_bool is_active;
};
struct node_linking {
int active;
struct node *node;
int input_id;
int input_pin;
};
struct node_editor {
int initialized;
struct node *node_buf[32];
struct node_link links[64];
struct node *output_node;
struct node *begin;
struct node *end;
int node_count;
int link_count;
struct nk_rect bounds;
struct node *selected;
int show_grid;
struct nk_vec2 scrolling;
struct node_linking linking;
};
/* === PROTOTYPES === */
/* The node implementations need these two functions. */
/* These could/should go in a header file along with the node and node_pin structs and be #included in the node implementations */
struct node* node_editor_add(struct node_editor *editor, size_t nodeSize, const char *name, struct nk_rect bounds, int in_count, int out_count);
void* node_editor_eval_connected(struct node *node, int input_pin_number);
/* ================== */
/* === NODE TYPE IMPLEMENTATIONS === */
#define NODE_DEFAULT_ROW_HEIGHT 25
// ----------------------------------------------------------------------------------------------------
// #include "node_output.h"
struct node_type_output {
struct node node;
struct nk_colorf input_val;
};
struct nk_colorf *node_output_get(struct node* node) {
struct node_type_output *output_node = (struct node_type_output*)node;
if (!node->inputs[0].is_connected) {
struct nk_colorf black = {0.0f, 0.0f, 0.0f, 0.0f};
output_node->input_val = black;
}
return &output_node->input_val;
}
static void node_output_display(struct nk_context *ctx, struct node *node) {
if (node->inputs[0].is_connected) {
struct node_type_output *output_node = (struct node_type_output*)node;
output_node->input_val = *(struct nk_colorf*)node_editor_eval_connected(node, 0);
nk_layout_row_dynamic(ctx, 60, 1);
nk_button_color(ctx, nk_rgba_cf(output_node->input_val));
}
}
struct node* node_output_create(struct node_editor *editor, struct nk_vec2 position) {
struct node_type_output *output_node = (struct node_type_output*)node_editor_add(editor, sizeof(struct node_type_output), "Output", nk_rect(position.x, position.y, 100, 100), 1, 0);
if (output_node){
output_node->node.inputs[0].pin_type = type_color;
output_node->node.display_func = node_output_display;
}
return (struct node*)output_node;
}
// ----------------------------------------------------------------------------------------------------
// #include "node_float.h"
struct node_type_float {
struct node node;
float output_val;
};
static float *node_float_eval(struct node* node, int oIndex) {
struct node_type_float *float_node = (struct node_type_float*)node;
NK_ASSERT(oIndex == 0);
return &float_node->output_val;
}
static void node_float_draw(struct nk_context *ctx, struct node *node) {
struct node_type_float *float_node = (struct node_type_float*)node;
nk_layout_row_dynamic(ctx, NODE_DEFAULT_ROW_HEIGHT, 1);
float_node->output_val = nk_propertyf(ctx, "#Value:", 0.0f, float_node->output_val, 1.0f, 0.01f, 0.01f);
}
void node_float_create(struct node_editor *editor, struct nk_vec2 position) {
struct node_type_float *float_node = (struct node_type_float*)node_editor_add(editor, sizeof(struct node_type_float), "Float", nk_rect(position.x, position.y, 180, 75), 0, 1);
if (float_node)
{
float_node->output_val = 1.0f;
float_node->node.display_func = node_float_draw;
float_node->node.eval_func = (void*(*)(struct node*, int)) node_float_eval;
}
}
// ----------------------------------------------------------------------------------------------------
// #include "node_color.h"
struct node_type_color {
struct node node;
float input_val[4];
struct nk_colorf output_val;
};
static struct nk_colorf *node_color_eval(struct node* node, int oIndex)
{
struct node_type_color *color_node = (struct node_type_color*)node;
NK_ASSERT(oIndex == 0); /* only one output connector */
return &color_node->output_val;
}
static void node_color_draw(struct nk_context *ctx, struct node *node)
{
struct node_type_color *color_node = (struct node_type_color*)node;
float eval_result; /* Get the values from connected nodes into this so the inputs revert on disconnect */
const char* labels[4] = {"#R:","#G:","#B:","#A:"};
float color_val[4]; /* Because we can't just loop through the struct... */
nk_layout_row_dynamic(ctx, NODE_DEFAULT_ROW_HEIGHT, 1);
nk_button_color(ctx, nk_rgba_cf(color_node->output_val));
for (int i = 0; i < 4; i++)
{
if (color_node->node.inputs[i].is_connected) {
eval_result = *(float*)node_editor_eval_connected(node, i);
eval_result = nk_propertyf(ctx, labels[i], eval_result, eval_result, eval_result, 0.01f, 0.01f);
color_val[i] = eval_result;
}
else {
color_node->input_val[i] = nk_propertyf(ctx, labels[i], 0.0f, color_node->input_val[i], 1.0f, 0.01f, 0.01f);
color_val[i] = color_node->input_val[i];
}
}
color_node->output_val.r = color_val[0];
color_node->output_val.g = color_val[1];
color_node->output_val.b = color_val[2];
color_node->output_val.a = color_val[3];
}
void node_color_create(struct node_editor *editor, struct nk_vec2 position)
{
struct node_type_color *color_node = (struct node_type_color*)node_editor_add(editor, sizeof(struct node_type_color), "Color", nk_rect(position.x, position.y, 180, 190), 4, 1);
if (color_node)
{
const struct nk_colorf black = {0.0f, 0.0f, 0.0f, 1.0f};
for (int i = 0; i < color_node->node.input_count; i++)
color_node->node.inputs[i].pin_type = type_float;
color_node->node.outputs[0].pin_type = type_color;
color_node->node.pin_spacing.in_padding_y += NODE_DEFAULT_ROW_HEIGHT;
color_node->input_val[0] = 0.0f;
color_node->input_val[1] = 0.0f;
color_node->input_val[2] = 0.0f;
color_node->input_val[3] = 1.0f;
color_node->output_val = black;
color_node->node.display_func = node_color_draw;
color_node->node.eval_func = (void*(*)(struct node*, int)) node_color_eval;
}
}
// ----------------------------------------------------------------------------------------------------
// #include "node_blend.h"
struct node_type_blend {
struct node node;
struct nk_colorf input_val[2];
struct nk_colorf output_val;
float blend_val;
};
static struct nk_colorf *node_blend_eval(struct node *node, int oIndex) {
struct node_type_blend* blend_node = (struct node_type_blend*)node;
return &blend_node->output_val;
}
static void node_blend_display(struct nk_context *ctx, struct node *node) {
struct node_type_blend *blend_node = (struct node_type_blend*)node;
const struct nk_colorf blank = {0.0f, 0.0f, 0.0f, 0.0f};
float blend_amnt;
nk_layout_row_dynamic(ctx, NODE_DEFAULT_ROW_HEIGHT, 1);
for (int i = 0; i < 2; i++){
if(node->inputs[i].is_connected) {
blend_node->input_val[i] = *(struct nk_colorf*)node_editor_eval_connected(node, i);
}
else {
blend_node->input_val[i] = blank;
}
nk_button_color(ctx, nk_rgba_cf(blend_node->input_val[i]));
}
if (node->inputs[2].is_connected) {
blend_amnt = *(float*)node_editor_eval_connected(node, 2);
blend_amnt = nk_propertyf(ctx, "#Blend", blend_amnt, blend_amnt, blend_amnt, 0.01f, 0.01f);
}
else {
blend_node->blend_val = nk_propertyf(ctx, "#Blend", 0.0f, blend_node->blend_val, 1.0f, 0.01f, 0.01f);
blend_amnt = blend_node->blend_val;
}
if(node->inputs[0].is_connected && node->inputs[1].is_connected) {
blend_node->output_val.r = blend_node->input_val[0].r * (1.0f-blend_amnt) + blend_node->input_val[1].r * blend_amnt;
blend_node->output_val.g = blend_node->input_val[0].g * (1.0f-blend_amnt) + blend_node->input_val[1].g * blend_amnt;
blend_node->output_val.b = blend_node->input_val[0].b * (1.0f-blend_amnt) + blend_node->input_val[1].b * blend_amnt;
blend_node->output_val.a = blend_node->input_val[0].a * (1.0f-blend_amnt) + blend_node->input_val[1].a * blend_amnt;
}
else {
blend_node->output_val = blank;
}
}
void node_blend_create(struct node_editor *editor, struct nk_vec2 position) {
struct node_type_blend* blend_node = (struct node_type_blend*)node_editor_add(editor, sizeof(struct node_type_blend), "Blend", nk_rect(position.x, position.y, 180, 130), 3, 1);
if (blend_node) {
const struct nk_colorf blank = {0.0f, 0.0f, 0.0f, 0.0f};
for (int i = 0; i < (int)NK_LEN(blend_node->input_val); i++)
blend_node->node.inputs[i].pin_type = type_color;
blend_node->node.outputs[0].pin_type = type_color;
// blend_node->node.pin_spacing.in_padding_y = 42.0f;
// blend_node->node.pin_spacing.in_spacing_y = 29.0f;
for (int i = 0; i < (int)NK_LEN(blend_node->input_val); i++)
blend_node->input_val[i] = blank;
blend_node->output_val = blank;
blend_node->blend_val = 0.5f;
blend_node->node.display_func = node_blend_display;
blend_node->node.eval_func = (void*(*)(struct node*, int)) node_blend_eval;
}
}
/* ================================= */
#define NK_RGB3(r,g,b) {r,g,b,255}
#define BG_COLOR ((struct nk_color){60,60,60,192}) // nk_rgba(0,0,0,192)
static
struct editor_node_style {
int pin_type;
const char *shape;
struct nk_color color_idle;
struct nk_color color_hover;
} styles[] = {
// order matters:
{ type_flow, "triangle_right", NK_RGB3(200,200,200), NK_RGB3(255,255,255) }, // if .num_links == 0
{ type_int, "circle", NK_RGB3(33,227,175), NK_RGB3(135,239,195) },
{ type_float, "circle", NK_RGB3(156,253,65), NK_RGB3(144,225,137) },
{ type_block, "circle", NK_RGB3(6,165,239), NK_RGB3(137,196,247) },
{ type_texture, "circle", NK_RGB3(148,0,0), NK_RGB3(183,137,137) },
{ type_image, "circle", NK_RGB3(200,130,255), NK_RGB3(220,170,255) },
{ type_color, "circle", NK_RGB3(252,200,35), NK_RGB3(255,217,140) },
};
#define COLOR_FLOW_HI styles[type_flow].color_hover
#define COLOR_FLOW_LO styles[type_flow].color_idle
#define GRID_SIZE 64.0f
#define GRID_COLOR ((struct nk_color)NK_RGB3(80,80,120))
#define GRID_THICKNESS 1.0f
// 4 colors: top-left, top-right, bottom-right, bottom-left
#define GRID_BG_COLORS ((struct nk_color){30,30,30,255}), ((struct nk_color){40,20,0,255}), ((struct nk_color){30,30,30,255}), ((struct nk_color){20,30,40,255})
#define LINK_THICKNESS 1.0f
#define LINK_DRAW(POINT_A,POINT_B,COLOR) do { \
vec2 a = (POINT_A); \
vec2 b = (POINT_B); \
nk_stroke_line(canvas, a.x, a.y, b.x, b.y, LINK_THICKNESS, COLOR); \
} while(0)
#undef LINK_DRAW
#define LINK_DRAW(POINT_A,POINT_B,COLOR) do { \
vec2 a = (POINT_A); \
vec2 b = (POINT_B); \
nk_stroke_curve(canvas, a.x, a.y, a.x+50, a.y, b.x-50, b.y, b.x, b.y, LINK_THICKNESS, COLOR); \
} while(0)
#undef LINK_DRAW
#define LINK_DRAW(POINT_A,POINT_B,COLOR) do { \
vec2 a = (POINT_A); \
vec2 b = (POINT_B); \
float dist2 = len2( sub2( ptr2(&b.x), ptr2(&a.x) ) ); \
vec2 mid_a = mix2( ptr2(&a.x), ptr2(&b.x), 0.25 ); mid_a.y += dist2/2; \
vec2 mid_b = mix2( ptr2(&a.x), ptr2(&b.x), 0.75 ); mid_b.y += dist2/3; \
nk_stroke_curve(canvas, a.x, a.y, mid_a.x, mid_a.y, mid_b.x, mid_b.y, b.x, b.y, LINK_THICKNESS, COLOR); \
} while(0)
#define PIN_RADIUS 12
#define PIN_THICKNESS 1.0f
#define PIN_DRAW(PIN_ADDR,POINT,RADIUS) do { \
circle.x = (POINT).x - (RADIUS) / 2; \
circle.y = (POINT).y - (RADIUS) / 2; \
circle.w = circle.h = (RADIUS); \
struct nk_color color = node_get_type_color((PIN_ADDR).pin_type); \
if((PIN_ADDR).is_connected) \
nk_fill_circle(canvas, circle, color); \
else \
nk_stroke_circle(canvas, circle, PIN_THICKNESS, color); \
} while(0)
static struct nk_color node_get_type_color(unsigned pin_type) {
for( int i = 0; i < type_total; ++i )
if( styles[i].pin_type == pin_type )
return styles[i].color_idle;
return ((struct nk_color)NK_RGB3(255,0,255));
}
static void node_editor_push(struct node_editor *editor, struct node *node) {
if (!editor->begin) {
node->next = NULL;
node->prev = NULL;
editor->begin = node;
editor->end = node;
} else {
node->prev = editor->end;
if (editor->end)
editor->end->next = node;
node->next = NULL;
editor->end = node;
}
}
static void node_editor_pop(struct node_editor *editor, struct node *node) {
if (node->next)
node->next->prev = node->prev;
if (node->prev)
node->prev->next = node->next;
if (editor->end == node)
editor->end = node->prev;
if (editor->begin == node)
editor->begin = node->next;
node->next = NULL;
node->prev = NULL;
}
static struct node* node_editor_find_by_id(struct node_editor *editor, int ID) {
struct node *iter = editor->begin;
while (iter) {
if (iter->ID == ID)
return iter;
iter = iter->next;
}
return NULL;
}
static struct node_link* node_editor_find_link_by_output(struct node_editor *editor, struct node *output_node, int node_input_connector) {
for( int i = 0; i < editor->link_count; i++ ) {
if (editor->links[i].output_node == output_node &&
editor->links[i].output_pin == node_input_connector &&
editor->links[i].is_active == nk_true) {
return &editor->links[i];
}
}
return NULL;
}
static struct node_link* node_editor_find_link_by_input(struct node_editor *editor, struct node *input_node, int node_output_connector) {
for( int i = 0; i < editor->link_count; i++ ) {
if (editor->links[i].input_node == input_node &&
editor->links[i].input_pin == node_output_connector &&
editor->links[i].is_active == nk_true) {
return &editor->links[i];
}
}
return NULL;
}
static void node_editor_delete_link(struct node_link *link) {
link->is_active = nk_false;
link->input_node->outputs[link->input_pin].is_connected = nk_false;
link->output_node->inputs[link->output_pin].is_connected = nk_false;
}
struct node* node_editor_add(struct node_editor *editor, size_t nodeSize, const char *name, struct nk_rect bounds, int in_count, int out_count) {
static int IDs = 0;
struct node *node = NULL;
if ((nk_size)editor->node_count < NK_LEN(editor->node_buf)) {
/* node_buf has unused pins */
node = MALLOC(nodeSize);
editor->node_buf[editor->node_count++] = node;
node->ID = IDs++;
} else {
/* check for freed up pins in node_buf */
for (int i = 0; i < editor->node_count; i++) {
if (editor->node_buf[i] == NULL) {
node = MALLOC(nodeSize);
editor->node_buf[i] = node;
node->ID = i;
break;
}
}
}
if (node == NULL) {
puts("Node creation failed");
return NULL;
}
node->bounds = bounds;
node->input_count = in_count;
node->output_count = out_count;
node->inputs = MALLOC(node->input_count * sizeof(struct node_pin));
node->outputs = MALLOC(node->output_count * sizeof(struct node_pin));
for (int i = 0; i < node->input_count; i++) {
node->inputs[i].is_connected = nk_false;
node->inputs[i].pin_type = type_float; /* default pin type */
}
for (int i = 0; i < node->output_count; i++) {
node->outputs[i].is_connected = nk_false;
node->outputs[i].pin_type = type_float; /* default pin type */
}
/* default pin spacing */
node->pin_spacing.in_padding_x = 2;
node->pin_spacing.in_padding_y = 32 + 25/2 + 6; // titlebar height + next half row + adjust
node->pin_spacing.in_spacing_y = 25; // row height+border
node->pin_spacing.out_padding_x = 3;
node->pin_spacing.out_padding_y = 32 + 25/2 + 6; // titlebar height + next half row + adjust
node->pin_spacing.out_spacing_y = 25; // row height+border
strcpy(node->name, name);
node_editor_push(editor, node);
return node;
}
void *node_editor_eval_connected(struct node* node, int input_pin_number) {
NK_ASSERT(node->inputs[input_pin_number].is_connected);
return node->inputs[input_pin_number].connected_node->eval_func(node->inputs[input_pin_number].connected_node, node->inputs[input_pin_number].connected_pin);
}
static void node_editor_link(struct node_editor *editor, struct node *in_node, int in_pin, struct node *out_node, int out_pin) {
/* Confusingly, in and out nodes/pins here refer to the inputs and outputs OF THE LINK ITSELF, not the nodes */
struct node_link *link = NULL;
if ((nk_size)editor->link_count < NK_LEN(editor->links)) {
link = &editor->links[editor->link_count++];
} else {
for (int i = 0; i < (int)NK_LEN(editor->links); i++)
{
if (editor->links[i].is_active == nk_false) {
link = &editor->links[i];
break;
}
}
}
if (link) {
out_node->inputs[out_pin].is_connected = nk_true;
in_node->outputs[in_pin].is_connected = nk_true;
out_node->inputs[out_pin].connected_node = in_node;
out_node->inputs[out_pin].connected_pin = in_pin;
link->input_node = in_node;
link->input_pin = in_pin;
link->output_node = out_node;
link->output_pin = out_pin;
link->is_active = nk_true;
} else {
puts("Too many links");
}
}
static void node_editor_init(struct node_editor *editor) {
if (editor->initialized) return;
struct nk_rect total_space = nk_window_get_content_region(ui_ctx);
struct nk_vec2 output_node_position = { total_space.w*2/3, total_space.h/3 };
struct nk_vec2 color_node_position = { total_space.w*1/4, total_space.h/3 };
memset(editor, 0, sizeof(*editor));
editor->output_node = node_output_create(editor, output_node_position);
node_color_create(editor, color_node_position);
editor->show_grid = nk_true;
editor->initialized = 1;
}
static int node_editor(struct node_editor *editor) {
int n = 0;
struct nk_rect total_space;
const struct nk_input *in = &ui_ctx->input;
struct nk_command_buffer *canvas = nk_window_get_canvas(ui_ctx);
struct node *updated = 0;
node_editor_init(editor);
{
/* allocate complete window space */
total_space = nk_window_get_content_region(ui_ctx);
nk_layout_space_begin(ui_ctx, NK_STATIC, total_space.h, editor->node_count);
{
struct node *it = editor->begin;
struct nk_rect size = nk_layout_space_bounds(ui_ctx);
struct nk_panel *nodePanel = 0;
//nk_fill_rect(canvas, size, 0/*rounding*/, ((struct nk_color){30,30,30,255})); // 20,30,40,255
nk_fill_rect_multi_color(canvas, size, GRID_BG_COLORS);
if (editor->show_grid) {
/* display grid */
for (float x = (float)fmod(size.x - editor->scrolling.x, GRID_SIZE); x < size.w; x += GRID_SIZE)
nk_stroke_line(canvas, x+size.x, size.y, x+size.x, size.y+size.h, GRID_THICKNESS, GRID_COLOR);
for (float y = (float)fmod(size.y - editor->scrolling.y, GRID_SIZE); y < size.h; y += GRID_SIZE)
nk_stroke_line(canvas, size.x, y+size.y, size.x+size.w, y+size.y, GRID_THICKNESS, GRID_COLOR);
}
/* execute each node as a movable group */
/* loop through nodes */
while (it) {
/* Output node window should not have a close button */
nk_flags nodePanel_flags = NK_WINDOW_MOVABLE|NK_WINDOW_NO_SCROLLBAR|NK_WINDOW_BORDER|NK_WINDOW_TITLE;
if (it != editor->output_node)
nodePanel_flags |= NK_WINDOW_CLOSABLE;
/* calculate scrolled node window position and size */
nk_layout_space_push(ui_ctx, nk_rect(it->bounds.x - editor->scrolling.x,
it->bounds.y - editor->scrolling.y, it->bounds.w, it->bounds.h));
/* execute node window */
char *name = va(" " ICON_MD_MENU " %s",it->name); //< @r-lyeh added some spacing+icon because of our UI customizations
struct nk_color bak = ui_ctx->style.window.fixed_background.data.color;
ui_ctx->style.window.fixed_background.data.color = BG_COLOR;
if (nk_group_begin(ui_ctx, name, nodePanel_flags))
{
/* always have last selected node on top */
nodePanel = nk_window_get_panel(ui_ctx);
if (nk_input_mouse_clicked(in, NK_BUTTON_LEFT, nodePanel->bounds) &&
(!(it->prev && nk_input_mouse_clicked(in, NK_BUTTON_LEFT,
nk_layout_space_rect_to_screen(ui_ctx, nodePanel->bounds)))) &&
editor->end != it)
{
updated = it;
}
if ((nodePanel->flags & NK_WINDOW_HIDDEN)) /* Node close button has been clicked */
{
/* Delete node */
struct node_link *link_remove;
node_editor_pop(editor, it);
for (int n = 0; n < it->input_count; n++) {
if ((link_remove = node_editor_find_link_by_output(editor, it, n)))
{
node_editor_delete_link(link_remove);
}
}
for (int n = 0; n < it -> output_count; n++) {
while((link_remove = node_editor_find_link_by_input(editor, it, n)))
{
node_editor_delete_link(link_remove);
}
}
NK_ASSERT(editor->node_buf[it->ID] == it);
editor->node_buf[it->ID] = NULL;
FREE(it->inputs);
FREE(it->outputs);
FREE(it);
}
else {
/* ================= NODE CONTENT ===================== */
it->display_func(ui_ctx, it);
/* ==================================================== */
}
nk_group_end(ui_ctx);
}
ui_ctx->style.window.fixed_background.data.color = bak;
if (!(nodePanel->flags & NK_WINDOW_HIDDEN))
{
/* node pin and linking */
struct nk_rect bounds;
bounds = nk_layout_space_rect_to_local(ui_ctx, nodePanel->bounds);
bounds.x += editor->scrolling.x;
bounds.y += editor->scrolling.y;
it->bounds = bounds;
/* output pins */
for (int n = 0; n < it->output_count; ++n) {
struct nk_rect circle;
struct nk_vec2 pt = {nodePanel->bounds.x, nodePanel->bounds.y};
pt.x += nodePanel->bounds.w - PIN_RADIUS / 2 + it->pin_spacing.out_padding_x;
pt.y += it->pin_spacing.out_padding_y + it->pin_spacing.out_spacing_y * (n);
PIN_DRAW(it->outputs[n],pt,PIN_RADIUS);
/* start linking process */
/* set linking active */
if (nk_input_has_mouse_click_down_in_rect(in, NK_BUTTON_LEFT, circle, nk_true)) {
editor->linking.active = nk_true;
editor->linking.node = it;
editor->linking.input_id = it->ID;
editor->linking.input_pin = n;
}
/* draw link being dragged (from linked pin to mouse position) */
if (editor->linking.active && editor->linking.node == it &&
editor->linking.input_pin == n) {
LINK_DRAW(vec2(circle.x+3,circle.y+3),ptr2(&in->mouse.pos.x),COLOR_FLOW_HI);
}
}
/* input pins */
for (int n = 0; n < it->input_count; ++n) {
struct nk_rect circle;
struct nk_vec2 pt = {nodePanel->bounds.x, nodePanel->bounds.y};
pt.x += it->pin_spacing.in_padding_x;
pt.y += it->pin_spacing.in_padding_y + it->pin_spacing.in_spacing_y * (n);
PIN_DRAW(it->inputs[n],pt,PIN_RADIUS);
/* Detach link */
if (nk_input_has_mouse_click_down_in_rect(in, NK_BUTTON_LEFT, circle, nk_true) &&
editor->linking.active == nk_false &&
it->inputs[n].is_connected == nk_true) {
struct node_link *node_relink = node_editor_find_link_by_output(editor, it, n);
editor->linking.active = nk_true;
editor->linking.node = node_relink->input_node;
editor->linking.input_id = node_relink->input_node->ID;
editor->linking.input_pin = node_relink->input_pin;
node_editor_delete_link(node_relink);
}
/* Create link */
if (nk_input_is_mouse_released(in, NK_BUTTON_LEFT) &&
nk_input_is_mouse_hovering_rect(in, circle) &&
editor->linking.active &&
editor->linking.node != it &&
it->inputs[n].pin_type == editor->linking.node->outputs[editor->linking.input_pin].pin_type &&
it->inputs[n].is_connected != nk_true) {
editor->linking.active = nk_false;
node_editor_link(editor, editor->linking.node,
editor->linking.input_pin, it, n);
}
}
}
it = it->next;
}
/* reset (output) linking connection */
if (editor->linking.active && (!!input(KEY_LCTRL) || !!input(KEY_RCTRL) || nk_input_is_mouse_released(in, NK_BUTTON_LEFT))) {
editor->linking.active = nk_false;
editor->linking.node = NULL;
}
/* draw each static link */
for (int n = 0; n < editor->link_count; ++n) {
struct node_link *link = &editor->links[n];
if (link->is_active == nk_true){
struct node *ni = link->input_node;
struct node *no = link->output_node;
struct nk_vec2 l0 = nk_layout_space_to_screen(ui_ctx, nk_vec2(ni->bounds.x + ni->bounds.w + ni->pin_spacing.out_padding_x, 3.0f + ni->bounds.y + ni->pin_spacing.out_padding_y + ni->pin_spacing.out_spacing_y * (link->input_pin)));
struct nk_vec2 l1 = nk_layout_space_to_screen(ui_ctx, nk_vec2(no->bounds.x + no->pin_spacing.in_padding_x, 3.0f + no->bounds.y + no->pin_spacing.in_padding_y + no->pin_spacing.in_spacing_y * (link->output_pin)));
l0.x -= editor->scrolling.x;
l0.y -= editor->scrolling.y;
l1.x -= editor->scrolling.x;
l1.y -= editor->scrolling.y;
struct nk_color color = node_get_type_color(no->inputs[link->output_pin].pin_type);
LINK_DRAW(ptr2(&l0.x), ptr2(&l1.x), color);
}
}
if (updated) {
/* reshuffle nodes to have least recently selected node on top */
node_editor_pop(editor, updated);
node_editor_push(editor, updated);
}
/* node selection */
if (nk_input_mouse_clicked(in, NK_BUTTON_LEFT, nk_layout_space_bounds(ui_ctx))) {
it = editor->begin;
editor->selected = NULL;
editor->bounds = nk_rect(in->mouse.pos.x, in->mouse.pos.y, 100, 200);
while (it) {
struct nk_rect b = nk_layout_space_rect_to_screen(ui_ctx, it->bounds);
b.x -= editor->scrolling.x;
b.y -= editor->scrolling.y;
if (nk_input_is_mouse_hovering_rect(in, b))
editor->selected = it;
it = it->next;
}
}
/* contextual menu */
if (nk_contextual_begin(ui_ctx, 0, nk_vec2(150, 220), nk_window_get_bounds(ui_ctx))) {
struct nk_vec2 wincoords = { in->mouse.pos.x-total_space.x-50, in->mouse.pos.y-total_space.y-32 };
#if 1
static char *filter = 0;
static int do_filter = 0;
if( input_down(KEY_F) ) if( input(KEY_LCTRL) || input(KEY_RCTRL) ) do_filter ^= 1;
int choice = ui_toolbar(ICON_MD_SEARCH ";");
if( choice == 1 ) do_filter = 1;
if( do_filter ) {
ui_string(ICON_MD_CLOSE " Filter " ICON_MD_SEARCH, &filter);
if( ui_label_icon_clicked_L.x > 0 && ui_label_icon_clicked_L.x <= 24 ) { // if clicked on CANCEL icon (1st icon)
do_filter = 0;
}
} else {
if( filter ) filter[0] = '\0';
}
char *filter_mask = filter && filter[0] ? va("*%s*", filter) : "*";
#endif
#define ui_label_filtered(lbl) (strmatchi(lbl,filter_mask) && ui_label(lbl))
int close = 0;
if (ui_label_filtered("=Add Color node")) close=1,node_color_create(editor, wincoords);
if (ui_label_filtered("=Add Float node")) close=1,node_float_create(editor, wincoords);
if (ui_label_filtered("=Add Blend Node")) close=1,node_blend_create(editor, wincoords);
if (ui_label_filtered(editor->show_grid ? "=Hide Grid" : "=Show Grid"))
close=1,editor->show_grid = !editor->show_grid;
if(close) do_filter = 0, (filter ? filter[0] = '\0' : '\0'), nk_contextual_close(ui_ctx);
nk_contextual_end(ui_ctx);
}
}
nk_layout_space_end(ui_ctx);
/* window content scrolling */
if (nk_input_is_mouse_hovering_rect(in, nk_window_get_bounds(ui_ctx)) &&
nk_input_is_mouse_down(in, NK_BUTTON_MIDDLE)) {
editor->scrolling.x += in->mouse.delta.x;
editor->scrolling.y += in->mouse.delta.y;
}
}
return !nk_window_is_closed(ui_ctx, "NodeEdit");
}
int editor_nodes(int window_mode) {
window_mode = EDITOR_WINDOW; // force window
if( editor_begin(NODES_TITLE, window_mode) ) {
static struct node_editor nodeEditor = {0};
node_editor(&nodeEditor);
editor_end(window_mode);
}
return 0;
}
AUTORUN {
array_push(editor.subeditors, editor_nodes);
}
#line 0
#line 1 "v4k_editor_script.h"
int ui_texture_fit(texture_t t, struct nk_rect bounds) {
// allocate complete window space
struct nk_rect total_space = nk_window_get_content_region(ui_ctx);
nk_layout_space_begin(ui_ctx, NK_DYNAMIC, total_space.h - 4, 1); // -4 to hide scrollbar Y
nk_layout_space_push(ui_ctx, nk_rect(0,0,1,1));
struct nk_command_buffer *canvas = nk_window_get_canvas(ui_ctx);
struct nk_image image = nk_image_id((int)t.id);
nk_draw_image(canvas, bounds, &image, nk_white);
nk_layout_space_end(ui_ctx);
return 0;
}
#define LITE_ICON ICON_MDI_SCRIPT_TEXT
#define LITE_TITLE "Script " LITE_ICON
EDITOR_BIND(script, "held(CTRL)&down(6)", { ui_show(LITE_TITLE, ui_visible(LITE_TITLE) ^ true); });
int editor_scripted(int window_mode) {
window_mode = EDITOR_WINDOW; // force mode
static lua_State *L = 0;
do_once {
L = script_init_env(SCRIPT_LUA|SCRIPT_DEBUGGER);
const char *platform = "" // "Android" "FreeBSD" "OpenBSD" "NetBSD"
ifdef(ems, "Emscripten")
ifdef(linux, "Linux")
ifdef(osx, "macOS")
ifdef(win32, "Windows")
;
const char *pathexe = vac("%s%s%s", app_path(), app_name(), ifdef(win32, ".exe", ""));
gleqInit();
gleqTrackWindow(window_handle());
lt_init(L, window_handle(), LT_DATAPATH, __argc, __argv, window_scale(), platform, pathexe);
}
unsigned lt_none = 0u;
unsigned lt_all = ~0u & ~(GLEQ_WINDOW_MOVED/*|GLEQ_WINDOW_RESIZED|GLEQ_WINDOW_REFRESH*/);
lt_events = lt_none;
int mouse_in_rect = 0;
if( editor_begin(LITE_TITLE, window_mode) ) {
lt_events = lt_all;
if( !nk_window_has_focus(ui_ctx) ) lt_events = lt_none;
struct nk_rect bounds = nk_window_get_content_region(ui_ctx);
lt_mx = input(MOUSE_X) - bounds.x;
lt_my = input(MOUSE_Y) - bounds.y;
lt_wx = bounds.x;
lt_wy = bounds.y;
lt_ww = bounds.w;
lt_wh = bounds.h;
if( lt_resizesurface(lt_getsurface(0), lt_ww, lt_wh) ) {
gleq_window_refresh_callback(window_handle());
}
// fullscreen_quad_rgb( lt_getsurface(0)->t, 1.2f );
ui_texture_fit(lt_getsurface(0)->t, bounds);
if( !!nk_input_is_mouse_hovering_rect(&ui_ctx->input, ((struct nk_rect){lt_wx+5,lt_wy+5,lt_ww-10,lt_wh-10})) ) {
lt_events &= ~(1<<31); // dont cursor shape
}
editor_end(window_mode);
}
lt_tick(L);
return 0;
}
AUTORUN {
array_push(editor.subeditors, editor_scripted);
}
#line 0
#line 1 "v4k_end.c"
// Enable more performant GPUs on laptops. Does this work into a dll?
// int NvOptimusEnablement = 1;
// int AmdPowerXpressRequestHighPerformance = 1;
#if is(linux) && is(tcc) // fixes `tcc: error: undefined symbol '__dso_handle'`
int __dso_handle; // compiled with: `tcc demo.c v4k.c -D__STDC_NO_VLA__ -lX11`
#endif
#if is(win32) && is(tcc) // fixes `tcc: error: undefined symbol '_InterlockedExchangeAdd'` when compiling with `-m64` flag
__CRT_INLINE LONG _InterlockedExchangeAdd(LONG volatile *add, LONG val) {
LONG old;
do old = *add; while( InterlockedCompareExchange(add, old + val, old) != old );
return old;
}
__CRT_INLINE LONGLONG _InterlockedExchangeAdd64(LONGLONG volatile *add, LONGLONG val) { // 64bit version, for completeness
LONGLONG old;
do old = *add; while( InterlockedCompareExchange64(add, old + val, old) != old );
return old;
}
#endif
#ifdef ZIG_CC
static int IN6_IS_ADDR_V4MAPPED(const struct in6_addr *a) { return ((a->s6_words[0]==0) && (a->s6_words[1]==0) && (a->s6_words[2]==0) && (a->s6_words[3]==0) && (a->s6_words[4]==0) && (a->s6_words[5]==0xffff)); }
const struct in6_addr in6addr_any; // = IN6ADDR_ANY_INIT;
//static const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT;
#endif
ifdef(retail, AUTORUN {
fclose(stderr);
fclose(stdout);
const char* null_stream = ifdef(win32, "nul:", "/dev/null");
if (!freopen(null_stream, "a", stdout)) PANIC("cannot recreate standard streams");
if (!freopen(null_stream, "a", stderr)) PANIC("cannot recreate standard streams");
} )
#line 0