parent
5a89e9ec7d
commit
fa4d3ed0ec
@ -0,0 +1,183 @@ |
||||
module ugui; |
||||
|
||||
import std::core::mem; |
||||
import std::math::random; |
||||
|
||||
random::SimpleRandom atlas_seed; |
||||
|
||||
fault UgAtlasError { |
||||
CANNOT_PLACE, |
||||
INVALID_FORMAT, |
||||
MISSING_ATLAS, |
||||
} |
||||
|
||||
enum AtlasFormat { |
||||
ATLAS_GRAYSCALE, |
||||
ATLAS_ALPHA, |
||||
} |
||||
|
||||
// black and white atlas |
||||
struct Atlas { |
||||
AtlasFormat format; |
||||
Id id; |
||||
ushort width, height; |
||||
char[] buffer; |
||||
|
||||
Point row; |
||||
ushort row_h; |
||||
} |
||||
|
||||
// FIXME: allocate atlases dynamically |
||||
fn void! Ctx.init_atlases(&ctx) |
||||
{ |
||||
atlas_seed.set_seed("ciao mamma"); |
||||
ctx.atlas = mem::new_array(Atlas, MAX_ATLAS); |
||||
} |
||||
|
||||
fn void Ctx.free_atlases(&ctx) |
||||
{ |
||||
foreach (a: ctx.atlas) { |
||||
a.free(); |
||||
} |
||||
mem::free(ctx.atlas); |
||||
} |
||||
|
||||
// FIXME: this could be slow |
||||
fn Atlas*! Ctx.get_atlas(&ctx, Id id) |
||||
{ |
||||
foreach (i, a: ctx.atlas) { |
||||
if (a.id == id) { |
||||
return &ctx.atlas[i]; |
||||
} |
||||
} |
||||
return UgAtlasError.MISSING_ATLAS?; |
||||
} |
||||
|
||||
// TODO: use the same allocator as all the other parts of the code |
||||
fn Atlas*! Ctx.request_new_atlas(&ctx, AtlasFormat format, Id *rid, ushort width, ushort height) |
||||
{ |
||||
usz idx; |
||||
bool free = false; |
||||
for (; idx < ctx.atlas.len; idx++) { |
||||
if (ctx.atlas[idx].id == 0) { |
||||
free = true; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (free) { |
||||
*rid = ctx.unique_atlas_id(); |
||||
ctx.atlas[idx].new(format, *rid, width, height)!; |
||||
return &ctx.atlas[idx]; |
||||
} else { |
||||
// TODO: reallocate the atlas vector |
||||
return UgAtlasError.MISSING_ATLAS?; |
||||
} |
||||
} |
||||
|
||||
// FIXME: WTF |
||||
fn Id Ctx.unique_atlas_id(&ctx) { |
||||
Id id = atlas_seed.next_long(); |
||||
bool ok = true; |
||||
foreach (a: ctx.atlas) { |
||||
if (a.id == id) { |
||||
ok = false; |
||||
} |
||||
} |
||||
return ok ? id : ctx.unique_atlas_id(); |
||||
} |
||||
|
||||
fn void Ctx.discard_atlas(&ctx, Id id) |
||||
{ |
||||
foreach (&a: ctx.atlas) { |
||||
if (a.id == id) { |
||||
a.free(); |
||||
*a = Atlas{}; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Try to place a sprite into an atlas of the correct type and return it's id |
||||
// new_width and new_height are used in case a new atlas has to be requested, in that |
||||
// case they are used as width and height for the new atlas |
||||
// FIXME: maybe there has to be a default value |
||||
fn Id! Ctx.place_sprite(&ctx, Point* uv, AtlasFormat format, char[] pixels, ushort w, ushort h, ushort new_width, ushort new_height) |
||||
{ |
||||
// try to place the sprite into an existing atlas |
||||
foreach (&a: ctx.atlas) { |
||||
if (a.id != 0 && a.format == format) { |
||||
Point! ouv = a.place(pixels, w, h); |
||||
if (catch ouv) { |
||||
continue; |
||||
} |
||||
*uv = ouv; |
||||
return a.id; |
||||
} |
||||
} |
||||
|
||||
// fail... try to request a new atlas and place it there |
||||
Id id; |
||||
Atlas* atlas = ctx.request_new_atlas(format, &id, new_width, new_height)!; |
||||
*uv = atlas.place(pixels, w, h)!; |
||||
return id; |
||||
} |
||||
|
||||
fn void! Atlas.new(&atlas, AtlasFormat format, Id id, ushort width, ushort height) |
||||
{ |
||||
atlas.format = format; |
||||
atlas.id = id; |
||||
atlas.width = width; |
||||
atlas.height = height; |
||||
|
||||
usz bpp = 1; |
||||
switch (format) { |
||||
case ATLAS_GRAYSCALE: nextcase; |
||||
case ATLAS_ALPHA: |
||||
bpp = 1; |
||||
default: |
||||
return UgAtlasError.INVALID_FORMAT?; |
||||
} |
||||
usz size = ((usz)atlas.width*atlas.height) * bpp; |
||||
atlas.buffer = mem::new_array(char, size); |
||||
} |
||||
|
||||
fn void Atlas.free(&atlas) |
||||
{ |
||||
free(atlas.buffer); |
||||
} |
||||
|
||||
// place a rect inside the atlas |
||||
// uses a row first algorithm |
||||
// TODO: use a skyline algorithm https://jvernay.fr/en/blog/skyline-2d-packer/implementation/ |
||||
// FIXME: untested with non-1bpp buffer depths |
||||
fn Point! Atlas.place(&atlas, char[] pixels, ushort w, ushort h) |
||||
{ |
||||
Point p; |
||||
|
||||
// TODO: simplify this and use rect_collision() and such |
||||
if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) { |
||||
p = atlas.row; |
||||
} else { |
||||
atlas.row.x = 0; |
||||
atlas.row.y = atlas.row.y + atlas.row_h; |
||||
atlas.row_h = 0; |
||||
if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) { |
||||
p = atlas.row; |
||||
} else { |
||||
return UgAtlasError.CANNOT_PLACE?; |
||||
} |
||||
} |
||||
|
||||
for (usz y = 0; y < h; y++) { |
||||
for (usz x = 0; x < w; x++) { |
||||
atlas.buffer[(usz)(p.y+y)*atlas.width + (p.x+x)] = pixels[y*w + x]; |
||||
} |
||||
} |
||||
|
||||
atlas.row.x += w; |
||||
if (h > atlas.row_h) { |
||||
atlas.row_h = h; |
||||
} |
||||
|
||||
return p; |
||||
} |
Loading…
Reference in new issue