/* 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 #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 #if is(mingw) #include #endif #else #include #include // sched_setaffinity(), CPU_ZERO(), CPU_COUNT() #include #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 // mingw64 does not have it #include // 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 #include #include #include #include #include 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 ); // 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 // #include // 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.ydesktop.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 #include // 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"); "" "" " Robert" " Smith" "
" " 12345 Sixth Ave" " Anytown" " CA" " 98765-4321" "
" "
"; 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 # define dlopen(name,flags) (void*)( (name) ? LoadLibraryA(name) : GetModuleHandleA(NULL)) # define dlsym(handle,symbol) GetProcAddress((HMODULE)(handle), symbol ) # define dlclose(handle) 0 #else # include # 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 = [[;;<>;]]\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 #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 #include #include #include 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; } 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; } // --- 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 #else # include #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 # 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 instead? static map( int64_t, array(netbuffer_t) ) buffers; // map> 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)&((1LL< 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): // 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 #include #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 #if is(win32) #include #else #include #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); } 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; } 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"; 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"; const char *glsl_version = ifdef(ems, gles, desktop); // detect GLSL version if set if (processed_src[0] == '#' && processed_src[1] == 'v') { #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 } return va("%s\n%s\n%s", glsl_version, defines ? defines : "", processed_src); } 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 : ""; 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; } } } 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); } // ----------------------------------------------------------------------------- // Occlusion queries static renderstate_t query_test_rs; static inline void query_test_rs_init() { do_once { query_test_rs = renderstate(); query_test_rs.depth_test_enabled = true; query_test_rs.depth_write_enabled = false; query_test_rs.depth_func = GL_LESS; query_test_rs.point_size_enabled = 1; query_test_rs.point_size = 1.0f; memset(query_test_rs.color_mask, 0, sizeof(query_test_rs.color_mask)); } } unsigned query_test_point(mat44 proj, mat44 view, vec3 pos, float size) { static int program = -1, vao = -1, u_mvp = -1, query = -1; if( program < 0 ) { const char* vs = vfs_read("shaders/query_point_vs.glsl"); const char* fs = vfs_read("shaders/query_point_fs.glsl"); program = shader(vs, fs, "", "fragcolor" , NULL); u_mvp = glGetUniformLocation(program, "u_mvp"); glGenVertexArrays( 1, (GLuint*)&vao ); glGenQueries(1, (GLuint*)&query); query_test_rs_init(); } query_test_rs.point_size = size; renderstate_apply(&query_test_rs); int oldprog = last_shader; glUseProgram( program ); mat44 M; translation44(M, pos.x, pos.y, pos.z); mat44 MVP; multiply44x3(MVP, proj, view, M); glUniformMatrix4fv(u_mvp, 1, GL_FALSE, MVP); glBindVertexArray( vao ); glBeginQuery(GL_SAMPLES_PASSED, query); glDrawArrays( GL_POINTS, 0, 1 ); glEndQuery(GL_SAMPLES_PASSED); GLuint samples_passed = 0; glGetQueryObjectuiv(query, GL_QUERY_RESULT, &samples_passed); glBindVertexArray( 0 ); glUseProgram( oldprog ); return samples_passed; } // ----------------------------------------------------------------------------- // 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); } } void skybox_sh_shader(skybox_t *sky) { shader_vec3v("u_coefficients_sh", 9, sky->cubemap.sh); } 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"); const char* fs = vfs_read("shaders/brdf_lut.glsl"); 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; } int srgb = load_as_srgb ? TEXTURE_SRGB : 0; // int srgb = 0; 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; GLfloat color[4]; 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; void model_set_texture(model_t *m, texture_t t) { if(!m->iqm) return; iqm_t *q = m->iqm; for( int i = 0; i < q->nummeshes; ++i) { // assume 1 texture per mesh q->textures[i] = t.id; if (m->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture) *m->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture = t; } } //@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 ) { vec3 pos = pos44(view); glUniform3fv( loc, 1, &pos.x ); } else if( (loc = glGetUniformLocation(shader, "cam_pos")) >= 0 ) { vec3 pos = pos44(view); 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 glVertexAttribPointer(11, 4, GL_FLOAT, GL_FALSE, sizeof(iqm_vertex), (GLvoid*)offsetof(iqm_vertex,color) ); 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; float *invertexcolor = NULL; 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); break; case IQM_COLOR: ASSERT(va->size == 4); ASSERT(va->format == IQM_FLOAT); invertexcolor = (float *)&q->buf[va->offset]; 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); } 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; } } 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)); m->meshcenters = CALLOC(hdr->num_meshes, sizeof(vec3)); m->meshbounds = CALLOC(hdr->num_meshes, sizeof(aabb)); m->meshradii = CALLOC(hdr->num_meshes, sizeof(float)); 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]; #if 0 GLfloat *pos = verts[q->meshes[i].first_vertex].position; m->meshcenters[i] = vec3(pos[0], pos[1], pos[2]); #else 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 } 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); colormap(&layer->map, texname, load_as_srgb); } static void model_load_pbr(material_t *mt) { // initialise default colors mt->layer[MATERIAL_CHANNEL_DIFFUSE].map.color = vec4(0.5,0.5,0.5,1.0); 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; material_color.a = ((material_color_hex[3] >= 'a') ? material_color_hex[3] - 'a' + 10 : material_color_hex[3] - '0') / 15.f; #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; irs[i] = renderstate(); } // Opaque pass renderstate_t *opaque_rs = &m->rs[RENDER_PASS_OPAQUE]; { #if 1 // @todo: we should keep blend_enabled=0, however our transparency detection still needs work opaque_rs->blend_enabled = 0; #else opaque_rs->blend_enabled = 1; opaque_rs->blend_src = GL_SRC_ALPHA; opaque_rs->blend_dst = GL_ONE_MINUS_SRC_ALPHA; #endif opaque_rs->cull_face_mode = GL_BACK; opaque_rs->front_face = GL_CW; } // 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; } // 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; } bool model_get_bone_position(model_t m, unsigned joint, mat44 M, vec3 *out) { if(!m.iqm) return false; iqm_t *q = m.iqm; mat34 f; if (!model_get_bone_pose(m, joint, &f)) return false; vec3 pos = vec3(f[3],f[7],f[11]); pos = transform344(M, pos); *out = pos; 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 ); } 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; } 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); #if 0 // ddraw_sphere(s.c, s.r); ddraw_aabb(box.min, box.max); ddraw_position(s.c, 3.0f); #endif if (!frustum_test_aabb(m.frustum_state, box)) { return false; } return true; } static void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_mat) { if(!m.iqm) return; iqm_t *q = m.iqm; handle old_shader = last_shader; shader_bind(shader); int rs_idx = model_getpass(); renderstate_t *rs = &m.rs[rs_idx]; glBindVertexArray( q->vao ); static array(int) required_rs = 0; array_resize(required_rs, q->nummeshes); for(int i = 0; i < q->nummeshes; i++) { struct iqmmesh *im = &q->meshes[i]; required_rs[i] = rs_idx; if (required_rs[i] < RENDER_PASS_OVERRIDES_BEGIN) { if (model_has_transparency_mesh(m, i)) { 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++) { if (!model_is_visible(m, i, model_mat)) continue; array_push(drawcalls, (drawcall_t){i, -1}); } } else { if(pass == -1 || pass == RENDER_PASS_OPAQUE) { for(int i = 0; i < q->nummeshes; i++) { if (!model_is_visible(m, i, model_mat)) continue; // collect opaque drawcalls if (required_rs[i] == RENDER_PASS_OPAQUE) { drawcall_t call; call.mesh = i; call.tex = m.textures[i]; call.distance = -1; 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++) { if (!model_is_visible(m, i, model_mat)) continue; // 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]))); } 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); } 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); } void model_render_instanced_pass(model_t m, mat44 proj, mat44 view, mat44* models, int shader, unsigned count, int pass) { 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]); 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); } void model_render(model_t m, mat44 proj, mat44 view, mat44 model, int shader) { model_render_pass(m, proj, view, model, shader, -1); } static inline void model_init_uniforms(model_t *m) { for (int i=0; iuniforms[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; } void model_shading_custom(model_t *m, int shading, const char *vs, const char *fs, const char *defines) { 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]); } } if (!vs) { vs = vfs_read("shaders/vs_323444143_16_3322_model.glsl"); } if (!fs) { fs = vfs_read("shaders/fs_32_4_model.glsl"); } /* needs to match SHADING_MODE */ const char *shading_defines[] = { "SHADING_NONE", "SHADING_PHONG", "SHADING_VERTEXLIT", "SHADING_PBR", }; ASSERT(shading < countof(shading_defines)); const char *shading_define = shading_defines[shading]; // rebind shader // @fixme: app crashes rn glUseProgram(0); if (m->program) glDeleteProgram(m->program); const char *symbols[] = { "{{include-shadowmap}}", vfs_read("shaders/fs_0_0_shadowmap_lit.glsl") }; // #define RIM int shaderprog = shader(strlerp(1,symbols,vs), strlerp(1,symbols,fs), //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,%s,%s", defines ? defines : "NO_CUSTOM_DEFINES", shading_define, (flags&MODEL_RIMLIGHT)?"RIM":"")); m->program = shaderprog; model_init_uniforms(m); } void model_shading(model_t *m, int shading) { model_shading_custom(m, shading, NULL, NULL, NULL); } void model_skybox(model_t *mdl, skybox_t sky, bool load_sh) { if (load_sh) { unsigned oldprog = last_shader; shader_bind(mdl->program); shader_vec3v("u_coefficients_sh", 9, sky.cubemap.sh); shader_bind(oldprog); } mdl->sky_refl = sky.refl; mdl->sky_env = sky.env; } void model_fog(model_t *mdl, unsigned mode, vec3 color, float start, float end, float density) { unsigned oldprog = last_shader; shader_bind(mdl->program); shader_vec3("u_fog_color", color); shader_float("u_fog_density", density); shader_float("u_fog_start", start); shader_float("u_fog_end", end); shader_int("u_fog_type", mode); shader_bind(oldprog); } // 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)); } 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); 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); } 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; array(float) positions = 0; array_resize(mdl->lod_collapse_map, mdl->num_verts); array_resize(permutation, mdl->num_verts); 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); // 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 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]]; } // 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 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); 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; 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); if(p0==p1 || p0==p2 || p1==p2) continue; 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); // if(q0==q1 || q0==q2 || q1==q2) continue; 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)); 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; 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; int idx = mdl->lod_num_verts; 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; ++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); } 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); FREE(m.meshcenters); FREE(m.meshbounds); FREE(m.meshradii); 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); } static unsigned model_renderpass = RENDER_PASS_OPAQUE; unsigned model_getpass() { return model_renderpass; } unsigned model_setpass(unsigned pass) { ASSERT(pass < NUM_RENDER_PASSES); ASSERT(pass != RENDER_PASS_OVERRIDES_BEGIN && pass != RENDER_PASS_OVERRIDES_END); 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++) { model_setpass(RENDER_PASS_LIGHTMAP); 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?JE=",\ "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?OF;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>GI 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) { array_push(obj->textures, tex); } 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.85f;// 31 deg l.outerCone = 0.9f; // 25 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; } void light_radius(light_t* l, float radius) { l->cached = 0; l->radius = radius; } 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); shader_float(va("u_lights[%d].radius", i), lv[i].radius); 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; } void scene_render(int flags) { camera_t *cam = camera_get_active(); #if 1 mat44 projview; multiply44x2(projview, cam->proj, cam->view); frustum frustum_state = frustum_build(projview); #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 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)); if (obj->skip_draw) continue; int do_retexturing = model->iqm && model->shading != SHADING_PBR && array_count(obj->textures) > 0; if( do_retexturing ) { for(int i = 0; i < model->iqm->nummeshes; ++i) { array_push(obj->old_texture_ids, model->iqm->textures[i]); 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); *model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture = (*array_back(obj->textures)); } } } 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); } model_skybox(model, last_scene->skybox, 0); if (!obj->disable_frustum_check) model_set_frustum(model, frustum_state); else model_clear_frustum(model); 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); } } 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; 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)) { if (model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture) *model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture = obj->old_textures[i]; } } array_resize(obj->old_texture_ids, 0); array_resize(obj->old_textures, 0); } } 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 // backtrace, backtrace_symbols #include // dladdr, Dl_info #elif is(gcc) && !is(ems) && !is(mingw) // maybe is(linux) is enough? #include // backtrace, backtrace_symbols #elif is(win32) // && !defined __TINYC__ #include // windows.h alternative #include #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 #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 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 #include #include #include #include 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 #include #import #import 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;;; … m Select RGB foreground color // 256-color console ESC[38;5;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 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 indices) + 1; j lengths[j] 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<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✱. // - 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. // ## 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 ↦ \ } \ 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