Text wrapping, string & utf8 helpers, linmath constants
This commit is contained in:
parent
ca7259aeb8
commit
9664530935
10 changed files with 246 additions and 85 deletions
|
@ -95,9 +95,10 @@ Simply add `#include "oogabooga/examples/some_example.c"` to build.c and compile
|
|||
|
||||
Other than examples, a great way to learn is to delve into the code of whatever module you're using. The codebase is written with this in mind.
|
||||
|
||||
## Known bugs
|
||||
## Known bugs & issues
|
||||
- Window positioning & sizing is fucky wucky
|
||||
- Converting 24-bit audio files doesn't really work
|
||||
- Compiling with msys, cygwin, mingw etc fails
|
||||
|
||||
## Licensing
|
||||
By default, the repository has an educational license that makes the engine free to use for personal projects.
|
||||
|
|
73
TODO
73
TODO
|
@ -1,73 +0,0 @@
|
|||
|
||||
- Gamepad
|
||||
|
||||
- Compile with msys2, cygwin, mingw64
|
||||
|
||||
- Audio
|
||||
- Allow audio programming
|
||||
- Inject mixer proc per player
|
||||
- Inject a mixer proc before and after filling output buffer
|
||||
- Better spacialization
|
||||
- Fix vorbis >FILE< streaming (#StbVorbisFileStream)
|
||||
- Handle audio sources which had their format defaulted to update when default device changes
|
||||
For streamed audio sources this should be easy enough, because the conversion happens from raw format to source format as we stream it.
|
||||
For loaded sources though, we would need to convert the source->pcm_frames.
|
||||
Probably just dirty flag
|
||||
- Optimize
|
||||
- Spam simd
|
||||
- Pool of intermediate buffers (2^)
|
||||
- Bugs / Issues:
|
||||
- Small fadeout on start/pause is sometimes noisy
|
||||
- If audio starts/end abrubtly we will get noise. Maybe detect that on load and apply small fade in/out?
|
||||
- Setting audio source to a format which differs from audio output format in both channels and bit_width at the same time will produce pure loud noise.
|
||||
- 24-Bit audio conversion doesn't really work
|
||||
|
||||
- General bugs & issues
|
||||
- Window width&height is zero when minimized (and we make a 0x0 swap chain)
|
||||
- Window positioning & sizing is fucky wucky
|
||||
- Memory error messages are misleading when no VERY_DEBUG
|
||||
|
||||
- Renderer
|
||||
- API to pass constant values to shader (codegen #define's)
|
||||
- Draw_Frame instances
|
||||
- Or draw to a custom Quad_Buffer, then draw quad buffers ?
|
||||
- draw_deferred ?
|
||||
- Draw_Frame Render_Image
|
||||
|
||||
- Fonts
|
||||
- Atlases are way too big, render atlases with size depending on font_height (say, 128 codepoints per atlas)
|
||||
|
||||
- OS
|
||||
- Window::bool is_minimized
|
||||
- don't set window.width & window.height to 0
|
||||
- Handle monitor change
|
||||
- Sockets recv, send
|
||||
- Mouse pointer
|
||||
- Hide mouse pointer
|
||||
|
||||
- Better hash table
|
||||
|
||||
- Memory
|
||||
- Arenas
|
||||
|
||||
- Examples/Guides:
|
||||
- Scaling text for pixel perfect rendering
|
||||
- Z sorting
|
||||
- Scissor boxing
|
||||
- Concurrency
|
||||
- strings
|
||||
|
||||
- Rework profiler
|
||||
- Store records and convert to google trace format on exit
|
||||
- Measure both time and cycles, output a google_trace_cycles.json & google_trace_time.json
|
||||
|
||||
- Concurrency
|
||||
- Event objects for binary semaphore
|
||||
|
||||
- Needs testing:
|
||||
- Audio format channel conversions
|
||||
- sample rate downsampling
|
||||
- non stereo or mono audio
|
||||
- Audio spacialization on anything that's not stereo
|
||||
- Compiling with msvc
|
||||
- Compiling with gcc
|
4
build.c
4
build.c
|
@ -35,14 +35,14 @@ typedef struct Context_Extra {
|
|||
// This is a minimal starting point for new projects. Copy & rename to get started
|
||||
// #include "oogabooga/examples/minimal_game_loop.c"
|
||||
|
||||
// #include "oogabooga/examples/text_rendering.c"
|
||||
#include "oogabooga/examples/text_rendering.c"
|
||||
// #include "oogabooga/examples/custom_logger.c"
|
||||
// #include "oogabooga/examples/renderer_stress_test.c"
|
||||
// #include "oogabooga/examples/tile_game.c"
|
||||
// #include "oogabooga/examples/audio_test.c"
|
||||
// #include "oogabooga/examples/custom_shader.c"
|
||||
// #include "oogabooga/examples/growing_array_example.c"
|
||||
#include "oogabooga/examples/input_example.c"
|
||||
// #include "oogabooga/examples/input_example.c"
|
||||
|
||||
// This is where you swap in your own project!
|
||||
// #include "entry_yourepicgamename.c"
|
||||
|
|
|
@ -1,8 +1,48 @@
|
|||
## v0.01.004
|
||||
- Input
|
||||
- Added Gamepad support
|
||||
- Gamepad keycodes for buttons & clicks
|
||||
- input_frame.left_stick .right_stick .left_trigger .right_trigger
|
||||
- event.gamepad_index for key inputs if it's from the gamepad
|
||||
- INPUT_EVENT_GAMEPAD_AXIS for same axes but in events and with the event.gamepad_index set
|
||||
- Deadzone globals:
|
||||
Vector2 deadzone_left_stick
|
||||
Vector2 deadzone_right_stick
|
||||
float32 deadzone_left_trigger
|
||||
float32 deadzone_right_trigger
|
||||
- Vibration
|
||||
set_gamepad_vibration(float32 left, float32 right)
|
||||
set_specific_gamepad_vibration(int gamepad_index, float32 left, float32 right)
|
||||
|
||||
- Text
|
||||
- Implemented word-wrapping text into lines with
|
||||
split_text_to_lines_with_wrapping()
|
||||
- Added Gfx_Font_Metrics.new_line_offset for an exact value for how much you should decrement the Y on newline
|
||||
|
||||
- strings
|
||||
- Added some utf8 helpers
|
||||
utf8_index_to_byte_index()
|
||||
utf8_slice()
|
||||
- Added whitespace trimming helpers
|
||||
string_trim() (trim right & left)
|
||||
string_trim_left()
|
||||
string_trim_right()
|
||||
|
||||
- Misc
|
||||
- input_example.c
|
||||
- Reworked os_get_current_time_in_seconds() -> os_get_elapsed_seconds()
|
||||
Now returns seconds sincs app init.
|
||||
- draw_frame.view -> draw_frame.camera_xform (deprecated .view)
|
||||
- Unecessary refactoring in drawing.c
|
||||
- Fixed temporary storage overflow warning (contributor kacpercwiklinski f9bf7ff)
|
||||
- Added build.sh for building on linux (contributor 0xf0adc 90f00b)
|
||||
- Added linmath constants
|
||||
v2_one
|
||||
v3_one
|
||||
v4_one
|
||||
v2_zero
|
||||
v3_zero
|
||||
v4_zero
|
||||
|
||||
## v0.01.003 - Mouse pointers, Audio improvement & features, bug fixes
|
||||
- Os layer
|
||||
|
|
|
@ -14,6 +14,9 @@ int entry(int argc, char **argv) {
|
|||
|
||||
const u32 font_height = 48;
|
||||
|
||||
seed_for_random = rdtsc();
|
||||
u64 gunk_seed = get_random();
|
||||
|
||||
while (!window.should_close) tm_scope("Frame") {
|
||||
reset_temporary_storage();
|
||||
|
||||
|
@ -29,6 +32,8 @@ int entry(int argc, char **argv) {
|
|||
draw_text(font, STR("I am text"), font_height, v2(-2, 2), v2(1, 1), COLOR_BLACK);
|
||||
draw_text(font, STR("I am text"), font_height, v2(0, 0), v2(1, 1), COLOR_WHITE);
|
||||
|
||||
draw_text(font, tprint("Time: %f", os_get_elapsed_seconds()), font_height, v2(0, 100), v2(1, 1), COLOR_WHITE);
|
||||
|
||||
float now = (float)os_get_elapsed_seconds();
|
||||
float animated_x = sin(now*0.1)*(window.width*0.5);
|
||||
|
||||
|
@ -57,7 +62,25 @@ int entry(int argc, char **argv) {
|
|||
local_persist bool show_bounds = false;
|
||||
if (is_key_just_pressed('E')) show_bounds = !show_bounds;
|
||||
|
||||
|
||||
string long_text = STR("Jaunty jackrabbits juggle quaint TTT quilts and quirky quinces, \nquickly queuing up for a jubilant, jazzy jamboree in the jungle.\nLorem ipsilum ");
|
||||
|
||||
// Generate some random gunk to add to the long text
|
||||
u64 n = ((sin(now)+1)/2.0)*100;
|
||||
seed_for_random = gunk_seed;
|
||||
if (n > 0) {
|
||||
string gunk = talloc_string(n);
|
||||
for (u64 i = 0; i < n; i++) {
|
||||
if (get_random_float32() < 0.2) {
|
||||
gunk.data[i] = ' ';
|
||||
continue;
|
||||
}
|
||||
gunk.data[i] = get_random_int_in_range('a', 'z');
|
||||
}
|
||||
|
||||
long_text = string_concat(long_text, gunk, get_temporary_allocator());
|
||||
}
|
||||
|
||||
if (show_bounds) {
|
||||
// Visualize the bounds we get from metrics
|
||||
Gfx_Text_Metrics m = measure_text(font, long_text, font_height, v2(1, 1));
|
||||
|
@ -66,6 +89,18 @@ int entry(int argc, char **argv) {
|
|||
}
|
||||
draw_text(font, long_text, font_height, v2(-600, -200), v2(1, 1), COLOR_WHITE);
|
||||
|
||||
// Wrap text and draw each returned line
|
||||
string *long_text_wrapped = split_text_to_lines_with_wrapping(long_text, window.width/2*0.9, font, font_height, v2_one, true);
|
||||
float32 y = 200;
|
||||
for (u64 i = 0; i < growing_array_get_valid_count(long_text_wrapped); i += 1) {
|
||||
string line = long_text_wrapped[i];
|
||||
Gfx_Font_Metrics m = get_font_metrics(font, font_height);
|
||||
|
||||
draw_text(font, line, font_height, v2(-window.width/2+20, y), v2(1, 1), COLOR_WHITE);
|
||||
|
||||
y -= m.new_line_offset;
|
||||
}
|
||||
|
||||
os_update();
|
||||
gfx_update();
|
||||
|
||||
|
|
120
oogabooga/font.c
120
oogabooga/font.c
|
@ -75,6 +75,8 @@ typedef struct Gfx_Font_Metrics {
|
|||
|
||||
float line_spacing;
|
||||
|
||||
float new_line_offset;
|
||||
|
||||
} Gfx_Font_Metrics;
|
||||
typedef struct Gfx_Glyph {
|
||||
u32 codepoint;
|
||||
|
@ -196,6 +198,9 @@ void font_variation_init(Gfx_Font_Variation *variation, Gfx_Font *font, u32 font
|
|||
variation->metrics.latin_ascent = c_ascent;
|
||||
}
|
||||
|
||||
variation->metrics.new_line_offset
|
||||
= (variation->metrics.latin_ascent-variation->metrics.latin_descent+variation->metrics.line_spacing);
|
||||
|
||||
variation->initted = true;
|
||||
}
|
||||
|
||||
|
@ -297,7 +302,7 @@ void walk_glyphs(Walk_Glyphs_Spec spec, Walk_Glyphs_Callback_Proc proc) {
|
|||
|
||||
if (c == '\n') {
|
||||
x = 0;
|
||||
y -= (variation->metrics.latin_ascent-variation->metrics.latin_descent+variation->metrics.line_spacing)*spec.scale.y;
|
||||
y -= variation->metrics.new_line_offset*spec.scale.y;
|
||||
last_c = 0;
|
||||
}
|
||||
|
||||
|
@ -343,11 +348,12 @@ Gfx_Font_Metrics get_font_metrics(Gfx_Font *font, u32 raster_height) {
|
|||
Gfx_Font_Metrics get_font_metrics_scaled(Gfx_Font *font, u32 raster_height, Vector2 scale) {
|
||||
Gfx_Font_Metrics metrics = get_font_metrics(font, raster_height);
|
||||
|
||||
metrics.latin_ascent *= scale.x;
|
||||
metrics.latin_descent *= scale.x;
|
||||
metrics.max_ascent *= scale.x;
|
||||
metrics.max_descent *= scale.x;
|
||||
metrics.line_spacing *= scale.x;
|
||||
metrics.latin_ascent *= scale.y;
|
||||
metrics.latin_descent *= scale.y;
|
||||
metrics.max_ascent *= scale.y;
|
||||
metrics.max_descent *= scale.y;
|
||||
metrics.line_spacing *= scale.y;
|
||||
metrics.new_line_offset *= scale.y;
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
@ -405,3 +411,105 @@ Gfx_Text_Metrics measure_text(Gfx_Font *font, string text, u32 raster_height, Ve
|
|||
return c.m;
|
||||
}
|
||||
|
||||
typedef struct State_For_Glyph_Line_Break_Search {
|
||||
u64 *line_break_indices;
|
||||
u64 *glyph_count_per_line;
|
||||
float32 width;
|
||||
u64 line_num;
|
||||
u64 start_index;
|
||||
u64 index;
|
||||
u64 last_space_index;
|
||||
float32 line_start_x;
|
||||
float32 last_space_x; // -elon
|
||||
u64 count;
|
||||
Vector2 scale;
|
||||
} State_For_Glyph_Line_Break_Search;
|
||||
bool text_line_wrapping_callback(Gfx_Glyph g, Gfx_Font_Atlas *atlas, float x, float y, void *ud) {
|
||||
State_For_Glyph_Line_Break_Search *state = (State_For_Glyph_Line_Break_Search*)ud;
|
||||
|
||||
bool is_newline = g.codepoint == '\n';
|
||||
if (g.codepoint < 32 && !is_newline) {
|
||||
state->index += 1;
|
||||
state->count += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
float32 glyph_right = x + g.width*state->scale.x;
|
||||
|
||||
if (state->last_space_index == state->index) state->last_space_x = x;
|
||||
|
||||
if ((g.codepoint != 32 && (glyph_right-state->line_start_x) > state->width) || is_newline) {
|
||||
|
||||
bool do_break_at_last_space = state->last_space_index > state->start_index && !is_newline;
|
||||
|
||||
u64 break_index;
|
||||
if (do_break_at_last_space) break_index = state->last_space_index;
|
||||
else break_index = state->index;
|
||||
|
||||
growing_array_add((void**)&state->line_break_indices, &state->start_index);
|
||||
|
||||
u64 count_from_start_to_space = break_index-state->start_index;
|
||||
growing_array_add((void**)&state->glyph_count_per_line, &count_from_start_to_space);
|
||||
|
||||
u64 count_from_space_to_now = state->index - break_index;
|
||||
state->count = count_from_space_to_now;
|
||||
|
||||
if (break_index == state->index && (is_newline || g.codepoint == ' ')) {
|
||||
break_index += 1; // Skip \n and space if that's what we break on
|
||||
}
|
||||
state->start_index = break_index;
|
||||
state->line_num += 1;
|
||||
|
||||
if (do_break_at_last_space) state->line_start_x = state->last_space_x;
|
||||
else state->line_start_x = x;
|
||||
} else {
|
||||
if (g.codepoint == ' ') {
|
||||
state->last_space_index = state->index+1;
|
||||
}
|
||||
}
|
||||
|
||||
state->index += 1;
|
||||
state->count += 1;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Returns a Growing_Array of string, allocated with temp allocator
|
||||
string *split_text_to_lines_with_wrapping(string str, float32 width, Gfx_Font *font, u32 raster_height, Vector2 scale, bool do_trim_lines) {
|
||||
|
||||
Gfx_Font_Variation *variation = &font->variations[raster_height];
|
||||
|
||||
string text = str;
|
||||
State_For_Glyph_Line_Break_Search result = ZERO(State_For_Glyph_Line_Break_Search);
|
||||
result.width = width;
|
||||
result.scale = scale;
|
||||
growing_array_init((void**)&result.line_break_indices, sizeof(u64), get_temporary_allocator());
|
||||
growing_array_init((void**)&result.glyph_count_per_line, sizeof(u64), get_temporary_allocator());
|
||||
|
||||
walk_glyphs((Walk_Glyphs_Spec){font, text, raster_height, scale, false, &result}, text_line_wrapping_callback);
|
||||
|
||||
string *lines;
|
||||
growing_array_init((void**)&lines, sizeof(string), get_temporary_allocator());
|
||||
|
||||
for (u64 i = 0; i < growing_array_get_valid_count(result.line_break_indices); i += 1) {
|
||||
u64 utf8_index = result.line_break_indices[i];
|
||||
u64 byte_index = utf8_index_to_byte_index(text, utf8_index);
|
||||
u64 utf8_count = result.glyph_count_per_line[i];
|
||||
u64 byte_count = utf8_index_to_byte_index(text, utf8_index + utf8_count) - byte_index;
|
||||
string line_str = string_view(text, byte_index, byte_count);
|
||||
if (do_trim_lines) line_str = string_trim(line_str);
|
||||
growing_array_add((void**)&lines, &line_str);
|
||||
}
|
||||
if (result.count > 0) {
|
||||
u64 utf8_index = result.start_index;
|
||||
u64 byte_index = utf8_index_to_byte_index(text, utf8_index);
|
||||
u64 utf8_count = result.count;
|
||||
u64 byte_count = utf8_index_to_byte_index(text, utf8_index + utf8_count) - byte_index;
|
||||
string line_str = string_view(text, byte_index, byte_count);
|
||||
if (do_trim_lines) line_str = string_trim(line_str);
|
||||
growing_array_add((void**)&lines, &line_str);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
|
@ -43,6 +43,14 @@ typedef union Vector4 {
|
|||
inline Vector4 v4(float32 x, float32 y, float32 z, float32 w) { return (Vector4){x, y, z, w}; }
|
||||
#define v4_expand(v) (v).x, (v).y, (v).z, (v).w
|
||||
|
||||
const Vector2 v2_one = {1, 1};
|
||||
const Vector3 v3_one = {1, 1, 1};
|
||||
const Vector4 v4_one = {1, 1, 1, 1};
|
||||
|
||||
const Vector2 v2_zero = {0, 0};
|
||||
const Vector3 v3_zero = {0, 0, 0};
|
||||
const Vector4 v4_zero = {0, 0, 0, 0};
|
||||
|
||||
inline Vector2 v2_add(Vector2 a, Vector2 b) {
|
||||
return v2(a.x+b.x, a.y+b.y);
|
||||
}
|
||||
|
|
|
@ -209,3 +209,24 @@ string_replace_all(string s, string old, string new, Allocator allocator) {
|
|||
return string_builder_get_string(builder);
|
||||
}
|
||||
|
||||
string
|
||||
string_trim_left(string s) {
|
||||
while (s.count > 0 && *s.data == ' ') {
|
||||
s.data += 1;
|
||||
s.count -= 1;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
string
|
||||
string_trim_right(string s) {
|
||||
|
||||
while (s.count > 0 && s.data[s.count-1] == ' ') {
|
||||
s.count -= 1;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
string
|
||||
string_trim(string s) {
|
||||
s = string_trim_left(s);
|
||||
return string_trim_right(s);
|
||||
}
|
|
@ -442,8 +442,6 @@ void test_strings() {
|
|||
assert(memcmp(multi_append_builder.buffer, expected_result, multi_append_builder.count) == 0, "Failed: multiple appends");
|
||||
dealloc(heap, multi_append_builder.buffer);
|
||||
|
||||
|
||||
|
||||
string cheese_hello = STR("HeCHEESElloCHEESE, WorCHEESEld!");
|
||||
string hello = string_replace_all(cheese_hello, STR("CHEESE"), STR(""), heap);
|
||||
assert(strings_match(hello, STR("Hello, World!")), "Failed: string_replace");
|
||||
|
|
|
@ -108,4 +108,27 @@ u32 next_utf8(string *s) {
|
|||
if (result.error) return 0;
|
||||
|
||||
return result.utf32;
|
||||
}
|
||||
}
|
||||
|
||||
u64 utf8_index_to_byte_index(string str, u64 index) {
|
||||
u64 byte_index = 0;
|
||||
u64 utf8_index = 0;
|
||||
while (utf8_index < index && str.count != 0) {
|
||||
string last_str = str;
|
||||
u32 codepoint = next_utf8(&str);
|
||||
if (!codepoint) break;
|
||||
|
||||
u64 byte_diff = ((u8*)str.data)-((u8*)last_str.data);
|
||||
assert(byte_diff != 0);
|
||||
byte_index += byte_diff;
|
||||
utf8_index += 1;
|
||||
}
|
||||
return byte_index;
|
||||
}
|
||||
string utf8_slice(string str, u64 index, u64 count) {
|
||||
u64 byte_index = utf8_index_to_byte_index(str, index);
|
||||
u64 byte_end_index = utf8_index_to_byte_index(str, index+count);
|
||||
u64 byte_count = byte_end_index - byte_index;
|
||||
|
||||
return string_view(str, byte_index, byte_count);
|
||||
}
|
||||
|
|
Reference in a new issue