diff --git a/bind/v4k.lua b/bind/v4k.lua index af7a92f..68874df 100644 --- a/bind/v4k.lua +++ b/bind/v4k.lua @@ -1428,6 +1428,15 @@ ffi.cdef([[ //lcpp INF [0000] vec3: macro name but used as C declaration in:STATIC void ddraw_prism(vec3 center, float radius, float height, vec3 normal, int segments); //lcpp INF [0000] vec3: macro name but used as C declaration in: void ddraw_prism(vec3 center, float radius, float height, vec3 normal, int segments); //lcpp INF [0000] vec3: macro name but used as C declaration in: void ddraw_prism(vec3 center, float radius, float height, vec3 normal, int segments); +//lcpp INF [0000] vec3: macro name but used as C declaration in:API int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); +//lcpp INF [0000] vec3: macro name but used as C declaration in:API int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); +//lcpp INF [0000] vec3: macro name but used as C declaration in:API int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); +//lcpp INF [0000] vec3: macro name but used as C declaration in:STATIC int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); +//lcpp INF [0000] vec3: macro name but used as C declaration in:STATIC int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); +//lcpp INF [0000] vec3: macro name but used as C declaration in:STATIC int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); +//lcpp INF [0000] vec3: macro name but used as C declaration in: int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); +//lcpp INF [0000] vec3: macro name but used as C declaration in: int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); +//lcpp INF [0000] vec3: macro name but used as C declaration in: int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); //lcpp INF [0000] vec3: macro name but used as C declaration in:vec3 position, updir, lookdir; //lcpp INF [0000] vec2: macro name but used as C declaration in:vec2 last_look; vec3 last_move; //lcpp INF [0000] vec3: macro name but used as C declaration in:vec2 last_look; vec3 last_move; @@ -1510,6 +1519,13 @@ ffi.cdef([[ //lcpp INF [0000] vec2: macro name but used as C declaration in:vec2 fire; //lcpp INF [0000] vec4: macro name but used as C declaration in:vec4 pos; //lcpp INF [0000] vec2: macro name but used as C declaration in:vec2 sca; +//lcpp INF [0000] vec4: macro name but used as C declaration in:void (*draw_rect_func)(void* userdata, gui_state_t state, const char *skin, vec4 rect); +//lcpp INF [0000] vec4: macro name but used as C declaration in:API void gui_panel(int id, vec4 rect, const char *skin); +//lcpp INF [0000] vec4: macro name but used as C declaration in:STATIC void gui_panel(int id, vec4 rect, const char *skin); +//lcpp INF [0000] vec4: macro name but used as C declaration in: void gui_panel(int id, vec4 rect, const char *skin); +//lcpp INF [0000] vec4: macro name but used as C declaration in:API bool gui_button(int id, vec4 rect, const char *skin); +//lcpp INF [0000] vec4: macro name but used as C declaration in:STATIC bool gui_button(int id, vec4 rect, const char *skin); +//lcpp INF [0000] vec4: macro name but used as C declaration in: bool gui_button(int id, vec4 rect, const char *skin); //lcpp INF [0000] test: macro name but used as C declaration in:API int (test)(const char *file, int line, const char *expr, bool result); //lcpp INF [0000] test: macro name but used as C declaration in:STATIC int (test)(const char *file, int line, const char *expr, bool result); //lcpp INF [0000] test: macro name but used as C declaration in: int (test)(const char *file, int line, const char *expr, bool result); @@ -1605,15 +1621,12 @@ ffi.cdef([[ //lcpp INF [0000] vec3: macro name but used as C declaration in:API vec3 editor_pick(float mouse_x, float mouse_y); //lcpp INF [0000] vec3: macro name but used as C declaration in:STATIC vec3 editor_pick(float mouse_x, float mouse_y); //lcpp INF [0000] vec3: macro name but used as C declaration in: vec3 editor_pick(float mouse_x, float mouse_y); -//lcpp INF [0000] vec3: macro name but used as C declaration in:API int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); -//lcpp INF [0000] vec3: macro name but used as C declaration in:API int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); -//lcpp INF [0000] vec3: macro name but used as C declaration in:API int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); -//lcpp INF [0000] vec3: macro name but used as C declaration in:STATIC int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); -//lcpp INF [0000] vec3: macro name but used as C declaration in:STATIC int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); -//lcpp INF [0000] vec3: macro name but used as C declaration in:STATIC int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); -//lcpp INF [0000] vec3: macro name but used as C declaration in: int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); -//lcpp INF [0000] vec3: macro name but used as C declaration in: int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); -//lcpp INF [0000] vec3: macro name but used as C declaration in: int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); +//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 editor_glyph(int x, int y, unsigned cp); +//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 editor_glyph(int x, int y, unsigned cp); +//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 editor_glyph(int x, int y, unsigned cp); +//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 editor_glyphstr(int x, int y, const char *utf8); +//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 editor_glyphstr(int x, int y, const char *utf8); +//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 editor_glyphstr(int x, int y, const char *utf8); typedef struct FILE FILE; typedef long int ptrdiff_t; typedef long unsigned int size_t; @@ -3032,6 +3045,9 @@ float *pixels; void ddraw_demo(); void ddraw_flush(); void ddraw_flush_projview(mat44 proj, mat44 view); + int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); + bool gizmo_active(); + bool gizmo_hover(); typedef struct camera_t { mat44 view, proj; vec3 position, updir, lookdir; @@ -3041,6 +3057,8 @@ float move_friction, move_damping; float look_friction, look_damping; vec2 last_look; vec3 last_move; bool damping; +bool orthographic; +float distance; } camera_t; camera_t camera(); void camera_teleport(camera_t *cam, vec3 pos); @@ -3154,6 +3172,7 @@ int u_coefficients_sh; char* strjoin(char** list, const char *separator); char * string8(const wchar_t *str); uint32_t* string32( const char *utf8 ); + const char* codepoint_to_utf8(unsigned cp); unsigned intern( const char *string ); const char *quark( unsigned key ); typedef struct quarks_db { @@ -3269,7 +3288,7 @@ unsigned play; bool paused; struct atlas_t *a; } sprite_t; -enum { OBJTYPE_sprite_t = 10 }; typedef struct { unsigned static_assert_on_L__LINE__ : !!(10 <= 255); } static_assert_on_Lconcat(_L,3930)___COUNTER__; typedef struct { unsigned static_assert_on_L__LINE__ : !!(sizeof(sprite_t)); } static_assert_on_Lconcat(_L,3930)___COUNTER__;; +enum { OBJTYPE_sprite_t = 10 }; typedef struct { unsigned static_assert_on_L__LINE__ : !!(10 <= 255); } static_assert_on_Lconcat(_L,3937)___COUNTER__; typedef struct { unsigned static_assert_on_L__LINE__ : !!(sizeof(sprite_t)); } static_assert_on_Lconcat(_L,3937)___COUNTER__;; void sprite_ctor(sprite_t *s); void sprite_dtor(sprite_t *s); void sprite_tick(sprite_t *s); @@ -3278,10 +3297,41 @@ enum { OBJTYPE_sprite_t = 10 }; typedef struct { unsigned static_assert_on sprite_t*sprite_new(const char *ase, int bindings[6]); void sprite_del(sprite_t *s); void sprite_setanim(sprite_t *s, unsigned name); +enum { +GUI_PANEL, +GUI_BUTTON, +}; +typedef struct gui_state_t { +int kind; +union { +struct { +bool held; +bool hover; +}; +}; +} gui_state_t; +typedef struct guiskin_t { +void (*draw_rect_func)(void* userdata, gui_state_t state, const char *skin, vec4 rect); +void (*free)(void* userdata); +void *userdata; +} guiskin_t; + void gui_pushskin(guiskin_t skin); + void* gui_userdata(); + void gui_panel(int id, vec4 rect, const char *skin); + bool gui_button(int id, vec4 rect, const char *skin); + void gui_popskin(); + guiskin_t gui_skinned(const char *inifile, float scale); +typedef struct skinned_t { +atlas_t atlas; +float scale; +char *panel; +char *button; +} skinned_t; void* thread( int (*thread_func)(void* user_data), void* user_data ); void thread_destroy( void *thd ); int argc(); char* argv(int); + void argvadd(const char *arg); int flag(const char *commalist); const char* option(const char *commalist, const char *defaults); int optioni(const char *commalist, int defaults); @@ -3620,7 +3670,7 @@ typedef struct { map base; struct { pair p; void* key; int val; } tmp, *ptr; int void editor_destroy_properties(void *o); void editor_load_on_boot(void); void editor_save_on_quit(void); -enum { +enum EDITOR_MODE { EDITOR_PANEL, EDITOR_WINDOW, EDITOR_WINDOW_NK, @@ -3641,25 +3691,21 @@ EDITOR_WINDOW_NK_SMALL, void editor_spawn1(); void editor_destroy_selected(); void editor_inspect(obj *o); - int editor_send(const char *cmd); - const char* editor_recv(int jobid, double timeout_ss); - void editor_pump(); - void editor_symbol(int x, int y, const char *sym); - void editor_frame( void (*game)(unsigned, float, double) ); - void editor_gizmos(int dim); - int editor_send(const char *command); vec3 editor_pick(float mouse_x, float mouse_y); char* editor_path(const char *path); + void editor_setmouse(int x, int y); + vec2 editor_glyph(int x, int y, unsigned cp); + vec2 editor_glyphstr(int x, int y, const char *utf8); + void editor_gizmos(int dim); + int editor_send(const char *cmd); + const char* editor_recv(int jobid, double timeout_ss); + void editor_pump(); + void editor_frame( void (*game)(unsigned, float, double) ); float* engine_getf(const char *key); int* engine_geti(const char *key); char** engine_gets(const char *key); int engine_send(const char *cmd, const char *optional_value); - int ui_debug(); - char* dialog_load(); - char* dialog_save(); - int gizmo(vec3 *pos, vec3 *rot, vec3 *sca); - bool gizmo_active(); - bool gizmo_hover(); + int ui_engine(); ]]) local _M = {} function _M.vec2(x,y) diff --git a/demos/99-gui.c b/demos/99-gui.c new file mode 100644 index 0000000..f24c87c --- /dev/null +++ b/demos/99-gui.c @@ -0,0 +1,38 @@ +#include "v4k.h" + +int main() { + window_create(75.0, 0); // 75% size, no extra flags + + font_face(FONT_FACE2, "lilita_one_regular.ttf", 32.0f, FONT_EU | FONT_2048); + + gui_pushskin(gui_skinned("golden.ase", 1.0f)); + atlas_t *atlas = &C_CAST(skinned_t*, gui_userdata())->atlas; + + vec4 pos = vec4(100,100,350,300); + + while( window_swap() && !input(KEY_ESC) ) { // game loop + if (ui_panel("Atlas", 0)) { + ui_atlas(atlas); + ui_panel_end(); + } + + if (input_down(MOUSE_R)) { + pos.x = input(MOUSE_X); + pos.y = input(MOUSE_Y); + } + if (input(MOUSE_R)) { + pos.z = input(MOUSE_X); + pos.w = input(MOUSE_Y); + } + + // + if (gui_button(pos, "button")) { + printf("%s\n", "Button pressed!"); + } + + font_color(FONT_COLOR9, WHITE); + font_print(va(FONT_MIDDLE FONT_CENTER FONT_FACE2 FONT_COLOR9 "%s", "Hello")); + } + + gui_popskin(); +} diff --git a/demos/art/gui/golden.ase b/demos/art/gui/golden.ase new file mode 100644 index 0000000..24b464b Binary files /dev/null and b/demos/art/gui/golden.ase differ diff --git a/demos/art/gui/lilita_one_regular.license b/demos/art/gui/lilita_one_regular.license new file mode 100644 index 0000000..d78a38d --- /dev/null +++ b/demos/art/gui/lilita_one_regular.license @@ -0,0 +1,94 @@ +Copyright (c) 2011 Juan Montoreano (juan@remolacha.biz), +with Reserved Font Name Lilita + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/demos/art/gui/lilita_one_regular.ttf b/demos/art/gui/lilita_one_regular.ttf new file mode 100644 index 0000000..091ddf9 Binary files /dev/null and b/demos/art/gui/lilita_one_regular.ttf differ diff --git a/engine/art/shaders/rect_2d.fs b/engine/art/shaders/rect_2d.fs new file mode 100644 index 0000000..bc3562d --- /dev/null +++ b/engine/art/shaders/rect_2d.fs @@ -0,0 +1,17 @@ +uniform sampler2D texture0; /*unit0*/ +uniform float u_inv_gamma; +uniform vec4 u_tint; +uniform int u_has_tex; + +in vec2 uv; +out vec4 fragcolor; + +void main() { + vec4 texel = texture( texture0, uv ); + if (u_has_tex > 0) { + fragcolor = vec4(texel.xyz*u_tint.xyz, texel.a*u_tint.a); + } else { + fragcolor = u_tint; + } + fragcolor.rgb = pow( fragcolor.rgb, vec3( u_inv_gamma ) ); // defaults: 1.0/2.2 gamma +} \ No newline at end of file diff --git a/engine/art/shaders/rect_2d.vs b/engine/art/shaders/rect_2d.vs new file mode 100644 index 0000000..c6765e6 --- /dev/null +++ b/engine/art/shaders/rect_2d.vs @@ -0,0 +1,18 @@ +#version 330 core + +layout (location = 0) in vec2 aPos; +layout (location = 1) in vec2 aTexCoord; +uniform float u_window_width; +uniform float u_window_height; +out vec2 uv; + +void main() { + // float x = float(((uint(gl_VertexID) + 2u) / 3u)%2u); + // float y = float(((uint(gl_VertexID) + 1u) / 3u)%2u); + float px = (aPos.x / u_window_width) * 2.0 - 1.0; + float py = 1.0 - (aPos.y / u_window_height) * 2.0; + gl_Position = vec4(px, py, 0.0, 1.0); + // gl_Position = vec4(-1.0 + x*2.0, 0.0-(-1.0+y*2.0), 0.0, 1.0); // normal(0+),flipped(0-) + // uv = vec2(x, y); // normal(y),flipped(1.0-y) + uv = aTexCoord; // normal(y),flipped(1.0-y) +} diff --git a/engine/editor.c b/engine/editor.c index 80a21ae..866d2b0 100644 --- a/engine/editor.c +++ b/engine/editor.c @@ -1,6 +1,8 @@ #include "v4k.h" #include "split/3rd_icon_mdi.h" +#define SWAP(T,a,b) do { T c = (a); (a) = (b); (b) = c; } while(0) + #if 0 // v4k_pack proposal static __thread char* mpin; static __thread unsigned mpinlen; @@ -127,15 +129,6 @@ right floating icons can be dragged: cam orientation, cam panning, cam zooming, ctrl-z undo, ctrl-shift-z redo #endif -static 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; -} bool is_hovering(vec4 rect, vec2 mouse) { // rect(x,y,x2,y2) if( mouse.x < rect.x ) return 0; if( mouse.x > rect.z ) return 0; @@ -169,8 +162,8 @@ int editor_toolbar(int x, int y, int incw, int inch, const char *sym) { for each_array(codepoints, uint32_t, g) { int selected = oo ? is_hovering(vec4(ix,iy,ix+inc,iy+inc),vec2(ox,oy)) : 0; int hovering = dragging ? 0 : is_hovering(vec4(ix,iy,ix+inc,iy+inc), vec2(mx,my)); - const char *str8 = va("%s%s", selected || hovering ? FONT_COLOR1 : FONT_COLOR2, codepoint_to_utf8_(g)); - editor_symbol(ix + inc/8, iy + inc/3 + 2, str8); + const char *str8 = va("%s%s", selected || hovering ? FONT_COLOR1 : FONT_COLOR2, codepoint_to_utf8(g)); + editor_glyphstr(ix + inc/8, iy + inc/3 + 2, str8); ix += incw; iy += inch; } @@ -191,8 +184,8 @@ int editor_toolbar(int x, int y, int incw, int inch, const char *sym) { int mcx = ((ox - x) / inc) + 1, mcy = ((oy - y) / inc) + 1; // mouse cells editor_toolbar_drag.x = mx - editor_toolbar_drag.z; editor_toolbar_drag.y = my - editor_toolbar_drag.w; - API void editor_cursorpos(int x, int y); - editor_cursorpos(ox, oy); + API void editor_setmouse(int x, int y); + editor_setmouse(ox, oy); editor_toolbar_drag.z = ox; editor_toolbar_drag.w = oy; return incw ? -mcx : -mcy; @@ -258,13 +251,13 @@ int lit_edit(lit *obj) { ICON_MDI_WEATHER_SUNNY // directional ICON_MDI_LIGHTBULB_FLUORESCENT_TUBE_OUTLINE ; - // editor_symbol(obj->pos.x+16,obj->pos.y-32,all_icons); + // editor_glyphstr(obj->pos.x+16,obj->pos.y-32,all_icons); if( editor_selected(obj) ) { obj->pos.x += input(KEY_RIGHT) - input(KEY_LEFT); obj->pos.y += input(KEY_DOWN) - input(KEY_UP); obj->type = (obj->type + !!input_down(KEY_SPACE)) % 4; } - editor_symbol(obj->pos.x,obj->pos.y,lit_icon(obj)); + editor_glyphstr(obj->pos.x,obj->pos.y,lit_icon(obj)); @@ -338,7 +331,7 @@ int kid_edit(kid *obj) { obj->pos.x += input(KEY_RIGHT) - input(KEY_LEFT); obj->pos.y += input(KEY_DOWN) - input(KEY_UP); - editor_symbol(obj->pos.x+16,obj->pos.y-16,ICON_MD_VIDEOGAME_ASSET); + editor_glyphstr(obj->pos.x+16,obj->pos.y-16,ICON_MD_VIDEOGAME_ASSET); } return 1; } @@ -470,15 +463,21 @@ int main(){ // https://github.com/glfw/glfw/pull/990 window_title("Editor " EDITOR_VERSION); - window_create( flag("--transparent") ? 101 : 80, flag("--windowed") ? 0 : WINDOW_BORDERLESS); + window_create( flag("--transparent") ? 101 : 80, WINDOW_MSAA4 | (flag("--windowed") ? 0 : WINDOW_BORDERLESS)); window_icon("scale-ruler-icon.png"); while( window_swap() ) { editor_frame(game); editor_gizmos(2); - int choice1 = editor_toolbar(window_width()-32, ui_has_menubar() ? 34 : 0, 0, 32, ICON_MD_VISIBILITY ICON_MD_360 ICON_MD_ZOOM_OUT_MAP ICON_MD_GRID_ON ); // ICON_MDI_ORBIT ICON_MDI_LOUPE ICON_MDI_GRID ); - //int choice2 = editor_toolbar(window_width()-32*2, ui_has_menubar() ? 34 : 0, -32, 0, ICON_MD_360 ICON_MD_ZOOM_OUT_MAP ICON_MD_GRID_ON ); // ICON_MDI_ORBIT ICON_MDI_LOUPE ICON_MDI_GRID ); + camera_t *cam = camera_get_active(); + + int choice1 = editor_toolbar(window_width()-32, ui_has_menubar() ? 34 : 0, 0, 32, + ICON_MD_VISIBILITY +ICON_MDI_ORBIT// ICON_MD_360 + ICON_MD_ZOOM_IN // ICON_MD_ZOOM_OUT_MAP + ICON_MD_GRID_ON ); // ICON_MDI_LOUPE ICON_MDI_GRID ); + int choice2 = editor_toolbar(window_width()-32*2, ui_has_menubar() ? 34 : 0, -32, 0, ICON_MD_SQUARE_FOOT ); if( choice1 > 0 ) { // clicked[>0] camera_t *cam = camera_get_active(); @@ -487,10 +486,12 @@ int main(){ if( choice1 < 0 ) { // dragged[<0] vec2 mouse_sensitivity = vec2(0.1, -0.1); // sensitivity + polarity vec2 drag = mul2( editor_toolbar_dragged(), mouse_sensitivity ); - camera_t *cam = camera_get_active(); if( choice1 == -1 ) camera_fps(cam, drag.x, drag.y ); if( choice1 == -2 ) camera_orbit(cam, drag.x, drag.y, 0); //len3(cam->position) ); if( choice1 == -3 ) camera_fov(cam, cam->fov += drag.y - drag.x); } + + // font demo + font_print(va(FONT_BOTTOM FONT_RIGHT FONT_H6 "(CAM: %5.2f,%5.2f,%5.2f)", cam->position.x, cam->position.y, cam->position.z)); } } diff --git a/engine/joint/v4k.h b/engine/joint/v4k.h index 7a68c7e..3b4c3fa 100644 --- a/engine/joint/v4k.h +++ b/engine/joint/v4k.h @@ -11,11 +11,11 @@ #ifndef ICON_MD_H #define ICON_MD_H -#define FONT_ICON_FILE_NAME_MD "MaterialIcons-Regular.ttf" +#define ICON_MD_FILENAME "MaterialIcons-Regular.ttf" -#define ICON_MIN_MD 0xe000 -#define ICON_MAX_16_MD 0xf8ff -#define ICON_MAX_MD 0x10fffd +#define ICON_MD_MIN 0xe000 +#define ICON_MD_MAX_16 0xf8ff +#define ICON_MD_MAX 0x10fffd #define ICON_MD_10K "\xee\xa5\x91" // U+e951 #define ICON_MD_10MP "\xee\xa5\x92" // U+e952 #define ICON_MD_11MP "\xee\xa5\x93" // U+e953 @@ -17917,6 +17917,8 @@ API char* strjoin(array(char*) list, const char *separator); API char * string8(const wchar_t *str); /// convert from wchar16(win) to utf8/ascii API array(uint32_t) string32( const char *utf8 ); /// convert from utf8 to utf32 +API const char* codepoint_to_utf8(unsigned cp); + // ----------------------------------------------------------------------------- // ## string interning (quarks) // - rlyeh, public domain. @@ -18108,6 +18110,57 @@ API void sprite_edit(sprite_t *s); API sprite_t*sprite_new(const char *ase, int bindings[6]); API void sprite_del(sprite_t *s); API void sprite_setanim(sprite_t *s, unsigned name); +#line 0 +#line 1 "v4k_gui.h" +// ---------------------------------------------------------------------------- +// game ui + +enum { + GUI_PANEL, + GUI_BUTTON, +}; + +typedef struct gui_state_t { + int kind; + + union { + struct { + bool held; + bool hover; + }; + }; +} gui_state_t; + +typedef struct guiskin_t { + void (*draw_rect_func)(void* userdata, gui_state_t state, const char *skin, vec4 rect); + void (*free)(void* userdata); + void *userdata; +} guiskin_t; + +API void gui_pushskin(guiskin_t skin); +API void* gui_userdata(); +// -- +API void gui_panel(int id, vec4 rect, const char *skin); +API bool gui_button(int id, vec4 rect, const char *skin); +API void gui_popskin(); + +// helpers +#define gui_panel(...) gui_panel(__LINE__, __VA_ARGS__) +#define gui_button(...) gui_button(__LINE__, __VA_ARGS__) + +// default skins +API guiskin_t gui_skinned(const char *inifile, float scale); + +typedef struct skinned_t { + atlas_t atlas; + float scale; + // unsigned framenum; + + //skins + char *panel; + char *button; +} skinned_t; + #line 0 #line 1 "v4k_system.h" @@ -18690,7 +18743,9 @@ API void editor_inspect(obj *o); API vec3 editor_pick(float mouse_x, float mouse_y); API char* editor_path(const char *path); -API void editor_symbol(int x, int y, const char *sym); +API void editor_setmouse(int x, int y); +API vec2 editor_glyph(int x, int y, unsigned cp); +API vec2 editor_glyphstr(int x, int y, const char *utf8); API void editor_gizmos(int dim); // ---------------------------------------------------------------------------------------- @@ -30438,11 +30493,11 @@ int gladLoadGL( GLADloadfunc load) { #ifndef ICON_MD_H #define ICON_MD_H -#define FONT_ICON_FILE_NAME_MD "MaterialIcons-Regular.ttf" +#define ICON_MD_FILENAME "MaterialIcons-Regular.ttf" -#define ICON_MIN_MD 0xe000 -#define ICON_MAX_16_MD 0xf8ff -#define ICON_MAX_MD 0x10fffd +#define ICON_MD_MIN 0xe000 +#define ICON_MD_MAX_16 0xf8ff +#define ICON_MD_MAX 0x10fffd #define ICON_MD_10K "\xee\xa5\x91" // U+e951 #define ICON_MD_10MP "\xee\xa5\x92" // U+e952 #define ICON_MD_11MP "\xee\xa5\x93" // U+e953 @@ -338550,11 +338605,11 @@ rpmalloc_linker_reference(void) { // for use with https://github.com/Templarian/MaterialDesign-Webfont/raw/master/fonts/materialdesignicons-webfont.ttf #pragma once -#define FONT_ICON_FILE_NAME_MDI "materialdesignicons-webfont.ttf" +#define ICON_MDI_FILENAME "materialdesignicons-webfont.ttf" -#define ICON_MIN_MDI 0xF68C -#define ICON_MAX_16_MDI 0xF68C -#define ICON_MAX_MDI 0xF1CC7 +#define ICON_MDI_MIN 0xF68C +#define ICON_MDI_MAX_16 0xF68C +#define ICON_MDI_MAX 0xF1CC7 #define ICON_MDI_AB_TESTING "\xf3\xb0\x87\x89" // U+F01C9 #define ICON_MDI_ABACUS "\xf3\xb1\x9b\xa0" // U+F16E0 #define ICON_MDI_ABJAD_ARABIC "\xf3\xb1\x8c\xa8" // U+F1328 @@ -346562,7 +346617,7 @@ int printi(int i) { return i; } -static const char* codepoint_to_utf8(unsigned c); +static const char* codepoint_to_utf8_(unsigned c); int lt_poll_event(lua_State *L) { // init.lua > core.step() wakes on mousemoved || inputtext int rc = 0; char buf[16]; @@ -346606,7 +346661,7 @@ int lt_poll_event(lua_State *L) { // init.lua > core.step() wakes on mousemoved goto bottom; break; case GLEQ_CODEPOINT_INPUT: - rc += lt_emit_event(L, "textinput", "s", codepoint_to_utf8(e.codepoint)); + rc += lt_emit_event(L, "textinput", "s", codepoint_to_utf8_(e.codepoint)); break; case GLEQ_BUTTON_PRESSED: rc += lt_emit_event(L, "mousepressed", "sddd", lt_button_name(e.mouse.button), lt_mx, lt_my, printi(1 + clicks)); @@ -346758,7 +346813,7 @@ struct RenFont { static struct { int left, top, right, bottom; } lt_clip; -static const char* codepoint_to_utf8(unsigned c) { //< @r-lyeh +static const char* codepoint_to_utf8_(unsigned c) { //< @r-lyeh static char s[4+1]; lt_memset(s, 0, 5); /**/ if (c < 0x80) s[0] = c, s[1] = 0; @@ -346767,7 +346822,7 @@ static const char* codepoint_to_utf8(unsigned c) { //< @r-lyeh 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; } -static const char* utf8_to_codepoint(const char *p, unsigned *dst) { +static const char* utf8_to_codepoint_(const char *p, unsigned *dst) { unsigned res, n; switch (*p & 0xf0) { case 0xf0 : res = *p & 0x07; n = 3; break; @@ -346943,7 +346998,7 @@ int ren_get_font_width(RenFont *font, const char *text) { const char *p = text; unsigned codepoint; while (*p) { - p = utf8_to_codepoint(p, &codepoint); + p = utf8_to_codepoint_(p, &codepoint); GlyphSet *set = get_glyphset(font, codepoint); stbtt_bakedchar *g = &set->glyphs[codepoint & 0xff]; x += g->xadvance; @@ -347048,7 +347103,7 @@ int ren_draw_text(RenFont *font, const char *text, int x, int y, RenColor color) const char *p = text; unsigned codepoint; while (*p) { - p = utf8_to_codepoint(p, &codepoint); + p = utf8_to_codepoint_(p, &codepoint); GlyphSet *set = get_glyphset(font, codepoint); stbtt_bakedchar *g = &set->glyphs[codepoint & 0xff]; rect.x = g->x0; @@ -348635,6 +348690,16 @@ array(uint32_t) string32( const char *utf8 ) { 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 @@ -349157,9 +349222,9 @@ void* ui_handle() { } static void nk_config_custom_fonts() { - #define UI_ICON_MIN ICON_MIN_MD - #define UI_ICON_MED ICON_MAX_16_MD - #define UI_ICON_MAX ICON_MAX_MD + #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 @@ -349190,7 +349255,7 @@ static void nk_config_custom_fonts() { 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_MIN_MDI*/, 0xF1CC7/*ICON_MAX_MDI*/, 0}}, + {"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 ) { @@ -358782,6 +358847,306 @@ vec2 font_rect(const char *str) { } #line 0 +#line 1 "v4k_gui.c" +// ---------------------------------------------------------------------------- +// game ui (utils) + +API vec2i draw_window_ui(); + +API void draw_rect(int rgba, vec2 start, vec2 end ); +API void draw_rect_tex( texture_t texture, int rgba, vec2 start, vec2 end ); +API void draw_rect_sheet( texture_t spritesheet, vec2 tex_start, vec2 tex_end, int rgba, vec2 start, vec2 end ); + +#define draw_rect_borders(color, x, y, w, h, borderWeight) do { \ + int x1 = (x); \ + int y1 = (y); \ + int x2 = (x) + (w) - 1; \ + int y2 = (y) + (h) - 1; \ + draw_rect(color, vec2(x1, y1), vec2(x2, y1 + (borderWeight) - 1)); \ + draw_rect(color, vec2(x1, y1), vec2(x1 + (borderWeight) - 1, y2)); \ + draw_rect(color, vec2(x1, y2 - (borderWeight) + 1), vec2(x2, y2)); \ + draw_rect(color, vec2(x2 - (borderWeight) + 1, y1), vec2(x2, y2)); \ + } while(0) + +// #define lay_draw_rect(rgba, rect) draw_rect(rgba, vec2(rect.e[0], rect.e[1]), vec2(rect.e[0]+rect.e[2], rect.e[1]+rect.e[3])) +// #define lay_draw_rect_borders(rgba, rect, borderWeight) draw_rect_borders(rgba, rect.e[0], rect.e[1], rect.e[2], rect.e[3], borderWeight) +// #define lay_draw_rect_tex(tex, rgba, rect) draw_rect_tex(tex, rgba, vec2(rect.e[0], rect.e[1]), vec2(rect.e[0]+rect.e[2], rect.e[1]+rect.e[3])) +// #define l2m(rect) (vec4(rect.e[0]+rect.e[2], rect.e[1]+rect.e[3])) +#define v42v2(rect) vec2(rect.x,rect.y), vec2(rect.z,rect.w) + + + +vec2i draw_window_ui() { + vec2 dpi = ifdef(osx, window_dpi(), vec2(1,1)); + int w = window_width(); + int h = window_height(); + return vec2i(w/dpi.x, h/dpi.y); +} + +void draw_rect_sheet( texture_t texture, vec2 tex_start, vec2 tex_end, int rgba, vec2 start, vec2 end ) { + float gamma = 1; + static int program = -1, vbo = -1, vao = -1, u_inv_gamma = -1, u_tint = -1, u_has_tex = -1, u_window_width = -1, u_window_height = -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_inv_gamma = glGetUniformLocation(program, "u_inv_gamma"); + 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); + + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + GLenum texture_type = texture.flags & TEXTURE_ARRAY ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D; +// glEnable( GL_BLEND ); + glUseProgram( program ); + glUniform1f( u_inv_gamma, 1.0f / (gamma + !gamma) ); + + 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 ); +// glDisable( GL_BLEND ); +} + +void draw_rect_tex( texture_t texture, int rgba, vec2 start, vec2 end ) { + draw_rect_sheet(texture, vec2(0, 0), vec2(texture.w, texture.h), rgba, start, end); +} + +void draw_rect(int rgba, vec2 start, vec2 end ) { + draw_rect_tex((texture_t){0}, rgba, start, end); +} + +// ---------------------------------------------------------------------------- +// game ui + +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 + +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; +} + +static +gui_state_t *gui_getstate(int id, int kind) { + if (!ctl_states) map_init(ctl_states, less_int, hash_int); + static gui_state_t st={0}; + st.kind=kind; + return map_find_or_add(ctl_states, id, st); +} + +bool (gui_button)(int id, vec4 r, const char *skin) { + gui_state_t *entry = gui_getstate(id, GUI_BUTTON); + bool was_clicked=0; + entry->hover = false; + + if (input(MOUSE_X) > r.x && input(MOUSE_X) < r.z && input(MOUSE_Y) > r.y && input(MOUSE_Y) < r.w) { + if (input_up(MOUSE_L) && entry->held) { + was_clicked=1; + } + + entry->held = input_held(MOUSE_L); + entry->hover = true; + } + else if (input_up(MOUSE_L) && entry->held) { + entry->held = false; + } + + if (last_skin->draw_rect_func) last_skin->draw_rect_func(last_skin->userdata, *entry, skin, r); + else { + draw_rect(entry->held ? 0x111111FF : entry->hover ? 0xEEEEEEFF : 0xFFFFFFFF, v42v2(r)); + } + + return was_clicked; +} + +void (gui_panel)(int id, vec4 r, const char *skin) { + gui_state_t *entry = gui_getstate(id, GUI_PANEL); + if (last_skin->draw_rect_func) last_skin->draw_rect_func(last_skin->userdata, *entry, skin?skin:"panel", r); + else { + draw_rect(0xFFFFFFFF, v42v2(r)); + } +} + +/* 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, const char *fallback) { + #define atlas_loop(n)\ + for (int i = 0; i < array_count(a->slices); i++)\ + if (!strcmp(quark_string(&a->db, a->slices[i].name), n))\ + return &a->slice_frames[a->slices[i].frames[0]]; + atlas_loop(name); + atlas_loop(fallback); + return NULL; + #undef atlas_loop +} + +static +void skinned_draw_missing_rect(vec4 r) { + draw_rect_tex(texture_checker(), 0xFFFFFFFF, v42v2(r)); +} + +static +void skinned_draw_sprite(float scale, atlas_t *a, atlas_slice_frame_t *f, vec4 r) { + if (!f->has_9slice) { + draw_rect_sheet(a->tex, v42v2(f->bounds), 0xFFFFFFFF, v42v2(r)); + return; + } + + vec4 outer = f->bounds; + 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}; + + draw_rect_sheet(a->tex, v42v2(center_slice), 0xFFFFFFFF, v42v2(center)); + draw_rect_sheet(a->tex, v42v2(top_left_slice), 0xFFFFFFFF, v42v2(top_left)); + draw_rect_sheet(a->tex, v42v2(top_right_slice), 0xFFFFFFFF, v42v2(top_right)); + draw_rect_sheet(a->tex, v42v2(bottom_left_slice), 0xFFFFFFFF, v42v2(bottom_left)); + draw_rect_sheet(a->tex, v42v2(bottom_right_slice), 0xFFFFFFFF, v42v2(bottom_right)); + draw_rect_sheet(a->tex, v42v2(top_middle_slice), 0xFFFFFFFF, v42v2(top)); + draw_rect_sheet(a->tex, v42v2(bottom_middle_slice), 0xFFFFFFFF, v42v2(bottom)); + draw_rect_sheet(a->tex, v42v2(middle_left_slice), 0xFFFFFFFF, v42v2(left)); + draw_rect_sheet(a->tex, v42v2(middle_right_slice), 0xFFFFFFFF, v42v2(right)); +} + +static +void skinned_draw_rect(void* userdata, gui_state_t state, const char *skin, vec4 r) { + skinned_t *a = C_CAST(skinned_t*, userdata); + + switch (state.kind) { + case GUI_BUTTON: { + char *btn = va("%s%s", skin?skin:a->button, state.held?"_press":state.hover?"_hover":""); + atlas_slice_frame_t *f = skinned_getsliceframe(&a->atlas, btn, skin?skin:a->button); + if (!f) skinned_draw_missing_rect(r); + else skinned_draw_sprite(a->scale, &a->atlas, f, r); + } break; + } +} + +static +void skinned_preset_skins(skinned_t *s) { + s->panel = "panel"; + s->button = "button"; +} + +guiskin_t gui_skinned(const char *inifile, float scale) { + skinned_t *a = REALLOC(0, sizeof(skinned_t)); + a->atlas = atlas_create(inifile, 0); + a->scale = scale?scale:1.0f; + guiskin_t skin={0}; + skin.userdata = a; + skin.draw_rect_func = skinned_draw_rect; + skin.free = skinned_free; + skinned_preset_skins(a); + return skin; +} +#line 0 + #line 1 "v4k_input.c" // input framework // - rlyeh, public domain @@ -376845,22 +377210,31 @@ void editor_pump() { // ---------------------------------------------------------------------------------------- -API void editor_cursorpos(int x, int y); -void editor_cursorpos(int x, int y) { +void editor_setmouse(int x, int y) { glfwSetCursorPos( window_handle(), x, y ); } -void editor_symbol(int x, int y, const char *sym) { +vec2 editor_glyph(int x, int y, unsigned cp) { // style: atlas size, unicode ranges and 6 font faces max do_once font_face(FONT_FACE2, "MaterialIconsSharp-Regular.otf", 24.f, FONT_EM|FONT_2048); + do_once font_face(FONT_FACE3, "materialdesignicons-webfont.ttf", 24.f, FONT_EM|FONT_2048); // {0xF68C /*ICON_MDI_MIN*/, 0xF1CC7/*ICON_MDI_MAX*/, 0}}, // style: 10 colors max do_once font_color(FONT_COLOR1, WHITE); do_once font_color(FONT_COLOR2, RGBX(0xE8F1FF,128)); // GRAY); do_once font_color(FONT_COLOR3, YELLOW); do_once font_color(FONT_COLOR4, ORANGE); do_once font_color(FONT_COLOR5, CYAN); + const char *sym = codepoint_to_utf8(cp); font_goto(x,y); - font_print(va(FONT_FACE2 /*FONT_WHITE*/ FONT_H1 "%s", sym)); + return font_print(va("%s" FONT_H1 "%s", cp >= ICON_MDI_MIN ? FONT_FACE3 : FONT_FACE2, sym)); +} + +vec2 editor_glyphstr(int x, int y, const char *utf8) { + vec2 dim = {x,y}; + array(unsigned) codepoints = string32(utf8); + for( int i = 0, end = array_count(codepoints); i < end; ++i) + add2(dim, editor_glyph(dim.x,dim.y,codepoints[i])); + return dim; } void editor_frame( void (*game)(unsigned, float, double) ) { diff --git a/engine/split/3rd_aseprite.h b/engine/split/3rd_aseprite.h new file mode 100644 index 0000000..f892b1e --- /dev/null +++ b/engine/split/3rd_aseprite.h @@ -0,0 +1,1348 @@ +#define ASE_TRIMS 1 //< @r-lyeh + +/* + ------------------------------------------------------------------------------ + Licensing information can be found at the end of the file. + ------------------------------------------------------------------------------ + + cute_aseprite.h - v1.02 + + To create implementation (the function definitions) + #define CUTE_ASEPRITE_IMPLEMENTATION + in *one* C/CPP file (translation unit) that includes this file + + + SUMMARY + + cute_asesprite.h is a single-file header that implements some functions to + parse .ase/.aseprite files. The entire file is parsed all at once and some + structs are filled out then handed back to you. + + + LIMITATIONS + + Only the "normal" blend mode for layers is supported. As a workaround try + using the "merge down" function in Aseprite to create a normal layer. + Supporting all blend modes would take too much code to be worth it. + + Does not support very old versions of Aseprite (with old palette chunks + 0x0004 or 0x0011). Also does not support deprecated mask chunk. + + sRGB and ICC profiles are parsed but completely ignored when blending + frames together. If you want these to be used when composing frames you + have to do this yourself. + + + SPECIAL THANKS + + Special thanks to Noel Berry for the blend code in his reference C++ + implementation (https://github.com/NoelFB/blah). + + Special thanks to Richard Mitton for the initial implementation of the + zlib inflater. + + + Revision history: + 1.00 (08/25/2020) initial release + 1.01 (08/31/2020) fixed memleaks, tag parsing bug (crash), blend bugs + 1.02 (02/05/2022) fixed icc profile parse bug, support transparent pal- + ette index, can parse 1.3 files (no tileset support) +*/ + +/* + DOCUMENTATION + + Simply load an .ase or .aseprite file from disk or from memory like so. + + ase_t* ase = cute_aseprite_load_from_file("data/player.aseprite", NULL); + + + Then access the fields directly, assuming you have your own `Animation` type. + + int w = ase->w; + int h = ase->h; + Animation anim = { 0 }; // Your custom animation data type. + + for (int i = 0; i < ase->frame_count; ++i) { + ase_frame_t* frame = ase->frames + i; + anim.add_frame(frame->duration_milliseconds, frame->pixels); + } + + + Then free it up when done. + + cute_aseprite_free(ase); + + + DATA STRUCTURES + + Aseprite files have frames, layers, and cels. A single frame is one frame of an + animation, formed by blending all the cels of an animation together. There is + one cel per layer per frame. Each cel contains its own pixel data. + + The frame's pixels are automatically assumed to have been blended by the `normal` + blend mode. A warning is emit if any other blend mode is encountered. Feel free + to update the pixels of each frame with your own implementation of blending + functions. The frame's pixels are merely provided like this for convenience. + + + BUGS AND CRASHES + + This header is quite new and it takes time to test all the parse paths. Don't be + shy about opening a GitHub issue if there's a crash! It's quite easy to update + the parser as long as you upload your .ase file that shows the bug. + + https://github.com/RandyGaul/cute_headers/issues +*/ + +#ifndef CUTE_ASEPRITE_H +#define CUTE_ASEPRITE_H + +typedef struct ase_t ase_t; + +ase_t* cute_aseprite_load_from_file(const char* path, void* mem_ctx); +ase_t* cute_aseprite_load_from_memory(const void* memory, int size, void* mem_ctx); +void cute_aseprite_free(ase_t* aseprite); + +#define CUTE_ASEPRITE_MAX_LAYERS (64) +#define CUTE_ASEPRITE_MAX_SLICES (128) +#define CUTE_ASEPRITE_MAX_PALETTE_ENTRIES (1024) +#define CUTE_ASEPRITE_MAX_TAGS (256) + +#include + +typedef struct ase_color_t ase_color_t; +typedef struct ase_frame_t ase_frame_t; +typedef struct ase_layer_t ase_layer_t; +typedef struct ase_cel_t ase_cel_t; +typedef struct ase_tag_t ase_tag_t; +typedef struct ase_slice_t ase_slice_t; +typedef struct ase_palette_entry_t ase_palette_entry_t; +typedef struct ase_palette_t ase_palette_t; +typedef struct ase_udata_t ase_udata_t; +typedef struct ase_cel_extra_chunk_t ase_cel_extra_chunk_t; +typedef struct ase_color_profile_t ase_color_profile_t; +typedef struct ase_fixed_t ase_fixed_t; +typedef struct ase_cel_extra_chunk_t ase_cel_extra_chunk_t; + +struct ase_color_t +{ + uint8_t r, g, b, a; +}; + +struct ase_fixed_t +{ + uint16_t a; + uint16_t b; +}; + +struct ase_udata_t +{ + int has_color; + ase_color_t color; + int has_text; + const char* text; +}; + +typedef enum ase_layer_flags_t +{ + ASE_LAYER_FLAGS_VISIBLE = 0x01, + ASE_LAYER_FLAGS_EDITABLE = 0x02, + ASE_LAYER_FLAGS_LOCK_MOVEMENT = 0x04, + ASE_LAYER_FLAGS_BACKGROUND = 0x08, + ASE_LAYER_FLAGS_PREFER_LINKED_CELS = 0x10, + ASE_LAYER_FLAGS_COLLAPSED = 0x20, + ASE_LAYER_FLAGS_REFERENCE = 0x40, +} ase_layer_flags_t; + +typedef enum ase_layer_type_t +{ + ASE_LAYER_TYPE_NORMAL, + ASE_LAYER_TYPE_GROUP, +} ase_layer_type_t; + +struct ase_layer_t +{ + ase_layer_flags_t flags; + ase_layer_type_t type; + const char* name; + ase_layer_t* parent; + float opacity; + ase_udata_t udata; +}; + +struct ase_cel_extra_chunk_t +{ + int precise_bounds_are_set; + ase_fixed_t precise_x; + ase_fixed_t precise_y; + ase_fixed_t w, h; +}; + +struct ase_cel_t +{ + ase_layer_t* layer; + void* pixels; + int w, h; + int x, y; + float opacity; + int is_linked; + uint16_t linked_frame_index; + int has_extra; + ase_cel_extra_chunk_t extra; + ase_udata_t udata; +}; + +struct ase_frame_t +{ + ase_t* ase; + int duration_milliseconds; + ase_color_t* pixels; + int cel_count; + ase_cel_t cels[CUTE_ASEPRITE_MAX_LAYERS]; +}; + +typedef enum ase_animation_direction_t +{ + ASE_ANIMATION_DIRECTION_FORWARDS, + ASE_ANIMATION_DIRECTION_BACKWARDS, + ASE_ANIMATION_DIRECTION_PINGPONG, +} ase_animation_direction_t; + +struct ase_tag_t +{ + int from_frame; + int to_frame; + ase_animation_direction_t loop_animation_direction; + uint8_t r, g, b; + const char* name; + ase_udata_t udata; +}; + +struct ase_slice_t +{ + const char* name; + int frame_number; + int origin_x; + int origin_y; + int w, h; + + int has_center_as_9_slice; + int center_x; + int center_y; + int center_w; + int center_h; + + int has_pivot; + int pivot_x; + int pivot_y; + + ase_udata_t udata; +}; + +struct ase_palette_entry_t +{ + ase_color_t color; + const char* color_name; +}; + +struct ase_palette_t +{ + int entry_count; + ase_palette_entry_t entries[CUTE_ASEPRITE_MAX_PALETTE_ENTRIES]; +}; + +typedef enum ase_color_profile_type_t +{ + ASE_COLOR_PROFILE_TYPE_NONE, + ASE_COLOR_PROFILE_TYPE_SRGB, + ASE_COLOR_PROFILE_TYPE_EMBEDDED_ICC, +} ase_color_profile_type_t; + +struct ase_color_profile_t +{ + ase_color_profile_type_t type; + int use_fixed_gamma; + ase_fixed_t gamma; + uint32_t icc_profile_data_length; + void* icc_profile_data; +}; + +typedef enum ase_mode_t +{ + ASE_MODE_RGBA, + ASE_MODE_GRAYSCALE, + ASE_MODE_INDEXED +} ase_mode_t; + +struct ase_t +{ + ase_mode_t mode; + int w, h; + int transparent_palette_entry_index; + int number_of_colors; + int pixel_w; + int pixel_h; + int grid_x; + int grid_y; + int grid_w; + int grid_h; + int has_color_profile; + ase_color_profile_t color_profile; + ase_palette_t palette; + + int layer_count; + ase_layer_t layers[CUTE_ASEPRITE_MAX_LAYERS]; + + int frame_count; + ase_frame_t* frames; + + int tag_count; + ase_tag_t tags[CUTE_ASEPRITE_MAX_TAGS]; + + int slice_count; + ase_slice_t slices[CUTE_ASEPRITE_MAX_SLICES]; + + void* mem_ctx; +}; + +#endif // CUTE_ASEPRITE_H + +#ifdef CUTE_ASEPRITE_IMPLEMENTATION +#ifndef CUTE_ASEPRITE_IMPLEMENTATION_ONCE +#define CUTE_ASEPRITE_IMPLEMENTATION_ONCE + +#ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS +#endif + +#ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE +#endif + +#if !defined(CUTE_ASEPRITE_ALLOC) + #include + #define CUTE_ASEPRITE_ALLOC(size, ctx) malloc(size) + #define CUTE_ASEPRITE_FREE(mem, ctx) free(mem) +#endif + +#if !defined(CUTE_ASEPRITE_UNUSED) + #if defined(_MSC_VER) + #define CUTE_ASEPRITE_UNUSED(x) (void)x + #else + #define CUTE_ASEPRITE_UNUSED(x) (void)(sizeof(x)) + #endif +#endif + +#if !defined(CUTE_ASEPRITE_MEMCPY) + #include // memcpy + #define CUTE_ASEPRITE_MEMCPY memcpy +#endif + +#if !defined(CUTE_ASEPRITE_MEMSET) + #include // memset + #define CUTE_ASEPRITE_MEMSET memset +#endif + +#if !defined(CUTE_ASEPRITE_ASSERT) + #include + #define CUTE_ASEPRITE_ASSERT assert +#endif + +#if !defined(CUTE_ASEPRITE_SEEK_SET) + #include // SEEK_SET + #define CUTE_ASEPRITE_SEEK_SET SEEK_SET +#endif + +#if !defined(CUTE_ASEPRITE_SEEK_END) + #include // SEEK_END + #define CUTE_ASEPRITE_SEEK_END SEEK_END +#endif + +#if !defined(CUTE_ASEPRITE_FILE) + #include // FILE + #define CUTE_ASEPRITE_FILE FILE +#endif + +#if !defined(CUTE_ASEPRITE_FOPEN) + #include // fopen + #define CUTE_ASEPRITE_FOPEN fopen +#endif + +#if !defined(CUTE_ASEPRITE_FSEEK) + #include // fseek + #define CUTE_ASEPRITE_FSEEK fseek +#endif + +#if !defined(CUTE_ASEPRITE_FREAD) + #include // fread + #define CUTE_ASEPRITE_FREAD fread +#endif + +#if !defined(CUTE_ASEPRITE_FTELL) + #include // ftell + #define CUTE_ASEPRITE_FTELL ftell +#endif + +#if !defined(CUTE_ASEPRITE_FCLOSE) + #include // fclose + #define CUTE_ASEPRITE_FCLOSE fclose +#endif + +static const char* s_error_file = NULL; // The filepath of the file being parsed. NULL if from memory. +static const char* s_error_reason; // Used to capture errors during DEFLATE parsing. + +#if !defined(CUTE_ASEPRITE_WARNING) + #define CUTE_ASEPRITE_WARNING(msg) cute_aseprite_warning(msg, __LINE__) + + static int s_error_cline; // The line in cute_aseprite.h where the error was triggered. + void cute_aseprite_warning(const char* warning, int line) + { + s_error_cline = line; + const char *error_file = s_error_file ? s_error_file : "MEMORY"; + printf("WARNING (cute_aseprite.h:%i): %s (%s)\n", s_error_cline, warning, error_file); + } +#endif + +#define CUTE_ASEPRITE_FAIL() do { goto ase_err; } while (0) +#define CUTE_ASEPRITE_CHECK(X, Y) do { if (!(X)) { s_error_reason = Y; CUTE_ASEPRITE_FAIL(); } } while (0) +#define CUTE_ASEPRITE_CALL(X) do { if (!(X)) goto ase_err; } while (0) +#define CUTE_ASEPRITE_DEFLATE_MAX_BITLEN 15 + +// DEFLATE tables from RFC 1951 +static uint8_t s_fixed_table[288 + 32] = { + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, +}; // 3.2.6 +static uint8_t s_permutation_order[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; // 3.2.7 +static uint8_t s_len_extra_bits[29 + 2] = { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0, 0,0 }; // 3.2.5 +static uint32_t s_len_base[29 + 2] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 0,0 }; // 3.2.5 +static uint8_t s_dist_extra_bits[30 + 2] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13, 0,0 }; // 3.2.5 +static uint32_t s_dist_base[30 + 2] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 0,0 }; // 3.2.5 + +typedef struct deflate_t +{ + uint64_t bits; + int count; + uint32_t* words; + int word_count; + int word_index; + int bits_left; + + int final_word_available; + uint32_t final_word; + + char* out; + char* out_end; + char* begin; + + uint32_t lit[288]; + uint32_t dst[32]; + uint32_t len[19]; + uint32_t nlit; + uint32_t ndst; + uint32_t nlen; +} deflate_t; + +static int s_would_overflow(deflate_t* s, int num_bits) +{ + return (s->bits_left + s->count) - num_bits < 0; +} + +static char* s_ptr(deflate_t* s) +{ + CUTE_ASEPRITE_ASSERT(!(s->bits_left & 7)); + return (char*)(s->words + s->word_index) - (s->count / 8); +} + +static uint64_t s_peak_bits(deflate_t* s, int num_bits_to_read) +{ + if (s->count < num_bits_to_read) + { + if (s->word_index < s->word_count) + { + uint32_t word = s->words[s->word_index++]; + s->bits |= (uint64_t)word << s->count; + s->count += 32; + CUTE_ASEPRITE_ASSERT(s->word_index <= s->word_count); + } + + else if (s->final_word_available) + { + uint32_t word = s->final_word; + s->bits |= (uint64_t)word << s->count; + s->count += s->bits_left; + s->final_word_available = 0; + } + } + + return s->bits; +} + +static uint32_t s_consume_bits(deflate_t* s, int num_bits_to_read) +{ + CUTE_ASEPRITE_ASSERT(s->count >= num_bits_to_read); + uint32_t bits = (uint32_t)(s->bits & (((uint64_t)1 << num_bits_to_read) - 1)); + s->bits >>= num_bits_to_read; + s->count -= num_bits_to_read; + s->bits_left -= num_bits_to_read; + return bits; +} + +static uint32_t s_read_bits(deflate_t* s, int num_bits_to_read) +{ + CUTE_ASEPRITE_ASSERT(num_bits_to_read <= 32); + CUTE_ASEPRITE_ASSERT(num_bits_to_read >= 0); + CUTE_ASEPRITE_ASSERT(s->bits_left > 0); + CUTE_ASEPRITE_ASSERT(s->count <= 64); + CUTE_ASEPRITE_ASSERT(!s_would_overflow(s, num_bits_to_read)); + s_peak_bits(s, num_bits_to_read); + uint32_t bits = s_consume_bits(s, num_bits_to_read); + return bits; +} + +static uint32_t s_rev16(uint32_t a) +{ + a = ((a & 0xAAAA) >> 1) | ((a & 0x5555) << 1); + a = ((a & 0xCCCC) >> 2) | ((a & 0x3333) << 2); + a = ((a & 0xF0F0) >> 4) | ((a & 0x0F0F) << 4); + a = ((a & 0xFF00) >> 8) | ((a & 0x00FF) << 8); + return a; +} + +// RFC 1951 section 3.2.2 +static uint32_t s_build(deflate_t* s, uint32_t* tree, uint8_t* lens, int sym_count) +{ + int n, codes[16], first[16], counts[16] = { 0 }; + CUTE_ASEPRITE_UNUSED(s); + + // Frequency count + for (n = 0; n < sym_count; n++) counts[lens[n]]++; + + // Distribute codes + counts[0] = codes[0] = first[0] = 0; + for (n = 1; n <= 15; ++n) + { + codes[n] = (codes[n - 1] + counts[n - 1]) << 1; + first[n] = first[n - 1] + counts[n - 1]; + } + + for (uint32_t i = 0; i < (uint32_t)sym_count; ++i) + { + uint8_t len = lens[i]; + + if (len != 0) + { + CUTE_ASEPRITE_ASSERT(len < 16); + uint32_t code = (uint32_t)codes[len]++; + uint32_t slot = (uint32_t)first[len]++; + tree[slot] = (code << (32 - (uint32_t)len)) | (i << 4) | len; + } + } + + return (uint32_t)first[15]; +} + +static int s_stored(deflate_t* s) +{ + char* p; + + // 3.2.3 + // skip any remaining bits in current partially processed byte + s_read_bits(s, s->count & 7); + + // 3.2.4 + // read LEN and NLEN, should complement each other + uint16_t LEN = (uint16_t)s_read_bits(s, 16); + uint16_t NLEN = (uint16_t)s_read_bits(s, 16); + uint16_t TILDE_NLEN = ~NLEN; + CUTE_ASEPRITE_CHECK(LEN == TILDE_NLEN, "Failed to find LEN and NLEN as complements within stored (uncompressed) stream."); + CUTE_ASEPRITE_CHECK(s->bits_left / 8 <= (int)LEN, "Stored block extends beyond end of input stream."); + p = s_ptr(s); + CUTE_ASEPRITE_MEMCPY(s->out, p, LEN); + s->out += LEN; + return 1; + +ase_err: + return 0; +} + +// 3.2.6 +static int s_fixed(deflate_t* s) +{ + s->nlit = s_build(s, s->lit, s_fixed_table, 288); + s->ndst = s_build(0, s->dst, s_fixed_table + 288, 32); + return 1; +} + +static int s_decode(deflate_t* s, uint32_t* tree, int hi) +{ + uint64_t bits = s_peak_bits(s, 16); + uint32_t search = (s_rev16((uint32_t)bits) << 16) | 0xFFFF; + int lo = 0; + while (lo < hi) + { + int guess = (lo + hi) >> 1; + if (search < tree[guess]) hi = guess; + else lo = guess + 1; + } + + uint32_t key = tree[lo - 1]; + uint32_t len = (32 - (key & 0xF)); + CUTE_ASEPRITE_ASSERT((search >> len) == (key >> len)); + + s_consume_bits(s, key & 0xF); + return (key >> 4) & 0xFFF; +} + +// 3.2.7 +static int s_dynamic(deflate_t* s) +{ + uint8_t lenlens[19] = { 0 }; + + uint32_t nlit = 257 + s_read_bits(s, 5); + uint32_t ndst = 1 + s_read_bits(s, 5); + uint32_t nlen = 4 + s_read_bits(s, 4); + + for (uint32_t i = 0 ; i < nlen; ++i) + lenlens[s_permutation_order[i]] = (uint8_t)s_read_bits(s, 3); + + // Build the tree for decoding code lengths + s->nlen = s_build(0, s->len, lenlens, 19); + uint8_t lens[288 + 32]; + + for (uint32_t n = 0; n < nlit + ndst;) + { + int sym = s_decode(s, s->len, (int)s->nlen); + switch (sym) + { + case 16: for (uint32_t i = 3 + s_read_bits(s, 2); i; --i, ++n) lens[n] = lens[n - 1]; break; + case 17: for (uint32_t i = 3 + s_read_bits(s, 3); i; --i, ++n) lens[n] = 0; break; + case 18: for (uint32_t i = 11 + s_read_bits(s, 7); i; --i, ++n) lens[n] = 0; break; + default: lens[n++] = (uint8_t)sym; break; + } + } + + s->nlit = s_build(s, s->lit, lens, (int)nlit); + s->ndst = s_build(0, s->dst, lens + nlit, (int)ndst); + return 1; +} + +// 3.2.3 +static int s_block(deflate_t* s) +{ + while (1) + { + int symbol = s_decode(s, s->lit, (int)s->nlit); + + if (symbol < 256) + { + CUTE_ASEPRITE_CHECK(s->out + 1 <= s->out_end, "Attempted to overwrite out buffer while outputting a symbol."); + *s->out = (char)symbol; + s->out += 1; + } + + else if (symbol > 256) + { + symbol -= 257; + uint32_t length = s_read_bits(s, (int)(s_len_extra_bits[symbol])) + s_len_base[symbol]; + int distance_symbol = s_decode(s, s->dst, (int)s->ndst); + uint32_t backwards_distance = s_read_bits(s, s_dist_extra_bits[distance_symbol]) + s_dist_base[distance_symbol]; + CUTE_ASEPRITE_CHECK(s->out - backwards_distance >= s->begin, "Attempted to write before out buffer (invalid backwards distance)."); + CUTE_ASEPRITE_CHECK(s->out + length <= s->out_end, "Attempted to overwrite out buffer while outputting a string."); + char* src = s->out - backwards_distance; + char* dst = s->out; + s->out += length; + + switch (backwards_distance) + { + case 1: // very common in images + CUTE_ASEPRITE_MEMSET(dst, *src, (size_t)length); + break; + default: while (length--) *dst++ = *src++; + } + } + + else break; + } + + return 1; + +ase_err: + return 0; +} + +// 3.2.3 +static int s_inflate(const void* in, int in_bytes, void* out, int out_bytes, void* mem_ctx) +{ + CUTE_ASEPRITE_UNUSED(mem_ctx); + deflate_t* s = (deflate_t*)CUTE_ASEPRITE_ALLOC(sizeof(deflate_t), mem_ctx); + s->bits = 0; + s->count = 0; + s->word_index = 0; + s->bits_left = in_bytes * 8; + + // s->words is the in-pointer rounded up to a multiple of 4 + int first_bytes = (int)((((size_t)in + 3) & (size_t)(~3)) - (size_t)in); + s->words = (uint32_t*)((char*)in + first_bytes); + s->word_count = (in_bytes - first_bytes) / 4; + int last_bytes = ((in_bytes - first_bytes) & 3); + + for (int i = 0; i < first_bytes; ++i) + s->bits |= (uint64_t)(((uint8_t*)in)[i]) << (i * 8); + + s->final_word_available = last_bytes ? 1 : 0; + s->final_word = 0; + for(int i = 0; i < last_bytes; i++) + s->final_word |= ((uint8_t*)in)[in_bytes - last_bytes + i] << (i * 8); + + s->count = first_bytes * 8; + + s->out = (char*)out; + s->out_end = s->out + out_bytes; + s->begin = (char*)out; + + int count = 0; + uint32_t bfinal; + do + { + bfinal = s_read_bits(s, 1); + uint32_t btype = s_read_bits(s, 2); + + switch (btype) + { + case 0: CUTE_ASEPRITE_CALL(s_stored(s)); break; + case 1: s_fixed(s); CUTE_ASEPRITE_CALL(s_block(s)); break; + case 2: s_dynamic(s); CUTE_ASEPRITE_CALL(s_block(s)); break; + case 3: CUTE_ASEPRITE_CHECK(0, "Detected unknown block type within input stream."); + } + + ++count; + } + while (!bfinal); + + CUTE_ASEPRITE_FREE(s, mem_ctx); + return 1; + +ase_err: + CUTE_ASEPRITE_FREE(s, mem_ctx); + return 0; +} + +typedef struct ase_state_t +{ + uint8_t* in; + uint8_t* end; + void* mem_ctx; +} ase_state_t; + +static uint8_t s_read_uint8(ase_state_t* s) +{ + CUTE_ASEPRITE_ASSERT(s->in <= s->end + sizeof(uint8_t)); + uint8_t** p = &s->in; + uint8_t value = **p; + ++(*p); + return value; +} + +static uint16_t s_read_uint16(ase_state_t* s) +{ + CUTE_ASEPRITE_ASSERT(s->in <= s->end + sizeof(uint16_t)); + uint8_t** p = &s->in; + uint16_t value; + value = (*p)[0]; + value |= (((uint16_t)((*p)[1])) << 8); + *p += 2; + return value; +} + +static ase_fixed_t s_read_fixed(ase_state_t* s) +{ + ase_fixed_t value; + value.a = s_read_uint16(s); + value.b = s_read_uint16(s); + return value; +} + +static uint32_t s_read_uint32(ase_state_t* s) +{ + CUTE_ASEPRITE_ASSERT(s->in <= s->end + sizeof(uint32_t)); + uint8_t** p = &s->in; + uint32_t value; + value = (*p)[0]; + value |= (((uint32_t)((*p)[1])) << 8); + value |= (((uint32_t)((*p)[2])) << 16); + value |= (((uint32_t)((*p)[3])) << 24); + *p += 4; + return value; +} + +#ifdef CUTE_ASPRITE_S_READ_UINT64 +// s_read_uint64() is not currently used. +static uint64_t s_read_uint64(ase_state_t* s) +{ + CUTE_ASEPRITE_ASSERT(s->in <= s->end + sizeof(uint64_t)); + uint8_t** p = &s->in; + uint64_t value; + value = (*p)[0]; + value |= (((uint64_t)((*p)[1])) << 8 ); + value |= (((uint64_t)((*p)[2])) << 16); + value |= (((uint64_t)((*p)[3])) << 24); + value |= (((uint64_t)((*p)[4])) << 32); + value |= (((uint64_t)((*p)[5])) << 40); + value |= (((uint64_t)((*p)[6])) << 48); + value |= (((uint64_t)((*p)[7])) << 56); + *p += 8; + return value; +} +#endif + +#define s_read_int16(s) (int16_t)s_read_uint16(s) +#define s_read_int32(s) (int32_t)s_read_uint32(s) + +#ifdef CUTE_ASPRITE_S_READ_BYTES +// s_read_bytes() is not currently used. +static void s_read_bytes(ase_state_t* s, uint8_t* bytes, int num_bytes) +{ + for (int i = 0; i < num_bytes; ++i) { + bytes[i] = s_read_uint8(s); + } +} +#endif + +static const char* s_read_string(ase_state_t* s) +{ + int len = (int)s_read_uint16(s); + char* bytes = (char*)CUTE_ASEPRITE_ALLOC(len + 1, s->mem_ctx); + for (int i = 0; i < len; ++i) { + bytes[i] = (char)s_read_uint8(s); + } + bytes[len] = 0; + return bytes; +} + +static void s_skip(ase_state_t* ase, int num_bytes) +{ + CUTE_ASEPRITE_ASSERT(ase->in <= ase->end + num_bytes); + ase->in += num_bytes; +} + +static char* s_fopen(const char* path, int* size, void* mem_ctx) +{ + CUTE_ASEPRITE_UNUSED(mem_ctx); + char* data = 0; + CUTE_ASEPRITE_FILE* fp = CUTE_ASEPRITE_FOPEN(path, "rb"); + int sz = 0; + + if (fp) + { + CUTE_ASEPRITE_FSEEK(fp, 0, CUTE_ASEPRITE_SEEK_END); + sz = CUTE_ASEPRITE_FTELL(fp); + CUTE_ASEPRITE_FSEEK(fp, 0, CUTE_ASEPRITE_SEEK_SET); + data = (char*)CUTE_ASEPRITE_ALLOC(sz + 1, mem_ctx); + CUTE_ASEPRITE_FREAD(data, sz, 1, fp); + data[sz] = 0; + CUTE_ASEPRITE_FCLOSE(fp); + } + + if (size) *size = sz; + return data; +} + +ase_t* cute_aseprite_load_from_file(const char* path, void* mem_ctx) +{ + s_error_file = path; + int sz; + void* file = s_fopen(path, &sz, mem_ctx); + if (!file) { + CUTE_ASEPRITE_WARNING("Unable to find map file."); + return NULL; + } + ase_t* aseprite = cute_aseprite_load_from_memory(file, sz, mem_ctx); + CUTE_ASEPRITE_FREE(file, mem_ctx); + s_error_file = NULL; + return aseprite; +} + +static int s_mul_un8(int a, int b) +{ + int t = (a * b) + 0x80; + return (((t >> 8) + t) >> 8); +} + +static ase_color_t s_blend(ase_color_t src, ase_color_t dst, uint8_t opacity) +{ + src.a = (uint8_t)s_mul_un8(src.a, opacity); + int a = src.a + dst.a - s_mul_un8(src.a, dst.a); + int r, g, b; + if (a == 0) { + r = g = b = 0; + } else { + r = dst.r + (src.r - dst.r) * src.a / a; + g = dst.g + (src.g - dst.g) * src.a / a; + b = dst.b + (src.b - dst.b) * src.a / a; + } + ase_color_t ret = { (uint8_t)r, (uint8_t)g, (uint8_t)b, (uint8_t)a }; + return ret; +} + +static int s_min(int a, int b) +{ + return a < b ? a : b; +} + +static int s_max(int a, int b) +{ + return a < b ? b : a; +} + +static ase_color_t s_color(ase_t* ase, void* src, int index) +{ + ase_color_t result; + if (ase->mode == ASE_MODE_RGBA) { + result = ((ase_color_t*)src)[index]; + } else if (ase->mode == ASE_MODE_GRAYSCALE) { + uint8_t saturation = ((uint8_t*)src)[index * 2]; + uint8_t a = ((uint8_t*)src)[index * 2 + 1]; + result.r = result.g = result.b = saturation; + result.a = a; + } else { + CUTE_ASEPRITE_ASSERT(ase->mode == ASE_MODE_INDEXED); + uint8_t palette_index = ((uint8_t*)src)[index]; + if (palette_index == ase->transparent_palette_entry_index) { + result.r = 0; + result.g = 0; + result.b = 0; + result.a = 0; + } else { + result = ase->palette.entries[palette_index].color; + } + } + return result; +} + +ase_t* cute_aseprite_load_from_memory(const void* memory, int size, void* mem_ctx) +{ + ase_t* ase = (ase_t*)CUTE_ASEPRITE_ALLOC(sizeof(ase_t), mem_ctx); + CUTE_ASEPRITE_MEMSET(ase, 0, sizeof(*ase)); + + ase_state_t state = { 0 }; + ase_state_t* s = &state; + s->in = (uint8_t*)memory; + s->end = s->in + size; + s->mem_ctx = mem_ctx; + + s_skip(s, sizeof(uint32_t)); // File size. + int magic = (int)s_read_uint16(s); + if (magic != 0xA5E0) return CUTE_ASEPRITE_FREE(ase, mem_ctx), 0; // CUTE_ASEPRITE_ASSERT(magic == 0xA5E0); //< r-lyeh: soft abort + + ase->frame_count = (int)s_read_uint16(s); + ase->w = s_read_uint16(s); + ase->h = s_read_uint16(s); + uint16_t bpp = s_read_uint16(s) / 8; + if (bpp == 4) ase->mode = ASE_MODE_RGBA; + else if (bpp == 2) ase->mode = ASE_MODE_GRAYSCALE; + else { + CUTE_ASEPRITE_ASSERT(bpp == 1); + ase->mode = ASE_MODE_INDEXED; + } + uint32_t valid_layer_opacity = s_read_uint32(s) & 1; + int speed = s_read_uint16(s); + s_skip(s, sizeof(uint32_t) * 2); // Spec says skip these bytes, as they're zero'd. + ase->transparent_palette_entry_index = s_read_uint8(s); + s_skip(s, 3); // Spec says skip these bytes. + ase->number_of_colors = (int)s_read_uint16(s); + ase->pixel_w = (int)s_read_uint8(s); + ase->pixel_h = (int)s_read_uint8(s); + ase->grid_x = (int)s_read_int16(s); + ase->grid_y = (int)s_read_int16(s); + ase->grid_w = (int)s_read_uint16(s); + ase->grid_h = (int)s_read_uint16(s); + s_skip(s, 84); // For future use (set to zero). + + ase->frames = (ase_frame_t*)CUTE_ASEPRITE_ALLOC((int)(sizeof(ase_frame_t)) * ase->frame_count, mem_ctx); + CUTE_ASEPRITE_MEMSET(ase->frames, 0, sizeof(ase_frame_t) * (size_t)ase->frame_count); + + ase_udata_t* last_udata = NULL; + int was_on_tags = 0; + int tag_index = 0; + + ase_layer_t* layer_stack[CUTE_ASEPRITE_MAX_LAYERS]; + + // Parse all chunks in the .aseprite file. + for (int i = 0; i < ase->frame_count; ++i) { + ase_frame_t* frame = ase->frames + i; + frame->ase = ase; + s_skip(s, sizeof(uint32_t)); // Frame size. + magic = (int)s_read_uint16(s); + CUTE_ASEPRITE_ASSERT(magic == 0xF1FA); + int chunk_count = (int)s_read_uint16(s); + frame->duration_milliseconds = s_read_uint16(s); + if (frame->duration_milliseconds == 0) frame->duration_milliseconds = speed; + s_skip(s, 2); // For future use (set to zero). + uint32_t new_chunk_count = s_read_uint32(s); + if (new_chunk_count) chunk_count = (int)new_chunk_count; + + for (int j = 0; j < chunk_count; ++j) { + uint32_t chunk_size = s_read_uint32(s); + uint16_t chunk_type = s_read_uint16(s); + chunk_size -= (uint32_t)(sizeof(uint32_t) + sizeof(uint16_t)); + uint8_t* chunk_start = s->in; + + switch (chunk_type) { + case 0x2004: // Layer chunk. + { + CUTE_ASEPRITE_ASSERT(ase->layer_count < CUTE_ASEPRITE_MAX_LAYERS); + ase_layer_t* layer = ase->layers + ase->layer_count++; + layer->flags = (ase_layer_flags_t)s_read_uint16(s); + layer->type = (ase_layer_type_t)s_read_uint16(s); + layer->parent = NULL; + int child_level = (int)s_read_uint16(s); + layer_stack[child_level] = layer; + if (child_level) { + layer->parent = layer_stack[child_level - 1]; + } + s_skip(s, sizeof(uint16_t)); // Default layer width in pixels (ignored). + s_skip(s, sizeof(uint16_t)); // Default layer height in pixels (ignored). + int blend_mode = (int)s_read_uint16(s); + if (blend_mode) CUTE_ASEPRITE_WARNING("Unknown blend mode encountered."); + layer->opacity = s_read_uint8(s) / 255.0f; + if (!valid_layer_opacity) layer->opacity = 1.0f; + s_skip(s, 3); // For future use (set to zero). + layer->name = s_read_string(s); + last_udata = &layer->udata; + } break; + + case 0x2005: // Cel chunk. + { + CUTE_ASEPRITE_ASSERT(frame->cel_count < CUTE_ASEPRITE_MAX_LAYERS); + ase_cel_t* cel = frame->cels + frame->cel_count++; + int layer_index = (int)s_read_uint16(s); + cel->layer = ase->layers + layer_index; + cel->x = s_read_int16(s); + cel->y = s_read_int16(s); + cel->opacity = s_read_uint8(s) / 255.0f; + int cel_type = (int)s_read_uint16(s); + s_skip(s, 7); // For future (set to zero). + switch (cel_type) { + case 0: // Raw cel. + cel->w = s_read_uint16(s); + cel->h = s_read_uint16(s); + cel->pixels = CUTE_ASEPRITE_ALLOC(cel->w * cel->h * bpp, mem_ctx); + CUTE_ASEPRITE_MEMCPY(cel->pixels, s->in, (size_t)(cel->w * cel->h * bpp)); + s_skip(s, cel->w * cel->h * bpp); + break; + + case 1: // Linked cel. + cel->is_linked = 1; + cel->linked_frame_index = s_read_uint16(s); + break; + + case 2: // Compressed image cel. + { + cel->w = s_read_uint16(s); + cel->h = s_read_uint16(s); + int zlib_byte0 = s_read_uint8(s); + int zlib_byte1 = s_read_uint8(s); + int deflate_bytes = (int)chunk_size - (int)(s->in - chunk_start); + void* pixels = s->in; + CUTE_ASEPRITE_ASSERT((zlib_byte0 & 0x0F) == 0x08); // Only zlib compression method (RFC 1950) is supported. + CUTE_ASEPRITE_ASSERT((zlib_byte0 & 0xF0) <= 0x70); // Innapropriate window size detected. + CUTE_ASEPRITE_ASSERT(!(zlib_byte1 & 0x20)); // Preset dictionary is present and not supported. + int pixels_sz = cel->w * cel->h * bpp; + void* pixels_decompressed = CUTE_ASEPRITE_ALLOC(pixels_sz, mem_ctx); + int ret = s_inflate(pixels, deflate_bytes, pixels_decompressed, pixels_sz, mem_ctx); + if (!ret) CUTE_ASEPRITE_WARNING(s_error_reason); + cel->pixels = pixels_decompressed; + s_skip(s, deflate_bytes); + } break; + } + last_udata = &cel->udata; + } break; + + case 0x2006: // Cel extra chunk. + { + ase_cel_t* cel = frame->cels + frame->cel_count; + cel->has_extra = 1; + cel->extra.precise_bounds_are_set = (int)s_read_uint32(s); + cel->extra.precise_x = s_read_fixed(s); + cel->extra.precise_y = s_read_fixed(s); + cel->extra.w = s_read_fixed(s); + cel->extra.h = s_read_fixed(s); + s_skip(s, 16); // For future use (set to zero). + } break; + + case 0x2007: // Color profile chunk. + { + ase->has_color_profile = 1; + ase->color_profile.type = (ase_color_profile_type_t)s_read_uint16(s); + ase->color_profile.use_fixed_gamma = (int)s_read_uint16(s) & 1; + ase->color_profile.gamma = s_read_fixed(s); + s_skip(s, 8); // For future use (set to zero). + if (ase->color_profile.type == ASE_COLOR_PROFILE_TYPE_EMBEDDED_ICC) { + // Use the embedded ICC profile. + ase->color_profile.icc_profile_data_length = s_read_uint32(s); + ase->color_profile.icc_profile_data = CUTE_ASEPRITE_ALLOC(ase->color_profile.icc_profile_data_length, mem_ctx); + CUTE_ASEPRITE_MEMCPY(ase->color_profile.icc_profile_data, s->in, ase->color_profile.icc_profile_data_length); + s->in += ase->color_profile.icc_profile_data_length; + } + } break; + + case 0x2018: // Tags chunk. + { + ase->tag_count = (int)s_read_uint16(s); + s_skip(s, 8); // For future (set to zero). + CUTE_ASEPRITE_ASSERT(ase->tag_count < CUTE_ASEPRITE_MAX_TAGS); + for (int k = 0; k < ase->tag_count; ++k) { + ase_tag_t tag; + tag.from_frame = (int)s_read_uint16(s); + tag.to_frame = (int)s_read_uint16(s); + tag.loop_animation_direction = (ase_animation_direction_t)s_read_uint8(s); + s_skip(s, 8); // For future (set to zero). + tag.r = s_read_uint8(s); + tag.g = s_read_uint8(s); + tag.b = s_read_uint8(s); + s_skip(s, 1); // Extra byte (zero). + tag.name = s_read_string(s); + ase->tags[k] = tag; + } + was_on_tags = 1; + } break; + + case 0x2019: // Palette chunk. + { + ase->palette.entry_count = (int)s_read_uint32(s); + CUTE_ASEPRITE_ASSERT(ase->palette.entry_count <= CUTE_ASEPRITE_MAX_PALETTE_ENTRIES); + int first_index = (int)s_read_uint32(s); + int last_index = (int)s_read_uint32(s); + s_skip(s, 8); // For future (set to zero). + for (int k = first_index; k <= last_index; ++k) { + int has_name = s_read_uint16(s); + ase_palette_entry_t entry; + entry.color.r = s_read_uint8(s); + entry.color.g = s_read_uint8(s); + entry.color.b = s_read_uint8(s); + entry.color.a = s_read_uint8(s); + if (has_name) { + entry.color_name = s_read_string(s); + } else { + entry.color_name = NULL; + } + CUTE_ASEPRITE_ASSERT(k < CUTE_ASEPRITE_MAX_PALETTE_ENTRIES); + ase->palette.entries[k] = entry; + } + } break; + + case 0x2020: // Udata chunk. + { + CUTE_ASEPRITE_ASSERT(last_udata || was_on_tags); + if (was_on_tags && !last_udata) { + CUTE_ASEPRITE_ASSERT(tag_index < ase->tag_count); + last_udata = &ase->tags[tag_index++].udata; + } + int flags = (int)s_read_uint32(s); + if (flags & 1) { + last_udata->has_text = 1; + last_udata->text = s_read_string(s); + } + if (flags & 2) { + last_udata->color.r = s_read_uint8(s); + last_udata->color.g = s_read_uint8(s); + last_udata->color.b = s_read_uint8(s); + last_udata->color.a = s_read_uint8(s); + } + last_udata = NULL; + } break; + + case 0x2022: // Slice chunk. + { + int slice_count = (int)s_read_uint32(s); + int flags = (int)s_read_uint32(s); + s_skip(s, sizeof(uint32_t)); // Reserved. + const char* name = s_read_string(s); + for (int k = 0; k < (int)slice_count; ++k) { + ase_slice_t slice = { 0 }; + slice.name = name; + slice.frame_number = (int)s_read_uint32(s); + slice.origin_x = (int)s_read_int32(s); + slice.origin_y = (int)s_read_int32(s); + slice.w = (int)s_read_uint32(s); + slice.h = (int)s_read_uint32(s); + if (flags & 1) { + // It's a 9-patches slice. + slice.has_center_as_9_slice = 1; + slice.center_x = (int)s_read_int32(s); + slice.center_y = (int)s_read_int32(s); + slice.center_w = (int)s_read_uint32(s); + slice.center_h = (int)s_read_uint32(s); + } else if (flags & 2) { + // Has pivot information. + slice.has_pivot = 1; + slice.pivot_x = (int)s_read_int32(s); + slice.pivot_y = (int)s_read_int32(s); + } + CUTE_ASEPRITE_ASSERT(ase->slice_count < CUTE_ASEPRITE_MAX_SLICES); + ase->slices[ase->slice_count++] = slice; + last_udata = &ase->slices[ase->slice_count - 1].udata; + } + } break; + + default: + s_skip(s, (int)chunk_size); + break; + } + + uint32_t size_read = (uint32_t)(s->in - chunk_start); + CUTE_ASEPRITE_ASSERT(size_read == chunk_size); + } + } + + //< @r-lyeh: if num_layers > 1, then assume last layer #0 is a background layer. hide it + #if ASE_TRIMS + if(ase->layer_count > 1) ase->layers[0].flags &= ~ASE_LAYER_FLAGS_VISIBLE; + if(0) + for( int i = 0; i < ase->layer_count; ++i ) { + int match = 0; + match = !strcmpi(ase->layers[i].name, "grid"); + match |= !strcmpi(ase->layers[i].name, "background"); + match |= !strcmpi(ase->layers[i].name, "fondo"); + if(match) ase->layers[i].flags &= ~ASE_LAYER_FLAGS_VISIBLE; + } + #endif + //< + + // Blend all cel pixels into each of their respective frames, for convenience. + for (int i = 0; i < ase->frame_count; ++i) { + ase_frame_t* frame = ase->frames + i; + frame->pixels = (ase_color_t*)CUTE_ASEPRITE_ALLOC((int)(sizeof(ase_color_t)) * ase->w * ase->h, mem_ctx); + CUTE_ASEPRITE_MEMSET(frame->pixels, 0, sizeof(ase_color_t) * (size_t)ase->w * (size_t)ase->h); + ase_color_t* dst = frame->pixels; + for (int j = 0; j < frame->cel_count; ++j) { + ase_cel_t* cel = frame->cels + j; + if (!(cel->layer->flags & ASE_LAYER_FLAGS_VISIBLE)) { + continue; + } + if (cel->layer->parent && !(cel->layer->parent->flags & ASE_LAYER_FLAGS_VISIBLE)) { + continue; + } + while (cel->is_linked) { + ase_frame_t* frame = ase->frames + cel->linked_frame_index; + int found = 0; + for (int k = 0; k < frame->cel_count; ++k) { + if (frame->cels[k].layer == cel->layer) { + cel = frame->cels + k; + found = 1; + break; + } + } + CUTE_ASEPRITE_ASSERT(found); + } + void* src = cel->pixels; + uint8_t opacity = (uint8_t)(cel->opacity * cel->layer->opacity * 255.0f); + int cx = cel->x; + int cy = cel->y; + int cw = cel->w; + int ch = cel->h; + int cl = -s_min(cx, 0); + int ct = -s_min(cy, 0); + int dl = s_max(cx, 0); + int dt = s_max(cy, 0); + int dr = s_min(ase->w, cw + cx); + int db = s_min(ase->h, ch + cy); + int aw = ase->w; + for (int dx = dl, sx = cl; dx < dr; dx++, sx++) { + for (int dy = dt, sy = ct; dy < db; dy++, sy++) { + int dst_index = aw * dy + dx; + ase_color_t src_color = s_color(ase, src, cw * sy + sx); + ase_color_t dst_color = dst[dst_index]; + ase_color_t result = s_blend(src_color, dst_color, opacity); + dst[dst_index] = result; + } + } + } + } + + ase->mem_ctx = mem_ctx; + return ase; +} + +void cute_aseprite_free(ase_t* ase) +{ + for (int i = 0; i < ase->frame_count; ++i) { + ase_frame_t* frame = ase->frames + i; + CUTE_ASEPRITE_FREE(frame->pixels, ase->mem_ctx); + for (int j = 0; j < frame->cel_count; ++j) { + ase_cel_t* cel = frame->cels + j; + CUTE_ASEPRITE_FREE(cel->pixels, ase->mem_ctx); + CUTE_ASEPRITE_FREE((void*)cel->udata.text, ase->mem_ctx); + } + } + for (int i = 0; i < ase->layer_count; ++i) { + ase_layer_t* layer = ase->layers + i; + CUTE_ASEPRITE_FREE((void*)layer->name, ase->mem_ctx); + CUTE_ASEPRITE_FREE((void*)layer->udata.text, ase->mem_ctx); + } + for (int i = 0; i < ase->tag_count; ++i) { + ase_tag_t* tag = ase->tags + i; + CUTE_ASEPRITE_FREE((void*)tag->name, ase->mem_ctx); + } + for (int i = 0; i < ase->slice_count; ++i) { + ase_slice_t* slice = ase->slices + i; + CUTE_ASEPRITE_FREE((void*)slice->udata.text, ase->mem_ctx); + } + if (ase->slice_count) { + CUTE_ASEPRITE_FREE((void*)ase->slices[0].name, ase->mem_ctx); + } + for (int i = 0; i < ase->palette.entry_count; ++i) { + CUTE_ASEPRITE_FREE((void*)ase->palette.entries[i].color_name, ase->mem_ctx); + } + CUTE_ASEPRITE_FREE(ase->color_profile.icc_profile_data, ase->mem_ctx); + CUTE_ASEPRITE_FREE(ase->frames, ase->mem_ctx); + CUTE_ASEPRITE_FREE(ase, ase->mem_ctx); +} + +#endif // CUTE_ASEPRITE_IMPLEMENTATION_ONCE +#endif // CUTE_ASEPRITE_IMPLEMENTATION + +/* + ------------------------------------------------------------------------------ + This software is available under 2 licenses - you may choose the one you like. + ------------------------------------------------------------------------------ + ALTERNATIVE A - zlib license + Copyright (c) 2017 Randy Gaul http://www.randygaul.net + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from + the use of this software. + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + ------------------------------------------------------------------------------ + ALTERNATIVE B - Public Domain (www.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. + ------------------------------------------------------------------------------ +*/ diff --git a/engine/split/3rd_atlasc.h b/engine/split/3rd_atlasc.h new file mode 100644 index 0000000..16b1cb6 --- /dev/null +++ b/engine/split/3rd_atlasc.h @@ -0,0 +1,886 @@ +// atlasc.c +// Copyright 2019 Sepehr Taghdisian (septag@github). All rights reserved. +// License: https://github.com/septag/atlasc#license-bsd-2-clause +// +// sx_math.h +// Copyright 2018 Sepehr Taghdisian (septag@github). All rights reserved. +// License: https://github.com/septag/sx#license-bsd-2-clause + +#ifndef ATLASC_HEADER +#define ATLASC_HEADER + +#define ATLASC_VERSION "1.2.3" + +#include +#include +#include + +#ifndef __cplusplus +#define ATLAS_CAST +#else +#define ATLAS_CAST(T) T +extern "C" { +#endif + +typedef union vec2 { struct { float x, y; }; float f[2]; } vec2; +typedef union vec3 { struct { float x, y, z; }; float f[3]; } vec3; +typedef union vec2i { struct { int x, y; }; int n[2]; } vec2i; +typedef union recti { struct { int xmin, ymin, xmax, ymax; }; struct { vec2i vmin, vmax; }; int f[4]; } recti; + +typedef struct atlas_flags { + int alpha_threshold; + float dist_threshold; + int max_width; + int max_height; + int border; + int pot; + int padding; + int mesh; + int max_verts_per_mesh; + float scale; +} atlas_flags; + +typedef struct atlas_image { + uint8_t* pixels; // only supports 32bpp RGBA format + int width; + int height; + char *name; +} atlas_image; + +typedef struct atlas_sprite { + uint8_t* src_image; // RGBA image buffer (32bpp) + vec2i src_size; // widthxheight + recti sprite_rect; // cropped rectangle relative to sprite's source image (pixels) + recti sheet_rect; // rectangle in final sheet (pixels) + char *name; + unsigned frame; + + // sprite-mesh data (if flag is set. see atlas_flags) + uint16_t num_tris; + int num_points; + vec2i* pts; + vec2i* uvs; + uint16_t* tris; +} atlas_sprite; + +typedef struct atlas_t { + atlas_sprite* sprites; + int num_sprites; + int* frames; + int num_frames; + atlas_image output; +} atlas_t; + +// receives input files and common arguments. returns atlas_t +// you have to free the data after use with `atlas_free` +atlas_t* atlas_loadfiles(array(char*) files, atlas_flags flags); + +// receives input image buffers and common arguments. returns atlas_t +// you have to free the data after use with `atlas_free` +atlas_t* atlas_loadimages(array(atlas_image) images, atlas_flags flags); + +// +bool atlas_save(const char *outfile, const atlas_t* atlas, atlas_flags flags); + +// frees atlas_t memory +void atlas_free(atlas_t* atlas); + +// returns the last error string +const char* atlas_last_error(); + +#ifdef __cplusplus +} +#endif +#endif // ATLASC_HEADER + +// +#ifdef ATLASC_IMPLEMENTATION +#include +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Types/Primitives + +#define vec2(x,y) (ATLAS_CAST(vec2) { (float)(x), (float)(y) }) +#define vec3(x,y,z) (ATLAS_CAST(vec3) { (float)(x), (float)(y), (float)(z) }) +#define vec2i(x,y) (ATLAS_CAST(vec2i) { (int)(x), (int)(y) }) +#define recti(x,y,X,Y) (ATLAS_CAST(recti) { (int)(x), (int)(y), (int)(X), (int)(Y) }) + +#define minf(a,b) ((a) < (b) ? (a) : (b)) +#define maxf(a,b) ((a) > (b) ? (a) : (b)) +#define clampf(a,b,c) ( (a) < (b) ? (b) : (a) > (c) ? (c) : (a)) + +static int nearest_pow2(int n) { return --n, n |= n >> 1, n |= n >> 2, n |= n >> 4, n |= n >> 8, n |= n >> 16, ++n; } // https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 +static float sx_abs(float _a) { union { float f; unsigned int ui; } u = { _a }; return u.ui &= 0x7FFFFFFF, u.f; } +static bool equalf(float _a, float _b, float _epsilon) { const float lhs = sx_abs(_a - _b), aa = sx_abs(_a), ab = sx_abs(_b), rhs = _epsilon * maxf(1.0f, maxf(aa, ab)); return lhs <= rhs; } // http://realtimecollisiondetection.net/blog/?t=89 + +static vec3 cross3(const vec3 _a, const vec3 _b) { return vec3(_a.y * _b.z - _a.z * _b.y, _a.z * _b.x - _a.x * _b.z, _a.x * _b.y - _a.y * _b.x); } + +static float dot2(const vec2 _a, const vec2 _b) { return _a.x * _b.x + _a.y * _b.y; } +static float len2(const vec2 _a) { return sqrt(dot2(_a, _a)); } +static vec2 norm2(const vec2 _a) { const float len = len2(_a); /*assert(len > 0 && "Divide by zero");*/ return vec2(_a.x / (len + !len), _a.y / (len + !len)); } +static vec2 add2(const vec2 _a, const vec2 _b) { return vec2(_a.x + _b.x, _a.y + _b.y); } +static vec2 sub2(const vec2 _a, const vec2 _b) { return vec2(_a.x - _b.x, _a.y - _b.y); } +static vec2 scale2(const vec2 _a, float _b) { return vec2(_a.x * _b, _a.y * _b); } + +static vec2i add2i(const vec2i _a, const vec2i _b) { return vec2i(_a.x + _b.x, _a.y + _b.y); } +static vec2i sub2i(const vec2i _a, const vec2i _b) { return vec2i(_a.x - _b.x, _a.y - _b.y); } +static vec2i min2i(const vec2i _a, const vec2i _b) { return vec2i(minf(_a.x, _b.x), minf(_a.y, _b.y)); } +static vec2i max2i(const vec2i _a, const vec2i _b) { return vec2i(maxf(_a.x, _b.x), maxf(_a.y, _b.y)); } + +static recti rectiwh(int _x, int _y, int _w, int _h) { return recti(_x, _y, _x + _w, _y + _h); } +static recti recti_expand(const recti rc, const vec2i expand) { return recti(rc.xmin - expand.x, rc.ymin - expand.y, rc.xmax + expand.x, rc.ymax + expand.y); } +static void recti_add_point(recti* rc, const vec2i pt) { rc->vmin = min2i(rc->vmin, pt); rc->vmax = max2i(rc->vmax, pt); } + +// ---------------------------------------------------------------------------- + +#ifndef ATLAS_REALLOC +#define ATLAS_REALLOC REALLOC +#endif +#ifndef ATLAS_MSIZE +#define ATLAS_MSIZE MSIZE +#endif +#ifndef ATLAS_CALLOC +#define ATLAS_CALLOC(n,m) memset(ATLAS_REALLOC(0, (n)*(m)), 0, (n)*(m)) +#endif +#ifndef ATLAS_FREE +#define ATLAS_FREE(ptr) ((ptr) = ATLAS_REALLOC((ptr), 0)) +#endif + +#define align_mask(_value, _mask) (((_value) + (_mask)) & ((~0) & (~(_mask)))) + +static void panic_if(int fail) { if(fail) exit(-fprintf(stderr, "out of memory!\n")); } + +static void path_unixpath(char *buf, unsigned buflen, const char *inpath) { + snprintf(buf, buflen, "%s", inpath); + while( strchr(buf, '\\') ) *strchr(buf, '\\') = '/'; +} +static void path_basename(char *buf, unsigned buflen, const char *inpath) { + const char *a = strrchr(inpath, '\\'); + const char *b = strrchr(inpath, '/'); + snprintf(buf, buflen, "%s", a > b ? a+1 : b > a ? b+1 : inpath ); +} +static bool path_isfile(const char* filepath) { + FILE *f = fopen(filepath, "rb"); + return f ? fclose(f), 1 : 0; +} + +static char g_error_str[512]; +const char* atlas_last_error() +{ + return g_error_str; +} + +static void atlas__free_sprites(atlas_sprite* sprites, int num_sprites) +{ + for (int i = 0; i < num_sprites; i++) { + if (sprites[i].src_image) { + stbi_image_free(sprites[i].src_image); + } + + if (sprites[i].tris) { + ATLAS_FREE(sprites[i].tris); + } + + if (sprites[i].pts) { + ATLAS_FREE(sprites[i].pts); + } + + if (sprites[i].uvs) { + ATLAS_FREE(sprites[i].uvs); + } + + if (sprites[i].name) { + ATLAS_FREE(sprites[i].name); + } + } + ATLAS_FREE(sprites); +} + +static void atlas__blit(uint8_t* dst, int dst_x, int dst_y, int dst_pitch, const uint8_t* src, + int src_x, int src_y, int src_w, int src_h, int src_pitch, int bpp) +{ + assert(dst); + assert(src); + + const int pixel_sz = bpp / 8; + const uint8_t* src_ptr = src + src_y * src_pitch + src_x * pixel_sz; + uint8_t* dst_ptr = dst + dst_y * dst_pitch + dst_x * pixel_sz; + for (int y = src_y; y < (src_y + src_h); y++) { + memcpy(dst_ptr, src_ptr, src_w * pixel_sz); + src_ptr += src_pitch; + dst_ptr += dst_pitch; + } +} + +static vec2 atlas__itof2(const s2o_point p) +{ + return vec2((float)p.x, (float)p.y); +} + +// modified version of: +// https://github.com/anael-seghezzi/Maratis-Tiny-C-library/blob/master/include/m_raster.h +static bool atlas__test_line(const uint8_t* buffer, int w, int h, s2o_point p0, s2o_point p1) +{ + const uint8_t* data = buffer; + + int x0 = p0.x; + int y0 = p0.y; + int x1 = p1.x; + int y1 = p1.y; + int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; + int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; + int err = dx + dy, e2; + + while (1) { + if (x0 > -1 && y0 > -1 && x0 < w && y0 < h) { + const uint8_t* pixel = data + (y0 * w + x0); + if (*pixel) + return true; // line intersects with image data + } + + if (x0 == x1 && y0 == y1) + break; + + e2 = 2 * err; + if (e2 >= dy) { + err += dy; + x0 += sx; + } + if (e2 <= dx) { + err += dx; + y0 += sy; + } + } + + return false; +} + +// returns true if 'pts' buffer is changed +static bool atlas__offset_pt(s2o_point* pts, int num_pts, int pt_idx, float amount, int w, int h) +{ + s2o_point ipt = pts[pt_idx]; + s2o_point _ipt = ipt; + vec2 pt = atlas__itof2(ipt); + vec2 prev_pt = (pt_idx > 0) ? atlas__itof2(pts[pt_idx - 1]) : atlas__itof2(pts[num_pts - 1]); + vec2 next_pt = (pt_idx + 1) < num_pts ? atlas__itof2(pts[pt_idx + 1]) : atlas__itof2(pts[0]); + vec2 edge1 = norm2(sub2(prev_pt, pt)); + vec2 edge2 = norm2(sub2(next_pt, pt)); + + // calculate normal vector to move the point away from the polygon + vec2 n; + vec3 c = cross3(vec3(edge1.x, edge1.y, 0), vec3(edge2.x, edge2.y, 0)); + if (equalf(c.z, 0.0f, 0.00001f)) { + n = scale2(vec2(-edge1.y, edge1.x), amount); + } else { + // c.z < 0 -> point intersecting convex edges + // c.z > 0 -> point intersecting concave edges + float k = c.z < 0.0f ? -1.0f : 1.0f; + n = scale2(norm2(add2(edge1, edge2)), k * amount); + } + + pt = add2(pt, n); + ipt.x = (int)pt.x; + ipt.y = (int)pt.y; + ipt.x = clampf(ipt.x, 0, w); + ipt.y = clampf(ipt.y, 0, h); + pts[pt_idx] = ipt; + return (_ipt.x != ipt.x) || (_ipt.y != ipt.y); +} + +static void atlas__fix_outline_pts(const uint8_t* thresholded, int tw, int th, s2o_point* pts, + int num_pts) +{ + // NOTE: winding is assumed to be CW + const float offset_amount = 2.0f; + + for (int i = 0; i < num_pts; i++) { + s2o_point pt = pts[i]; + int next_i = (i + 1) < num_pts ? (i + 1) : 0; + +// assert(!thresholded[pt.y * tw + pt.x]); // point shouldn't be inside threshold + + s2o_point next_pt = pts[next_i]; + while (atlas__test_line(thresholded, tw, th, pt, next_pt)) { + if (!atlas__offset_pt(pts, num_pts, i, offset_amount, tw, th)) + break; + atlas__offset_pt(pts, num_pts, next_i, offset_amount, tw, th); + // refresh points for the new line intersection test + pt = pts[i]; + next_pt = pts[next_i]; + } + } +} + +static void atlas__make_mesh(atlas_sprite* spr, const s2o_point* pts, int pt_count, int max_verts, + const uint8_t* thresholded, int width, int height) +{ + s2o_point* temp_pts = ATLAS_CALLOC(pt_count,sizeof(s2o_point)); + panic_if(!temp_pts); + + memcpy(temp_pts, pts, sizeof(s2o_point)*pt_count); + int num_verts = pt_count; + + if (width > 1 && height > 1) { + const float delta = 0.5f; + const float threshold_start = 0.5f; + float threshold = threshold_start; + + for(;;) { + s2o_distance_based_path_simplification(temp_pts, &num_verts, threshold); + + if(num_verts <= max_verts) break; + + memcpy(temp_pts, pts, sizeof(s2o_point)*pt_count); + num_verts = pt_count; + + threshold += delta; + } + + // fix any collisions with the actual image // @r-lyeh: method below is buggy. will return dupe points + atlas__fix_outline_pts(thresholded, width, height, temp_pts, num_verts); + } + + //< @r-lyeh: remove dupes + for (int i = 0; i < num_verts - 1; i++) { + for (int j = i + 1; j < num_verts; j++) { + if( temp_pts[i].x == temp_pts[j].x && temp_pts[i].y == temp_pts[j].y ) { + temp_pts[j].x = temp_pts[num_verts - 1].x; + temp_pts[j].y = temp_pts[num_verts - 1].y; + --num_verts; + --j; + } + } + } + //< + + // triangulate + del_point2d_t* dpts = ATLAS_CALLOC(num_verts, sizeof(del_point2d_t)); + panic_if(!dpts); + + for (int i = 0; i < num_verts; i++) { + dpts[i].x = (double)temp_pts[i].x; + dpts[i].y = (double)temp_pts[i].y; + //printf("%d) %f,%f\n", i, dpts[i].x, dpts[i].y); //< @r-lyeh: debug dupe points + } + + delaunay2d_t* polys = delaunay2d_from(dpts, num_verts); + assert(polys); + tri_delaunay2d_t* tris = tri_delaunay2d_from(polys); + assert(tris); + ATLAS_FREE(dpts); + delaunay2d_release(polys); + + assert(tris->num_triangles < UINT16_MAX); + spr->tris = ATLAS_CALLOC(tris->num_triangles * 3,sizeof(uint16_t)); + spr->pts = ATLAS_CALLOC(tris->num_points, sizeof(vec2i)); + assert(spr->tris); + assert(spr->pts); + + for (unsigned int i = 0; i < tris->num_triangles; i++) { + unsigned int index = i * 3; + spr->tris[index] = (uint16_t)tris->tris[index]; + spr->tris[index + 1] = (uint16_t)tris->tris[index + 1]; + spr->tris[index + 2] = (uint16_t)tris->tris[index + 2]; + } + for (unsigned int i = 0; i < tris->num_points; i++) { + spr->pts[i] = vec2i((int)tris->points[i].x, (int)tris->points[i].y); + } + spr->num_tris = (uint16_t)tris->num_triangles; + spr->num_points = (int)tris->num_points; + + tri_delaunay2d_release(tris); + ATLAS_FREE(temp_pts); +} + +atlas_t* atlas_loadimages(array(atlas_image) images, atlas_flags flags) +{ + assert(images); + + array(int) frames = 0; + array(atlas_sprite) sprites = 0; + + for (int i = 0; i < array_count(images); i++) { + + // find is_cached + { + int found = 0, k = 0; + static array(uint64_t) cache = 0; + static array(uint64_t) index = 0; + uint64_t hash = hash_init; + hash = hash_bin(&images[i].width, sizeof(images[i].width), hash); + hash = hash_bin(&images[i].height, sizeof(images[i].height), hash); + hash = hash_bin((char*)images[i].pixels, images[i].width * images[i].height * 4, hash); + for (; k < array_count(cache); ++k) + if (cache[k] == hash) { found = 1; break; } + if (found) { + array_push(frames, index[k]); + continue; + } else { + array_push(cache, hash); + array_push(index, k); + array_push(frames, k); + } + //printf("%d) %llx\n", array_count(cache), hash); + } + + atlas_sprite zero = {0}; + atlas_sprite* spr = &zero; + if(images[i].name) spr->name = STRDUP(images[i].name); + spr->frame = i; + + spr->src_size.x = images[i].width; + spr->src_size.y = images[i].height; + assert(images[i].width > 0 && images[i].height > 0); + assert(images[i].pixels); + uint8_t* pixels = images[i].pixels; + + // rescale + if (!equalf(flags.scale, 1.0f, 0.0001f)) { + int target_w = (int)((float)spr->src_size.x * flags.scale); + int target_h = (int)((float)spr->src_size.y * flags.scale); + uint8_t* resized_pixels = ATLAS_CALLOC(1, 4 * target_w * target_h); + panic_if(!resized_pixels); + + if (!stbir_resize_uint8(pixels, spr->src_size.x, spr->src_size.y, 4 * spr->src_size.x, + resized_pixels, target_w, target_h, 4 * target_w, 4)) { + snprintf(g_error_str, sizeof(g_error_str), "could not resize image: #%d", i + 1); + atlas__free_sprites(sprites, array_count(sprites)); + return NULL; + } + + stbi_image_free(pixels); + + spr->src_size.x = target_w; + spr->src_size.y = target_h; + pixels = resized_pixels; + } + + spr->src_image = pixels; + + recti sprite_rect = {0}; + int pt_count = 0; + s2o_point* pts = 0; + uint8_t* alpha = s2o_rgba_to_alpha(spr->src_image, spr->src_size.x, spr->src_size.y); + uint8_t* thresholded = s2o_alpha_to_thresholded(alpha, spr->src_size.x, spr->src_size.y, flags.alpha_threshold); + free(alpha); + + if (flags.mesh && spr->src_size.x > 1 && spr->src_size.y > 1) { + uint8_t* dilate_thres = s2o_dilate_thresholded(thresholded, spr->src_size.x, spr->src_size.y); + + uint8_t* outlined = s2o_thresholded_to_outlined(dilate_thres, spr->src_size.x, spr->src_size.y); + free(dilate_thres); + + pts = s2o_extract_outline_path(outlined, spr->src_size.x, spr->src_size.y, &pt_count, NULL); + free(outlined); + + //< @r-lyeh @fixme: many sprites will return extremely low num of points (like 8) even if the sprite is complex enough. + //< this will lead to produce here a nearly zero sprite_rect, then sheet_rect, then eventually an empty frame at end of pipeline. + + // calculate cropped rectangle + sprite_rect = recti(INT_MAX, INT_MAX, INT_MIN, INT_MIN); + for (int k = 0; k < pt_count; k++) { + recti_add_point(&sprite_rect, vec2i(pts[k].x, pts[k].y)); + } + sprite_rect.xmax++; + sprite_rect.ymax++; + } else { + sprite_rect = recti(0, 0, spr->src_size.x, spr->src_size.y); + pt_count = 4; + pts = ATLAS_CALLOC(pt_count, sizeof(s2o_point)); + pts[0] = (s2o_point) {0, 0}; + pts[1] = (s2o_point) {spr->src_size.x, 0}; + pts[2] = (s2o_point) {spr->src_size.x, spr->src_size.y}; + pts[3] = (s2o_point) {0, spr->src_size.y}; + } + + // generate mesh if set in arguments + if (flags.mesh) { + atlas__make_mesh(spr, pts, pt_count, flags.max_verts_per_mesh, thresholded, + spr->src_size.x, spr->src_size.y); + } + + ATLAS_FREE(pts); + free(thresholded); + spr->sprite_rect = sprite_rect; + + array_push(sprites, *spr); + } + + int num_sprites = array_count(sprites); + + // pack sprites into a sheet + stbrp_context rp_ctx = {0}; + int max_width = flags.max_width; + int max_height = flags.max_height; + int num_rp_nodes = max_width + max_height; + stbrp_rect* rp_rects = ATLAS_CALLOC(num_sprites, sizeof(stbrp_rect)); + stbrp_node* rp_nodes = ATLAS_CALLOC(num_rp_nodes, sizeof(stbrp_node)); + panic_if(!rp_rects || !rp_nodes); + + for (int i = 0; i < num_sprites; i++) { + recti rc = sprites[i].sprite_rect; + int rc_resize = (flags.border + flags.padding) * 2; + rp_rects[i].w = (rc.xmax - rc.xmin) + rc_resize; + rp_rects[i].h = (rc.ymax - rc.ymin) + rc_resize; + } + stbrp_init_target(&rp_ctx, max_width, max_height, rp_nodes, num_rp_nodes); + recti final_rect = recti(INT_MAX, INT_MAX, INT_MIN, INT_MIN); + if (stbrp_pack_rects(&rp_ctx, rp_rects, num_sprites)) { + for (int i = 0; i < num_sprites; i++) { + atlas_sprite* spr = &sprites[i]; + recti sheet_rect = rectiwh(rp_rects[i].x, rp_rects[i].y, rp_rects[i].w, rp_rects[i].h); + + // calculate the total size of output image + recti_add_point(&final_rect, sheet_rect.vmin); + recti_add_point(&final_rect, sheet_rect.vmax); + + // shrink back rect and set the real sheet_rect for the sprite + spr->sheet_rect = + recti_expand(sheet_rect, vec2i(-flags.border, -flags.border)); + } + } + + int dst_w = final_rect.xmax - final_rect.xmin; + int dst_h = final_rect.ymax - final_rect.ymin; + // make output size divide by 4 by default + dst_w = align_mask(dst_w, 3); + dst_h = align_mask(dst_h, 3); + + if (flags.pot) { + dst_w = nearest_pow2(dst_w); + dst_h = nearest_pow2(dst_h); + } + + uint8_t* dst = ATLAS_CALLOC(1, dst_w * dst_h * 4); + panic_if(!dst); + + // calculate UVs for sprite meshes + if (flags.mesh) { + for (int i = 0; i < num_sprites; i++) { + atlas_sprite* spr = &sprites[i]; + // if sprite has mesh, calculate UVs for it + if (spr->pts && spr->num_points) { + const int padding = flags.padding; + vec2i offset = spr->sprite_rect.vmin; + vec2i sheet_pos = + vec2i(spr->sheet_rect.xmin + padding, spr->sheet_rect.ymin + padding); + vec2i* uvs = ATLAS_CALLOC(spr->num_points, sizeof(vec2i)); + assert(uvs); + for (int pi = 0; pi < spr->num_points; pi++) { + vec2i pt = spr->pts[pi]; + uvs[pi] = add2i(sub2i(pt, offset), sheet_pos); + } + + spr->uvs = uvs; + } // generate uvs + } + } + + for (int i = 0; i < num_sprites; i++) { + const atlas_sprite* spr = &sprites[i]; + + // calculate UVs for sprite-meshes + + // remove padding and blit from src_image to dst + recti dstrc = recti_expand(spr->sheet_rect, vec2i(-flags.padding, -flags.padding)); + recti srcrc = spr->sprite_rect; + atlas__blit(dst, dstrc.xmin, dstrc.ymin, dst_w * 4, spr->src_image, srcrc.xmin, srcrc.ymin, + srcrc.xmax - srcrc.xmin, srcrc.ymax - srcrc.ymin, spr->src_size.x * 4, 32); + } + + atlas_t* atlas = ATLAS_CALLOC(1, sizeof(atlas_t)); + panic_if(!atlas); + + atlas->output.pixels = dst; + atlas->output.width = dst_w; + atlas->output.height = dst_h; + atlas->sprites = sprites; + atlas->num_sprites = num_sprites; + atlas->frames = frames; + atlas->num_frames = array_count(frames); + + ATLAS_FREE(rp_nodes); + ATLAS_FREE(rp_rects); + + return atlas; +} + +static char *atlas_anims = 0; +static char *atlas_slices = 0; +static char *atlas_current_anim = 0; + +atlas_t* atlas_loadfiles(array(char*) files, atlas_flags flags) +{ + assert(files); + + array(atlas_image) images = 0; + + for (int i = 0; i < array_count(files); ++i) { + if (!path_isfile(files[i])) { + snprintf(g_error_str, sizeof(g_error_str), "input image not found: %s", files[i]); + goto err_cleanup; + } + + int comp; + atlas_image img = {0}; + img.pixels = stbi_load(files[i], &img.width, &img.height, &comp, 4); + +#ifdef CUTE_ASEPRITE_H + if (!img.pixels) { + bool loaded = 0; + + for( ase_t* ase = cute_aseprite_load_from_file(files[i], NULL); ase; cute_aseprite_free(ase), ase = 0, loaded = 1) { + ase_tag_t *parent = ase->tags + 0; + + //< abc/def/ghi.aseprite -> ghi + if( atlas_current_anim ) *atlas_current_anim = '\0'; + strcatf(&atlas_current_anim, files[i]); + path_basename(atlas_current_anim, strlen(atlas_current_anim), files[i]); + if( strrchr(atlas_current_anim, '.')) *strrchr(atlas_current_anim, '.') = '\0'; + trimspace(atlas_current_anim); + //< + + for( int f = 0; f < ase->frame_count; ++f) { + ase_frame_t *frame = ase->frames + f; + + // find rect + int x = INT_MAX, y = INT_MAX, x2 = INT_MIN, y2 = INT_MIN; + for( int c = 0; c < frame->cel_count; ++c ) { + ase_cel_t *cel = frame->cels + c; + if( cel->layer->flags & ASE_LAYER_FLAGS_VISIBLE ) { + if( cel->x < x ) x = cel->x; + if( cel->h < y ) y = cel->y; + if( (cel->x + cel->w) > x2 ) x2 = cel->x + cel->w; + if( (cel->y + cel->h) > y2 ) y2 = cel->y + cel->h; + } + } + if (x2 <= 0 || y2 <= 0) { // submit empty frame + img.width = 1; + img.height = 1; + img.pixels = calloc(1, 1*1*4); + array_push(images, img); + continue; + } + int cx = x; + int cy = y; + int cw = x2-x; + int ch = y2-y; + int tn = 4; + int tw = ase->w; + + // find clip + img.width = cw; + img.height = ch; + img.pixels = calloc(1, cw*ch*4); // @fixme: because of a stbi_image_free() within rescale section, this should be allocated with stbi allocator + for( unsigned y = 0; y < ch; ++y ) + memcpy((char *)img.pixels + (0+(0+y)*cw)*tn, (char*)frame->pixels + (cx+(cy+y)*tw)*tn, cw*tn); + array_push(images, img); + } + + static int slice_idx = -1; + static int slice_frame_idx = 0; + static const char *slice_name = 0; + if(!atlas_slices) strcatf(&atlas_slices, "[slices]\n"); + + for( int t = 0; t < ase->slice_count; ++t) { + ase_slice_t *slice = ase->slices + t; + if (!slice_name || strcmp(slice_name, slice->name)) { + ++slice_idx; + strcatf(&atlas_slices, "[%d].sl_name=%s\n", slice_idx, slice->name); + strcatf(&atlas_slices, "[%d].sl_frames=", slice_idx); + for( int u = 0; u < ase->slice_count; ++u) { + if (!strcmp(slice->name, ase->slices[u].name)) { + strcatf(&atlas_slices, "%d,", u); + } + } + strcatf(&atlas_slices, "\n"); + } + strcatf(&atlas_slices, "[%d].sl_bounds=%d,%d,%d,%d\n", slice_idx, slice->origin_x, slice->origin_y, slice->w, slice->h); + strcatf(&atlas_slices, "[%d].sl_9slice=%d\n", slice_idx, slice->has_center_as_9_slice); + if (slice->has_center_as_9_slice) + strcatf(&atlas_slices, "[%d].sl_core=%d,%d,%d,%d\n", slice_idx, slice->center_x, slice->center_y, slice->center_w, slice->center_h); + + slice_name = slice->name; + ++slice_frame_idx; + } + + static int anim_idx = 0; + if(!atlas_anims) strcatf(&atlas_anims, "[anims]\n"); + + for( int t = 0; t < ase->tag_count; ++t) { + ase_tag_t *tag = ase->tags + t; + + // find full name + int range[2] = {tag->from_frame, tag->to_frame}; + char name[256] = {0}; + for( int tt = 0; tt < ase->tag_count; ++tt ) { + ase_tag_t *ttag = ase->tags + tt; + if( range[0] >= ttag->from_frame && range[1] <= ttag->to_frame ) + strcat(name, "."), strcat(name, ttag->name); + } + trimspace(name); + + char *sep = ""; + strcatf(&atlas_anims, "[%d].name=%s.%s\n", anim_idx, atlas_current_anim, name+1); + strcatf(&atlas_anims, "[%d].frames=", anim_idx); + if( tag->loop_animation_direction != ASE_ANIMATION_DIRECTION_BACKWARDS) + for( int from = tag->from_frame; from <= tag->to_frame; ++from ) { + strcatf(&atlas_anims, "%s%d,%d", sep, from, ase->frames[from].duration_milliseconds), sep = ","; + } + sep = ""; + if( tag->loop_animation_direction != ASE_ANIMATION_DIRECTION_FORWARDS) + for( int from = tag->from_frame; from <= tag->to_frame; ++from ) { + strcatf(&atlas_anims, "%s%d,%d", sep, from, ase->frames[from].duration_milliseconds), sep = ","; + } + strcatf(&atlas_anims,"\n"); + + ++anim_idx; + } + } + + if( loaded ) continue; + } +#endif + + if (!img.pixels) { + continue; //< @r-lyeh: keep going + + snprintf(g_error_str, sizeof(g_error_str), "invalid image format: %s", files[i]); + goto err_cleanup; + } + + if( !img.name ) img.name = STRDUP(files[i]); + + array_push(images, img); + } + + atlas_t* atlas = atlas_loadimages(images, flags); + return atlas; + +err_cleanup: + for (int i = 0; i < array_count(images); i++) { + if (images[i].pixels) { + stbi_image_free(images[i].pixels); + } + if (images[i].name) { + ATLAS_FREE(images[i].name); + } + } + array_free(images); + return NULL; +} + +void atlas_free(atlas_t* atlas) +{ + assert(atlas); + + if (atlas->sprites) + atlas__free_sprites(atlas->sprites, atlas->num_sprites); + if (atlas->frames) + ATLAS_FREE(atlas->frames); + if (atlas->output.pixels) + ATLAS_FREE(atlas->output.pixels); + ATLAS_FREE(atlas); +} + + + +// custom write function +typedef struct { + int offset; + void *buffer; +} stbi_mem_context; +static void stbi_write_mem(void *context, void *data, int size) { + stbi_mem_context *ctx = (stbi_mem_context*)context; + memcpy( ctx->buffer, data, size ); + ctx->offset += size; +} + +bool atlas_save(const char *outfile, const atlas_t *atlas, atlas_flags flags) +{ + assert(outfile); + + const bool is_file = strcmp(outfile, "stdout"); + const atlas_sprite* sprites = atlas->sprites; + const int* frames = atlas->frames; + const int num_frames = atlas->num_frames; + const int num_sprites = atlas->num_sprites; + const uint8_t* dst = atlas->output.pixels; + const int dst_w = atlas->output.width; + const int dst_h = atlas->output.height; + + char image_filepath[256]; + char image_filename[256]; + snprintf(image_filepath, sizeof(image_filepath), "%s.png", outfile); + path_basename(image_filename, sizeof(image_filename), image_filepath); + + stbi_write_png_compression_level = 5; // 8 + + // write texture, if needed + if( is_file ) { + if (!stbi_write_png(image_filepath, dst_w, dst_h, 4, dst, dst_w * 4)) { + fprintf(stderr, "could not write image file `%s`\n", image_filepath); + return false; + } + } + + // write atlas description into .ini file + FILE *writer = is_file ? fopen(outfile, "wt") : stdout; + if (!writer) { + fprintf(stderr, "could not write ini file `%s`\n", outfile); + return false; + } + + fprintf(writer, "[atlas]\n"); + + if (is_file) { + fprintf(writer, "file=%s\n", image_filepath); + } else { + stbi_mem_context ctx = {0, ATLAS_CALLOC(1, dst_w*dst_h*4+256) }; + int result = stbi_write_png_to_func(stbi_write_mem, &ctx, dst_w, dst_h, 4, dst, dst_w*4); + char *b64 = base64_encode(ctx.buffer, ctx.offset); + fprintf(writer, "bitmap=%s\n", b64); // %d:%s\n", ctx.offset, b64); + ATLAS_FREE(ctx.buffer); + FREE(b64); + } + + fprintf(writer, "size=%d,%d\n", dst_w, dst_h); + fprintf(writer, "border=%d,%d\n", flags.border, flags.border); + fprintf(writer, "padding=%d,%d\n", flags.padding, flags.padding); + + for( int i = 0; i < num_frames; i++ ) { + const atlas_sprite* spr = sprites + frames[i]; + + char name[256]; + path_unixpath(name, sizeof(name), spr->name ? spr->name : ""); + + if(name[0]) + fprintf(writer, "[%d].name=%s\n", i, name); + fprintf(writer, "[%d].frame=%u\n", i, spr->frame); + //fprintf(writer, "[%d].size=%d,%d\n", i, spr->src_size.n[0], spr->src_size.n[1]); + //fprintf(writer, "[%d].rect=%u,%u,%u,%u\n", i, spr->sprite_rect.f[0], spr->sprite_rect.f[1], spr->sprite_rect.f[2], spr->sprite_rect.f[3]); + fprintf(writer, "[%d].sheet=%u,%u,%u,%u\n", i, spr->sheet_rect.f[0], spr->sheet_rect.f[1], spr->sheet_rect.f[2], spr->sheet_rect.f[3]); + if( spr->num_tris ) { + fprintf(writer, "[%d].indices=", i); // %d:", i, (int)spr->num_tris * 3); + for( int j = 0, jend = (int)spr->num_tris * 3; j < jend; ++j ) + fprintf(writer, "%u%s", spr->tris[j], j < (jend-1) ? "," : "\n"); + + fprintf(writer, "[%d].coords=", i); // %d:", i, spr->num_points*2); + for( int j = 0, jend = spr->num_points; j < jend; j++ ) + fprintf(writer, "%.f,%.f%s", (double)spr->pts[j].x, (double)spr->pts[j].y, j < (jend-1) ? ",":"\n" ); + + fprintf(writer, "[%d].uvs=", i); // %d:", i, spr->num_points*2); + for( int j = 0, jend = spr->num_points; j < jend; j++ ) + fprintf(writer, "%.f,%.f%s", (double)spr->uvs[j].x, (double)spr->uvs[j].y, j < (jend-1) ? ",":"\n" ); + } + } + + if( atlas_anims ) fprintf(writer, "%s\n", atlas_anims); + if( atlas_slices ) fprintf(writer, "%s\n", atlas_slices); + + if(writer != stdout) fclose(writer); + return true; +} + +#endif // ATLASC_IMPLEMENTATION diff --git a/engine/split/3rd_delaunay.h b/engine/split/3rd_delaunay.h new file mode 100644 index 0000000..0ca03dc --- /dev/null +++ b/engine/split/3rd_delaunay.h @@ -0,0 +1,1059 @@ +#ifndef DELAUNAY_H +#define DELAUNAY_H + +/* +** delaunay.c : compute 2D delaunay triangulation in the plane. +** Copyright (C) 2005 Wael El Oraiby +** +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as +** published by the Free Software Foundation, either version 3 of the +** License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +*/ + + + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef double real; + +typedef struct { + real x, y; +} del_point2d_t; + +typedef struct { + /** input points count */ + unsigned int num_points; + + /** the input points */ + del_point2d_t* points; + + /** number of returned faces */ + unsigned int num_faces; + + /** the faces are given as a sequence: num verts, verts indices, num verts, verts indices... + * the first face is the external face */ + unsigned int* faces; +} delaunay2d_t; + +/* + * build the 2D Delaunay triangulation given a set of points of at least 3 points + * + * @points: point set given as a sequence of tuple x0, y0, x1, y1, .... + * @num_points: number of given point + * @preds: the incircle predicate + * @faces: the triangles given as a sequence: num verts, verts indices, num verts, verts indices. + * Note that the first face is the external face + * @return: the created topology + */ +delaunay2d_t* delaunay2d_from(del_point2d_t *points, unsigned int num_points); + +/* + * release a delaunay2d object + */ +void delaunay2d_release(delaunay2d_t* del); + + +typedef struct { + /** input points count */ + unsigned int num_points; + + /** input points */ + del_point2d_t* points; + + /** number of triangles */ + unsigned int num_triangles; + + /** the triangles indices v0,v1,v2, v0,v1,v2 .... */ + unsigned int* tris; +} tri_delaunay2d_t; + +/** + * build a tri_delaunay2d_t out of a delaunay2d_t object + */ +tri_delaunay2d_t* tri_delaunay2d_from(delaunay2d_t* del); + +/** + * release a tri_delaunay2d_t object + */ +void tri_delaunay2d_release(tri_delaunay2d_t* tdel); + +#ifdef __cplusplus +} +#endif + +#endif // DELAUNAY_H + +#ifdef DELAUNAY_C + +/* +** delaunay.c : compute 2D delaunay triangulation in the plane. +** Copyright (C) 2005 Wael El Oraiby +** +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as +** published by the Free Software Foundation, either version 3 of the +** License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include + +#define ON_RIGHT 1 +#define ON_SEG 0 +#define ON_LEFT -1 + +#define OUTSIDE -1 +#define ON_CIRCLE 0 +#define INSIDE 1 + +struct point2d_s; +struct face_s; +struct halfedge_s; +struct delaunay_s; + + +#define REAL_ZERO 0.0l +#define REAL_ONE 1.0l +#define REAL_TWO 2.0l +#define REAL_FOUR 4.0l + + +typedef struct point2d_s point2d_t; +typedef struct face_s face_t; +typedef struct halfedge_s halfedge_t; +typedef struct delaunay_s delaunay_t; +typedef struct working_set_s working_set_t; + +typedef long double lreal; +typedef lreal mat3_t[3][3]; + +struct point2d_s { + real x, y; /* point coordinates */ + halfedge_t* he; /* point halfedge */ + unsigned int idx; /* point index in input buffer */ +}; + +struct face_s { + halfedge_t* he; /* a pointing half edge */ + unsigned int num_verts; /* number of vertices on this face */ +}; + +struct halfedge_s { + point2d_t* vertex; /* vertex */ + halfedge_t* pair; /* pair */ + halfedge_t* next; /* next */ + halfedge_t* prev; /* next^-1 */ + face_t* face; /* halfedge face */ +}; + +struct delaunay_s { + halfedge_t* rightmost_he; /* right most halfedge */ + halfedge_t* leftmost_he; /* left most halfedge */ + point2d_t* points; /* pointer to points */ + face_t* faces; /* faces of delaunay */ + unsigned int num_faces; /* face count */ + unsigned int start_point; /* start point index */ + unsigned int end_point; /* end point index */ +}; + +struct working_set_s { + halfedge_t* edges; /* all the edges (allocated in one shot) */ + face_t* faces; /* all the faces (allocated in one shot) */ + + unsigned int max_edge; /* maximum edge count: 2 * 3 * n where n is point count */ + unsigned int max_face; /* maximum face count: 2 * n where n is point count */ + + unsigned int num_edges; /* number of allocated edges */ + unsigned int num_faces; /* number of allocated faces */ + + halfedge_t* free_edge; /* pointer to the first free edge */ + face_t* free_face; /* pointer to the first free face */ +}; + +/* +* 3x3 matrix determinant +*/ +static lreal det3(mat3_t m) +{ + lreal res = m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1]) + - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) + + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]); + + return res; +} + +/* +* allocate a halfedge +*/ +static halfedge_t* halfedge_alloc() +{ + halfedge_t* d; + + d = (halfedge_t*)malloc(sizeof(halfedge_t)); + assert( NULL != d ); + memset(d, 0, sizeof(halfedge_t)); + + return d; +} + +/* +* free a halfedge +*/ +static void halfedge_free( halfedge_t* d ) +{ + assert( d != NULL ); + memset(d, 0, sizeof(halfedge_t)); + free(d); +} + +/* +* free all delaunay halfedges +*/ +void del_free_halfedges( delaunay_t *del ) +{ + unsigned int i; + halfedge_t *d, *sig; + + /* if there is nothing to do */ + if( del->points == NULL ) + return; + + for( i = 0; i <= (del->end_point - del->start_point); i++ ) + { + /* free all the halfedges around the point */ + d = del->points[i].he; + if( d != NULL ) + { + do { + sig = d->next; + halfedge_free( d ); + d = sig; + } while( d != del->points[i].he ); + del->points[i].he = NULL; + } + } +} + +/* +* compare 2 points when sorting +*/ +static int cmp_points( const void *_pt0, const void *_pt1 ) +{ + point2d_t *pt0, *pt1; + + pt0 = (point2d_t*)(_pt0); + pt1 = (point2d_t*)(_pt1); + + if( pt0->x < pt1->x ) + return -1; + else if( pt0->x > pt1->x ) + return 1; + else if( pt0->y < pt1->y ) + return -1; + else if( pt0->y > pt1->y ) + return 1; + printf("2 or more points share the same exact coordinate: (%f,%f)(%f,%f)\n", pt0->x,pt0->y,pt1->x,pt1->y); + assert(0 && "2 or more points share the same exact coordinate"); + return 0; /* Should not be given! */ +} + +/* +* classify a point relative to a segment +*/ +static int classify_point_seg( point2d_t *s, point2d_t *e, point2d_t *pt ) +{ + lreal se_x, se_y, spt_x, spt_y; + lreal res; + + se_x = e->x - s->x; + se_y = e->y - s->y; + + spt_x = pt->x - s->x; + spt_y = pt->y - s->y; + + res = (( se_x * spt_y ) - ( se_y * spt_x )); + if( res < REAL_ZERO ) + return ON_RIGHT; + else if( res > REAL_ZERO ) + return ON_LEFT; + + return ON_SEG; +} + +/* +* classify a point relative to a halfedge, -1 is left, 0 is on, 1 is right +*/ +static int del_classify_point( halfedge_t *d, point2d_t *pt ) +{ + point2d_t *s, *e; + + s = d->vertex; + e = d->pair->vertex; + + return classify_point_seg(s, e, pt); +} + +/* +* test if a point is inside a circle given by 3 points, 1 if inside, 0 if outside +*/ +static int in_circle( point2d_t *pt0, point2d_t *pt1, point2d_t *pt2, point2d_t *p ) +{ + // reduce the computational complexity by substracting the last row of the matrix + // ref: https://www.cs.cmu.edu/~quake/robust.html + lreal p0p_x, p0p_y, p1p_x, p1p_y, p2p_x, p2p_y, p0p, p1p, p2p, res; + mat3_t m; + + p0p_x = pt0->x - p->x; + p0p_y = pt0->y - p->y; + + p1p_x = pt1->x - p->x; + p1p_y = pt1->y - p->y; + + p2p_x = pt2->x - p->x; + p2p_y = pt2->y - p->y; + + p0p = p0p_x * p0p_x + p0p_y * p0p_y; + p1p = p1p_x * p1p_x + p1p_y * p1p_y; + p2p = p2p_x * p2p_x + p2p_y * p2p_y; + + m[0][0] = p0p_x; + m[0][1] = p0p_y; + m[0][2] = p0p; + + m[1][0] = p1p_x; + m[1][1] = p1p_y; + m[1][2] = p1p; + + m[2][0] = p2p_x; + m[2][1] = p2p_y; + m[2][2] = p2p; + + res = -det3(m); + + if( res < REAL_ZERO ) + return INSIDE; + else if( res > REAL_ZERO ) + return OUTSIDE; + + return ON_CIRCLE; +} + +/* +* initialize delaunay segment +*/ +static int del_init_seg( delaunay_t *del, int start ) +{ + halfedge_t *d0, *d1; + point2d_t *pt0, *pt1; + + /* init delaunay */ + del->start_point = start; + del->end_point = start + 1; + + /* setup pt0 and pt1 */ + pt0 = &(del->points[start]); + pt1 = &(del->points[start + 1]); + + /* allocate the halfedges and setup them */ + d0 = halfedge_alloc(); + d1 = halfedge_alloc(); + + d0->vertex = pt0; + d1->vertex = pt1; + + d0->next = d0->prev = d0; + d1->next = d1->prev = d1; + + d0->pair = d1; + d1->pair = d0; + + pt0->he = d0; + pt1->he = d1; + + del->rightmost_he = d1; + del->leftmost_he = d0; + + + return 0; +} + +/* +* initialize delaunay triangle +*/ +static int del_init_tri( delaunay_t *del, int start ) +{ + halfedge_t *d0, *d1, *d2, *d3, *d4, *d5; + point2d_t *pt0, *pt1, *pt2; + + /* initiate delaunay */ + del->start_point = start; + del->end_point = start + 2; + + /* setup the points */ + pt0 = &(del->points[start]); + pt1 = &(del->points[start + 1]); + pt2 = &(del->points[start + 2]); + + /* allocate the 6 halfedges */ + d0 = halfedge_alloc(); + d1 = halfedge_alloc(); + d2 = halfedge_alloc(); + d3 = halfedge_alloc(); + d4 = halfedge_alloc(); + d5 = halfedge_alloc(); + + if( classify_point_seg(pt0, pt2, pt1) == ON_LEFT ) /* first case */ + { + /* set halfedges points */ + d0->vertex = pt0; + d1->vertex = pt2; + d2->vertex = pt1; + + d3->vertex = pt2; + d4->vertex = pt1; + d5->vertex = pt0; + + /* set points halfedges */ + pt0->he = d0; + pt1->he = d2; + pt2->he = d1; + + /* next and next -1 setup */ + d0->next = d5; + d0->prev = d5; + + d1->next = d3; + d1->prev = d3; + + d2->next = d4; + d2->prev = d4; + + d3->next = d1; + d3->prev = d1; + + d4->next = d2; + d4->prev = d2; + + d5->next = d0; + d5->prev = d0; + + /* set halfedges pair */ + d0->pair = d3; + d3->pair = d0; + + d1->pair = d4; + d4->pair = d1; + + d2->pair = d5; + d5->pair = d2; + + del->rightmost_he = d1; + del->leftmost_he = d0; + + } else /* 2nd case */ + { + /* set halfedges points */ + d0->vertex = pt0; + d1->vertex = pt1; + d2->vertex = pt2; + + d3->vertex = pt1; + d4->vertex = pt2; + d5->vertex = pt0; + + /* set points halfedges */ + pt0->he = d0; + pt1->he = d1; + pt2->he = d2; + + /* next and next -1 setup */ + d0->next = d5; + d0->prev = d5; + + d1->next = d3; + d1->prev = d3; + + d2->next = d4; + d2->prev = d4; + + d3->next = d1; + d3->prev = d1; + + d4->next = d2; + d4->prev = d2; + + d5->next = d0; + d5->prev = d0; + + /* set halfedges pair */ + d0->pair = d3; + d3->pair = d0; + + d1->pair = d4; + d4->pair = d1; + + d2->pair = d5; + d5->pair = d2; + + del->rightmost_he = d2; + del->leftmost_he = d0; + } + + return 0; +} + +/* +* remove an edge given a halfedge +*/ +static void del_remove_edge( halfedge_t *d ) +{ + halfedge_t *next, *prev, *pair, *orig_pair; + + orig_pair = d->pair; + + next = d->next; + prev = d->prev; + pair = d->pair; + + assert(next != NULL); + assert(prev != NULL); + + next->prev = prev; + prev->next = next; + + + /* check to see if we have already removed pair */ + if( pair ) + pair->pair = NULL; + + /* check to see if the vertex points to this halfedge */ + if( d->vertex->he == d ) + d->vertex->he = next; + + d->vertex = NULL; + d->next = NULL; + d->prev = NULL; + d->pair = NULL; + + next = orig_pair->next; + prev = orig_pair->prev; + pair = orig_pair->pair; + + assert(next != NULL); + assert(prev != NULL); + + next->prev = prev; + prev->next = next; + + + /* check to see if we have already removed pair */ + if( pair ) + pair->pair = NULL; + + /* check to see if the vertex points to this halfedge */ + if( orig_pair->vertex->he == orig_pair ) + orig_pair->vertex->he = next; + + orig_pair->vertex = NULL; + orig_pair->next = NULL; + orig_pair->prev = NULL; + orig_pair->pair = NULL; + + + /* finally free the halfedges */ + halfedge_free(d); + halfedge_free(orig_pair); +} + +/* +* pass through all the halfedges on the left side and validate them +*/ +static halfedge_t* del_valid_left( halfedge_t* b ) +{ + point2d_t *g, *d, *u, *v; + halfedge_t *c, *du, *dg; + + g = b->vertex; /* base halfedge point */ + dg = b; + + d = b->pair->vertex; /* pair(halfedge) point */ + b = b->next; + + u = b->pair->vertex; /* next(pair(halfedge)) point */ + du = b->pair; + + v = b->next->pair->vertex; /* pair(next(next(halfedge)) point */ + + if( classify_point_seg(g, d, u) == ON_LEFT ) + { + /* 3 points aren't colinear */ + /* as long as the 4 points belong to the same circle, do the cleaning */ + assert( v != u && "1: floating point precision error"); + while( v != d && v != g && in_circle(g, d, u, v) == INSIDE ) + { + c = b->next; + du = b->next->pair; + del_remove_edge(b); + b = c; + u = du->vertex; + v = b->next->pair->vertex; + } + + assert( v != u && "2: floating point precision error"); + if( v != d && v != g && in_circle(g, d, u, v) == ON_CIRCLE ) + { + du = du->prev; + del_remove_edge(b); + } + } else /* treat the case where the 3 points are colinear */ + du = dg; + + assert(du->pair); + return du; +} + +/* +* pass through all the halfedges on the right side and validate them +*/ +static halfedge_t* del_valid_right( halfedge_t *b ) +{ + point2d_t *rv, *lv, *u, *v; + halfedge_t *c, *dd, *du; + + b = b->pair; + rv = b->vertex; + dd = b; + lv = b->pair->vertex; + b = b->prev; + u = b->pair->vertex; + du = b->pair; + + v = b->prev->pair->vertex; + + if( classify_point_seg(lv, rv, u) == ON_LEFT ) + { + assert( v != u && "1: floating point precision error"); + while( v != lv && v != rv && in_circle(lv, rv, u, v) == INSIDE ) + { + c = b->prev; + du = c->pair; + del_remove_edge(b); + b = c; + u = du->vertex; + v = b->prev->pair->vertex; + } + + assert( v != u && "1: floating point precision error"); + if( v != lv && v != rv && in_circle(lv, rv, u, v) == ON_CIRCLE ) + { + du = du->next; + del_remove_edge(b); + } + } else + du = dd; + + assert(du->pair); + return du; +} + + +/* +* validate a link +*/ +static halfedge_t* del_valid_link( halfedge_t *b ) +{ + point2d_t *g, *g_p, *d, *d_p; + halfedge_t *gd, *dd, *new_gd, *new_dd; + int a; + + g = b->vertex; + gd = del_valid_left(b); + g_p = gd->vertex; + + assert(b->pair); + d = b->pair->vertex; + dd = del_valid_right(b); + d_p = dd->vertex; + assert(b->pair); + + if( g != g_p && d != d_p ) { + a = in_circle(g, d, g_p, d_p); + + if( a != ON_CIRCLE ) { + if( a == INSIDE ) { + g_p = g; + gd = b; + } else { + d_p = d; + dd = b->pair; + } + } + } + + /* create the 2 halfedges */ + new_gd = halfedge_alloc(); + new_dd = halfedge_alloc(); + + /* setup new_gd and new_dd */ + + new_gd->vertex = gd->vertex; + new_gd->pair = new_dd; + new_gd->prev = gd; + new_gd->next = gd->next; + gd->next->prev = new_gd; + gd->next = new_gd; + + new_dd->vertex = dd->vertex; + new_dd->pair = new_gd; + new_dd->prev = dd->prev; + dd->prev->next = new_dd; + new_dd->next = dd; + dd->prev = new_dd; + + return new_gd; +} + +/* +* find the lower tangent between the two delaunay, going from left to right (returns the left half edge) +*/ +static halfedge_t* del_get_lower_tangent( delaunay_t *left, delaunay_t *right ) +{ + point2d_t *pl, *pr; + halfedge_t *right_d, *left_d, *new_ld, *new_rd; + int sl, sr; + + left_d = left->rightmost_he; + right_d = right->leftmost_he; + + do { + pl = left_d->prev->pair->vertex; + pr = right_d->pair->vertex; + + if( (sl = classify_point_seg(left_d->vertex, right_d->vertex, pl)) == ON_RIGHT ) { + left_d = left_d->prev->pair; + } + + if( (sr = classify_point_seg(left_d->vertex, right_d->vertex, pr)) == ON_RIGHT ) { + right_d = right_d->pair->next; + } + + } while( sl == ON_RIGHT || sr == ON_RIGHT ); + + /* create the 2 halfedges */ + new_ld = halfedge_alloc(); + new_rd = halfedge_alloc(); + + /* setup new_gd and new_dd */ + new_ld->vertex = left_d->vertex; + new_ld->pair = new_rd; + new_ld->prev = left_d->prev; + left_d->prev->next = new_ld; + new_ld->next = left_d; + left_d->prev = new_ld; + + new_rd->vertex = right_d->vertex; + new_rd->pair = new_ld; + new_rd->prev = right_d->prev; + right_d->prev->next = new_rd; + new_rd->next = right_d; + right_d->prev = new_rd; + + return new_ld; +} + +/* +* link the 2 delaunay together +*/ +static void del_link( delaunay_t *result, delaunay_t *left, delaunay_t *right ) +{ + point2d_t *u, *v, *ml, *mr; + halfedge_t *base; + + assert( left->points == right->points ); + + /* save the most right point and the most left point */ + ml = left->leftmost_he->vertex; + mr = right->rightmost_he->vertex; + + base = del_get_lower_tangent(left, right); + + u = base->next->pair->vertex; + v = base->pair->prev->pair->vertex; + + while( del_classify_point(base, u) == ON_LEFT || + del_classify_point(base, v) == ON_LEFT ) + { + base = del_valid_link(base); + u = base->next->pair->vertex; + v = base->pair->prev->pair->vertex; + } + + right->rightmost_he = mr->he; + left->leftmost_he = ml->he; + + /* TODO: this part is not needed, and can be optimized */ + while( del_classify_point( right->rightmost_he, right->rightmost_he->prev->pair->vertex ) == ON_RIGHT ) + right->rightmost_he = right->rightmost_he->prev; + + while( del_classify_point( left->leftmost_he, left->leftmost_he->prev->pair->vertex ) == ON_RIGHT ) + left->leftmost_he = left->leftmost_he->prev; + + result->leftmost_he = left->leftmost_he; + result->rightmost_he = right->rightmost_he; + result->points = left->points; + result->start_point = left->start_point; + result->end_point = right->end_point; +} + +/* +* divide and conquer delaunay +*/ +void del_divide_and_conquer( delaunay_t *del, int start, int end ) +{ + delaunay_t left, right; + int i, n; + + n = (end - start + 1); + + if( n > 3 ) { + i = (n / 2) + (n & 1); + left.points = del->points; + right.points = del->points; + del_divide_and_conquer( &left, start, start + i - 1 ); + del_divide_and_conquer( &right, start + i, end ); + del_link( del, &left, &right ); + } else { + if( n == 3 ) { + del_init_tri( del, start ); + } else { + if( n == 2 ) { + del_init_seg( del, start ); + } + } + } +} + +static void build_halfedge_face( delaunay_t *del, halfedge_t *d ) +{ + halfedge_t *curr; + + /* test if the halfedge has already a pointing face */ + if( d->face != NULL ) + return; + + /* TODO: optimize this */ + del->faces = (face_t*)realloc(del->faces, (del->num_faces + 1) * sizeof(face_t)); + assert( NULL != del->faces ); + + face_t *f = &(del->faces[del->num_faces]); + curr = d; + f->he = d; + f->num_verts = 0; + do { + curr->face = f; + (f->num_verts)++; + curr = curr->pair->prev; + } while( curr != d ); + + (del->num_faces)++; +} + +/* +* build the faces for all the halfedge +*/ +void del_build_faces( delaunay_t *del ) +{ + unsigned int i; + halfedge_t *curr; + + del->num_faces = 0; + del->faces = NULL; + + /* build external face first */ + build_halfedge_face(del, del->rightmost_he->pair); + + for( i = del->start_point; i <= del->end_point; i++ ) + { + curr = del->points[i].he; + + do { + build_halfedge_face( del, curr ); + curr = curr->next; + } while( curr != del->points[i].he ); + } +} + +/* +*/ +delaunay2d_t* delaunay2d_from(del_point2d_t *points, unsigned int num_points) { + delaunay2d_t* res = NULL; + delaunay_t del; + unsigned int i, j, fbuff_size = 0; + unsigned int* faces = NULL; + + /* allocate the points */ + del.points = (point2d_t*)malloc(num_points * sizeof(point2d_t)); + assert( NULL != del.points ); + memset(del.points, 0, num_points * sizeof(point2d_t)); + + /* copy the points */ + for( i = 0; i < num_points; i++ ) + { + del.points[i].idx = i; + del.points[i].x = points[i].x; + del.points[i].y = points[i].y; + } + + qsort(del.points, num_points, sizeof(point2d_t), cmp_points); + + if( num_points >= 3 ) { + del_divide_and_conquer( &del, 0, num_points - 1 ); + + del_build_faces( &del ); + + fbuff_size = 0; + for( i = 0; i < del.num_faces; i++ ) + fbuff_size += del.faces[i].num_verts + 1; + + faces = (unsigned int*)malloc(sizeof(unsigned int) * fbuff_size); + assert( NULL != faces ); + + j = 0; + for( i = 0; i < del.num_faces; i++ ) + { + halfedge_t *curr; + + faces[j] = del.faces[i].num_verts; + j++; + + curr = del.faces[i].he; + do { + faces[j] = curr->vertex->idx; + j++; + curr = curr->pair->prev; + } while( curr != del.faces[i].he ); + } + + del_free_halfedges( &del ); + + free(del.faces); + free(del.points); + } + + res = (delaunay2d_t*)malloc(sizeof(delaunay2d_t)); + assert( NULL != res ); + res->num_points = num_points; + res->points = (del_point2d_t*)malloc(sizeof(del_point2d_t) * num_points); + assert( NULL != res->points ); + memcpy(res->points, points, sizeof(del_point2d_t) * num_points); + res->num_faces = del.num_faces; + res->faces = faces; + + return res; +} + +void delaunay2d_release(delaunay2d_t *del) { + free(del->faces); + free(del->points); + free(del); +} + + +tri_delaunay2d_t* tri_delaunay2d_from(delaunay2d_t* del) { + unsigned int v_offset = del->faces[0] + 1; /* ignore external face */ + unsigned int dst_offset = 0; + unsigned int i; + + tri_delaunay2d_t* tdel = (tri_delaunay2d_t*)malloc(sizeof(tri_delaunay2d_t)); + assert( NULL != tdel ); + tdel->num_triangles = 0; + + /* count the number of triangles */ + if( 1 == del->num_faces ) { /* degenerate case: only external face exists */ + unsigned int nv = del->faces[0]; + tdel->num_triangles += nv - 2; + } else { + for( i = 1; i < del->num_faces; ++i ) { + unsigned int nv = del->faces[v_offset]; + tdel->num_triangles += nv - 2; + v_offset += nv + 1; + } + } + + /* copy points */ + tdel->num_points = del->num_points; + tdel->points = (del_point2d_t*)malloc(sizeof(del_point2d_t) * del->num_points); + assert( NULL != tdel->points ); + memcpy(tdel->points, del->points, sizeof(del_point2d_t) * del->num_points); + + /* build the triangles */ + tdel->tris = (unsigned int*)malloc(sizeof(unsigned int) * 3 * tdel->num_triangles); + assert( NULL != tdel->tris ); + + v_offset = del->faces[0] + 1; /* ignore external face */ + + if( 1 == del->num_faces ) { + /* handle the degenerated case where only the external face exists */ + unsigned int nv = del->faces[0]; + unsigned int j = 0; + v_offset = 1; + for( ; j < nv - 2; ++j ) { + tdel->tris[dst_offset] = del->faces[v_offset + j]; + tdel->tris[dst_offset + 1] = del->faces[(v_offset + j + 1) % nv]; + tdel->tris[dst_offset + 2] = del->faces[v_offset + j]; + dst_offset += 3; + } + } else { + for( i = 1; i < del->num_faces; ++i ) { + unsigned int nv = del->faces[v_offset]; + unsigned int j = 0; + unsigned int first = del->faces[v_offset + 1]; + + + for( ; j < nv - 2; ++j ) { + tdel->tris[dst_offset] = first; + tdel->tris[dst_offset + 1] = del->faces[v_offset + j + 2]; + tdel->tris[dst_offset + 2] = del->faces[v_offset + j + 3]; + dst_offset += 3; + } + + v_offset += nv + 1; + } + } + + return tdel; +} + + +void tri_delaunay2d_release(tri_delaunay2d_t* tdel) { + free(tdel->tris); + free(tdel->points); + free(tdel); +} + +#endif diff --git a/engine/split/3rd_icon_md.h b/engine/split/3rd_icon_md.h index 1b74cb4..36d2865 100644 --- a/engine/split/3rd_icon_md.h +++ b/engine/split/3rd_icon_md.h @@ -4,11 +4,11 @@ #ifndef ICON_MD_H #define ICON_MD_H -#define FONT_ICON_FILE_NAME_MD "MaterialIcons-Regular.ttf" +#define ICON_MD_FILENAME "MaterialIcons-Regular.ttf" -#define ICON_MIN_MD 0xe000 -#define ICON_MAX_16_MD 0xf8ff -#define ICON_MAX_MD 0x10fffd +#define ICON_MD_MIN 0xe000 +#define ICON_MD_MAX_16 0xf8ff +#define ICON_MD_MAX 0x10fffd #define ICON_MD_10K "\xee\xa5\x91" // U+e951 #define ICON_MD_10MP "\xee\xa5\x92" // U+e952 #define ICON_MD_11MP "\xee\xa5\x93" // U+e953 diff --git a/engine/split/3rd_icon_mdi.h b/engine/split/3rd_icon_mdi.h index 8a795c9..fa4a08c 100644 --- a/engine/split/3rd_icon_mdi.h +++ b/engine/split/3rd_icon_mdi.h @@ -3,11 +3,11 @@ // for use with https://github.com/Templarian/MaterialDesign-Webfont/raw/master/fonts/materialdesignicons-webfont.ttf #pragma once -#define FONT_ICON_FILE_NAME_MDI "materialdesignicons-webfont.ttf" +#define ICON_MDI_FILENAME "materialdesignicons-webfont.ttf" -#define ICON_MIN_MDI 0xF68C -#define ICON_MAX_16_MDI 0xF68C -#define ICON_MAX_MDI 0xF1CC7 +#define ICON_MDI_MIN 0xF68C +#define ICON_MDI_MAX_16 0xF68C +#define ICON_MDI_MAX 0xF1CC7 #define ICON_MDI_AB_TESTING "\xf3\xb0\x87\x89" // U+F01C9 #define ICON_MDI_ABACUS "\xf3\xb1\x9b\xa0" // U+F16E0 #define ICON_MDI_ABJAD_ARABIC "\xf3\xb1\x8c\xa8" // U+F1328 diff --git a/engine/split/3rd_lite.h b/engine/split/3rd_lite.h index dc7574a..5787ce6 100644 --- a/engine/split/3rd_lite.h +++ b/engine/split/3rd_lite.h @@ -125,7 +125,7 @@ struct RenFont { static struct { int left, top, right, bottom; } lt_clip; -static const char* codepoint_to_utf8(unsigned c) { //< @r-lyeh +static const char* codepoint_to_utf8_(unsigned c) { //< @r-lyeh static char s[4+1]; lt_memset(s, 0, 5); /**/ if (c < 0x80) s[0] = c, s[1] = 0; @@ -134,7 +134,7 @@ static const char* codepoint_to_utf8(unsigned c) { //< @r-lyeh 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; } -static const char* utf8_to_codepoint(const char *p, unsigned *dst) { +static const char* utf8_to_codepoint_(const char *p, unsigned *dst) { unsigned res, n; switch (*p & 0xf0) { case 0xf0 : res = *p & 0x07; n = 3; break; @@ -310,7 +310,7 @@ int ren_get_font_width(RenFont *font, const char *text) { const char *p = text; unsigned codepoint; while (*p) { - p = utf8_to_codepoint(p, &codepoint); + p = utf8_to_codepoint_(p, &codepoint); GlyphSet *set = get_glyphset(font, codepoint); stbtt_bakedchar *g = &set->glyphs[codepoint & 0xff]; x += g->xadvance; @@ -415,7 +415,7 @@ int ren_draw_text(RenFont *font, const char *text, int x, int y, RenColor color) const char *p = text; unsigned codepoint; while (*p) { - p = utf8_to_codepoint(p, &codepoint); + p = utf8_to_codepoint_(p, &codepoint); GlyphSet *set = get_glyphset(font, codepoint); stbtt_bakedchar *g = &set->glyphs[codepoint & 0xff]; rect.x = g->x0; diff --git a/engine/split/3rd_lite_sys.h b/engine/split/3rd_lite_sys.h index 475add4..cc9cc82 100644 --- a/engine/split/3rd_lite_sys.h +++ b/engine/split/3rd_lite_sys.h @@ -206,7 +206,7 @@ int printi(int i) { return i; } -static const char* codepoint_to_utf8(unsigned c); +static const char* codepoint_to_utf8_(unsigned c); int lt_poll_event(lua_State *L) { // init.lua > core.step() wakes on mousemoved || inputtext int rc = 0; char buf[16]; @@ -250,7 +250,7 @@ int lt_poll_event(lua_State *L) { // init.lua > core.step() wakes on mousemoved goto bottom; break; case GLEQ_CODEPOINT_INPUT: - rc += lt_emit_event(L, "textinput", "s", codepoint_to_utf8(e.codepoint)); + rc += lt_emit_event(L, "textinput", "s", codepoint_to_utf8_(e.codepoint)); break; case GLEQ_BUTTON_PRESSED: rc += lt_emit_event(L, "mousepressed", "sddd", lt_button_name(e.mouse.button), lt_mx, lt_my, printi(1 + clicks)); diff --git a/engine/split/3rd_mid.h b/engine/split/3rd_mid.h new file mode 100644 index 0000000..61b6d64 --- /dev/null +++ b/engine/split/3rd_mid.h @@ -0,0 +1,464 @@ +/* +------------------------------------------------------------------------------ + Licensing information can be found at the end of the file. +------------------------------------------------------------------------------ + +mid.h - v0.1 - Midi playback library using the TinySoundFont library. + +Do this: + #define MID_IMPLEMENTATION +before you include this file in *one* C/C++ file to create the implementation. +*/ + +#ifndef mid_h +#define mid_h + +#define _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_SECURE_NO_WARNINGS +#include + +typedef struct mid_t mid_t; +typedef struct tsf tsf; + +mid_t* mid_create( void const* midi_data, size_t midi_size, void* memctx ); +void mid_destroy( mid_t* mid ); + +int mid_render_short( mid_t* mid, short* sample_pairs, int sample_pairs_count, tsf* sound_font ); +int mid_render_float( mid_t* mid, float* sample_pairs, int sample_pairs_count, tsf* sound_font ); + +void mid_skip_leading_silence( mid_t* mid, tsf* sound_font ); + +#endif /* mid_h */ + +#ifdef MID_ENABLE_RAW + +#ifndef mid_raw_h +#define mid_raw_h + +#ifndef MID_U8 + #define MID_U8 unsigned char +#endif + +#ifndef MID_U16 + #define MID_U16 unsigned short +#endif + +#ifndef MID_U32 + #define MID_U32 unsigned int +#endif + +#ifndef MID_U64 + #define MID_U64 unsigned long long +#endif + +typedef struct mid_event_t + { + MID_U32 delay_us; + MID_U8 channel; + MID_U8 type; + union + { + struct { MID_U8 program; } program_change; + struct { MID_U8 note; MID_U8 velocity; } note_on; + struct { MID_U8 note; } note_off; + struct { MID_U8 key; MID_U8 key_pressure; } key_pressure; + struct { MID_U16 value; } pitch_bend; + struct { MID_U8 control, control_value; } control_change; + struct { MID_U8 channel_pressure; } channel_pressure; + } data; + } mid_event_t; + + +typedef struct mid_song_t + { + int event_count; + mid_event_t* events; + } mid_song_t; + + +struct mid_t + { + void* memctx; + mid_song_t song; + int percussion_preset; + MID_U64 playback_accumulated_time_us; + int playback_sample_pos; + int playback_event_pos; + }; + +int mid_init_raw( mid_t* mid, void const* raw_data, size_t raw_size ); + +size_t mid_save_raw( mid_t* mid, void* data, size_t capacity ); + + +#endif /* MID_ENABLE_RAW */ + +#endif /* mid_raw_h */ + +/* +---------------------- + IMPLEMENTATION +---------------------- +*/ + +#ifdef MID_IMPLEMENTATION +#undef MID_IMPLEMENTATION + +#ifndef MID_U8 + #define MID_U8 unsigned char +#endif + +#ifndef MID_U16 + #define MID_U16 unsigned short +#endif + +#ifndef MID_U32 + #define MID_U32 unsigned int +#endif + +#ifndef MID_U64 + #define MID_U64 unsigned long long +#endif + +#ifndef MID_MALLOC + #define _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_SECURE_NO_WARNINGS + #include + #if defined(_cplusplus) + #define MID_MALLOC( ctx, size ) ( ::malloc( size ) ) + #define MID_FREE( ctx, ptr ) ( ::free( ptr ) ) + #else + #define MID_MALLOC( ctx, size ) ( malloc( size ) ) + #define MID_FREE( ctx, ptr ) ( free( ptr ) ) + #endif +#endif +#include +#define MID_LOG(...) (void) __VA_ARGS__ + +#include + +#pragma warning( push ) +#pragma warning( disable: 4242 ) +#pragma warning( disable: 4244 ) +#pragma warning( disable: 4365 ) +#pragma warning( disable: 4668 ) +#pragma warning( disable: 4701 ) +#pragma warning( disable: 4703 ) + +#ifndef MID_NO_TSF_IMPLEMENTATION + #define TSF_NO_STDIO + #define TSF_IMPLEMENTATION +#endif +#include "3rd_tsf.h" + +#pragma warning( disable: 4201 ) + +#ifndef MID_NO_TML_IMPLEMENTATION + #define TML_NO_STDIO + #define TML_IMPLEMENTATION +#endif +#include "3rd_tml.h" + +#pragma warning( pop ) + + + + +#ifndef MID_ENABLE_RAW + +typedef struct mid_event_t + { + MID_U32 delay_us; + MID_U8 channel; + MID_U8 type; + union + { + struct { MID_U8 program; } program_change; + struct { MID_U8 note; MID_U8 velocity; } note_on; + struct { MID_U8 note; } note_off; + struct { MID_U8 key; MID_U8 key_pressure; } key_pressure; + struct { MID_U16 value; } pitch_bend; + struct { MID_U8 control, control_value; } control_change; + struct { MID_U8 channel_pressure; } channel_pressure; + } data; + } mid_event_t; + + +typedef struct mid_song_t + { + int event_count; + mid_event_t* events; + } mid_song_t; + + +struct mid_t + { + void* memctx; + mid_song_t song; + int percussion_preset; + MID_U64 playback_accumulated_time_us; + int playback_sample_pos; + int playback_event_pos; + }; + + +#endif /* MID_ENABLE_RAW */ + + +mid_t* mid_create( void const* midi_data, size_t midi_size, void* memctx ) + { + tml_message* mid_file = tml_load_memory( midi_data, (int) midi_size ); + if( !mid_file ) return NULL; + int count = 0; + tml_message* iter = mid_file; + while( iter ) + { + if( iter->type == TML_PROGRAM_CHANGE || iter->type == TML_NOTE_ON || iter->type == TML_NOTE_OFF || + iter->type == TML_PITCH_BEND || iter->type == TML_CONTROL_CHANGE ) + { + ++count; + } + iter = iter->next; + } + + mid_event_t* events = (mid_event_t*) malloc( sizeof( mid_event_t ) * count ); + int events_count = 0; + unsigned int time = 0; + tml_message* msg = mid_file; + while( msg ) + { + if( msg->type == TML_PROGRAM_CHANGE || msg->type == TML_NOTE_ON || msg->type == TML_NOTE_OFF || + msg->type == TML_PITCH_BEND || msg->type == TML_CONTROL_CHANGE ) + { + mid_event_t* event = &events[ events_count++ ]; + event->delay_us = ( msg->time - time ) * 1000; + time = msg->time; + event->channel = msg->channel; + event->type = msg->type; + switch( msg->type ) + { + case TML_PROGRAM_CHANGE: + event->data.program_change.program = (MID_U8) msg->program; + break; + case TML_NOTE_ON: //play a note + event->data.note_on.note = (MID_U8) msg->key; + event->data.note_on.velocity = (MID_U8) msg->velocity; + break; + case TML_NOTE_OFF: //stop a note + event->data.note_off.note = (MID_U8) msg->key; + break; + case TML_PITCH_BEND: //pitch wheel modification + event->data.pitch_bend.value = (MID_U16) msg->pitch_bend; + break; + case TML_CONTROL_CHANGE: //MIDI controller messages + event->data.control_change.control = (MID_U8) msg->control; + event->data.control_change.control_value = (MID_U8) msg->control_value; + break; + } + } + + msg = msg->next; + } + + tml_free( mid_file ); + + mid_t* mid = (mid_t*) MID_MALLOC( memctx, sizeof( mid_t ) ); + mid->memctx = memctx; + mid->song.event_count = events_count; + mid->song.events = events; + + mid->playback_accumulated_time_us = 0ull; + mid->playback_sample_pos = 0; + mid->playback_event_pos = 0; + + return mid; + } + + +void mid_destroy( mid_t* mid ) + { + if( mid->song.events ) MID_FREE( mid->memctx, mid->song.events ); + MID_FREE( mid->memctx, mid ); + } + + +int mid_init_raw( mid_t* mid, void const* raw_data, size_t raw_size ) + { + int events_count = *(int*)raw_data; + if( sizeof( mid_event_t ) * events_count != raw_size - sizeof( int ) ) return 0; + + mid->memctx = NULL; + + mid->song.event_count = events_count; + mid->song.events = (mid_event_t*)( ( (int*)raw_data ) + 1 ); + + mid->playback_accumulated_time_us = 0ull; + mid->playback_sample_pos = 0; + mid->playback_event_pos = 0; + + return 1; + } + + +size_t mid_save_raw( mid_t* mid, void* data, size_t capacity ) + { + size_t size = sizeof( mid_event_t ) * mid->song.event_count + sizeof( int ); + if( data && capacity >= size ) + { + *(int*)data = mid->song.event_count; + memcpy( ( (int*)data ) + 1, mid->song.events, sizeof( mid_event_t ) * mid->song.event_count ); + } + return size; + } + + +void mid_skip_leading_silence( mid_t* mid, tsf* sound_font ) + { + (void) sound_font; + for( ; ; ) + { + MID_U64 next_event_delay_us = mid->song.events[ mid->playback_event_pos ].delay_us; + MID_U64 playback_time_us = ( mid->playback_sample_pos * 1000000ull ) / 44100ull; + MID_U64 next_event_time_us = mid->playback_accumulated_time_us + next_event_delay_us; + assert( next_event_time_us >= playback_time_us ); + MID_U64 time_until_next_event = next_event_time_us - playback_time_us; + int samples_until_next_event = (int)( ( time_until_next_event * 44100ull ) / 1000000ull ); + mid_event_t* event = &mid->song.events[ mid->playback_event_pos ]; + switch( event->type ) + { + case TML_PROGRAM_CHANGE: + tsf_channel_set_presetnumber( sound_font, event->channel, event->data.program_change.program, ( event->channel == 9 ) ); + break; + case TML_NOTE_ON: + return; + case TML_NOTE_OFF: //stop a note + tsf_channel_note_off( sound_font, event->channel, event->data.note_off.note ); + break; + case TML_PITCH_BEND: //pitch wheel modification + tsf_channel_set_pitchwheel( sound_font, event->channel, event->data.pitch_bend.value ); + break; + case TML_CONTROL_CHANGE: //MIDI controller messages + tsf_channel_midi_control( sound_font, event->channel, event->data.control_change.control, event->data.control_change.control_value ); + break; + } + mid->playback_sample_pos += samples_until_next_event; + mid->playback_accumulated_time_us += next_event_delay_us; + mid->playback_event_pos++; + } + } + + +int mid_render_short( mid_t* mid, short* sample_pairs, int sample_pairs_count, tsf* sound_font ) + { + int samples_rendered = 0; + memset( sample_pairs, 0, sample_pairs_count * sizeof( short ) * 2 ); + while( samples_rendered < sample_pairs_count ) + { + MID_U64 next_event_delay_us = mid->song.events[ mid->playback_event_pos ].delay_us; + MID_U64 playback_time_us = ( mid->playback_sample_pos * 1000000ull ) / 44100ull; + MID_U64 next_event_time_us = mid->playback_accumulated_time_us + next_event_delay_us; + assert( next_event_time_us >= playback_time_us ); + MID_U64 time_until_next_event = next_event_time_us - playback_time_us; + int samples_until_next_event = (int)( ( time_until_next_event * 44100ull ) / 1000000ull ); + int samples_to_render = samples_until_next_event; + if( samples_to_render > sample_pairs_count - samples_rendered ) + { + samples_to_render = sample_pairs_count - samples_rendered; + tsf_render_short( sound_font, sample_pairs + samples_rendered * 2, + samples_to_render, 1 ); + samples_rendered += samples_to_render; + mid->playback_sample_pos += samples_to_render; + return samples_rendered; + } + else + { + tsf_render_short( sound_font, sample_pairs + samples_rendered * 2, + samples_to_render, 1 ); + samples_rendered += samples_to_render; + mid->playback_sample_pos += samples_to_render; + } + + + mid->playback_accumulated_time_us += next_event_delay_us; + mid_event_t* event = &mid->song.events[ mid->playback_event_pos++ ]; + switch( event->type ) + { + case TML_PROGRAM_CHANGE: + tsf_channel_set_presetnumber( sound_font, event->channel, event->data.program_change.program, ( event->channel == 9 ) ); + break; + case TML_NOTE_ON: + tsf_channel_note_on( sound_font, event->channel, event->data.note_on.note, event->data.note_on.velocity / 127.0f ); + break; + case TML_NOTE_OFF: //stop a note + tsf_channel_note_off( sound_font, event->channel, event->data.note_off.note ); + break; + case TML_PITCH_BEND: //pitch wheel modification + tsf_channel_set_pitchwheel( sound_font, event->channel, event->data.pitch_bend.value ); + break; + case TML_CONTROL_CHANGE: //MIDI controller messages + tsf_channel_midi_control( sound_font, event->channel, event->data.control_change.control, event->data.control_change.control_value ); + break; + } + } + + return samples_rendered; + } + + +#endif /* MID_IMPLEMENTATION */ + +/* +------------------------------------------------------------------------------ + +This software is available under 2 licenses - you may choose the one you like. + +------------------------------------------------------------------------------ + +ALTERNATIVE A - MIT License + +Copyright (c) 2016 Mattias Gustavsson + +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, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. + +------------------------------------------------------------------------------ + +ALTERNATIVE B - Public Domain (www.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. + +------------------------------------------------------------------------------ +*/ diff --git a/engine/split/3rd_sproutline.h b/engine/split/3rd_sproutline.h new file mode 100644 index 0000000..462cbe8 --- /dev/null +++ b/engine/split/3rd_sproutline.h @@ -0,0 +1,441 @@ +/* sproutline - v0.10 - public domain sprite outline detector - http://github.org/ands/sproutline + no warranty implied; use at your own risk + + Do this: + #define S2O_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define S2O_IMPLEMENTATION + #include "sproutline.h" + + You can #define S2O_MALLOC to avoid using malloc + + + QUICK NOTES: + Primarily of interest to game developers. + - Recommended to be used with stb_image. + - Detects outlines in sprite images with alpha channels. + - Extracts outlines as clockwise paths. + - Simplifies outlines based on a distance metric. + + Full documentation under "DOCUMENTATION" below. + + + Revision 0.10 release notes: + + - Initial release of sproutline.h. + + - Added S2O_MALLOC macro for replacing the memory allocator. + Unlike most STB libraries, this macro doesn't support a context parameter, + so if you need to pass a context in to the allocator, you'll have to + store it in a global or a thread-local variable. + + + Revision history: + 0.10 (2015-10-22) initial version + + ============================ Contributors ========================= + + Andreas Mantler (ands) + +License: + This software is in the public domain. Where that dedication is not + recognized, you are granted a perpetual, irrevocable license to copy + and modify this file however you want. + +*/ + +#ifndef S2O_INCLUDE_SPROUTLINE_H +#define S2O_INCLUDE_SPROUTLINE_H + +// DOCUMENTATION +// +// Limitations: +// - currently only works with images that have alpha channels +// +// Basic usage (with stb_image): +// int w, h, n, l; +// unsigned char *rgba = stbi_load(filename, &w, &h, &n, 4); +// unsigned char *alpha = s2o_rgba_to_alpha(rgba, w, h); +// unsigned char *thresholded = s2o_alpha_to_thresholded(alpha, w, h, ALPHA_THRESHOLD); +// unsigned char *outlined = s2o_thresholded_to_outlined(thresholded, w, h); +// s2o_point *outline = s2o_extract_outline_path(outlined, w, h, &l, 0); +// while(l) +// { +// s2o_distance_based_path_simplification(outline, &l, DISTANCE_THRESHOLD); +// // ... process outline here ... +// // ... l = number of points in outline +// // ... ALPHA_THRESHOLD = 1..255 (the min value to be considered solid) +// // ... DISTANCE_THRESHOLD = 0.0f..Inf (~0.5f is a suitable value) +// // ... a greater value results in fewer points in the output +// +// outline = s2o_extract_outline_path(outlined, w, h, &l, outline); +// }; +// free(outline); +// free(outlined); +// free(thresholded); +// free(alpha); +// free(rgba); +// +// s2o_rgba_to_alpha: +// Expects an 'unsigned char *' to memory of w * h 4-byte pixels in 'RGBA' order. +// The return value is an 'unsigned char *' to memory of w * h 1-byte pixel alpha components. +// +// s2o_alpha_to_thresholded: +// Expects an 'unsigned char *' to memory of w * h 1-byte pixel alpha components. +// The return value is an 'unsigned char *' to memory of w * h 1-byte values +// that are 255 if the corresponding input is >= the specified threshold, otherwise 0. +// +// s2o_thresholded_to_outlined: +// Expects an 'unsigned char *' to memory of w * h 1-byte pixels indicating their solidity {0, nonzero}. +// The return value is an 'unsigned char *' to memory of w * h 1-byte pixels that indicate if the +// corresponding input value is part of an outline (= is solid and has a non-solid neighbour). +// +// s2o_extract_outline_path: +// Expects an 'unsigned char *' to memory of w * h 1-byte pixels indicating their outline membership. +// The return value is an 's2o_point *' to memory of l s2o_point values consisting of a short x and y value. +// The procedure scans the input data from top to bottom and starts extracting the first outline it finds. +// The pixels corresponding to the extracted outline are set to 0 in the input, so that a subsequent call to +// s2o_extract_outline_path extracts a different outline. +// The length is set to 0 if no outline was found. +// +// s2o_distance_based_path_simplification: +// Expects an 's2o_point *' to memory of l outline points. +// The procedure throws out points in place that lie on or close to linear sections of the outline. +// The distanceThreshold parameter specifies the min distance value for points to remain in the outline. +// +// =========================================================================== +// +// Philosophy +// +// This library is designed with the stb philosophy in mind. +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Some secondary priorities arise directly from the first two, some of which +// make more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small footprint ("easy to maintain") +// - No dependencies ("ease of use") +// + +typedef unsigned char s2o_uc; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef S2O_STATIC +#define S2ODEF static +#else +#define S2ODEF extern +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API +// + +S2ODEF s2o_uc * s2o_rgba_to_alpha (const s2o_uc *data, int w, int h); +S2ODEF s2o_uc * s2o_alpha_to_thresholded (const s2o_uc *data, int w, int h, s2o_uc threshold); +S2ODEF s2o_uc * s2o_thresholded_to_outlined(const s2o_uc *data, int w, int h); + +typedef struct { short x, y; } s2o_point; +S2ODEF s2o_point * s2o_extract_outline_path(s2o_uc *data, int w, int h, int *point_count, s2o_point *reusable_outline); +S2ODEF void s2o_distance_based_path_simplification(s2o_point *outline, int *outline_length, float distance_threshold); + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // S2O_INCLUDE_SPROUTLINE_H + +#ifdef S2O_IMPLEMENTATION + +#include // sqrtf, abs + +#ifndef S2O_MALLOC +#include // malloc +#define S2O_MALLOC(sz) malloc(sz) +#endif + +/////////////////////////////////////////////// +// +// locally used types + +typedef int s2o_bool; + +// 2d point type helpers +#define S2O_POINT_ADD(result, a, b) { (result).x = (a).x + (b).x; (result).y = (a).y + (b).y; } +#define S2O_POINT_SUB(result, a, b) { (result).x = (a).x - (b).x; (result).y = (a).y - (b).y; } +#define S2O_POINT_IS_INSIDE(a, w, h) ((a).x >= 0 && (a).y >= 0 && (a).x < (w) && (a).y < (h)) +#define S2O_POINT_IS_NEXT_TO(a, b) ((a).x - (b).x <= 1 && (a).x - (b).x >= -1 && (a).y - (b).y <= 1 && (a).y - (b).y >= -1) + +// direction type +typedef int s2o_direction; // 8 cw directions: >, _|, v, |_, <, |", ^, "| +#define S2O_DIRECTION_OPPOSITE(dir) ((dir + 4) & 7) +static const s2o_point s2o_direction_to_pixel_offset[] = { {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0}, {-1,1}, {0,1}, {1,1} }; + +// image manipulation functions +S2ODEF s2o_uc * s2o_rgba_to_alpha(const s2o_uc *data, int w, int h) +{ + s2o_uc *result = (s2o_uc*)S2O_MALLOC(w * h); + int x, y; + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + result[y * w + x] = data[(y * w + x) * 4 + 3]; + return result; +} + +S2ODEF s2o_uc * s2o_alpha_to_thresholded(const s2o_uc *data, int w, int h, s2o_uc threshold) +{ + s2o_uc *result = (s2o_uc*)S2O_MALLOC(w * h); + int x, y; + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + result[y * w + x] = data[y * w + x] >= threshold ? 255 : 0; + return result; +} + +S2ODEF s2o_uc * s2o_dilate_thresholded(const s2o_uc *data, int w, int h) +{ + int x, y, dx, dy, cx, cy; + s2o_uc *result = (s2o_uc*)S2O_MALLOC(w * h); + for (y = 0; y < h; y++) + { + for (x = 0; x < w; x++) + { + result[y * w + x] = 0; + for (dy = -1; dy <= 1; dy++) + { + for (dx = -1; dx <= 1; dx++) + { + cx = x + dx; + cy = y + dy; + if (cx >= 0 && cx < w && cy >= 0 && cy < h) + { + if (data[cy * w + cx]) + { + result[y * w + x] = 255; + dy = 1; + break; + } + } + } + } + } + } + return result; +} + +S2ODEF s2o_uc * s2o_thresholded_to_outlined(const s2o_uc *data, int w, int h) +{ + s2o_uc *result = (s2o_uc*)S2O_MALLOC(w * h); + int x, y; + for (x = 0; x < w; x++) + { + result[x] = data[x]; + result[(h - 1) * w + x] = data[(h - 1) * w + x]; + } + for (y = 1; y < h - 1; y++) + { + result[y * w] = data[y * w]; + for (x = 1; x < w - 1; x++) + { + if (data[y * w + x] && + ( + !data[y * w + x - 1] || + !data[y * w + x + 1] || + !data[y * w + x - w] || + !data[y * w + x + w] + )) + { + result[y * w + x] = 255; + } + else + { + result[y * w + x] = 0; + } + } + result[y * w + w - 1] = data[y * w + w - 1]; + } + return result; +} + +// outline path procedures +static s2o_bool s2o_find_first_filled_pixel(const s2o_uc *data, int w, int h, s2o_point *first) +{ + int x, y; + for (y = 0; y < h; y++) + { + for (x = 0; x < w; x++) + { + if (data[y * w + x]) + { + first->x = (short)x; + first->y = (short)y; + return 1; + } + } + } + return 0; +} + +static s2o_bool s2o_find_next_filled_pixel(const s2o_uc *data, int w, int h, s2o_point current, s2o_direction *dir, s2o_point *next) +{ + // turn around 180°, then make a clockwise scan for a filled pixel + *dir = S2O_DIRECTION_OPPOSITE(*dir); + int i; + for (i = 0; i < 8; i++) + { + S2O_POINT_ADD(*next, current, s2o_direction_to_pixel_offset[*dir]); + + if (S2O_POINT_IS_INSIDE(*next, w, h) && data[next->y * w + next->x]) + return 1; + + // move to next angle (clockwise) + *dir = *dir - 1; + if (*dir < 0) + *dir = 7; + } + return 0; +} + +S2ODEF s2o_point * s2o_extract_outline_path(s2o_uc *data, int w, int h, int *point_count, s2o_point *reusable_outline) +{ + s2o_point *outline = reusable_outline; + if (!outline) + outline = (s2o_point*)S2O_MALLOC(w * h * sizeof(s2o_point)); + + s2o_point current, next; + +restart: + if (!s2o_find_first_filled_pixel(data, w, h, ¤t)) + { + *point_count = 0; + return outline; + } + + int count = 0; + s2o_direction dir = 0; + + while(S2O_POINT_IS_INSIDE(current, w, h) && count < (w*h)) //< @r-lyeh: buffer overflow: add count= 0 && count < (w * h); prev--) //< @r-lyeh: buffer overflow: add count 1; l--) + { + int a, b = l; + for (a = 0; a < length; a++) + { + s2o_point ab; + S2O_POINT_SUB(ab, outline[b], outline[a]); + float lab = sqrtf((float)(ab.x * ab.x + ab.y * ab.y)); + float ilab = 1.0f / lab; + float abnx = ab.x * ilab, abny = ab.y * ilab; + + if (lab != 0.0f) + { + s2o_bool found = 1; + int i = (a + 1) % length; + while (i != b) + { + s2o_point ai; + S2O_POINT_SUB(ai, outline[i], outline[a]); + float t = (abnx * ai.x + abny * ai.y) * ilab; + float distance = -abny * ai.x + abnx * ai.y; + if (t < 0.0f || t > 1.0f || distance > distance_threshold || -distance > distance_threshold) + { + found = 0; + break; + } + + if (++i == length) + i = 0; + } + + if (found) + { + int i; + if (a < b) + { + for (i = 0; i < length - b; i++) + outline[a + i + 1] = outline[b + i]; + length -= b - a - 1; + } + else + { + length = a - b + 1; + for (i = 0; i < length; i++) + outline[i] = outline[b + i]; + } + if (l >= length) + l = length - 1; + } + } + + if (++b >= length) + b = 0; + } + } + *outline_length = length; +} + +#endif // S2O_IMPLEMENTATION diff --git a/engine/split/v4k.c.inl b/engine/split/v4k.c.inl index 291e241..71eaab8 100644 --- a/engine/split/v4k.c.inl +++ b/engine/split/v4k.c.inl @@ -126,6 +126,8 @@ {{FILE:v4k_font.c}} +{{FILE:v4k_gui.c}} + {{FILE:v4k_input.c}} {{FILE:v4k_math.c}} diff --git a/engine/split/v4k.h.inl b/engine/split/v4k.h.inl index 43bfeb7..fe7e0c5 100644 --- a/engine/split/v4k.h.inl +++ b/engine/split/v4k.h.inl @@ -145,6 +145,7 @@ extern "C" { {{FILE:v4k_string.h}} {{FILE:v4k_sprite.h}} +{{FILE:v4k_gui.h}} {{FILE:v4k_system.h}} diff --git a/engine/split/v4k_editor.c b/engine/split/v4k_editor.c index 927e134..e9ab31f 100644 --- a/engine/split/v4k_editor.c +++ b/engine/split/v4k_editor.c @@ -507,22 +507,31 @@ void editor_pump() { // ---------------------------------------------------------------------------------------- -API void editor_cursorpos(int x, int y); -void editor_cursorpos(int x, int y) { +void editor_setmouse(int x, int y) { glfwSetCursorPos( window_handle(), x, y ); } -void editor_symbol(int x, int y, const char *sym) { +vec2 editor_glyph(int x, int y, unsigned cp) { // style: atlas size, unicode ranges and 6 font faces max do_once font_face(FONT_FACE2, "MaterialIconsSharp-Regular.otf", 24.f, FONT_EM|FONT_2048); + do_once font_face(FONT_FACE3, "materialdesignicons-webfont.ttf", 24.f, FONT_EM|FONT_2048); // {0xF68C /*ICON_MDI_MIN*/, 0xF1CC7/*ICON_MDI_MAX*/, 0}}, // style: 10 colors max do_once font_color(FONT_COLOR1, WHITE); do_once font_color(FONT_COLOR2, RGBX(0xE8F1FF,128)); // GRAY); do_once font_color(FONT_COLOR3, YELLOW); do_once font_color(FONT_COLOR4, ORANGE); do_once font_color(FONT_COLOR5, CYAN); + const char *sym = codepoint_to_utf8(cp); font_goto(x,y); - font_print(va(FONT_FACE2 /*FONT_WHITE*/ FONT_H1 "%s", sym)); + return font_print(va("%s" FONT_H1 "%s", cp >= ICON_MDI_MIN ? FONT_FACE3 : FONT_FACE2, sym)); +} + +vec2 editor_glyphstr(int x, int y, const char *utf8) { + vec2 dim = {x,y}; + array(unsigned) codepoints = string32(utf8); + for( int i = 0, end = array_count(codepoints); i < end; ++i) + add2(dim, editor_glyph(dim.x,dim.y,codepoints[i])); + return dim; } void editor_frame( void (*game)(unsigned, float, double) ) { diff --git a/engine/split/v4k_editor.h b/engine/split/v4k_editor.h index d6271e6..2602fd9 100644 --- a/engine/split/v4k_editor.h +++ b/engine/split/v4k_editor.h @@ -89,7 +89,9 @@ API void editor_inspect(obj *o); API vec3 editor_pick(float mouse_x, float mouse_y); API char* editor_path(const char *path); -API void editor_symbol(int x, int y, const char *sym); +API void editor_setmouse(int x, int y); +API vec2 editor_glyph(int x, int y, unsigned cp); +API vec2 editor_glyphstr(int x, int y, const char *utf8); API void editor_gizmos(int dim); // ---------------------------------------------------------------------------------------- diff --git a/engine/split/v4k_gui.c b/engine/split/v4k_gui.c new file mode 100644 index 0000000..81a2cb3 --- /dev/null +++ b/engine/split/v4k_gui.c @@ -0,0 +1,297 @@ +// ---------------------------------------------------------------------------- +// game ui (utils) + +API vec2i draw_window_ui(); + +API void draw_rect(int rgba, vec2 start, vec2 end ); +API void draw_rect_tex( texture_t texture, int rgba, vec2 start, vec2 end ); +API void draw_rect_sheet( texture_t spritesheet, vec2 tex_start, vec2 tex_end, int rgba, vec2 start, vec2 end ); + +#define draw_rect_borders(color, x, y, w, h, borderWeight) do { \ + int x1 = (x); \ + int y1 = (y); \ + int x2 = (x) + (w) - 1; \ + int y2 = (y) + (h) - 1; \ + draw_rect(color, vec2(x1, y1), vec2(x2, y1 + (borderWeight) - 1)); \ + draw_rect(color, vec2(x1, y1), vec2(x1 + (borderWeight) - 1, y2)); \ + draw_rect(color, vec2(x1, y2 - (borderWeight) + 1), vec2(x2, y2)); \ + draw_rect(color, vec2(x2 - (borderWeight) + 1, y1), vec2(x2, y2)); \ + } while(0) + +// #define lay_draw_rect(rgba, rect) draw_rect(rgba, vec2(rect.e[0], rect.e[1]), vec2(rect.e[0]+rect.e[2], rect.e[1]+rect.e[3])) +// #define lay_draw_rect_borders(rgba, rect, borderWeight) draw_rect_borders(rgba, rect.e[0], rect.e[1], rect.e[2], rect.e[3], borderWeight) +// #define lay_draw_rect_tex(tex, rgba, rect) draw_rect_tex(tex, rgba, vec2(rect.e[0], rect.e[1]), vec2(rect.e[0]+rect.e[2], rect.e[1]+rect.e[3])) +// #define l2m(rect) (vec4(rect.e[0]+rect.e[2], rect.e[1]+rect.e[3])) +#define v42v2(rect) vec2(rect.x,rect.y), vec2(rect.z,rect.w) + + + +vec2i draw_window_ui() { + vec2 dpi = ifdef(osx, window_dpi(), vec2(1,1)); + int w = window_width(); + int h = window_height(); + return vec2i(w/dpi.x, h/dpi.y); +} + +void draw_rect_sheet( texture_t texture, vec2 tex_start, vec2 tex_end, int rgba, vec2 start, vec2 end ) { + float gamma = 1; + static int program = -1, vbo = -1, vao = -1, u_inv_gamma = -1, u_tint = -1, u_has_tex = -1, u_window_width = -1, u_window_height = -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_inv_gamma = glGetUniformLocation(program, "u_inv_gamma"); + 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); + + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + GLenum texture_type = texture.flags & TEXTURE_ARRAY ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D; +// glEnable( GL_BLEND ); + glUseProgram( program ); + glUniform1f( u_inv_gamma, 1.0f / (gamma + !gamma) ); + + 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 ); +// glDisable( GL_BLEND ); +} + +void draw_rect_tex( texture_t texture, int rgba, vec2 start, vec2 end ) { + draw_rect_sheet(texture, vec2(0, 0), vec2(texture.w, texture.h), rgba, start, end); +} + +void draw_rect(int rgba, vec2 start, vec2 end ) { + draw_rect_tex((texture_t){0}, rgba, start, end); +} + +// ---------------------------------------------------------------------------- +// game ui + +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 + +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; +} + +static +gui_state_t *gui_getstate(int id, int kind) { + if (!ctl_states) map_init(ctl_states, less_int, hash_int); + static gui_state_t st={0}; + st.kind=kind; + return map_find_or_add(ctl_states, id, st); +} + +bool (gui_button)(int id, vec4 r, const char *skin) { + gui_state_t *entry = gui_getstate(id, GUI_BUTTON); + bool was_clicked=0; + entry->hover = false; + + if (input(MOUSE_X) > r.x && input(MOUSE_X) < r.z && input(MOUSE_Y) > r.y && input(MOUSE_Y) < r.w) { + if (input_up(MOUSE_L) && entry->held) { + was_clicked=1; + } + + entry->held = input_held(MOUSE_L); + entry->hover = true; + } + else if (input_up(MOUSE_L) && entry->held) { + entry->held = false; + } + + if (last_skin->draw_rect_func) last_skin->draw_rect_func(last_skin->userdata, *entry, skin, r); + else { + draw_rect(entry->held ? 0x111111FF : entry->hover ? 0xEEEEEEFF : 0xFFFFFFFF, v42v2(r)); + } + + return was_clicked; +} + +void (gui_panel)(int id, vec4 r, const char *skin) { + gui_state_t *entry = gui_getstate(id, GUI_PANEL); + if (last_skin->draw_rect_func) last_skin->draw_rect_func(last_skin->userdata, *entry, skin?skin:"panel", r); + else { + draw_rect(0xFFFFFFFF, v42v2(r)); + } +} + +/* 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, const char *fallback) { + #define atlas_loop(n)\ + for (int i = 0; i < array_count(a->slices); i++)\ + if (!strcmp(quark_string(&a->db, a->slices[i].name), n))\ + return &a->slice_frames[a->slices[i].frames[0]]; + atlas_loop(name); + atlas_loop(fallback); + return NULL; + #undef atlas_loop +} + +static +void skinned_draw_missing_rect(vec4 r) { + draw_rect_tex(texture_checker(), 0xFFFFFFFF, v42v2(r)); +} + +static +void skinned_draw_sprite(float scale, atlas_t *a, atlas_slice_frame_t *f, vec4 r) { + if (!f->has_9slice) { + draw_rect_sheet(a->tex, v42v2(f->bounds), 0xFFFFFFFF, v42v2(r)); + return; + } + + vec4 outer = f->bounds; + 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}; + + draw_rect_sheet(a->tex, v42v2(center_slice), 0xFFFFFFFF, v42v2(center)); + draw_rect_sheet(a->tex, v42v2(top_left_slice), 0xFFFFFFFF, v42v2(top_left)); + draw_rect_sheet(a->tex, v42v2(top_right_slice), 0xFFFFFFFF, v42v2(top_right)); + draw_rect_sheet(a->tex, v42v2(bottom_left_slice), 0xFFFFFFFF, v42v2(bottom_left)); + draw_rect_sheet(a->tex, v42v2(bottom_right_slice), 0xFFFFFFFF, v42v2(bottom_right)); + draw_rect_sheet(a->tex, v42v2(top_middle_slice), 0xFFFFFFFF, v42v2(top)); + draw_rect_sheet(a->tex, v42v2(bottom_middle_slice), 0xFFFFFFFF, v42v2(bottom)); + draw_rect_sheet(a->tex, v42v2(middle_left_slice), 0xFFFFFFFF, v42v2(left)); + draw_rect_sheet(a->tex, v42v2(middle_right_slice), 0xFFFFFFFF, v42v2(right)); +} + +static +void skinned_draw_rect(void* userdata, gui_state_t state, const char *skin, vec4 r) { + skinned_t *a = C_CAST(skinned_t*, userdata); + + switch (state.kind) { + case GUI_BUTTON: { + char *btn = va("%s%s", skin?skin:a->button, state.held?"_press":state.hover?"_hover":""); + atlas_slice_frame_t *f = skinned_getsliceframe(&a->atlas, btn, skin?skin:a->button); + if (!f) skinned_draw_missing_rect(r); + else skinned_draw_sprite(a->scale, &a->atlas, f, r); + } break; + } +} + +static +void skinned_preset_skins(skinned_t *s) { + s->panel = "panel"; + s->button = "button"; +} + +guiskin_t gui_skinned(const char *inifile, float scale) { + skinned_t *a = REALLOC(0, sizeof(skinned_t)); + a->atlas = atlas_create(inifile, 0); + a->scale = scale?scale:1.0f; + guiskin_t skin={0}; + skin.userdata = a; + skin.draw_rect_func = skinned_draw_rect; + skin.free = skinned_free; + skinned_preset_skins(a); + return skin; +} diff --git a/engine/split/v4k_gui.h b/engine/split/v4k_gui.h new file mode 100644 index 0000000..6375a91 --- /dev/null +++ b/engine/split/v4k_gui.h @@ -0,0 +1,49 @@ +// ---------------------------------------------------------------------------- +// game ui + +enum { + GUI_PANEL, + GUI_BUTTON, +}; + +typedef struct gui_state_t { + int kind; + + union { + struct { + bool held; + bool hover; + }; + }; +} gui_state_t; + +typedef struct guiskin_t { + void (*draw_rect_func)(void* userdata, gui_state_t state, const char *skin, vec4 rect); + void (*free)(void* userdata); + void *userdata; +} guiskin_t; + +API void gui_pushskin(guiskin_t skin); +API void* gui_userdata(); +// -- +API void gui_panel(int id, vec4 rect, const char *skin); +API bool gui_button(int id, vec4 rect, const char *skin); +API void gui_popskin(); + +// helpers +#define gui_panel(...) gui_panel(__LINE__, __VA_ARGS__) +#define gui_button(...) gui_button(__LINE__, __VA_ARGS__) + +// default skins +API guiskin_t gui_skinned(const char *inifile, float scale); + +typedef struct skinned_t { + atlas_t atlas; + float scale; + // unsigned framenum; + + //skins + char *panel; + char *button; +} skinned_t; + diff --git a/engine/split/v4k_string.c b/engine/split/v4k_string.c index addce48..8053355 100644 --- a/engine/split/v4k_string.c +++ b/engine/split/v4k_string.c @@ -343,6 +343,16 @@ array(uint32_t) string32( const char *utf8 ) { 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 diff --git a/engine/split/v4k_string.h b/engine/split/v4k_string.h index 44b97fe..8b8496f 100644 --- a/engine/split/v4k_string.h +++ b/engine/split/v4k_string.h @@ -76,6 +76,8 @@ API char* strjoin(array(char*) list, const char *separator); API char * string8(const wchar_t *str); /// convert from wchar16(win) to utf8/ascii API array(uint32_t) string32( const char *utf8 ); /// convert from utf8 to utf32 +API const char* codepoint_to_utf8(unsigned cp); + // ----------------------------------------------------------------------------- // ## string interning (quarks) // - rlyeh, public domain. diff --git a/engine/split/v4k_ui.c b/engine/split/v4k_ui.c index f595cf0..c0114fa 100644 --- a/engine/split/v4k_ui.c +++ b/engine/split/v4k_ui.c @@ -174,9 +174,9 @@ void* ui_handle() { } static void nk_config_custom_fonts() { - #define UI_ICON_MIN ICON_MIN_MD - #define UI_ICON_MED ICON_MAX_16_MD - #define UI_ICON_MAX ICON_MAX_MD + #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 @@ -207,7 +207,7 @@ static void nk_config_custom_fonts() { 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_MIN_MDI*/, 0xF1CC7/*ICON_MAX_MDI*/, 0}}, + {"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 ) { diff --git a/engine/v4k b/engine/v4k index 98bdb88..7852c92 100644 --- a/engine/v4k +++ b/engine/v4k @@ -11695,11 +11695,11 @@ int gladLoadGL( GLADloadfunc load) { #ifndef ICON_MD_H #define ICON_MD_H -#define FONT_ICON_FILE_NAME_MD "MaterialIcons-Regular.ttf" +#define ICON_MD_FILENAME "MaterialIcons-Regular.ttf" -#define ICON_MIN_MD 0xe000 -#define ICON_MAX_16_MD 0xf8ff -#define ICON_MAX_MD 0x10fffd +#define ICON_MD_MIN 0xe000 +#define ICON_MD_MAX_16 0xf8ff +#define ICON_MD_MAX 0x10fffd #define ICON_MD_10K "\xee\xa5\x91" // U+e951 #define ICON_MD_10MP "\xee\xa5\x92" // U+e952 #define ICON_MD_11MP "\xee\xa5\x93" // U+e953 @@ -319807,11 +319807,11 @@ rpmalloc_linker_reference(void) { // for use with https://github.com/Templarian/MaterialDesign-Webfont/raw/master/fonts/materialdesignicons-webfont.ttf #pragma once -#define FONT_ICON_FILE_NAME_MDI "materialdesignicons-webfont.ttf" +#define ICON_MDI_FILENAME "materialdesignicons-webfont.ttf" -#define ICON_MIN_MDI 0xF68C -#define ICON_MAX_16_MDI 0xF68C -#define ICON_MAX_MDI 0xF1CC7 +#define ICON_MDI_MIN 0xF68C +#define ICON_MDI_MAX_16 0xF68C +#define ICON_MDI_MAX 0xF1CC7 #define ICON_MDI_AB_TESTING "\xf3\xb0\x87\x89" // U+F01C9 #define ICON_MDI_ABACUS "\xf3\xb1\x9b\xa0" // U+F16E0 #define ICON_MDI_ABJAD_ARABIC "\xf3\xb1\x8c\xa8" // U+F1328 @@ -327819,7 +327819,7 @@ int printi(int i) { return i; } -static const char* codepoint_to_utf8(unsigned c); +static const char* codepoint_to_utf8_(unsigned c); int lt_poll_event(lua_State *L) { // init.lua > core.step() wakes on mousemoved || inputtext int rc = 0; char buf[16]; @@ -327863,7 +327863,7 @@ int lt_poll_event(lua_State *L) { // init.lua > core.step() wakes on mousemoved goto bottom; break; case GLEQ_CODEPOINT_INPUT: - rc += lt_emit_event(L, "textinput", "s", codepoint_to_utf8(e.codepoint)); + rc += lt_emit_event(L, "textinput", "s", codepoint_to_utf8_(e.codepoint)); break; case GLEQ_BUTTON_PRESSED: rc += lt_emit_event(L, "mousepressed", "sddd", lt_button_name(e.mouse.button), lt_mx, lt_my, printi(1 + clicks)); @@ -328015,7 +328015,7 @@ struct RenFont { static struct { int left, top, right, bottom; } lt_clip; -static const char* codepoint_to_utf8(unsigned c) { //< @r-lyeh +static const char* codepoint_to_utf8_(unsigned c) { //< @r-lyeh static char s[4+1]; lt_memset(s, 0, 5); /**/ if (c < 0x80) s[0] = c, s[1] = 0; @@ -328024,7 +328024,7 @@ static const char* codepoint_to_utf8(unsigned c) { //< @r-lyeh 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; } -static const char* utf8_to_codepoint(const char *p, unsigned *dst) { +static const char* utf8_to_codepoint_(const char *p, unsigned *dst) { unsigned res, n; switch (*p & 0xf0) { case 0xf0 : res = *p & 0x07; n = 3; break; @@ -328200,7 +328200,7 @@ int ren_get_font_width(RenFont *font, const char *text) { const char *p = text; unsigned codepoint; while (*p) { - p = utf8_to_codepoint(p, &codepoint); + p = utf8_to_codepoint_(p, &codepoint); GlyphSet *set = get_glyphset(font, codepoint); stbtt_bakedchar *g = &set->glyphs[codepoint & 0xff]; x += g->xadvance; @@ -328305,7 +328305,7 @@ int ren_draw_text(RenFont *font, const char *text, int x, int y, RenColor color) const char *p = text; unsigned codepoint; while (*p) { - p = utf8_to_codepoint(p, &codepoint); + p = utf8_to_codepoint_(p, &codepoint); GlyphSet *set = get_glyphset(font, codepoint); stbtt_bakedchar *g = &set->glyphs[codepoint & 0xff]; rect.x = g->x0; diff --git a/engine/v4k.c b/engine/v4k.c index 88a02ec..837f528 100644 --- a/engine/v4k.c +++ b/engine/v4k.c @@ -846,6 +846,16 @@ array(uint32_t) string32( const char *utf8 ) { 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 @@ -1368,9 +1378,9 @@ void* ui_handle() { } static void nk_config_custom_fonts() { - #define UI_ICON_MIN ICON_MIN_MD - #define UI_ICON_MED ICON_MAX_16_MD - #define UI_ICON_MAX ICON_MAX_MD + #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 @@ -1401,7 +1411,7 @@ static void nk_config_custom_fonts() { 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_MIN_MDI*/, 0xF1CC7/*ICON_MAX_MDI*/, 0}}, + {"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 ) { @@ -10993,6 +11003,306 @@ vec2 font_rect(const char *str) { } #line 0 +#line 1 "v4k_gui.c" +// ---------------------------------------------------------------------------- +// game ui (utils) + +API vec2i draw_window_ui(); + +API void draw_rect(int rgba, vec2 start, vec2 end ); +API void draw_rect_tex( texture_t texture, int rgba, vec2 start, vec2 end ); +API void draw_rect_sheet( texture_t spritesheet, vec2 tex_start, vec2 tex_end, int rgba, vec2 start, vec2 end ); + +#define draw_rect_borders(color, x, y, w, h, borderWeight) do { \ + int x1 = (x); \ + int y1 = (y); \ + int x2 = (x) + (w) - 1; \ + int y2 = (y) + (h) - 1; \ + draw_rect(color, vec2(x1, y1), vec2(x2, y1 + (borderWeight) - 1)); \ + draw_rect(color, vec2(x1, y1), vec2(x1 + (borderWeight) - 1, y2)); \ + draw_rect(color, vec2(x1, y2 - (borderWeight) + 1), vec2(x2, y2)); \ + draw_rect(color, vec2(x2 - (borderWeight) + 1, y1), vec2(x2, y2)); \ + } while(0) + +// #define lay_draw_rect(rgba, rect) draw_rect(rgba, vec2(rect.e[0], rect.e[1]), vec2(rect.e[0]+rect.e[2], rect.e[1]+rect.e[3])) +// #define lay_draw_rect_borders(rgba, rect, borderWeight) draw_rect_borders(rgba, rect.e[0], rect.e[1], rect.e[2], rect.e[3], borderWeight) +// #define lay_draw_rect_tex(tex, rgba, rect) draw_rect_tex(tex, rgba, vec2(rect.e[0], rect.e[1]), vec2(rect.e[0]+rect.e[2], rect.e[1]+rect.e[3])) +// #define l2m(rect) (vec4(rect.e[0]+rect.e[2], rect.e[1]+rect.e[3])) +#define v42v2(rect) vec2(rect.x,rect.y), vec2(rect.z,rect.w) + + + +vec2i draw_window_ui() { + vec2 dpi = ifdef(osx, window_dpi(), vec2(1,1)); + int w = window_width(); + int h = window_height(); + return vec2i(w/dpi.x, h/dpi.y); +} + +void draw_rect_sheet( texture_t texture, vec2 tex_start, vec2 tex_end, int rgba, vec2 start, vec2 end ) { + float gamma = 1; + static int program = -1, vbo = -1, vao = -1, u_inv_gamma = -1, u_tint = -1, u_has_tex = -1, u_window_width = -1, u_window_height = -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_inv_gamma = glGetUniformLocation(program, "u_inv_gamma"); + 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); + + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + GLenum texture_type = texture.flags & TEXTURE_ARRAY ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D; +// glEnable( GL_BLEND ); + glUseProgram( program ); + glUniform1f( u_inv_gamma, 1.0f / (gamma + !gamma) ); + + 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 ); +// glDisable( GL_BLEND ); +} + +void draw_rect_tex( texture_t texture, int rgba, vec2 start, vec2 end ) { + draw_rect_sheet(texture, vec2(0, 0), vec2(texture.w, texture.h), rgba, start, end); +} + +void draw_rect(int rgba, vec2 start, vec2 end ) { + draw_rect_tex((texture_t){0}, rgba, start, end); +} + +// ---------------------------------------------------------------------------- +// game ui + +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 + +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; +} + +static +gui_state_t *gui_getstate(int id, int kind) { + if (!ctl_states) map_init(ctl_states, less_int, hash_int); + static gui_state_t st={0}; + st.kind=kind; + return map_find_or_add(ctl_states, id, st); +} + +bool (gui_button)(int id, vec4 r, const char *skin) { + gui_state_t *entry = gui_getstate(id, GUI_BUTTON); + bool was_clicked=0; + entry->hover = false; + + if (input(MOUSE_X) > r.x && input(MOUSE_X) < r.z && input(MOUSE_Y) > r.y && input(MOUSE_Y) < r.w) { + if (input_up(MOUSE_L) && entry->held) { + was_clicked=1; + } + + entry->held = input_held(MOUSE_L); + entry->hover = true; + } + else if (input_up(MOUSE_L) && entry->held) { + entry->held = false; + } + + if (last_skin->draw_rect_func) last_skin->draw_rect_func(last_skin->userdata, *entry, skin, r); + else { + draw_rect(entry->held ? 0x111111FF : entry->hover ? 0xEEEEEEFF : 0xFFFFFFFF, v42v2(r)); + } + + return was_clicked; +} + +void (gui_panel)(int id, vec4 r, const char *skin) { + gui_state_t *entry = gui_getstate(id, GUI_PANEL); + if (last_skin->draw_rect_func) last_skin->draw_rect_func(last_skin->userdata, *entry, skin?skin:"panel", r); + else { + draw_rect(0xFFFFFFFF, v42v2(r)); + } +} + +/* 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, const char *fallback) { + #define atlas_loop(n)\ + for (int i = 0; i < array_count(a->slices); i++)\ + if (!strcmp(quark_string(&a->db, a->slices[i].name), n))\ + return &a->slice_frames[a->slices[i].frames[0]]; + atlas_loop(name); + atlas_loop(fallback); + return NULL; + #undef atlas_loop +} + +static +void skinned_draw_missing_rect(vec4 r) { + draw_rect_tex(texture_checker(), 0xFFFFFFFF, v42v2(r)); +} + +static +void skinned_draw_sprite(float scale, atlas_t *a, atlas_slice_frame_t *f, vec4 r) { + if (!f->has_9slice) { + draw_rect_sheet(a->tex, v42v2(f->bounds), 0xFFFFFFFF, v42v2(r)); + return; + } + + vec4 outer = f->bounds; + 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}; + + draw_rect_sheet(a->tex, v42v2(center_slice), 0xFFFFFFFF, v42v2(center)); + draw_rect_sheet(a->tex, v42v2(top_left_slice), 0xFFFFFFFF, v42v2(top_left)); + draw_rect_sheet(a->tex, v42v2(top_right_slice), 0xFFFFFFFF, v42v2(top_right)); + draw_rect_sheet(a->tex, v42v2(bottom_left_slice), 0xFFFFFFFF, v42v2(bottom_left)); + draw_rect_sheet(a->tex, v42v2(bottom_right_slice), 0xFFFFFFFF, v42v2(bottom_right)); + draw_rect_sheet(a->tex, v42v2(top_middle_slice), 0xFFFFFFFF, v42v2(top)); + draw_rect_sheet(a->tex, v42v2(bottom_middle_slice), 0xFFFFFFFF, v42v2(bottom)); + draw_rect_sheet(a->tex, v42v2(middle_left_slice), 0xFFFFFFFF, v42v2(left)); + draw_rect_sheet(a->tex, v42v2(middle_right_slice), 0xFFFFFFFF, v42v2(right)); +} + +static +void skinned_draw_rect(void* userdata, gui_state_t state, const char *skin, vec4 r) { + skinned_t *a = C_CAST(skinned_t*, userdata); + + switch (state.kind) { + case GUI_BUTTON: { + char *btn = va("%s%s", skin?skin:a->button, state.held?"_press":state.hover?"_hover":""); + atlas_slice_frame_t *f = skinned_getsliceframe(&a->atlas, btn, skin?skin:a->button); + if (!f) skinned_draw_missing_rect(r); + else skinned_draw_sprite(a->scale, &a->atlas, f, r); + } break; + } +} + +static +void skinned_preset_skins(skinned_t *s) { + s->panel = "panel"; + s->button = "button"; +} + +guiskin_t gui_skinned(const char *inifile, float scale) { + skinned_t *a = REALLOC(0, sizeof(skinned_t)); + a->atlas = atlas_create(inifile, 0); + a->scale = scale?scale:1.0f; + guiskin_t skin={0}; + skin.userdata = a; + skin.draw_rect_func = skinned_draw_rect; + skin.free = skinned_free; + skinned_preset_skins(a); + return skin; +} +#line 0 + #line 1 "v4k_input.c" // input framework // - rlyeh, public domain @@ -29056,22 +29366,31 @@ void editor_pump() { // ---------------------------------------------------------------------------------------- -API void editor_cursorpos(int x, int y); -void editor_cursorpos(int x, int y) { +void editor_setmouse(int x, int y) { glfwSetCursorPos( window_handle(), x, y ); } -void editor_symbol(int x, int y, const char *sym) { +vec2 editor_glyph(int x, int y, unsigned cp) { // style: atlas size, unicode ranges and 6 font faces max do_once font_face(FONT_FACE2, "MaterialIconsSharp-Regular.otf", 24.f, FONT_EM|FONT_2048); + do_once font_face(FONT_FACE3, "materialdesignicons-webfont.ttf", 24.f, FONT_EM|FONT_2048); // {0xF68C /*ICON_MDI_MIN*/, 0xF1CC7/*ICON_MDI_MAX*/, 0}}, // style: 10 colors max do_once font_color(FONT_COLOR1, WHITE); do_once font_color(FONT_COLOR2, RGBX(0xE8F1FF,128)); // GRAY); do_once font_color(FONT_COLOR3, YELLOW); do_once font_color(FONT_COLOR4, ORANGE); do_once font_color(FONT_COLOR5, CYAN); + const char *sym = codepoint_to_utf8(cp); font_goto(x,y); - font_print(va(FONT_FACE2 /*FONT_WHITE*/ FONT_H1 "%s", sym)); + return font_print(va("%s" FONT_H1 "%s", cp >= ICON_MDI_MIN ? FONT_FACE3 : FONT_FACE2, sym)); +} + +vec2 editor_glyphstr(int x, int y, const char *utf8) { + vec2 dim = {x,y}; + array(unsigned) codepoints = string32(utf8); + for( int i = 0, end = array_count(codepoints); i < end; ++i) + add2(dim, editor_glyph(dim.x,dim.y,codepoints[i])); + return dim; } void editor_frame( void (*game)(unsigned, float, double) ) { diff --git a/engine/v4k.h b/engine/v4k.h index 34f089e..b9f81f4 100644 --- a/engine/v4k.h +++ b/engine/v4k.h @@ -3984,6 +3984,8 @@ API char* strjoin(array(char*) list, const char *separator); API char * string8(const wchar_t *str); /// convert from wchar16(win) to utf8/ascii API array(uint32_t) string32( const char *utf8 ); /// convert from utf8 to utf32 +API const char* codepoint_to_utf8(unsigned cp); + // ----------------------------------------------------------------------------- // ## string interning (quarks) // - rlyeh, public domain. @@ -4175,6 +4177,57 @@ API void sprite_edit(sprite_t *s); API sprite_t*sprite_new(const char *ase, int bindings[6]); API void sprite_del(sprite_t *s); API void sprite_setanim(sprite_t *s, unsigned name); +#line 0 +#line 1 "v4k_gui.h" +// ---------------------------------------------------------------------------- +// game ui + +enum { + GUI_PANEL, + GUI_BUTTON, +}; + +typedef struct gui_state_t { + int kind; + + union { + struct { + bool held; + bool hover; + }; + }; +} gui_state_t; + +typedef struct guiskin_t { + void (*draw_rect_func)(void* userdata, gui_state_t state, const char *skin, vec4 rect); + void (*free)(void* userdata); + void *userdata; +} guiskin_t; + +API void gui_pushskin(guiskin_t skin); +API void* gui_userdata(); +// -- +API void gui_panel(int id, vec4 rect, const char *skin); +API bool gui_button(int id, vec4 rect, const char *skin); +API void gui_popskin(); + +// helpers +#define gui_panel(...) gui_panel(__LINE__, __VA_ARGS__) +#define gui_button(...) gui_button(__LINE__, __VA_ARGS__) + +// default skins +API guiskin_t gui_skinned(const char *inifile, float scale); + +typedef struct skinned_t { + atlas_t atlas; + float scale; + // unsigned framenum; + + //skins + char *panel; + char *button; +} skinned_t; + #line 0 #line 1 "v4k_system.h" @@ -4757,7 +4810,9 @@ API void editor_inspect(obj *o); API vec3 editor_pick(float mouse_x, float mouse_y); API char* editor_path(const char *path); -API void editor_symbol(int x, int y, const char *sym); +API void editor_setmouse(int x, int y); +API vec2 editor_glyph(int x, int y, unsigned cp); +API vec2 editor_glyphstr(int x, int y, const char *utf8); API void editor_gizmos(int dim); // ----------------------------------------------------------------------------------------