From 6da10bec7ca045c163164d46d1d1610ea7f872b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Madar=C3=A1sz?= Date: Sun, 26 Nov 2023 16:27:33 +0100 Subject: [PATCH] integrate v4k_gui --- bind/v4k.lua | 94 +- demos/99-gui.c | 38 + demos/art/gui/golden.ase | Bin 0 -> 47099 bytes demos/art/gui/lilita_one_regular.license | 94 ++ demos/art/gui/lilita_one_regular.ttf | Bin 0 -> 26828 bytes engine/art/shaders/rect_2d.fs | 17 + engine/art/shaders/rect_2d.vs | 18 + engine/editor.c | 41 +- engine/joint/v4k.h | 428 ++++++- engine/split/3rd_aseprite.h | 1348 ++++++++++++++++++++++ engine/split/3rd_atlasc.h | 886 ++++++++++++++ engine/split/3rd_delaunay.h | 1059 +++++++++++++++++ engine/split/3rd_icon_md.h | 8 +- engine/split/3rd_icon_mdi.h | 8 +- engine/split/3rd_lite.h | 8 +- engine/split/3rd_lite_sys.h | 4 +- engine/split/3rd_mid.h | 464 ++++++++ engine/split/3rd_sproutline.h | 441 +++++++ engine/split/v4k.c.inl | 2 + engine/split/v4k.h.inl | 1 + engine/split/v4k_editor.c | 17 +- engine/split/v4k_editor.h | 4 +- engine/split/v4k_gui.c | 297 +++++ engine/split/v4k_gui.h | 49 + engine/split/v4k_string.c | 10 + engine/split/v4k_string.h | 2 + engine/split/v4k_ui.c | 8 +- engine/v4k | 28 +- engine/v4k.c | 335 +++++- engine/v4k.h | 57 +- 30 files changed, 5649 insertions(+), 117 deletions(-) create mode 100644 demos/99-gui.c create mode 100644 demos/art/gui/golden.ase create mode 100644 demos/art/gui/lilita_one_regular.license create mode 100644 demos/art/gui/lilita_one_regular.ttf create mode 100644 engine/art/shaders/rect_2d.fs create mode 100644 engine/art/shaders/rect_2d.vs create mode 100644 engine/split/3rd_aseprite.h create mode 100644 engine/split/3rd_atlasc.h create mode 100644 engine/split/3rd_delaunay.h create mode 100644 engine/split/3rd_mid.h create mode 100644 engine/split/3rd_sproutline.h create mode 100644 engine/split/v4k_gui.c create mode 100644 engine/split/v4k_gui.h 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 0000000000000000000000000000000000000000..24b464b69f28619b0ccf29e1855a5d67e385d8cb GIT binary patch literal 47099 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 J`5B8X{|`@-fCT^m literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..091ddf9e3b84f3655d5cd441a37801acd0b8555c GIT binary patch literal 26828 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 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); // ----------------------------------------------------------------------------------------