--- /dev/null
+/*
+
+Bitmap Fonts
+
+Simple bitmap font renderer. Splits an image of all font characters a set of glyphs
+that can be used to render individual characters / text.
+
+When creating a bitmap font, you need to provide the font texture, and the order of the
+glyphs in the texture in a string. You can use non-ASCII characters in the string using
+'\xAA' as a character.
+
+The font data provided is an RGBA byte array, with interlaced components.
+In other words the data should look like:
+
+ R G B A R G B A R G B A ...
+
+The top left (0-th) pixel is taken to be the separator color. It is used to determine the
+outline of every glyph. Glyphs are read from the top left, left to right, top to bottom.
+The separator color must be present on the entire left and top sides of the glyph.
+
+*/
+
+
+
+
+
+package bitmap_font
+use package core
+
+Bitmap_Font :: struct {
+ font_texture : Bitmap_Font_Texture;
+
+ glyphs : map.Map(u32, Glyph);
+
+ Glyph :: struct {
+ x0, y0, x1, y1: f32;
+ }
+
+ get_glyph :: (use bmp: ^Bitmap_Font, char: u8) -> ^Glyph {
+ return map.get_ptr(^glyphs, ~~char);
+ }
+}
+
+Bitmap_Font_Texture :: struct {
+ Components_Per_Pixel :: 4; // NOTE: RGBA only
+
+ data : [] u8;
+ width, height : u32;
+
+ // NOTE: Assumes pixels are laid out in RGBA format.
+ get_pixel :: (use texture: ^Bitmap_Font_Texture, x: u32, y: u32) -> u32 {
+ return (cast(u32) data[(y * width + x) * Components_Per_Pixel + 0] << 24)
+ | (cast(u32) data[(y * width + x) * Components_Per_Pixel + 1] << 16)
+ | (cast(u32) data[(y * width + x) * Components_Per_Pixel + 2] << 8)
+ | (cast(u32) data[(y * width + x) * Components_Per_Pixel + 3] << 0);
+ }
+}
+
+
+// NOTE: Takes the raw RGBA font data. Therefore, font_data.count is expected to
+// be a multiple of 4 in length, with interlaced red, green, blue and alpha components.
+// The font data can be load from another source using this method, which is why it
+// is preferred.
+bitmap_font_create :: (bft: Bitmap_Font_Texture, glyph_str: str) -> Bitmap_Font {
+ assert(bft.data.count <= bft.width * bft.height * Bitmap_Font_Texture.Components_Per_Pixel, "Bad font size.");
+
+ bmp: Bitmap_Font;
+ bmp.font_texture = bft;
+ map.init(^bmp.glyphs, .{0,0,0,0});
+
+ success := bitmap_font_prepare_glyphs(^bmp, glyph_str);
+ assert(success, "Failed to load glyphs out of font.");
+
+ return bmp;
+}
+
+bitmap_font_prepare_glyphs :: (use bmp: ^Bitmap_Font, glyph_str: str) -> bool {
+ separator_color := font_texture->get_pixel(0, 0);
+
+ g_x0, g_y0: u32;
+ g_x0, g_y0 = 0, 0;
+
+ for glyph_char: glyph_str {
+ // These will need to be converted to floating point when they are inserted into the glyph,
+ // but it is easier to think about them as actual pixel coordinates when parsing the glyphs.
+ x0, y0, x1, y1: u32;
+ x0, y0 = g_x0 + 1, g_y0 + 1;
+ x1, y1 = x0, y0;
+
+ while font_texture->get_pixel(x1, y0) != separator_color do x1 += 1;
+ while font_texture->get_pixel(x0, y1) != separator_color do y1 += 1;
+
+ map.put(^glyphs, ~~glyph_char, .{
+ x0 = cast(f32) x0 / ~~font_texture.width,
+ y0 = cast(f32) y0 / ~~font_texture.height,
+ x1 = cast(f32) x1 / ~~font_texture.width,
+ y1 = cast(f32) y1 / ~~font_texture.height,
+ });
+
+ g_x0 = x1 + 1;
+ reset := g_x0 >= font_texture.width;
+ if !reset do reset = font_texture->get_pixel(g_x0, g_y0) != separator_color;
+
+ if reset {
+ g_x0 = 0;
+ g_y0 = y1 + 1;
+ }
+ }
+
+ return true;
+}
#private_file gl :: package gl
#private_file gfx :: package immediate_mode
-main :: (args: [] cstr) {
- println("Hello World!");
+#private_file bitmap_font :: package bitmap_font
+
+
+
+font : bitmap_font.Bitmap_Font;
+font_texture : gl.GLTexture;
+main :: (args: [] cstr) {
gl.init("game");
events.init();
gfx.immediate_renderer_init();
+ init_font();
+
start_loop :: () -> void #foreign "game" "start_loop" ---
start_loop();
}
+init_font :: () {
+ font_data := #file_contents "src/res/font.data";
+
+ bft := bitmap_font.Bitmap_Font_Texture.{
+ data = font_data,
+ width = 256,
+ height = 256,
+ };
+
+ font = bitmap_font.bitmap_font_create(bft, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 \xff");
+
+ font_texture = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, font_texture);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 256, 0, gl.RGBA, gl.UNSIGNED_BYTE, font_data);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+ gl.bindTexture(gl.TEXTURE_2D, -1);
+}
+
+last_time := 0;
#export "loop" () {
+ time_now :: () -> i32 #foreign "game" "time_now" ---
+
+ if last_time == 0 do last_time = time_now();
+
+ now := time_now();
+ dt := cast(f32) (now - last_time) / 1000.0f;
+ last_time = now;
+
poll_events();
+ update(dt);
draw();
}
+window_width := 0
+window_height := 0
poll_events :: () {
for event: events.consume() do switch event.kind {
case .MouseDown do println("Mouse was down!");
case .Resize {
printf("Window was resized to: %i %i\n", event.resize.width, event.resize.height);
+ window_width = event.resize.width;
+ window_height = event.resize.height;
+
gl.setSize(event.resize.width, event.resize.height);
gl.viewport(0, 0, event.resize.width, event.resize.height);
- gfx.use_ortho_projection(0, ~~event.resize.width, 0, ~~event.resize.height);
}
}
}
+update :: (dt: f32) {
+}
+
draw :: () {
- gl.clearColor(0, 0, 0, 1);
+ gl.clearColor(0.1, 0.1, 0.1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
- gfx.quad(.{ 0, 0 }, .{ 400, 400 }, color=.{ 1, 0, 1 });
- gfx.quad(.{ 20, 0 }, .{ 400, 400 }, color=.{ 1, 1, 1 });
+ gfx.use_ortho_projection(0, ~~window_width, 0, ~~window_height);
+
+ draw_text("Hello World", 100, 100, 128);
+ draw_text("something else...", 100, 230, 32);
+
gfx.flush();
}
+
+
+
+
+draw_text :: (text: str, x: f32, y: f32, size := 32.0f) {
+
+ gl.bindTexture(gl.TEXTURE_2D, font_texture);
+ gfx.set_texture(0);
+ gfx.use_ortho_projection(0, ~~window_width, 0, ~~window_height);
+
+ for char: text {
+ glyph := font->get_glyph(char);
+
+ if glyph == null {
+ glyph = font->get_glyph(255);
+ assert(glyph != null, "NO NULL GLYPH");
+ }
+
+ gfx.textured_quad(
+ .{ x, y },
+ .{ size, size },
+ .{ glyph.x0, glyph.y0 },
+ .{ glyph.x1 - glyph.x0, glyph.y1 - glyph.y0 });
+
+ x += size;
+ }
+
+ gfx.flush();
+ gl.bindTexture(gl.TEXTURE_2D, -1);
+}