├── README.md ├── examples ├── README.md ├── console.cpp └── ui_system.cpp ├── misc ├── console.gif └── text-buffer-assembly.gif └── vertext.h /README.md: -------------------------------------------------------------------------------- 1 | # vertext 2 | Single header C library for assembling textured quads for rendering text. This library does not perform rendering directly and instead provides a vertex buffer of vertices (in either Screen Space or Clip Space) and texture coordinates. This allows the library to work seamlessly with both OpenGL and DirectX. Very lightweight: ~700 lines of code. 3 | 4 | PURPOSE: Text rendering is a non-trivial task, and this library strives to make it easy and frictionless. 5 | 6 | This library REQUIRES [Sean Barrett's stb_truetype.h](https://github.com/nothings/stb/blob/master/stb_truetype.h) library (which is also a single header). 7 | 8 | ![](https://github.com/kevinmkchin/vertext/blob/main/misc/text-buffer-assembly.gif?raw=true) 9 | 10 | ![](https://github.com/kevinmkchin/vertext/blob/main/misc/console.gif?raw=true) 11 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | NOTE: THESE ARE CODE EXAMPLES USING VERTEXT PULLED STRAIGHT FROM OTHER PROJECTS. THEY ARE NOT MEANT TO BE TUTORIALS OR A COMPLETE REFERENCE TO COPY FROM. -------------------------------------------------------------------------------- /examples/console.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "console.h" 8 | #include "../core/kc_math.h" 9 | #include 10 | #include "../renderer/texture.h" 11 | #include "../renderer/mesh.h" 12 | #include "../renderer/shader.h" 13 | #include "../renderer/deferred_renderer.h" 14 | #include "../core/timer.h" 15 | #include "../game/game_state.h" 16 | #include "../game_statics.h" 17 | 18 | /** 19 | 20 | QUAKE-STYLE IN-GAME CONSOLE IMPLEMENTATION 21 | 22 | There are two parts to the in-game console: 23 | 24 | 1. console.h/cpp: 25 | console.h is the interface that the rest of the game uses to communicate with console.cpp, 26 | console.cpp handles the console visuals, inputs, outputs, and logic related to console messages 27 | 28 | 2. noclip.h: 29 | noclip.h is the backend of the console and handles executing commands 30 | 31 | */ 32 | 33 | INTERNAL noclip::console console_backend; 34 | noclip::console& get_console() 35 | { 36 | return console_backend; 37 | } 38 | 39 | #define CONSOLE_MAX_PRINT_MSGS 8096 40 | #define CONSOLE_SCROLL_SPEED 2000.f 41 | #define CONSOLE_COLS_MAX 124 // char columns in line 42 | #define CONSOLE_ROWS_MAX 27 // we can store more messages than this, but this is just rows that are being displayed 43 | enum console_state_t 44 | { 45 | CONSOLE_HIDING, 46 | CONSOLE_HIDDEN, 47 | CONSOLE_SHOWING, 48 | CONSOLE_SHOWN 49 | }; 50 | INTERNAL GLuint console_background_vao_id = 0; 51 | INTERNAL GLuint console_background_vbo_id = 0; 52 | INTERNAL GLfloat console_background_vertex_buffer[] = { 53 | 0.f, 0.f, 0.f, 0.f, 54 | 0.f, 400.f, 0.f, 1.f, 55 | 1280.f, 400.f, 1.f, 1.f, 56 | 1280.f, 0.f, 1.f, 0.f, 57 | 0.f, 0.f, 0.f, 0.f, 58 | 1280.f, 400.f, 1.f, 1.f 59 | }; 60 | INTERNAL GLuint console_line_vao_id = 0; 61 | INTERNAL GLuint console_line_vbo_id = 0; 62 | INTERNAL GLfloat console_line_vertex_buffer[] = { 63 | 0.f, 400.f, 64 | 1280.f, 400.f 65 | }; 66 | 67 | INTERNAL bool console_b_initialized = false; 68 | INTERNAL console_state_t console_state = CONSOLE_HIDDEN; 69 | INTERNAL float console_y; 70 | 71 | INTERNAL float CONSOLE_HEIGHT = 400.f; 72 | INTERNAL u8 CONSOLE_TEXT_SIZE = 20; 73 | INTERNAL u8 CONSOLE_TEXT_PADDING_BOTTOM = 4; 74 | INTERNAL u16 CONSOLE_INPUT_DRAW_X = 4; 75 | INTERNAL u16 CONSOLE_INPUT_DRAW_Y = (u16) (CONSOLE_HEIGHT - (float) CONSOLE_TEXT_PADDING_BOTTOM); 76 | 77 | // Input character buffer 78 | INTERNAL char console_input_buffer[CONSOLE_COLS_MAX]; 79 | INTERNAL bool console_b_input_buffer_dirty = false; 80 | INTERNAL u8 console_input_cursor = 0; 81 | INTERNAL u8 console_input_buffer_count = 0; 82 | 83 | // Hidden character buffer 84 | INTERNAL char console_messages[CONSOLE_MAX_PRINT_MSGS] = {}; 85 | INTERNAL u16 console_messages_read_cursor = 0; 86 | INTERNAL u16 console_messages_write_cursor = 0; 87 | INTERNAL bool console_b_messages_dirty = false; 88 | 89 | // Text visuals 90 | INTERNAL vtxt_font* console_font_handle; 91 | INTERNAL texture_t console_font_atlas; 92 | // Input text & Messages VAOs 93 | INTERNAL mesh_t console_inputtext_vao; // console_inputtext_vao gets added to console_messages_vaos if user "returns" command 94 | INTERNAL mesh_t console_messages_vaos[CONSOLE_ROWS_MAX] = {}; // one vao is one line 95 | 96 | // TODO buffer to hold previous commands (max 20 commands) 97 | 98 | void console_initialize(vtxt_font* in_console_font_handle, texture_t in_console_font_atlas) 99 | { 100 | console_font_handle = in_console_font_handle; 101 | console_font_atlas = in_console_font_atlas; 102 | 103 | // INIT TEXT mesh_t OBJECTS 104 | vtxt_clear_buffer(); 105 | vtxt_move_cursor(CONSOLE_INPUT_DRAW_X, CONSOLE_INPUT_DRAW_Y); 106 | vtxt_append_glyph('>', console_font_handle, CONSOLE_TEXT_SIZE); 107 | vtxt_vertex_buffer vb = vtxt_grab_buffer(); 108 | mesh_t::gl_create_mesh(console_inputtext_vao, vb.vertex_buffer, vb.index_buffer, 109 | vb.vertices_array_count, vb.indices_array_count, 110 | 2, 2, 0, GL_DYNAMIC_DRAW); 111 | // INIT MESSAGES mesh_t OBJECTS 112 | for(int i = 0; i < CONSOLE_ROWS_MAX; ++i) 113 | { 114 | mesh_t::gl_create_mesh(console_messages_vaos[i], nullptr, nullptr, 115 | 0, 0, 116 | 2, 2, 0, GL_DYNAMIC_DRAW); 117 | } 118 | 119 | // todo update console vertex buffer on window size change 120 | // INIT CONSOLE GUI 121 | deferred_renderer* i_render_manager = game_statics::the_renderer; 122 | vec2i buffer_dimensions = i_render_manager->get_buffer_size(); 123 | console_background_vertex_buffer[8] = (float) buffer_dimensions.x; 124 | console_background_vertex_buffer[12] = (float) buffer_dimensions.x; 125 | console_background_vertex_buffer[20] = (float) buffer_dimensions.x; 126 | console_background_vertex_buffer[5] = CONSOLE_HEIGHT; 127 | console_background_vertex_buffer[9] = CONSOLE_HEIGHT; 128 | console_background_vertex_buffer[21] = CONSOLE_HEIGHT; 129 | glGenVertexArrays(1, &console_background_vao_id); 130 | glBindVertexArray(console_background_vao_id); 131 | glGenBuffers(1, &console_background_vbo_id); 132 | glBindBuffer(GL_ARRAY_BUFFER, console_background_vbo_id); 133 | glBufferData(GL_ARRAY_BUFFER, sizeof(console_background_vertex_buffer), console_background_vertex_buffer, GL_STATIC_DRAW); 134 | glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, 0); 135 | glEnableVertexAttribArray(0); 136 | glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, (void*)(sizeof(float) * 2)); 137 | glEnableVertexAttribArray(1); 138 | glBindBuffer(GL_ARRAY_BUFFER, 0); 139 | console_line_vertex_buffer[1] = CONSOLE_HEIGHT - (float) CONSOLE_TEXT_SIZE - CONSOLE_TEXT_PADDING_BOTTOM; 140 | console_line_vertex_buffer[2] = (float) buffer_dimensions.x; 141 | console_line_vertex_buffer[3] = CONSOLE_HEIGHT - (float) CONSOLE_TEXT_SIZE - CONSOLE_TEXT_PADDING_BOTTOM; 142 | glGenVertexArrays(1, &console_line_vao_id); 143 | glBindVertexArray(console_line_vao_id); 144 | glGenBuffers(1, &console_line_vbo_id); 145 | glBindBuffer(GL_ARRAY_BUFFER, console_line_vbo_id); 146 | glBufferData(GL_ARRAY_BUFFER, sizeof(console_line_vertex_buffer), console_line_vertex_buffer, GL_STATIC_DRAW); 147 | glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); 148 | glEnableVertexAttribArray(0); 149 | glBindBuffer(GL_ARRAY_BUFFER, 0); 150 | glBindVertexArray(0); 151 | 152 | console_b_initialized = true; 153 | console_print("Console initialized.\n"); 154 | } 155 | 156 | /** logs the message into the messages buffer */ 157 | void console_print(const char* message) 158 | { 159 | 160 | #if INTERNAL_BUILD & SLOW_BUILD 161 | printf(message); 162 | #endif 163 | 164 | // commands get con_printed when returned 165 | int i = 0; 166 | while(*(message + i) != '\0') 167 | { 168 | console_messages[console_messages_write_cursor] = *(message + i); 169 | ++console_messages_write_cursor; 170 | if(console_messages_write_cursor >= CONSOLE_MAX_PRINT_MSGS) 171 | { 172 | console_messages_write_cursor = 0; 173 | } 174 | ++i; 175 | } 176 | console_messages_read_cursor = console_messages_write_cursor; 177 | console_b_messages_dirty = true; 178 | } 179 | 180 | void console_printf(const char* fmt, ...) 181 | { 182 | va_list argptr; 183 | 184 | char message[1024]; 185 | va_start(argptr, fmt); 186 | stbsp_vsprintf(message, fmt, argptr); 187 | va_end(argptr); 188 | 189 | console_print(message); 190 | } 191 | 192 | void console_command(char* text_command) 193 | { 194 | // TODO (Check if this bug still exists after switching to noclip) - FUCKING MEMORY BUG TEXT_COMMAND GETS NULL TERMINATED EARLY SOMETIMES 195 | char text_command_buffer[CONSOLE_COLS_MAX]; 196 | strcpy_s(text_command_buffer, CONSOLE_COLS_MAX, text_command);//because text_command might point to read-only data 197 | 198 | if(*text_command_buffer == '\0') 199 | { 200 | return; 201 | } 202 | 203 | std::string cmd = std::string(text_command_buffer); 204 | std::string cmd_print_format = ">" + cmd + "\n"; 205 | console_print(cmd_print_format.c_str()); 206 | 207 | std::istringstream cmd_input_str(cmd); 208 | std::ostringstream cmd_output_str; 209 | get_console().execute(cmd_input_str, cmd_output_str); 210 | 211 | console_print(cmd_output_str.str().c_str()); 212 | } 213 | 214 | void console_toggle() 215 | { 216 | if(console_state == CONSOLE_HIDING || console_state == CONSOLE_SHOWING) 217 | { 218 | return; 219 | } 220 | 221 | if(console_state == CONSOLE_HIDDEN) 222 | { 223 | game_statics::gameState->b_is_update_running = false; 224 | SDL_SetRelativeMouseMode(SDL_FALSE); 225 | console_state = CONSOLE_SHOWING; 226 | } 227 | else if(console_state == CONSOLE_SHOWN) 228 | { 229 | game_statics::gameState->b_is_update_running = true; 230 | SDL_SetRelativeMouseMode(SDL_TRUE); 231 | console_state = CONSOLE_HIDING; 232 | } 233 | } 234 | 235 | void console_update_messages() 236 | { 237 | if(console_b_messages_dirty) 238 | { 239 | int msg_iterator = console_messages_read_cursor - 1; 240 | for(int row = 0; 241 | row < CONSOLE_ROWS_MAX; 242 | ++row) 243 | { 244 | // get line 245 | int line_len = 0; 246 | if(console_messages[msg_iterator] == '\n') 247 | { 248 | ++line_len; 249 | --msg_iterator; 250 | } 251 | for(char c = console_messages[msg_iterator]; 252 | c != '\n' && c != '\0'; 253 | c = console_messages[msg_iterator]) 254 | { 255 | ++line_len; 256 | --msg_iterator; 257 | if(msg_iterator < 0) 258 | { 259 | msg_iterator = CONSOLE_MAX_PRINT_MSGS - 1; 260 | } 261 | } 262 | // rebind vao 263 | { 264 | vtxt_clear_buffer(); 265 | vtxt_move_cursor(CONSOLE_INPUT_DRAW_X, CONSOLE_INPUT_DRAW_Y); 266 | for(int i = 0; i < line_len; ++i) 267 | { 268 | int j = msg_iterator + i + 1; 269 | if(j >= CONSOLE_MAX_PRINT_MSGS) 270 | { 271 | j -= CONSOLE_MAX_PRINT_MSGS; 272 | } 273 | char c = console_messages[j]; 274 | if(c != '\n') 275 | { 276 | vtxt_append_glyph(c, console_font_handle, CONSOLE_TEXT_SIZE); 277 | } 278 | else 279 | { 280 | vtxt_new_line(CONSOLE_INPUT_DRAW_X, console_font_handle); 281 | } 282 | } 283 | vtxt_vertex_buffer vb = vtxt_grab_buffer(); 284 | console_messages_vaos[row].gl_rebind_buffer_objects(vb.vertex_buffer, vb.index_buffer, 285 | vb.vertices_array_count, vb.indices_array_count); 286 | } 287 | } 288 | 289 | console_b_messages_dirty = false; 290 | } 291 | } 292 | 293 | void console_update() 294 | { 295 | if(!console_b_initialized || console_state == CONSOLE_HIDDEN) 296 | { 297 | return; 298 | } 299 | 300 | switch(console_state) 301 | { 302 | case CONSOLE_SHOWN: 303 | { 304 | if(console_b_input_buffer_dirty) 305 | { 306 | // update input vao 307 | vtxt_clear_buffer(); 308 | vtxt_move_cursor(CONSOLE_INPUT_DRAW_X, CONSOLE_INPUT_DRAW_Y); 309 | std::string input_text = ">" + std::string(console_input_buffer); 310 | vtxt_append_line(input_text.c_str(), console_font_handle, CONSOLE_TEXT_SIZE); 311 | vtxt_vertex_buffer vb = vtxt_grab_buffer(); 312 | console_inputtext_vao.gl_rebind_buffer_objects(vb.vertex_buffer, vb.index_buffer, 313 | vb.vertices_array_count, vb.indices_array_count); 314 | console_b_input_buffer_dirty = false; 315 | } 316 | 317 | console_update_messages(); 318 | } break; 319 | case CONSOLE_HIDING: 320 | { 321 | console_y -= CONSOLE_SCROLL_SPEED * timer::delta_time; 322 | if(console_y < 0.f) 323 | { 324 | console_y = 0.f; 325 | console_state = CONSOLE_HIDDEN; 326 | } 327 | } break; 328 | case CONSOLE_SHOWING: 329 | { 330 | console_y += CONSOLE_SCROLL_SPEED * timer::delta_time; 331 | if(console_y > CONSOLE_HEIGHT) 332 | { 333 | console_y = CONSOLE_HEIGHT; 334 | console_state = CONSOLE_SHOWN; 335 | } 336 | 337 | console_update_messages(); 338 | } break; 339 | } 340 | } 341 | 342 | void console_render(shader_t* ui_shader, shader_t* text_shader) 343 | { 344 | if(!console_b_initialized || console_state == CONSOLE_HIDDEN) 345 | { 346 | return; 347 | } 348 | 349 | mat4& matrix_projection_ortho = game_statics::the_renderer->matrix_projection_ortho; 350 | 351 | float console_translation_y = console_y - (float) CONSOLE_HEIGHT; 352 | mat4 con_transform = identity_mat4(); 353 | con_transform *= translation_matrix(0.f, console_translation_y, 0.f); 354 | 355 | // render console 356 | shader_t::gl_use_shader(*ui_shader); 357 | ui_shader->gl_bind_1i("b_use_colour", true); 358 | ui_shader->gl_bind_matrix4fv("matrix_model", 1, con_transform.ptr()); 359 | ui_shader->gl_bind_matrix4fv("matrix_proj_orthographic", 1, matrix_projection_ortho.ptr()); 360 | glBindVertexArray(console_background_vao_id); 361 | ui_shader->gl_bind_4f("ui_element_colour", 0.1f, 0.1f, 0.1f, 0.7f); 362 | glDrawArrays(GL_TRIANGLES, 0, 6); 363 | glBindVertexArray(console_line_vao_id); 364 | ui_shader->gl_bind_4f("ui_element_colour", 0.8f, 0.8f, 0.8f, 1.f); 365 | glDrawArrays(GL_LINES, 0, 2); 366 | glBindVertexArray(0); 367 | 368 | shader_t::gl_use_shader(*text_shader); 369 | // RENDER CONSOLE TEXT 370 | text_shader->gl_bind_matrix4fv("matrix_proj_orthographic", 1, matrix_projection_ortho.ptr()); 371 | console_font_atlas.gl_use_texture(); 372 | text_shader->gl_bind_1i("font_atlas_sampler", 1); 373 | 374 | // Input text visual 375 | text_shader->gl_bind_3f("text_colour", 1.f, 1.f, 1.f); 376 | text_shader->gl_bind_matrix4fv("matrix_model", 1, con_transform.ptr()); 377 | if(console_inputtext_vao.indices_count > 0) 378 | { 379 | console_inputtext_vao.gl_render_mesh(); 380 | } 381 | // move transform matrix up a lil 382 | con_transform[3][1] -= 30.f; 383 | 384 | // Messages text visual 385 | text_shader->gl_bind_3f("text_colour", 0.8f, 0.8f, 0.8f); 386 | for(int i = 0; i < CONSOLE_ROWS_MAX; ++i) 387 | { 388 | mesh_t m = console_messages_vaos[i]; 389 | if(m.indices_count > 0) 390 | { 391 | text_shader->gl_bind_matrix4fv("matrix_model", 1, con_transform.ptr()); 392 | con_transform[3][1] -= (float) CONSOLE_TEXT_SIZE + 3.f; 393 | m.gl_render_mesh(); 394 | } 395 | } 396 | glUseProgram(0); 397 | } 398 | 399 | void console_scroll_up() 400 | { 401 | int temp_cursor = console_messages_read_cursor - 1; 402 | char c = console_messages[temp_cursor]; 403 | if(c == '\n') 404 | { 405 | --temp_cursor; 406 | if(temp_cursor < 0) 407 | { 408 | temp_cursor += CONSOLE_MAX_PRINT_MSGS; 409 | } 410 | c = console_messages[temp_cursor]; 411 | } 412 | while(c != '\n' && c != '\0' && temp_cursor != console_messages_write_cursor) 413 | { 414 | --temp_cursor; 415 | if(temp_cursor < 0) 416 | { 417 | temp_cursor += CONSOLE_MAX_PRINT_MSGS; 418 | } 419 | c = console_messages[temp_cursor]; 420 | } 421 | console_messages_read_cursor = temp_cursor + 1; 422 | if(console_messages_read_cursor < 0) 423 | { 424 | console_messages_read_cursor += CONSOLE_MAX_PRINT_MSGS; 425 | } 426 | console_b_messages_dirty = true; 427 | } 428 | 429 | void console_scroll_down() 430 | { 431 | if(console_messages_read_cursor != console_messages_write_cursor) 432 | { 433 | int temp_cursor = console_messages_read_cursor; 434 | char c = console_messages[temp_cursor]; 435 | while(c != '\n' && c != '\0' && temp_cursor != console_messages_write_cursor - 1) 436 | { 437 | ++temp_cursor; 438 | if(temp_cursor >= CONSOLE_MAX_PRINT_MSGS) 439 | { 440 | temp_cursor = 0; 441 | } 442 | c = console_messages[temp_cursor]; 443 | } 444 | console_messages_read_cursor = temp_cursor + 1; 445 | if(console_messages_read_cursor > CONSOLE_MAX_PRINT_MSGS) 446 | { 447 | console_messages_read_cursor = 0; 448 | } 449 | console_b_messages_dirty = true; 450 | } 451 | } 452 | 453 | void console_keydown(SDL_KeyboardEvent& keyevent) 454 | { 455 | SDL_Keycode keycode = keyevent.keysym.sym; 456 | 457 | // SPECIAL KEYS 458 | switch(keycode) 459 | { 460 | case SDLK_ESCAPE: 461 | { 462 | console_toggle(); 463 | return; 464 | } 465 | // COMMAND 466 | case SDLK_RETURN: 467 | { 468 | // take current input buffer and use that as command 469 | console_command(console_input_buffer); 470 | memset(console_input_buffer, 0, console_input_buffer_count); 471 | console_input_cursor = 0; 472 | console_input_buffer_count = 0; 473 | console_b_input_buffer_dirty = true; 474 | }break; 475 | // Delete char before cursor 476 | case SDLK_BACKSPACE: 477 | { 478 | if(console_input_cursor > 0) 479 | { 480 | --console_input_cursor; 481 | console_input_buffer[console_input_cursor] = 0; 482 | --console_input_buffer_count; 483 | console_b_input_buffer_dirty = true; 484 | } 485 | }break; 486 | case SDLK_PAGEUP: 487 | { 488 | for(int i=0;i<10;++i) 489 | { 490 | console_scroll_up(); 491 | } 492 | }break; 493 | case SDLK_PAGEDOWN: 494 | { 495 | loop(10) 496 | { 497 | console_scroll_down(); 498 | } 499 | }break; 500 | // TODO Move cursor left right 501 | case SDLK_LEFT: 502 | { 503 | 504 | }break; 505 | case SDLK_RIGHT: 506 | { 507 | 508 | }break; 509 | // TODO Flip through previously entered commands and fill command buffer w previous command 510 | case SDLK_UP: 511 | { 512 | 513 | }break; 514 | case SDLK_DOWN: 515 | { 516 | 517 | }break; 518 | } 519 | 520 | // CHECK MODIFIERS 521 | if(keyevent.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT)) 522 | { 523 | if(97 <= keycode && keycode <= 122) 524 | { 525 | keycode -= 32; 526 | } 527 | else if(keycode == 50) 528 | { 529 | keycode = 64; 530 | } 531 | else if(49 <= keycode && keycode <= 53) 532 | { 533 | keycode -= 16; 534 | } 535 | else if(91 <= keycode && keycode <= 93) 536 | { 537 | keycode += 32; 538 | } 539 | else 540 | { 541 | switch(keycode) 542 | { 543 | case 48: { keycode = 41; } break; 544 | case 54: { keycode = 94; } break; 545 | case 55: { keycode = 38; } break; 546 | case 56: { keycode = 42; } break; 547 | case 57: { keycode = 40; } break; 548 | case 45: { keycode = 95; } break; 549 | case 61: { keycode = 43; } break; 550 | case 39: { keycode = 34; } break; 551 | case 59: { keycode = 58; } break; 552 | case 44: { keycode = 60; } break; 553 | case 46: { keycode = 62; } break; 554 | case 47: { keycode = 63; } break; 555 | } 556 | } 557 | } 558 | 559 | // CHECK INPUT 560 | if((ASCII_SPACE <= keycode && keycode <= ASCII_TILDE)) 561 | { 562 | if(console_input_buffer_count < CONSOLE_COLS_MAX) 563 | { 564 | console_input_buffer[console_input_cursor] = keycode; 565 | ++console_input_cursor; 566 | ++console_input_buffer_count; 567 | console_b_input_buffer_dirty = true; 568 | } 569 | } 570 | } 571 | 572 | bool console_is_shown() 573 | { 574 | return console_b_initialized && console_state == CONSOLE_SHOWN; 575 | } 576 | 577 | bool console_is_hidden() 578 | { 579 | return console_state == CONSOLE_HIDDEN; 580 | } 581 | -------------------------------------------------------------------------------- /examples/ui_system.cpp: -------------------------------------------------------------------------------- 1 | #include "ui_system.hpp" 2 | #include "common.hpp" 3 | #include "render_system.hpp" 4 | #include "world_system.hpp" 5 | #include "player_system.hpp" 6 | #include "file_system.hpp" 7 | #include "input.hpp" 8 | 9 | #define STB_TRUETYPE_IMPLEMENTATION 10 | #include 11 | #define VERTEXT_IMPLEMENTATION 12 | #include "vertext.h" 13 | 14 | #define TEXT_SIZE 64 15 | INTERNAL vtxt_font font_c64; 16 | INTERNAL TextureHandle texture_c64; 17 | INTERNAL vtxt_font font_medusa_gothic; 18 | INTERNAL TextureHandle texture_medusa_gothic; 19 | 20 | 21 | void LoadFont(vtxt_font* font_handle, TextureHandle* font_atlas, const char* font_path, u8 font_size, bool useNearest) 22 | { 23 | BinaryFileHandle fontfile; 24 | ReadFileBinary(fontfile, font_path); 25 | assert(fontfile.memory); 26 | vtxt_init_font(font_handle, (u8*) fontfile.memory, font_size); 27 | FreeFileBinary(fontfile); 28 | CreateTextureFromBitmap(*font_atlas, font_handle->font_atlas.pixels, font_handle->font_atlas.width, 29 | font_handle->font_atlas.height, GL_RED, GL_RED, (useNearest ? GL_NEAREST : GL_LINEAR)); 30 | free(font_handle->font_atlas.pixels); 31 | } 32 | 33 | UISystem::UISystem() 34 | { 35 | cachedGameStage = GAME_NOT_STARTED; 36 | } 37 | 38 | void UISystem::Init(RenderSystem* render_sys_arg, WorldSystem* world_sys_arg, PlayerSystem* player_sys_arg) 39 | { 40 | renderer = render_sys_arg; 41 | world = world_sys_arg; 42 | playerSystem = player_sys_arg; 43 | 44 | LoadFont(&font_c64, &texture_c64, font_path("c64.ttf").c_str(), 32, true); 45 | LoadFont(&font_medusa_gothic, &texture_medusa_gothic, font_path("medusa-gothic.otf").c_str(), TEXT_SIZE); 46 | 47 | renderer->worldTextFontPtr = &font_c64; 48 | renderer->worldTextFontAtlas = texture_c64; 49 | } 50 | 51 | void UISystem::PushWorldText(vec2 pos, const std::string& text, u32 size) 52 | { 53 | WorldText newWorldText; 54 | newWorldText.pos = pos; 55 | newWorldText.size = size; 56 | newWorldText.text = text; 57 | renderer->worldTextsThisFrame.push_back(newWorldText); 58 | } 59 | 60 | void UISystem::UpdateHealthBarUI(float dt) 61 | { 62 | if(registry.players.size() > 0) 63 | { 64 | auto e = registry.players.entities[0]; 65 | 66 | auto& playerHP = registry.healthBar.get(e); 67 | 68 | float currentHP = playerHP.health; 69 | float lowerBound = 0.f; 70 | float upperBound = playerHP.maxHealth; 71 | 72 | renderer->healthPointsNormalized = ((currentHP - lowerBound) / (upperBound - lowerBound)); 73 | } 74 | } 75 | 76 | void UISystem::UpdateExpUI(float dt) 77 | { 78 | if(registry.players.size() > 0) 79 | { 80 | Player playerComponent = registry.players.components[0]; 81 | 82 | float currentExp = playerComponent.experience; 83 | float lowerBound = 0.f; 84 | float upperBound = 9999.f; 85 | for(int i = 1; i < ARRAY_COUNT(PLAYER_EXP_THRESHOLDS_ARRAY); ++i) 86 | { 87 | float il = PLAYER_EXP_THRESHOLDS_ARRAY[i-1]; 88 | float iu = PLAYER_EXP_THRESHOLDS_ARRAY[i]; 89 | if(currentExp < iu) 90 | { 91 | lowerBound = il; 92 | upperBound = iu; 93 | break; 94 | } 95 | } 96 | 97 | renderer->expProgressNormalized = ((currentExp - lowerBound) / (upperBound - lowerBound)); 98 | } 99 | } 100 | 101 | #pragma warning(push) 102 | #pragma warning(disable : 4996) 103 | void UISystem::UpdateTextUI(float dt) 104 | { 105 | vtxt_setflags(VTXT_CREATE_INDEX_BUFFER|VTXT_USE_CLIPSPACE_COORDS); 106 | vtxt_backbuffersize(UI_LAYER_RESOLUTION_WIDTH, UI_LAYER_RESOLUTION_HEIGHT); 107 | 108 | vtxt_clear_buffer(); 109 | 110 | switch(world->GetCurrentMode()) 111 | { 112 | case MODE_MAINMENU: 113 | { 114 | vtxt_move_cursor(350, 340); 115 | vtxt_append_line("ASCENT", &font_c64, 72); 116 | vtxt_move_cursor(350, 500); 117 | vtxt_append_line("PLAY (ENTER)", &font_c64, 48); 118 | vtxt_move_cursor(350, 660); 119 | vtxt_append_line("HELP (H)", &font_c64, 48); 120 | vtxt_move_cursor(350, 820); 121 | vtxt_append_line("EXIT (Q)", &font_c64, 48); 122 | vtxt_move_cursor(350, 960); 123 | if (world->GetCurrentDifficulty() == DIFFICULTY_EASY) { 124 | vtxt_append_line("SWAP DIFFICULTY [STANDARD] (R)", &font_c64, 36); 125 | } 126 | else { 127 | vtxt_append_line("SWAP DIFFICULTY [HARD] (R)", &font_c64, 36); 128 | } 129 | 130 | }break; 131 | case MODE_INGAME: 132 | { 133 | Entity playerEntity = registry.players.entities[0]; 134 | TransformComponent& playerTransform = registry.transforms.get(playerEntity); 135 | MotionComponent& playerMotion = registry.motions.get(playerEntity); 136 | CollisionComponent& playerCollider = registry.colliders.get(playerEntity); 137 | HealthBar& playerHealth = registry.healthBar.get(playerEntity); 138 | GoldBar& playerGold = registry.goldBar.get(playerEntity); 139 | 140 | char textBuffer[128]; 141 | 142 | int displayHealth = (playerHealth.health > 0.f && playerHealth.health < 1.f) ? 1 : (int) playerHealth.health; 143 | sprintf(textBuffer, "HP: %d/%d", displayHealth, (int) playerHealth.maxHealth); 144 | vtxt_move_cursor(270, 70); 145 | vtxt_append_line(textBuffer, &font_c64, 24); 146 | 147 | sprintf(textBuffer, "GOLD: %d", (int)playerGold.coins); 148 | vtxt_move_cursor(40, 130); 149 | vtxt_append_line(textBuffer, &font_c64, 32); 150 | 151 | sprintf(textBuffer, "Lvl %d", (int) 1); 152 | if(registry.players.size() > 0) 153 | { 154 | Player playerComponent = registry.players.components[0]; 155 | sprintf(textBuffer, "Lvl %d", playerComponent.level); 156 | } 157 | vtxt_move_cursor(900, UI_LAYER_RESOLUTION_HEIGHT - 8); 158 | vtxt_append_line(textBuffer, &font_c64, 24); 159 | 160 | if(world->gamePaused) 161 | { 162 | vtxt_move_cursor(860, 556); 163 | vtxt_append_line("PAUSED", &font_c64, 32); 164 | } 165 | 166 | if(registry.players.size() > 0) 167 | { 168 | Player playerComponent = registry.players.components[0]; 169 | if(playerComponent.bDead) 170 | { 171 | vtxt_move_cursor(700, 580); 172 | vtxt_append_line("GAME OVER", &font_c64, 64); 173 | } 174 | } 175 | 176 | }break; 177 | } 178 | 179 | vtxt_vertex_buffer vb = vtxt_grab_buffer(); 180 | renderer->textLayer1FontAtlas = texture_c64; 181 | renderer->textLayer1Colour = vec4(1.f,1.f,1.f,1.0f); 182 | RebindMeshBufferObjects(renderer->textLayer1VAO, vb.vertex_buffer, vb.index_buffer, vb.vertices_array_count, vb.indices_array_count); 183 | 184 | LOCAL_PERSIST float showTutorialTimer = 0.f; 185 | 186 | // chapter change text 187 | LOCAL_PERSIST bool showChapterText = false; 188 | LOCAL_PERSIST float chapterTextAlpha = 0.f; 189 | if(world->GetCurrentStage() != cachedGameStage) 190 | { 191 | cachedGameStage = world->GetCurrentStage(); 192 | showChapterText = true; 193 | chapterTextAlpha = 3.0f; 194 | } 195 | if(showChapterText) 196 | { 197 | if(!world->gamePaused) 198 | { 199 | chapterTextAlpha -= 0.5f * dt; 200 | } 201 | 202 | // 2022-03-29 (Kevin): Commented out showing the tutorial after chapter text goes away 203 | // if(chapterTextAlpha < 0.f) 204 | // { 205 | // showChapterText = false; 206 | 207 | // if(cachedGameStage == CHAPTER_ONE_STAGE_ONE) 208 | // { 209 | // *GlobalPauseForSeconds = 9999.f; 210 | // showTutorialTimer = 9999.f; 211 | // world->darkenGameFrame = true; 212 | // } 213 | // } 214 | 215 | vtxt_clear_buffer(); 216 | 217 | if(!world->gamePaused) 218 | { 219 | switch(cachedGameStage) 220 | { 221 | case CHAPTER_TUTORIAL: 222 | { 223 | vtxt_move_cursor(100,800); 224 | vtxt_append_line("Prologue", &font_medusa_gothic, 110); 225 | vtxt_move_cursor(100,930); 226 | vtxt_append_line("Village at the Base of the Mountain", &font_medusa_gothic, 80); 227 | }break; 228 | case CHAPTER_ONE_STAGE_ONE: 229 | { 230 | vtxt_move_cursor(100,800); 231 | vtxt_append_line("Chapter One", &font_medusa_gothic, 110); 232 | vtxt_move_cursor(100,930); 233 | vtxt_append_line("Ancestral Caves", &font_medusa_gothic, 80); 234 | }break; 235 | case CHAPTER_TWO_STAGE_ONE: 236 | { 237 | vtxt_move_cursor(100,800); 238 | vtxt_append_line("Chapter Two", &font_medusa_gothic, 110); 239 | vtxt_move_cursor(100,930); 240 | vtxt_append_line("Eternal Forest", &font_medusa_gothic, 80); 241 | }break; 242 | case CHAPTER_THREE_STAGE_ONE: 243 | { 244 | vtxt_move_cursor(100,800); 245 | vtxt_append_line("Chapter Three", &font_medusa_gothic, 110); 246 | vtxt_move_cursor(100,930); 247 | vtxt_append_line("Mountaintop of Warriors", &font_medusa_gothic, 80); 248 | }break; 249 | case CHAPTER_BOSS: 250 | { 251 | vtxt_move_cursor(100,800); 252 | vtxt_append_line("Evil Sorcerer Izual", &font_medusa_gothic, 110); 253 | vtxt_move_cursor(100,930); 254 | vtxt_append_line("Final Fight", &font_medusa_gothic, 80); 255 | }break; 256 | } 257 | } 258 | 259 | vb = vtxt_grab_buffer(); 260 | renderer->textLayer2FontAtlas = texture_medusa_gothic; 261 | renderer->textLayer2Colour = vec4(1.f,1.f,1.f,chapterTextAlpha); 262 | RebindMeshBufferObjects(renderer->textLayer2VAO, vb.vertex_buffer, vb.index_buffer, vb.vertices_array_count, vb.indices_array_count); 263 | } 264 | 265 | if(showTutorialTimer > 0.f && !world->gamePaused) 266 | { 267 | vtxt_clear_buffer(); 268 | 269 | showTutorialTimer -= dt; 270 | 271 | if(Input::HasKeyBeenPressed(SDL_SCANCODE_J) || Input::GetGamepad(0).HasBeenPressed(GAMEPAD_A)) 272 | { 273 | showTutorialTimer = -1.f; 274 | *GlobalPauseForSeconds = -1.f; 275 | world->darkenGameFrame = false; 276 | } 277 | 278 | if(Input::GetGamepad(0).isConnected) 279 | { 280 | vtxt_move_cursor(260, 190); 281 | vtxt_append_line("D-Pad or Left Thumbstick to move.", &font_c64, 40); 282 | vtxt_move_cursor(260, 260); 283 | vtxt_append_line("A to jump", &font_c64, 40); 284 | vtxt_move_cursor(260, 330); 285 | vtxt_append_line("X to attack", &font_c64, 40); 286 | vtxt_move_cursor(260, 400); 287 | vtxt_append_line("B to pick up item", &font_c64, 40); 288 | vtxt_move_cursor(260, 450); 289 | vtxt_append_line("B while holding item to throw item", &font_c64, 40); 290 | vtxt_move_cursor(260, 500); 291 | vtxt_append_line("B + down to drop item", &font_c64, 40); 292 | vtxt_move_cursor(320, 800); 293 | vtxt_append_line("Press A to continue...", &font_c64, 40); 294 | } 295 | else 296 | { 297 | vtxt_move_cursor(260, 190); 298 | vtxt_append_line("WASD to move.", &font_c64, 40); 299 | vtxt_move_cursor(260, 260); 300 | vtxt_append_line("J to jump", &font_c64, 40); 301 | vtxt_move_cursor(260, 330); 302 | vtxt_append_line("K to attack", &font_c64, 40); 303 | vtxt_move_cursor(260, 400); 304 | vtxt_append_line("L to pick up item", &font_c64, 40); 305 | vtxt_move_cursor(260, 450); 306 | vtxt_append_line("L while holding item to throw item", &font_c64, 40); 307 | vtxt_move_cursor(260, 500); 308 | vtxt_append_line("L + down to drop item", &font_c64, 40); 309 | vtxt_move_cursor(320, 800); 310 | vtxt_append_line("Press J to continue...", &font_c64, 40); 311 | } 312 | 313 | vtxt_vertex_buffer vb = vtxt_grab_buffer(); 314 | renderer->textLayer1FontAtlas = texture_c64; 315 | renderer->textLayer1Colour = vec4(1.f,1.f,1.f,1.0f); 316 | RebindMeshBufferObjects(renderer->textLayer1VAO, vb.vertex_buffer, vb.index_buffer, vb.vertices_array_count, vb.indices_array_count); 317 | } 318 | } 319 | 320 | void UISystem::UpdateLevelUpUI(float dt) 321 | { 322 | vtxt_clear_buffer(); 323 | 324 | renderer->showMutationSelect = false; 325 | 326 | switch(world->GetCurrentMode()) 327 | { 328 | case MODE_INGAME: 329 | { 330 | Entity playerEntity = registry.players.entities[0]; 331 | 332 | LOCAL_PERSIST float levelUpTextTimer = 0.f; 333 | LOCAL_PERSIST bool pickThreeMutations = false; 334 | LOCAL_PERSIST Mutation mutationOptions[3]; 335 | if(playerSystem->bLeveledUpLastFrame) 336 | { 337 | levelUpTextTimer = 100000.0f; 338 | *GlobalPauseForSeconds = 100000.0f; 339 | pickThreeMutations = true; 340 | } 341 | 342 | if(levelUpTextTimer > 0.f) 343 | { 344 | if(!world->gamePaused) 345 | { 346 | levelUpTextTimer -= dt; 347 | } 348 | 349 | if(levelUpTextTimer > 99999.f) 350 | { 351 | vtxt_move_cursor(670, 580); 352 | vtxt_append_line("Level Up!", &font_c64, 80); 353 | } 354 | else 355 | { 356 | if(pickThreeMutations) 357 | { 358 | std::vector mutations = world->allPossibleMutations; 359 | // TODO(Kevin): remove mutations that player already has 360 | int mut1; 361 | int mut2; 362 | int mut3; 363 | PickThreeRandomInts(&mut1, &mut2, &mut3, (int)mutations.size()); 364 | mutationOptions[0] = mutations[mut1]; 365 | mutationOptions[1] = mutations[mut2]; 366 | mutationOptions[2] = mutations[mut3]; 367 | pickThreeMutations = false; 368 | } 369 | 370 | // mutationOptions 371 | vtxt_move_cursor(180, 350); 372 | vtxt_append_line(mutationOptions[0].name.c_str(), &font_c64, 28); 373 | vtxt_move_cursor(754, 350); 374 | vtxt_append_line(mutationOptions[1].name.c_str(), &font_c64, 28); 375 | vtxt_move_cursor(1333, 350); 376 | vtxt_append_line(mutationOptions[2].name.c_str(), &font_c64, 28); 377 | 378 | // 19 char wide 379 | std::string mut1desc = mutationOptions[0].description; 380 | std::string mut2desc = mutationOptions[1].description; 381 | std::string mut3desc = mutationOptions[2].description; 382 | int descCursorY = 500; 383 | while(mut1desc.length() > 0) 384 | { 385 | std::string toPrint = mut1desc.substr(0, 24); 386 | mut1desc = mut1desc.erase(0, 24); 387 | vtxt_move_cursor(180, descCursorY); 388 | descCursorY += 25; 389 | vtxt_append_line(toPrint.c_str(), &font_c64, 20); 390 | } 391 | 392 | descCursorY = 500; 393 | while(mut2desc.length() > 0) 394 | { 395 | std::string toPrint = mut2desc.substr(0, 24); 396 | mut2desc = mut2desc.erase(0, 24); 397 | vtxt_move_cursor(754, descCursorY); 398 | descCursorY += 25; 399 | vtxt_append_line(toPrint.c_str(), &font_c64, 20); 400 | } 401 | 402 | descCursorY = 500; 403 | while(mut3desc.length() > 0) 404 | { 405 | std::string toPrint = mut3desc.substr(0, 24); 406 | mut3desc = mut3desc.erase(0, 24); 407 | vtxt_move_cursor(1333, descCursorY); 408 | descCursorY += 25; 409 | vtxt_append_line(toPrint.c_str(), &font_c64, 20); 410 | } 411 | 412 | vtxt_move_cursor(574, 900); 413 | if(Input::GetGamepad(0).isConnected) 414 | { 415 | vtxt_append_line("Press A to select mutation...", &font_c64, 32); 416 | } 417 | else 418 | { 419 | vtxt_append_line("Press SPACE to select mutation...", &font_c64, 32); 420 | } 421 | 422 | if(Input::GameLeftHasBeenPressed()) 423 | { 424 | --(renderer->mutationSelectionIndex); 425 | if(Mix_PlayChannel(-1, world->blip_select_sound, 0) == -1) 426 | { 427 | printf("Mix_PlayChannel: %s\n",Mix_GetError()); 428 | } 429 | } 430 | if(Input::GameRightHasBeenPressed()) 431 | { 432 | ++(renderer->mutationSelectionIndex); 433 | if(Mix_PlayChannel(-1, world->blip_select_sound, 0) == -1) 434 | { 435 | printf("Mix_PlayChannel: %s\n",Mix_GetError()); 436 | } 437 | } 438 | renderer->mutationSelectionIndex = (renderer->mutationSelectionIndex + 3) % 3; 439 | renderer->showMutationSelect = true; 440 | 441 | if(Input::GameJumpHasBeenPressed()) 442 | { 443 | Mutation mutationToAdd = mutationOptions[renderer->mutationSelectionIndex]; 444 | ActiveMutationsComponent& playerActiveMutations = registry.mutations.get(playerEntity); 445 | playerActiveMutations.mutations.push_back(mutationToAdd); 446 | 447 | renderer->mutationSelectionIndex = 1; 448 | levelUpTextTimer = -1.f; 449 | *GlobalPauseForSeconds = 0.0f; 450 | } 451 | } 452 | } 453 | 454 | }break; 455 | default: 456 | {}break; 457 | } 458 | 459 | vtxt_vertex_buffer vb = vtxt_grab_buffer(); 460 | renderer->textLayer3FontAtlas = texture_c64; 461 | renderer->textLayer3Colour = vec4(1.f,1.f,1.f,1.0f); 462 | RebindMeshBufferObjects(renderer->textLayer3VAO, vb.vertex_buffer, vb.index_buffer, vb.vertices_array_count, vb.indices_array_count); 463 | } 464 | 465 | void UISystem::UpdateShopUI(float dt) { 466 | 467 | renderer->showShopSelect = false; 468 | vtxt_clear_buffer(); 469 | 470 | if (world->GetCurrentMode() == MODE_INGAME) { 471 | Entity playerEntity = registry.players.entities[0]; 472 | 473 | if (registry.activeShopItems.size() > 0) 474 | { 475 | renderer->showShopSelect = true; 476 | ActiveShopItem& activeShopItem = registry.activeShopItems.components[0]; 477 | Entity& shopEntity = activeShopItem.linkedEntity[0]; 478 | ShopItem& shopItem = registry.shopItems.get(shopEntity); 479 | *GlobalPauseForSeconds = 100000.0f; 480 | std::vector mutations = world->allPossibleMutations; 481 | Mutation buy = {}; 482 | 483 | switch (shopItem.mutationIndex) { 484 | case 0: 485 | buy = mutations[2]; 486 | break; 487 | case 1: 488 | buy = mutations[0]; 489 | break; 490 | case 2: 491 | buy = mutations[1]; 492 | break; 493 | case 3: 494 | buy = mutations[3]; 495 | break; 496 | case 4: 497 | buy = mutations[6]; 498 | break; 499 | default: 500 | break; 501 | } 502 | 503 | vtxt_move_cursor(754, 350); 504 | vtxt_append_line(buy.name.c_str(), &font_c64, 28); 505 | 506 | int descCursorY = 500; 507 | std::string mut_desc = buy.description; 508 | while (mut_desc.length() > 0) 509 | { 510 | std::string toPrint = mut_desc.substr(0, 24); 511 | mut_desc = mut_desc.erase(0, 24); 512 | vtxt_move_cursor(754, descCursorY); 513 | descCursorY += 25; 514 | vtxt_append_line(toPrint.c_str(), &font_c64, 20); 515 | } 516 | 517 | vtxt_move_cursor(620, 850); 518 | if (Input::GetGamepad(0).isConnected) 519 | { 520 | vtxt_append_line("Press A to buy for 50 gold.", &font_c64, 32); 521 | } 522 | else 523 | { 524 | vtxt_append_line("Press Z to buy for 50 gold.", &font_c64, 32); 525 | } 526 | 527 | vtxt_move_cursor(930, 900); 528 | vtxt_append_line("or", &font_c64, 32); 529 | 530 | vtxt_move_cursor(730, 950); 531 | if (Input::GetGamepad(0).isConnected) 532 | { 533 | vtxt_append_line("Press X to exit...", &font_c64, 32); 534 | } 535 | else 536 | { 537 | vtxt_append_line("Press X to exit...", &font_c64, 32); 538 | } 539 | 540 | if (Input::GameAttackHasBeenPressed()) 541 | { 542 | GoldBar& playerGold = registry.goldBar.get(playerEntity); 543 | 544 | if (playerGold.coins >= 50) { 545 | 546 | playerGold.coins -= 50; 547 | 548 | ActiveMutationsComponent& playerActiveMutations = registry.mutations.get(playerEntity); 549 | playerActiveMutations.mutations.push_back(buy); 550 | 551 | registry.remove_all_components_of(shopEntity); 552 | } 553 | else { 554 | // TODO: display UI saying not enough 555 | registry.activeShopItems.clear(); 556 | } 557 | 558 | *GlobalPauseForSeconds = 0.0f; 559 | } 560 | 561 | if (Input::GameCycleItemRightBeenPressed() || Input::GamePickUpHasBeenPressed()) 562 | { 563 | registry.activeShopItems.clear(); 564 | *GlobalPauseForSeconds = 0.0f; 565 | } 566 | 567 | } 568 | } 569 | 570 | vtxt_vertex_buffer vb = vtxt_grab_buffer(); 571 | renderer->textLayer4FontAtlas = texture_c64; 572 | renderer->textLayer4Colour = vec4(1.f, 1.f, 1.f, 1.0f); 573 | RebindMeshBufferObjects(renderer->textLayer4VAO, vb.vertex_buffer, vb.index_buffer, vb.vertices_array_count, vb.indices_array_count); 574 | } 575 | 576 | #pragma warning(pop) 577 | 578 | void UISystem::Step(float deltaTime) 579 | { 580 | // Check pause 581 | if(world->GetCurrentMode() == MODE_INGAME && Input::GamePauseHasBeenPressed()) 582 | { 583 | if(world->gamePaused) 584 | { 585 | // unpause 586 | world->gamePaused = false; 587 | world->darkenGameFrame = false; 588 | } 589 | else 590 | { 591 | // pause 592 | world->gamePaused = true; 593 | world->darkenGameFrame = true; 594 | } 595 | } 596 | 597 | UpdateHealthBarUI(deltaTime); 598 | UpdateExpUI(deltaTime); 599 | UpdateTextUI(deltaTime); 600 | UpdateLevelUpUI(deltaTime); 601 | UpdateShopUI(deltaTime); 602 | } 603 | -------------------------------------------------------------------------------- /misc/console.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinmkchin/vertext/006771bf67d1244aaae555edf22b73de708039fc/misc/console.gif -------------------------------------------------------------------------------- /misc/text-buffer-assembly.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinmkchin/vertext/006771bf67d1244aaae555edf22b73de708039fc/misc/text-buffer-assembly.gif -------------------------------------------------------------------------------- /vertext.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | vertext.h 4 | 5 | . . 6 | .o8 .o8 7 | oooo ooo .ooooo. oooo d8b .o888oo .ooooo. oooo ooo .o888oo 8 | `88. .8' d88' `88b `888""8P 888 d88' `88b `88b..8P' 888 9 | `88..8' 888ooo888 888 888 888ooo888 Y888' 888 10 | `888' 888 .o 888 888 . 888 .o .o8"'88b 888 . 11 | `8' `Y8bod8P' d888b "888" `Y8bod8P' o88' 888o "888" 12 | 13 | 14 | 15 | By Kevin Chin 2022 (https://kevch.in/) 16 | 17 | Requires stb_truetype.h (https://github.com/nothings/stb/blob/master/stb_truetype.h) 18 | Do this: 19 | #define VERTEXT_IMPLEMENTATION 20 | before you include this file in *one* C or C++ file to create the implementation. 21 | // i.e. it should look like this: 22 | #include ... 23 | #include ... 24 | #define STB_TRUETYPE_IMPLEMENTATION 25 | #include "stb_truetype.h" 26 | #define VERTEXT_IMPLEMENTATION 27 | #include "vertext.h" 28 | 29 | INTRO & PURPOSE: 30 | Single-header C library to generate textured quads for rendering text to the screen. 31 | This library does not perform rendering directly and instead provides a vertex buffer 32 | of vertices (in either Screen Space or Clip Space) and texture coordinates. This allows 33 | the library to work seamlessly with both OpenGL and DirectX. Probably also works with 34 | other graphics APIs too... 35 | 36 | Text rendering is a non-trivial task, and this library strives to make it easy and frictionless. 37 | 38 | This library strives to solve 2 problems: 39 | - Creating an individual vertex buffer / textured quad for every single character you 40 | want to draw is extremely inefficient - especially if each character has their own 41 | texture that needs to be binded to the graphics card. 42 | - Every character/glyph has varying sizes and parameters that affect how they should 43 | be drawn relative to all the other characters/glyphs. These must be considered when 44 | drawing a line of text. 45 | 46 | This library solves these problems by 47 | 1. generating a single, large texture combining all the individual textures of the 48 | characters - called a font Texture Atlas 49 | 2. generating a single vertex buffer containing the vertices of all the quads 50 | (https://en.wikipedia.org/wiki/Quadrilateral - one quad can draw one character) 51 | required by the text you want to draw 52 | 3. handling the assembly of the quad vertices for the single vertex buffer by 53 | using the character/glyph metrics (size, offsets, etc.) 54 | 55 | CONCEPT: 56 | This library serves as a text drawing "canvas". 57 | You can "append" lines of text or characters to the "canvas". You can control where 58 | the text gets "appended" to the "canvas" by moving the "cursor" (setting its location 59 | on the screen). Then when you want to draw the "canvas" with your graphics API (OpenGL, 60 | DirectX, etc.), you can "grab" the "canvas" (vertex buffer). 61 | Then, it is up to you and your graphics API to create the VAO and VBO on the graphics card 62 | using the vertex buffer you "grabbed" from this library. 63 | Check the USAGE EXAMPLEs below to see how this would translate to code. 64 | 65 | USAGE: 66 | This library REQUIRES Sean Barrett's stb_truetype.h to be included beforehand: 67 | https://raw.githubusercontent.com/nothings/stb/master/stb_truetype.h 68 | 69 | This library's purpose is to return an array of vertices and texture coordinates: 70 | [ x, y, u, v, x, y, u, v, ......, x, y, u, v ] 71 | You can feed this into a Vertex Buffer with a Stride of 4. 72 | 73 | Since this library only returns an array of vertex and texture coordinates, you 74 | should be able to feed that array into the vertex buffer of any graphics API and 75 | get it working. 76 | 77 | You can also make the library create and return an index buffer if you are using indexed 78 | drawing. You don't need to do this if you are not using indexed drawing. 79 | See the following links if you don't know what vertex indices or index buffers are: 80 | http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-9-vbo-indexing/ 81 | https://www.learnopengles.com/tag/index-buffer-object/ 82 | 83 | This library is not responsible for telling the graphics card to render text. You can 84 | do that on your own in your preferred graphics API using the vertex buffer (and index 85 | buffer if you enabled index drawing) that this library creates and a quad/ui rendering shader. 86 | 87 | > Vertex Coordinates: 88 | By default, Screen Space coordinates are generated instead of Clip Space coordinates. 89 | 90 | Quick reminder - Screen Space coordinates are where the top left corner of the 91 | window/screen is defined as x: 0, y: 0 and the bottom right corner is x: window width, 92 | y: window height. Clip Space coordinates are where the top left corner is x: -1, y: 1 93 | and the bottom right corner is x: 1, y: -1 (with 0, 0 being the center of the window). 94 | (https://learnopengl.com/Getting-started/Coordinate-Systems) 95 | 96 | You can pick which coordinate system to generate vertices for. If you already have an 97 | orthographic projection matrix for rendering UI or text, then you can make this library 98 | generate Screen Space coordinates and apply the orthographic projection matrix in your 99 | UI or text shader. You could also just create an orthographic projection matrix for this 100 | text-rendering purpose: 101 | https://glm.g-truc.net/0.9.1/api/a00237.html#ga71777a3b1d4fe1729cccf6eda05c8127 102 | https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glOrtho.xml 103 | 104 | If you just want to use Clip Space coordinates, then you need to set the flag for it: 105 | vtxt_setflags(VTXT_USE_CLIPSPACE_COORDS); 106 | and tell the library the window size: 107 | vtxt_backbuffersize(WIDTH, HEIGHT); 108 | If you are using Screen Space coordinates, you don't need to do any of that. 109 | 110 | > Config Flags: 111 | Use the following flags with vtxt_setflags(_flags_): 112 | VTXT_CREATE_INDEX_BUFFER: 113 | Sets the libary to use indexed vertices and return a index buffer as well when 114 | grabbing buffers. 115 | VTXT_USE_CLIPSPACE_COORDS: 116 | Sets the library to use generate vertices in Clip Space coordinates instead of 117 | Screen Space coordinates. 118 | VTXT_NEWLINE_ABOVE: 119 | Sets the library to move the cursor above the current line instead of below 120 | when calling vtxt_new_line. 121 | VTXT_FLIP_Y: 122 | Flips y vertex position in case you are using a coordinate system where "up" is negative y. 123 | 124 | > By Default: 125 | - no indexed drawing (unless specified with flag VTXT_CREATE_INDEX_BUFFER) 126 | - generates vertices in screenspace coordinates (unless specified with flag VTXT_USE_CLIPSPACE_COORDS) 127 | - "next line" is the line below the line we are on (unless specified with flag VTXT_NEWLINE_ABOVE) 128 | 129 | > The following "#define"s are unnecessary but optional: 130 | #define VTXT_MAX_CHAR_IN_BUFFER X (before including this library) where X is the 131 | maximum number of characters you want to allow in the vertex buffer at once. By default this value 132 | is 800 characters. Consider your memory use when setting this value because the memory for the 133 | vertex buffer and index buffers are located in the .data segment of the program's alloted memory. 134 | Every character increases the combined size of the two buffers by 120 bytes (e.g. 800 characters 135 | allocates 800 * 120 = 96000 bytes in the .data segment of memory) 136 | e.g. #define VTXT_MAX_CHAR_IN_BUFFER 500 137 | #define VERTEXT_IMPLEMENTATION 138 | #include "vertext.h" 139 | 140 | #define VTXT_ASCII_FROM X and #define VTXT_ASCII_TO Y where X and Y are the start and 141 | end ASCII codepoints to collect the font data for. In other words, if X is the character 'a' and Y is 142 | the character 'z', then the library will only collect the font data for the ASCII characters from 'a' 143 | to 'z'. By default, the start and end codepoints are set to ' ' and '~'. 144 | e.g. #define VTXT_ASCII_FROM 'a' 145 | #define VTXT_ASCII_TO 'z' 146 | #define VERTEXT_IMPLEMENTATION 147 | #include "vertext.h" 148 | 149 | #define VTXT_STATIC to make function declarations and function definitions static. This makes 150 | the implementation private to the source file that creates it. This allows you to have multiple 151 | instances of this library in your project without collision. You could use multiple vertex 152 | buffers at the same time without clearing the buffers. 153 | 154 | > Things to be aware of: 155 | - vtxt_font is around ~4KB, so don't copy it around. Just declare it once and then pass around 156 | a POINTER to it instead of passing it around by value. 157 | 158 | > Some types: 159 | vtxt_vertex_buffer - see comment at definition - This is what you want to get back from this library 160 | vtxt_bitmap - see comment at definition - basically data and pointer to bitmap image in memory 161 | vtxt_glyph - see comment at definition - info about specific ASCII glyph 162 | vtxt_font - see comment at definition - info about font - HUGE struct 163 | 164 | EXAMPLE (Pseudocode): 165 | 166 | Do only once: 167 | Optional: vtxt_setflags(VTXT_CREATE_INDEX_BUFFER); <-- Configures the library with given flags 168 | | 169 | V 170 | Optional: vtxt_backbuffersize(window width=1920, window height=1080); <-- Only required if using VTXT_USE_CLIPSPACE_COORDS 171 | | 172 | V 173 | Required: vtxt_init_font(font_handle, font_buffer, font_height_px); <-- DO ONLY ONCE PER FONT (or per font resolution) 174 | 175 | Loop: 176 | Optional: vtxt_move_cursor(x = 640, y = 360); <-- Set the cursor to x y (where to start drawing) 177 | | 178 | V 179 | Required: vtxt_append_line("some text", font_handle, text_height_px); <-- text to draw 180 | | 181 | V 182 | Optional: vtxt_new_line(x = 640, font); <-- Go to next line, set cursor x to 640 183 | | 184 | V 185 | Optional: vtxt_append_line("next lin", font_handle, text_height_px); <-- next line to draw 186 | | 187 | V 188 | Optional: vtxt_append_glyph('e', font_handle, text_height_px); <-- Can append individual glyphs also 189 | | 190 | V 191 | Required: vtxt_vertex_buffer grabbedBuffer = vtxt_grab_buffer(); <-- Grabs the buffer 192 | | 193 | V 194 | Required: * create/bind Vertex Array Object and Vertex Buffer Objects on GPU using your graphics API and grabbedBuffer 195 | | 196 | V 197 | Optional: vtxt_clear_buffer(); <-- REQUIRED IF you want to clear the appended text to append NEW text 198 | Only clear AFTER you bind the vertex and index buffers of grabbedBuffer 199 | to the VAO and VBO on the GPU. 200 | 201 | 202 | EXAMPLE (C code using OpenGL): 203 | 204 | #define TEXT_SIZE 30 205 | 206 | unsigned char* font_file; 207 | // Read the font file on disk (e.g. "arial.ttf") into a byte buffer in memory (e.g. font_file) using your own method 208 | // If you are using SDL, you can use SDL_RW. You could also use stdio.h's file operations (fopen, fread, fclose, etc.). 209 | 210 | vtxt_setflags(VTXT_CREATE_INDEX_BUFFER|VTXT_USE_CLIPSPACE_COORDS); // vertext.h setting 211 | vtxt_backbuffersize(WIDTH, HEIGHT); 212 | 213 | vtxt_font font_handle; 214 | vtxt_init_font(&font_handle, font_file, TEXT_SIZE); 215 | // TEXT_SIZE in init_font doesn't need to be the same as the TEXT_SIZE we use when we call append_line or glyph 216 | // Now font_handle has all the info we need. 217 | 218 | vtxt_clear_buffer(); 219 | vtxt_move_cursor(100, 100); 220 | vtxt_append_line("Hello, world!", &font_handle); 221 | 222 | vtxt_vertex_buffer vb = vtxt_grab_buffer(); 223 | 224 | // That's it. That's all you need to interact with this library. Everything below is just 225 | // using the vertex buffer from the library to actually get the text drawing in OpenGL. 226 | 227 | 228 | GLuint VAO_ID, VBO_ID, IBO_ID, TextureID; 229 | // Creating the VAO for our text in the GPU memory 230 | glBindVertexArray(VAO_ID); 231 | glBindBuffer(GL_ARRAY_BUFFER, VBO_ID); 232 | glBufferData(GL_ARRAY_BUFFER, 233 | vb.vertices_array_count * 4, 234 | vb.vertex_buffer, 235 | GL_STATIC_DRAW); 236 | glEnableVertexAttribArray(0); // vertices coordinates stream 237 | glVertexAttribPointer(0, 2, // 2 because vertex is 2 floats (x and y) 238 | GL_FLOAT, GL_FALSE, 239 | 4*4, // 4 stride (x y u v) * 4 bytes (size of float) 240 | 0); 241 | glEnableVertexAttribArray(1); // texture coordinates stream 242 | glVertexAttribPointer(0, 2, // 2 because texture coord is 2 floats (u and v) 243 | GL_FLOAT, GL_FALSE, 244 | 4*4, // 4 stride (x y u v) * 4 bytes (size of float) 245 | 2*4); // offset of 8 because two floats, x y, come before u v 246 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO_ID); 247 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, 248 | vb.indices_array_count * 4, 249 | vb.index_buffer, 250 | GL_STATIC_DRAW); 251 | glBindBuffer(GL_ARRAY_BUFFER, 0); 252 | glBindVertexArray(0); 253 | // Creating the font texture in GPU memory 254 | glGenTextures(1, &TextureID); 255 | glBindTexture(GL_TEXTURE_2D, TextureID); 256 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, // for both the target and source format, we can put GL_RED 257 | font_handle.font_atlas.width, // this just means the bit depth is 1 byte (just the alpha) 258 | font_handle.font_atlas.height, 259 | 0, GL_RED, GL_UNSIGNED_BYTE, 260 | font_handle.font_atlas.pixels); 261 | glActiveTexture(GL_TEXTURE0); 262 | // Draw call 263 | glUseProgram(SHADER PROGRAM FOR TEXT); // The text shader should use an orthographic projection matrix 264 | glBindVertexArray(mesh.id_vao); 265 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.id_ibo); 266 | glDrawElements(GL_TRIANGLES, vb.indices_array_count, GL_UNSIGNED_INT, nullptr); 267 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); 268 | glBindVertexArray(0); 269 | glUseProgram(0); 270 | 271 | TODO: 272 | - Signed Distance Fields 273 | - Kerning 274 | - Top-to-bottom text (vertical text) 275 | - Add flag to choose winding order (currently counter-clockwise winding order) 276 | */ 277 | #ifndef _INCLUDE_VERTEXT_H_ 278 | #define _INCLUDE_VERTEXT_H_ 279 | 280 | #ifndef VTXT_ASCII_FROM 281 | #define VTXT_ASCII_FROM ' ' // starting ASCII codepoint to collect font data for 282 | #endif 283 | #ifndef VTXT_ASCII_TO 284 | #define VTXT_ASCII_TO '~' // ending ASCII codepoint to collect font data for 285 | #endif 286 | #define VTXT_GLYPH_COUNT VTXT_ASCII_TO - VTXT_ASCII_FROM + 1 287 | 288 | #ifdef VTXT_STATIC 289 | #define VTXT_DEF static 290 | #else 291 | #define VTXT_DEF extern 292 | #endif 293 | 294 | /** Stores a pointer to the vertex buffer assembly array and the count of vertices in the 295 | array (total length of array would be count of vertices * 4). 296 | */ 297 | typedef struct vtxt_vertex_buffer 298 | { 299 | int vertex_count; // count of vertices in vertex buffer array (4 elements per vertex, so vertices_array_count / 4 = vertex_count) 300 | int vertices_array_count; // count of elements in vertex buffer array 301 | int indices_array_count; // count of elements in index buffer array 302 | float* vertex_buffer; // pointer to vertex buffer array 303 | unsigned int* index_buffer; // pointer to index buffer array 304 | } vtxt_vertex_buffer; 305 | 306 | /** vtxt_bitmap is a handle to hold a pointer to an unsigned byte bitmap in memory. Length/count 307 | of bitmap elements = width * height. 308 | */ 309 | typedef struct vtxt_bitmap 310 | { 311 | int width; // bitmap width 312 | int height; // bitmap height 313 | unsigned char* pixels; // unsigned byte bitmap 314 | } vtxt_bitmap; 315 | 316 | /** Stores information about a glyph. The glyph is identified via codepoint (ASCII). 317 | Check out https://learnopengl.com/img/in-practice/glyph.png 318 | Learn more about glyph metrics if you want. 319 | */ 320 | typedef struct vtxt_glyph 321 | { 322 | float width, height, advance,offset_x,offset_y,min_u,min_v,max_u,max_v; 323 | char codepoint; 324 | } vtxt_glyph; 325 | 326 | /** vtxt_font is a handle to hold font information. It's around ~4KB, so don't copy it around all the time. 327 | */ 328 | typedef struct vtxt_font 329 | { 330 | int font_height_px; // how tall the font's vertical extent is in pixels 331 | float ascender; // https://en.wikipedia.org/wiki/Ascender_(typography) 332 | float descender; // https://en.wikipedia.org/wiki/Descender 333 | float linegap; // gap between the bottom of the descender of one line to the top of the ascender of the line below 334 | vtxt_bitmap font_atlas; // stores the bitmap for the font texture atlas (https://en.wikipedia.org/wiki/Texture_atlas#/media/File:Texture_Atlas.png) 335 | vtxt_glyph glyphs[VTXT_GLYPH_COUNT]; // array for glyphs information 336 | } vtxt_font; 337 | 338 | enum _vtxt_config_flags_t 339 | { 340 | VTXT_CREATE_INDEX_BUFFER = 1 << 0, 341 | VTXT_USE_CLIPSPACE_COORDS = 1 << 1, 342 | VTXT_NEWLINE_ABOVE = 1 << 2, 343 | VTXT_FLIP_Y = 1 << 3, 344 | }; 345 | 346 | /** Configures this library to use the settings defined by _vtxt_config_flags_t. 347 | e.g. vtxt_setflags(VTXT_CREATE_INDEX_BUFFER | VTXT_USE_CLIPSPACE_COORDS); 348 | */ 349 | VTXT_DEF void vtxt_setflags(int newconfig); 350 | 351 | /** ONLY IF YOU WANT CLIPSPACE COORDINATES INSTEAD OF SCREENSPACE COORDINATES 352 | Tells vertext.h the size of your application's backbuffer size. If the 353 | backbuffer size changes, then you should call this again to update the size. 354 | */ 355 | VTXT_DEF void vtxt_backbuffersize(int width, int height); 356 | 357 | /** Initializes a vtxt_font font handle to store glyphs information, font information, and the font texture atlas. 358 | Collects the glyphs and font information from the font file (given as a binary buffer in memory) 359 | and generates the font texture atlas (https://en.wikipedia.org/wiki/Texture_atlas#/media/File:Texture_Atlas.png) 360 | Expensive, so you should only do this ONCE per font (and font size) and just keep the vtxt_font around somewhere. 361 | font_height_in_pixels := How tall the font's vertical extent (above the baseline) should be in pixels 362 | */ 363 | VTXT_DEF void vtxt_init_font(vtxt_font* font_handle, 364 | unsigned char* font_buffer, 365 | int font_height_in_pixels); 366 | 367 | /** Move cursor location (cursor represents the position on the screen where text is placed) 368 | */ 369 | VTXT_DEF void vtxt_move_cursor(int x, 370 | int y); 371 | 372 | /** Go to new line and set X location of cursor 373 | */ 374 | VTXT_DEF void vtxt_new_line(int x, 375 | vtxt_font* font, 376 | int text_height_px); 377 | 378 | /** Assemble quads for a line of text and append to vertex buffer. 379 | line_of_text is the text you want to draw e.g. "some text I want to Draw". 380 | font is the vtxt_font font handle that contains the font you want to use. 381 | text_height_px is the desired text height in pixels. 382 | */ 383 | VTXT_DEF void vtxt_append_line(const char* line_of_text, 384 | vtxt_font* font, 385 | int text_height_px); 386 | 387 | /** Same as vtxt_append_line but center horizontally where the cursor is. */ 388 | VTXT_DEF void vtxt_append_line_centered(const char* line_of_text, 389 | vtxt_font* font, 390 | int text_height_px); 391 | 392 | /** Same as vtxt_append_line but with text using right-alignment (the text is to the left of the cursor). */ 393 | VTXT_DEF void vtxt_append_line_align_right(const char* line_of_text, 394 | vtxt_font* font, 395 | int text_height_px); 396 | 397 | /** Assemble quad for a glyph and append to vertex buffer. 398 | font is the vtxt_font font handle that contains the font you want to use. 399 | text_height_px is the maximum vertical extent of the glyph in pixels 400 | */ 401 | VTXT_DEF void vtxt_append_glyph(const char in_glyph, 402 | vtxt_font* font, 403 | int text_height_px); 404 | 405 | /** Get vtxt_vertex_buffer with a pointer to the vertex buffer array 406 | and vertex buffer information. 407 | */ 408 | VTXT_DEF vtxt_vertex_buffer vtxt_grab_buffer(); 409 | 410 | /** Call before starting to append new text. 411 | Clears the vertex buffer that text is being appended to. 412 | If you called vtxt_grab_buffer and want to use the buffer you received, 413 | make sure you pass the buffer to OpenGL (glBufferData) or make a copy of 414 | the buffer before calling vtxt_clear_buffer. 415 | */ 416 | VTXT_DEF void vtxt_clear_buffer(); 417 | 418 | /** Set an offset to font linegap. Default is 0. */ 419 | VTXT_DEF void vtxt_set_linegap_offset(float offset); 420 | 421 | /** Returns the width and height of the minimum bounding box containing 422 | the given text using the given font and text height. */ 423 | VTXT_DEF void vtxt_get_text_bounding_box_info(float* width_out, 424 | float* height_out, 425 | const char* text, 426 | vtxt_font* font, 427 | int text_height_px); 428 | 429 | 430 | #endif // _INCLUDE_VERTEXT_H_ 431 | 432 | 433 | ///////////////////// IMPLEMENTATION ////////////////////////// 434 | #ifdef VERTEXT_IMPLEMENTATION 435 | 436 | #define _vtxt_internal static // vtxt local static variable 437 | #ifndef VTXT_MAX_CHAR_IN_BUFFER 438 | #define VTXT_MAX_CHAR_IN_BUFFER 800 // maximum characters allowed in vertex buffer ("canvas") 439 | #endif 440 | #define VTXT_MAX_FONT_RESOLUTION 100 // maximum font resolution when initializing font 441 | #define VTXT_DESIRED_ATLAS_WIDTH 400 // width of the font atlas 442 | #define VTXT_ATLAS_PAD_X 1 // x padding between the glyph textures on the texture atlas 443 | #define VTXT_ATLAS_PAD_Y 1 // y padding between the glyph textures on the texture atlas 444 | 445 | #define _vtxt_ceil(num) ((num) == (float)((int)(num)) ? (int)(num) : (((int)(num)) + 1)) 446 | 447 | // Buffers for vertices and texture_coords before they are written to GPU memory. 448 | // If you have a pointer to these buffers, DO NOT let these buffers be overwritten 449 | // before you bind the data to GPU memory. 450 | _vtxt_internal float _vtxt_vertex_buffer[VTXT_MAX_CHAR_IN_BUFFER * 6 * 4]; // 800 characters * 6 vertices * (2 xy + 2 uv) 451 | _vtxt_internal int _vtxt_vertex_count = 0; // Each vertex takes up 4 places in the assembly_buffer 452 | _vtxt_internal unsigned int _vtxt_index_buffer[VTXT_MAX_CHAR_IN_BUFFER * 6]; 453 | _vtxt_internal int _vtxt_index_count = 0; 454 | _vtxt_internal int _vtxt_config = 0b0; 455 | _vtxt_internal float _vtxt_linegap_offset = 0.f; 456 | _vtxt_internal int _vtxt_cursor_x = 0; // top left of the screen is pixel (0, 0), bot right of the screen is pixel (screen buffer width, screen buffer height) 457 | _vtxt_internal int _vtxt_cursor_y = 100; // cursor points to the base line at which to start drawing the glyph 458 | _vtxt_internal int _vtxt_screen_w_for_clipspace = 800; 459 | _vtxt_internal int _vtxt_screen_h_for_clipspace = 600; 460 | 461 | VTXT_DEF void 462 | vtxt_setflags(int newconfig) 463 | { 464 | int oldconfig = _vtxt_config; 465 | _vtxt_config = newconfig; 466 | 467 | if((oldconfig & VTXT_CREATE_INDEX_BUFFER) != (_vtxt_config & VTXT_CREATE_INDEX_BUFFER)) 468 | { 469 | vtxt_clear_buffer(); 470 | } 471 | } 472 | 473 | VTXT_DEF void 474 | vtxt_set_linegap_offset(float offset) 475 | { 476 | _vtxt_linegap_offset = offset; 477 | } 478 | 479 | VTXT_DEF void 480 | vtxt_backbuffersize(int width, int height) 481 | { 482 | _vtxt_screen_w_for_clipspace = width; 483 | _vtxt_screen_h_for_clipspace = height; 484 | } 485 | 486 | VTXT_DEF void 487 | vtxt_init_font(vtxt_font* font_handle, unsigned char* font_buffer, int font_height_in_pixels) 488 | { 489 | int desired_atlas_width = VTXT_DESIRED_ATLAS_WIDTH; 490 | 491 | if(font_height_in_pixels > VTXT_MAX_FONT_RESOLUTION) 492 | { 493 | return; 494 | } 495 | 496 | // Font metrics 497 | stbtt_fontinfo stb_font_info; 498 | stbtt_InitFont(&stb_font_info, font_buffer, 0); 499 | float stb_scale = stbtt_ScaleForMappingEmToPixels(&stb_font_info, (float)font_height_in_pixels); 500 | int stb_ascender; 501 | int stb_descender; 502 | int stb_linegap; 503 | stbtt_GetFontVMetrics(&stb_font_info, &stb_ascender, &stb_descender, &stb_linegap); 504 | font_handle->font_height_px = font_height_in_pixels; 505 | font_handle->ascender = (float)stb_ascender * stb_scale; 506 | font_handle->descender = (float)stb_descender * stb_scale; 507 | font_handle->linegap = (float)stb_linegap * stb_scale; 508 | 509 | // LOAD GLYPH BITMAP AND INFO FOR EVERY CHARACTER WE WANT IN THE FONT 510 | vtxt_bitmap temp_glyph_bitmaps[VTXT_GLYPH_COUNT]; 511 | int tallest_glyph_height = 0; 512 | int aggregate_glyph_width = 0; 513 | // load glyph data 514 | for(char char_index = VTXT_ASCII_FROM; char_index <= VTXT_ASCII_TO; ++char_index) // ASCII 515 | { 516 | vtxt_glyph glyph; 517 | 518 | // get glyph metrics from stbtt 519 | int stb_advance; 520 | int stb_leftbearing; 521 | stbtt_GetCodepointHMetrics(&stb_font_info, 522 | char_index, 523 | &stb_advance, 524 | &stb_leftbearing); 525 | glyph.codepoint = char_index; 526 | glyph.advance = (float)stb_advance * stb_scale; 527 | int stb_width, stb_height; 528 | int stb_offset_x, stb_offset_y; 529 | unsigned char* stb_bitmap_temp = stbtt_GetCodepointBitmap(&stb_font_info, 530 | 0, stb_scale, 531 | char_index, 532 | &stb_width, 533 | &stb_height, 534 | &stb_offset_x, 535 | &stb_offset_y); 536 | glyph.width = (float)stb_width; 537 | glyph.height = (float)stb_height; 538 | glyph.offset_x = (float)stb_offset_x; 539 | glyph.offset_y = (float)stb_offset_y; 540 | 541 | // Copy stb_bitmap_temp bitmap into glyph's pixels bitmap so we can free stb_bitmap_temp 542 | int iter = char_index - VTXT_ASCII_FROM; 543 | temp_glyph_bitmaps[iter].pixels = (unsigned char*) calloc((size_t)glyph.width * (size_t)glyph.height, 1); 544 | for(int row = 0; row < (int) glyph.height; ++row) 545 | { 546 | for(int col = 0; col < (int) glyph.width; ++col) 547 | { 548 | // Flip the bitmap image from top to bottom to bottom to top 549 | temp_glyph_bitmaps[iter].pixels[row * (int) glyph.width + col] = stb_bitmap_temp[((int) glyph.height - row - 1) * (int) glyph.width + col]; 550 | } 551 | } 552 | temp_glyph_bitmaps[iter].width = (int) glyph.width; 553 | temp_glyph_bitmaps[iter].height = (int) glyph.height; 554 | aggregate_glyph_width += (int)glyph.width + VTXT_ATLAS_PAD_X; 555 | if(tallest_glyph_height < (int)glyph.height) 556 | { 557 | tallest_glyph_height = (int)glyph.height; 558 | } 559 | stbtt_FreeBitmap(stb_bitmap_temp, 0); 560 | 561 | font_handle->glyphs[iter] = glyph; 562 | } 563 | 564 | int desired_atlas_height = (tallest_glyph_height + VTXT_ATLAS_PAD_Y) 565 | * _vtxt_ceil((float)aggregate_glyph_width / (float)desired_atlas_width); 566 | // Build font atlas bitmap based on these parameters 567 | vtxt_bitmap atlas; 568 | atlas.pixels = (unsigned char*) calloc(desired_atlas_width * desired_atlas_height, 1); // TODO avoid calloc here 569 | atlas.width = desired_atlas_width; 570 | atlas.height = desired_atlas_height; 571 | // COMBINE ALL GLYPH BITMAPS INTO FONT ATLAS 572 | int glyph_count = VTXT_GLYPH_COUNT; 573 | int atlas_x = 0; 574 | int atlas_y = 0; 575 | for(int i = 0; i < glyph_count; ++i) 576 | { 577 | vtxt_bitmap glyph_bitmap = temp_glyph_bitmaps[i]; 578 | if (atlas_x + glyph_bitmap.width > atlas.width) // check if move atlas bitmap cursor to next line 579 | { 580 | atlas_x = 0; 581 | atlas_y += tallest_glyph_height + VTXT_ATLAS_PAD_Y; 582 | } 583 | 584 | for(int glyph_y = 0; glyph_y < glyph_bitmap.height; ++glyph_y) 585 | { 586 | for(int glyph_x = 0; glyph_x < glyph_bitmap.width; ++glyph_x) 587 | { 588 | atlas.pixels[(atlas_y + glyph_y) * atlas.width + atlas_x + glyph_x] 589 | = glyph_bitmap.pixels[glyph_y * glyph_bitmap.width + glyph_x]; 590 | } 591 | } 592 | font_handle->glyphs[i].min_u = (float) atlas_x / (float) atlas.width; 593 | font_handle->glyphs[i].min_v = (float) atlas_y / (float) atlas.height; 594 | font_handle->glyphs[i].max_u = (float) (atlas_x + glyph_bitmap.width) / (float) atlas.width; 595 | font_handle->glyphs[i].max_v = (float) (atlas_y + glyph_bitmap.height) / (float) atlas.height; 596 | 597 | atlas_x += glyph_bitmap.width + VTXT_ATLAS_PAD_X; // move the atlas bitmap cursor by glyph bitmap width 598 | 599 | free(glyph_bitmap.pixels); 600 | } 601 | font_handle->font_atlas = atlas; 602 | } 603 | 604 | VTXT_DEF void 605 | vtxt_move_cursor(int x, int y) 606 | { 607 | _vtxt_cursor_x = x; 608 | _vtxt_cursor_y = y; 609 | } 610 | 611 | VTXT_DEF void 612 | vtxt_new_line(int x, vtxt_font* font, int text_height_px) 613 | { 614 | float scale = (float)text_height_px / (float)font->font_height_px; 615 | float linegap = font->linegap + _vtxt_linegap_offset; 616 | _vtxt_cursor_x = x; 617 | if(_vtxt_config & VTXT_NEWLINE_ABOVE) 618 | { 619 | if(_vtxt_config & VTXT_FLIP_Y) 620 | { 621 | _vtxt_cursor_y += (int) ((-font->descender + linegap + font->ascender)*scale); 622 | } 623 | else 624 | { 625 | _vtxt_cursor_y -= (int) ((-font->descender + linegap + font->ascender)*scale); 626 | } 627 | } 628 | else 629 | { 630 | if(_vtxt_config & VTXT_FLIP_Y) 631 | { 632 | _vtxt_cursor_y -= (int) ((-font->descender + linegap + font->ascender)*scale); 633 | } 634 | else 635 | { 636 | _vtxt_cursor_y += (int) ((-font->descender + linegap + font->ascender)*scale); 637 | } 638 | } 639 | } 640 | 641 | VTXT_DEF void 642 | __private_vtxt_append_glyph(const char in_glyph, vtxt_font* font, int text_height_px, float x_offset_from_cursor) 643 | { 644 | if(in_glyph < VTXT_ASCII_FROM || in_glyph > VTXT_ASCII_TO) // Make sure we have the data for this glyph 645 | { 646 | return; 647 | } 648 | 649 | if(VTXT_MAX_CHAR_IN_BUFFER * 6 < _vtxt_vertex_count + 6) // Make sure we are not exceeding the array size 650 | { 651 | return; 652 | } 653 | 654 | float scale = (float)text_height_px / (float)font->font_height_px; 655 | vtxt_glyph glyph = font->glyphs[in_glyph - VTXT_ASCII_FROM]; 656 | glyph.advance *= scale; 657 | glyph.width *= scale; // NOTE(Kevin): 2022-06-15 scale was float, but width and height were integers so rounding was causing text to render strangely - fixed by just changing width and height to floats 658 | glyph.height *= scale; 659 | glyph.offset_x *= scale; 660 | glyph.offset_y *= scale; 661 | 662 | // For each of the 6 vertices, fill in the _vtxt_vertex_buffer in the order x y u v 663 | int STRIDE = 4; 664 | 665 | float top = _vtxt_cursor_y + glyph.offset_y; 666 | float bot = _vtxt_cursor_y + glyph.offset_y + glyph.height; 667 | float left = _vtxt_cursor_x + glyph.offset_x + x_offset_from_cursor; 668 | float right = _vtxt_cursor_x + glyph.offset_x + glyph.width + x_offset_from_cursor; 669 | if(_vtxt_config & VTXT_FLIP_Y) 670 | { 671 | top = _vtxt_cursor_y - glyph.offset_y; 672 | bot = _vtxt_cursor_y - glyph.offset_y - glyph.height; 673 | } 674 | 675 | if(_vtxt_config & VTXT_USE_CLIPSPACE_COORDS) 676 | { 677 | top = (1.f - ((top / _vtxt_screen_h_for_clipspace) * 2.f)); 678 | bot = (1.f - ((bot / _vtxt_screen_h_for_clipspace) * 2.f)); 679 | left = ((left / _vtxt_screen_w_for_clipspace) * 2.f) - 1.f; 680 | right = ((right / _vtxt_screen_w_for_clipspace) * 2.f) - 1.f; 681 | } 682 | 683 | if(_vtxt_config & VTXT_CREATE_INDEX_BUFFER) 684 | { 685 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 0] = left; 686 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 1] = bot; 687 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 2] = glyph.min_u; 688 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 3] = glyph.min_v; 689 | 690 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 4] = left; 691 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 5] = top; 692 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 6] = glyph.min_u; 693 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 7] = glyph.max_v; 694 | 695 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 8] = right; 696 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 9] = top; 697 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 10] = glyph.max_u; 698 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 11] = glyph.max_v; 699 | 700 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 12] = right; 701 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 13] = bot; 702 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 14] = glyph.max_u; 703 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 15] = glyph.min_v; 704 | 705 | _vtxt_index_buffer[_vtxt_index_count + 0] = _vtxt_vertex_count + 0; 706 | _vtxt_index_buffer[_vtxt_index_count + 1] = _vtxt_vertex_count + 2; 707 | _vtxt_index_buffer[_vtxt_index_count + 2] = _vtxt_vertex_count + 1; 708 | _vtxt_index_buffer[_vtxt_index_count + 3] = _vtxt_vertex_count + 0; 709 | _vtxt_index_buffer[_vtxt_index_count + 4] = _vtxt_vertex_count + 3; 710 | _vtxt_index_buffer[_vtxt_index_count + 5] = _vtxt_vertex_count + 2; 711 | 712 | _vtxt_vertex_count += 4; 713 | _vtxt_index_count += 6; 714 | } 715 | else 716 | { 717 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 0] = left; 718 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 1] = bot; 719 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 2] = glyph.min_u; 720 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 3] = glyph.min_v; 721 | 722 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 4] = right; 723 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 5] = top; 724 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 6] = glyph.max_u; 725 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 7] = glyph.max_v; 726 | 727 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 8] = left; 728 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 9] = top; 729 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 10] = glyph.min_u; 730 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 11] = glyph.max_v; 731 | 732 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 12] = right; 733 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 13] = bot; 734 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 14] = glyph.max_u; 735 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 15] = glyph.min_v; 736 | 737 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 16] = right; 738 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 17] = top; 739 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 18] = glyph.max_u; 740 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 19] = glyph.max_v; 741 | 742 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 20] = left; 743 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 21] = bot; 744 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 22] = glyph.min_u; 745 | _vtxt_vertex_buffer[_vtxt_vertex_count * STRIDE + 23] = glyph.min_v; 746 | 747 | _vtxt_vertex_count += 6; 748 | } 749 | 750 | // Advance the cursor 751 | _vtxt_cursor_x += (int) glyph.advance; 752 | } 753 | 754 | VTXT_DEF void 755 | vtxt_append_glyph(const char in_glyph, vtxt_font* font, int text_height_px) 756 | { 757 | __private_vtxt_append_glyph(in_glyph, font, text_height_px, 0.f); 758 | } 759 | 760 | VTXT_DEF void 761 | vtxt_append_line(const char* line_of_text, vtxt_font* font, int text_height_px) 762 | { 763 | int line_start_x = _vtxt_cursor_x; 764 | while(*line_of_text != '\0') 765 | { 766 | if(*line_of_text != '\n') 767 | { 768 | if(VTXT_MAX_CHAR_IN_BUFFER * 6 < _vtxt_vertex_count + 6) // Make sure we are not exceeding the array size 769 | { 770 | break; 771 | } 772 | vtxt_append_glyph(*line_of_text, font, text_height_px); 773 | } 774 | else 775 | { 776 | vtxt_new_line(line_start_x, font, text_height_px); 777 | } 778 | ++line_of_text;// next character 779 | } 780 | } 781 | 782 | VTXT_DEF void 783 | vtxt_append_line_align_right(const char* line_of_text, vtxt_font* font, int text_height_px) 784 | { 785 | int line_start_x = _vtxt_cursor_x; 786 | char line_buffer[256]; 787 | int lb_index = 0; 788 | while (*line_of_text != '\0' && *line_of_text != '\n') 789 | { 790 | line_buffer[lb_index++] = *line_of_text++; 791 | } 792 | float line_length = 0.f; 793 | for (int i = 0; i < lb_index; ++i) 794 | { 795 | char in_glyph = line_buffer[i]; 796 | float scale = (float)text_height_px / (float)font->font_height_px; 797 | vtxt_glyph glyph = font->glyphs[in_glyph - VTXT_ASCII_FROM]; 798 | glyph.advance *= scale; 799 | line_length += glyph.advance; 800 | } 801 | for (int i = 0; i < lb_index; ++i) 802 | { 803 | char in_glyph = line_buffer[i]; 804 | if (VTXT_MAX_CHAR_IN_BUFFER * 6 < _vtxt_vertex_count + 6) // Make sure we are not exceeding the array size 805 | { 806 | break; 807 | } 808 | __private_vtxt_append_glyph(in_glyph, font, text_height_px, -line_length); 809 | } 810 | 811 | if (*line_of_text == '\n') 812 | { 813 | vtxt_new_line(line_start_x, font, text_height_px); 814 | ++line_of_text; 815 | vtxt_append_line_align_right(line_of_text, font, text_height_px); 816 | } 817 | else // terminate 818 | { 819 | 820 | } 821 | } 822 | 823 | VTXT_DEF void 824 | vtxt_append_line_centered(const char* line_of_text, vtxt_font* font, int text_height_px) 825 | { 826 | int line_start_x = _vtxt_cursor_x; 827 | char line_buffer[256]; 828 | int lb_index = 0; 829 | while(*line_of_text != '\0' && *line_of_text != '\n') 830 | { 831 | line_buffer[lb_index++] = *line_of_text++; 832 | } 833 | float line_length = 0.f; 834 | for(int i = 0; i < lb_index; ++i) 835 | { 836 | char in_glyph = line_buffer[i]; 837 | float scale = (float)text_height_px / (float)font->font_height_px; 838 | vtxt_glyph glyph = font->glyphs[in_glyph - VTXT_ASCII_FROM]; 839 | glyph.advance *= scale; 840 | line_length += glyph.advance; 841 | } 842 | float half_line_length = line_length/2.f; 843 | for(int i = 0; i < lb_index; ++i) 844 | { 845 | char in_glyph = line_buffer[i]; 846 | if(VTXT_MAX_CHAR_IN_BUFFER * 6 < _vtxt_vertex_count + 6) // Make sure we are not exceeding the array size 847 | { 848 | break; 849 | } 850 | __private_vtxt_append_glyph(in_glyph, font, text_height_px, -half_line_length); 851 | } 852 | 853 | if(*line_of_text == '\n') 854 | { 855 | vtxt_new_line(line_start_x, font, text_height_px); 856 | ++line_of_text; 857 | vtxt_append_line_centered(line_of_text, font, text_height_px); 858 | } 859 | else // terminate 860 | { 861 | 862 | } 863 | } 864 | 865 | VTXT_DEF void 866 | vtxt_get_text_bounding_box_info(float* width_out, 867 | float* height_out, 868 | const char* text, 869 | vtxt_font* font, 870 | int text_height_px) 871 | { 872 | float wSumLargestSoFar = 0; 873 | float wSumCurrent = 0; 874 | float hSum = 0; 875 | 876 | while (*text != '\0') 877 | { 878 | if (*text != '\n') 879 | { 880 | if (*text < VTXT_ASCII_FROM || *text > VTXT_ASCII_TO) // Make sure we have the data for this glyph 881 | { 882 | continue; 883 | } 884 | 885 | bool isLastGlyphInLine = *(text + 1) == '\0' || *(text + 1) == '\n'; 886 | float scale = (float)text_height_px / (float)font->font_height_px; 887 | vtxt_glyph glyph = font->glyphs[*text - VTXT_ASCII_FROM]; 888 | glyph.advance *= scale; 889 | glyph.width *= scale; 890 | glyph.height *= scale; 891 | glyph.offset_x *= scale; 892 | glyph.offset_y *= scale; 893 | 894 | wSumCurrent += isLastGlyphInLine ? glyph.offset_x + glyph.width : glyph.advance; 895 | 896 | if (isLastGlyphInLine) 897 | { 898 | if (wSumCurrent > wSumLargestSoFar) 899 | { 900 | wSumLargestSoFar = wSumCurrent; 901 | } 902 | 903 | float linegap = font->linegap + _vtxt_linegap_offset; 904 | float heightOfThisLine = (font->ascender - font->descender + linegap) * scale; 905 | // Note(Kevin): honestly could prob just use the tallest glyph.height + glyph.offset_y instead 906 | // TODO(Kevin): Only add font->linegap if there is a new line with legit characters 907 | hSum += heightOfThisLine; 908 | } 909 | } 910 | ++text; // next character 911 | } 912 | 913 | *width_out = wSumLargestSoFar; 914 | *height_out = hSum; 915 | } 916 | 917 | VTXT_DEF vtxt_vertex_buffer 918 | vtxt_grab_buffer() 919 | { 920 | vtxt_vertex_buffer retval; 921 | retval.vertex_buffer = _vtxt_vertex_buffer; 922 | if(_vtxt_config & VTXT_CREATE_INDEX_BUFFER) 923 | { 924 | retval.vertices_array_count = _vtxt_vertex_count * 4; 925 | retval.index_buffer = _vtxt_index_buffer; 926 | retval.indices_array_count = _vtxt_index_count; 927 | } 928 | else 929 | { 930 | retval.vertices_array_count = _vtxt_vertex_count * 6; 931 | retval.index_buffer = NULL; 932 | retval.indices_array_count = 0; 933 | } 934 | retval.vertex_count = _vtxt_vertex_count; 935 | return retval; 936 | } 937 | 938 | VTXT_DEF void 939 | vtxt_clear_buffer() 940 | { 941 | // No need to actually clear the vertex and index buffers back to 0 942 | // Setting the counts back to 0 will suffice 943 | _vtxt_vertex_count = 0; 944 | _vtxt_index_count = 0; 945 | } 946 | 947 | // clean up 948 | #undef VTXT_DEF 949 | #undef _vtxt_internal 950 | #undef VTXT_ASCII_FROM 951 | #undef VTXT_ASCII_TO 952 | #undef VTXT_MAX_CHAR_IN_BUFFER 953 | #undef VTXT_GLYPH_COUNT 954 | #undef VTXT_MAX_FONT_RESOLUTION 955 | #undef VTXT_DESIRED_ATLAS_WIDTH 956 | #undef VTXT_ATLAS_PAD_X 957 | #undef VTXT_ATLAS_PAD_Y 958 | 959 | #undef VERTEXT_IMPLEMENTATION 960 | #endif // VERTEXT_IMPLEMENTATION 961 | --------------------------------------------------------------------------------