power_play/src/draw/draw_core.c
2025-07-30 14:27:30 -05:00

475 lines
15 KiB
C

Global struct {
G_Resource *solid_white_texture;
} G = ZI, DebugAlias(G, G_draw);
/* ========================== *
* Startup
* ========================== */
D_StartupReceipt draw_startup(F_StartupReceipt *font_sr)
{
__prof;
(UNUSED)font_sr;
u32 pixel_white = 0xFFFFFFFF;
G.solid_white_texture = gp_texture_alloc(GP_TEXTURE_FORMAT_R8G8B8A8_UNORM, 0, VEC2I32(1, 1), &pixel_white);
return (D_StartupReceipt) { 0 };
}
/* ========================== *
* Material
* ========================== */
void draw_material(G_RenderSig *sig, D_MaterialParams params)
{
G_RenderCmdDesc cmd = ZI;
cmd.kind = GP_RENDER_CMD_KIND_DRAW_MATERIAL;
cmd.material.xf = params.xf;
cmd.material.texture = params.texture;
cmd.material.clip = params.clip;
cmd.material.tint = params.tint;
cmd.material.is_light = params.is_light;
cmd.material.light_emittance = params.light_emittance;
gp_push_render_cmd(sig, &cmd);
}
/* ========================== *
* Fill shapes
* ========================== */
void draw_poly_ex(G_RenderSig *sig, V2Array vertices, G_Indices indices, u32 color)
{
G_RenderCmdDesc cmd = ZI;
cmd.kind = GP_RENDER_CMD_KIND_DRAW_UI_SHAPE;
cmd.ui_shape.vertices = vertices;
cmd.ui_shape.indices = indices;
cmd.ui_shape.color = color;
gp_push_render_cmd(sig, &cmd);
}
/* Draws a filled polygon using triangles in a fan pattern */
void draw_poly(G_RenderSig *sig, V2Array vertices, u32 color)
{
if (vertices.count >= 3) {
TempArena scratch = BeginScratchNoConflict();
u32 num_tris = vertices.count - 2;
u32 num_indices = num_tris * 3;
/* Generate indices in a fan pattern */
G_Indices indices = {
.count = num_indices,
.indices = PushArrayNoZero(scratch.arena, u32, num_indices)
};
for (u32 i = 0; i < num_tris; ++i) {
u32 tri_offset = i * 3;
indices.indices[tri_offset + 0] = 0;
indices.indices[tri_offset + 1] = (i + 1);
indices.indices[tri_offset + 2] = (i + 2);
}
draw_poly_ex(sig, vertices, indices, color);
EndScratch(scratch);
}
}
void draw_circle(G_RenderSig *sig, Vec2 pos, f32 radius, u32 color, u32 detail)
{
TempArena scratch = BeginScratchNoConflict();
Vec2 *points = PushArrayNoZero(scratch.arena, Vec2, detail);
for (u32 i = 0; i < detail; ++i) {
f32 angle = ((f32)i / (f32)detail) * Tau;
Vec2 p = VEC2(
radius * CosF32(angle),
radius * SinF32(angle)
);
points[i] = AddVec2(pos, p);
}
V2Array vertices = {
.points = points,
.count = detail
};
draw_poly(sig, vertices, color);
EndScratch(scratch);
}
void draw_quad(G_RenderSig *sig, Quad quad, u32 color)
{
LocalPersist u32 indices_array[6] = {
0, 1, 2,
0, 2, 3
};
V2Array vertices = { .count = 4, .points = quad.e };
G_Indices indices = { .count = 6, .indices = indices_array };
draw_poly_ex(sig, vertices, indices, color);
}
/* ========================== *
* Line shapes
* ========================== */
void draw_gradient_line(G_RenderSig *sig, Vec2 start, Vec2 end, f32 thickness, u32 start_color, u32 end_color)
{
#if 0
Quad quad = QuadFromLine(start, end, thickness);
draw_material(sig, DRAW_MATERIAL_PARAMS(.texture = G.solid_white_texture, .tint0 = start_color, .tint1 = end_color, .quad = quad));
#else
/* Placeholder */
(UNUSED)end_color;
Quad quad = QuadFromLine(start, end, thickness);
draw_quad(sig, quad, start_color);
#endif
}
void draw_line(G_RenderSig *sig, Vec2 start, Vec2 end, f32 thickness, u32 color)
{
Quad quad = QuadFromLine(start, end, thickness);
draw_quad(sig, quad, color);
}
void draw_ray(G_RenderSig *sig, Vec2 pos, Vec2 rel, f32 thickness, u32 color)
{
Quad quad = QuadFromRay(pos, rel, thickness);
draw_quad(sig, quad, color);
}
void draw_poly_line(G_RenderSig *sig, V2Array points, b32 loop, f32 thickness, u32 color)
{
if (points.count >= 2) {
for (u64 i = 1; i < points.count; ++i) {
Vec2 p1 = points.points[i - 1];
Vec2 p2 = points.points[i];
Quad q = QuadFromLine(p1, p2, thickness);
draw_quad(sig, q, color);
}
if (loop && points.count > 2) {
Vec2 p1 = points.points[points.count - 1];
Vec2 p2 = points.points[0];
Quad q = QuadFromLine(p1, p2, thickness);
draw_quad(sig, q, color);
}
}
}
void draw_circle_line(G_RenderSig *sig, Vec2 pos, f32 radius, f32 thickness, u32 color, u32 detail)
{
TempArena scratch = BeginScratchNoConflict();
Vec2 *points = PushArrayNoZero(scratch.arena, Vec2, detail);
for (u32 i = 0; i < detail; ++i) {
f32 angle = ((f32)i / (f32)detail) * Tau;
Vec2 p = VEC2(
radius * CosF32(angle),
radius * SinF32(angle)
);
points[i] = AddVec2(pos, p);
}
V2Array a = {
.points = points,
.count = detail
};
draw_poly_line(sig, a, 1, thickness, color);
EndScratch(scratch);
}
void draw_quad_line(G_RenderSig *sig, Quad quad, f32 thickness, u32 color)
{
Vec2 points[] = { quad.p0, quad.p1, quad.p2, quad.p3 };
V2Array a = { .points = points, .count = countof(points) };
draw_poly_line(sig, a, 1, thickness, color);
}
void draw_arrow_line(G_RenderSig *sig, Vec2 start, Vec2 end, f32 thickness, f32 arrowhead_height, u32 color)
{
const f32 head_width_ratio = 0.5f; /* Width of arrowhead relative to its length */
const f32 max_height_to_line_ratio = 0.9f; /* Maximum length of arrowhead relative to total line length */
arrowhead_height = MinF32(arrowhead_height, Vec2Len(SubVec2(end, start)) * max_height_to_line_ratio);
Vec2 head_start_dir = SubVec2(start, end);
head_start_dir = NormVec2(head_start_dir);
head_start_dir = MulVec2(head_start_dir, arrowhead_height);
Vec2 head_start = AddVec2(end, head_start_dir);
Vec2 head_p1_dir = MulPerpVec2(head_start_dir, head_width_ratio);
Vec2 head_p2_dir = NegVec2(head_p1_dir);
Vec2 head_p1 = AddVec2(head_start, head_p1_dir);
Vec2 head_p2 = AddVec2(head_start, head_p2_dir);
Vec2 head_points[] = { end, head_p1, head_p2 };
V2Array head_points_v2_array = {
.points = head_points,
.count = countof(head_points)
};
draw_poly(sig, head_points_v2_array, color);
Quad line_quad = QuadFromLine(start, head_start, thickness);
draw_quad(sig, line_quad, color);
}
void draw_arrow_ray(G_RenderSig *sig, Vec2 pos, Vec2 rel, f32 thickness, f32 arrowhead_height, u32 color)
{
Vec2 end = AddVec2(pos, rel);
draw_arrow_line(sig, pos, end, thickness, arrowhead_height, color);
}
void draw_collider_line(G_RenderSig *sig, CLD_Shape shape, Xform shape_xf, f32 thickness, u32 color, u32 detail)
{
TempArena scratch = BeginScratchNoConflict();
V2Array poly = ZI;
if (shape.radius == 0) {
poly.count = shape.count;
poly.points = PushArrayNoZero(scratch.arena, Vec2, shape.count);
for (u32 i = 0; i < shape.count; ++i) {
Vec2 p = MulXformV2(shape_xf, shape.points[i]);
poly.points[i] = p;
}
} else {
poly.count = detail;
poly.points = PushArrayNoZero(scratch.arena, Vec2, detail);
for (u32 i = 0; i < detail; ++i) {
f32 angle = ((f32)i / (f32)detail) * Tau;
Vec2 dir = VEC2(CosF32(angle), SinF32(angle));
Vec2 p = collider_get_support_point(&shape, shape_xf, dir).p;
poly.points[i] = p;
}
}
draw_poly_line(sig, poly, 1, thickness, color);
EndScratch(scratch);
}
/* ========================== *
* Grid
* ========================== */
void draw_grid(G_RenderSig *sig, Xform xf, u32 bg0_color, u32 bg1_color, u32 line_color, u32 x_color, u32 y_color, f32 thickness, f32 spacing, Vec2 offset)
{
i32 grid_id = 0;
{
G_RenderCmdDesc cmd = ZI;
cmd.kind = GP_RENDER_CMD_KIND_PUSH_GRID;
cmd.grid.bg0_color = bg0_color;
cmd.grid.bg1_color = bg1_color;
cmd.grid.line_color = line_color;
cmd.grid.x_color = x_color;
cmd.grid.y_color = y_color;
cmd.grid.line_thickness = thickness;
cmd.grid.line_spacing = spacing;
cmd.grid.offset = offset;
grid_id = gp_push_render_cmd(sig, &cmd);
}
G_RenderCmdDesc cmd = ZI;
cmd.kind = GP_RENDER_CMD_KIND_DRAW_MATERIAL;
cmd.material.xf = xf;
cmd.material.tint = ColorWhite;
cmd.material.grid_cmd_id = grid_id;
gp_push_render_cmd(sig, &cmd);
}
/* ========================== *
* UI
* ========================== */
void draw_ui_rect(G_RenderSig *sig, D_UiRectParams params)
{
G_RenderCmdDesc cmd = ZI;
cmd.kind = GP_RENDER_CMD_KIND_DRAW_UI_RECT;
cmd.ui_rect.xf = params.xf;
cmd.ui_rect.texture = params.texture;
cmd.ui_rect.clip = params.clip;
cmd.ui_rect.tint = params.tint;
gp_push_render_cmd(sig, &cmd);
}
/* ========================== *
* Text
* ========================== */
/* Returns the rect of the text area */
Rect draw_text(G_RenderSig *sig, D_TextParams params)
{
TempArena scratch = BeginScratchNoConflict();
f32 inv_font_image_width = 1.0 / (f32)params.font->image_width;
f32 inv_font_image_height = 1.0 / (f32)params.font->image_height;
f32 line_spacing = params.font->point_size * 1.5f * params.scale;
/* ========================== *
* Build line glyphs
* ========================== */
struct drawable_glyph {
f32 off_x;
f32 off_y;
f32 width;
f32 height;
f32 advance;
ClipRect clip;
};
struct drawable_line {
f32 line_width;
u32 num_glyphs;
struct drawable_glyph *glyphs;
struct drawable_line *next;
};
u64 num_lines = 0;
f32 widest_line = 0;
struct drawable_line *first_line = 0;
struct drawable_line *last_line = 0;
f32 first_line_top_offset = 0;
f32 last_line_bottom_offset = 0;
if (params.str.len > 0) {
b32 string_done = 0;
struct string_codepoint_iter iter = string_codepoint_iter_begin(params.str);
while (!string_done) {
f32 line_width = 0;
f32 top_offset = 0;
f32 bottom_offset = 0;
u64 num_line_glyphs = 0;
struct drawable_glyph *line_glyphs = PushDry(scratch.arena, struct drawable_glyph);
b32 line_done = 0;
while (!line_done) {
string_done = !string_codepoint_iter_next(&iter);
if (string_done) {
line_done = 1;
} else {
u32 codepoint = iter.codepoint;
if (codepoint == '\n') {
line_done = 1;
} else {
struct drawable_glyph *tg = PushStruct(scratch.arena, struct drawable_glyph);
++num_line_glyphs;
F_Glyph *glyph = font_get_glyph(params.font, codepoint);
tg->off_x = glyph->off_x * params.scale;
tg->off_y = glyph->off_y * params.scale;
tg->width = glyph->width * params.scale;
tg->height = glyph->height * params.scale;
tg->advance = glyph->advance * params.scale;
Rect glyph_atlas_rect = glyph->atlas_rect;
tg->clip = (ClipRect) {
{
glyph_atlas_rect.x * inv_font_image_width,
glyph_atlas_rect.y * inv_font_image_height
},
{
(glyph_atlas_rect.x + glyph_atlas_rect.width) * inv_font_image_width,
(glyph_atlas_rect.y + glyph_atlas_rect.height) * inv_font_image_height
}
};
line_width += tg->advance;
top_offset = MinF32(top_offset, tg->off_y);
bottom_offset = MaxF32(bottom_offset, tg->off_y + tg->height);
}
}
}
/* Line ended */
/* TODO: Only create nodes for non-empty lines. Embed line number in the node. */
struct drawable_line *node = PushStruct(scratch.arena, struct drawable_line);
node->line_width = line_width;
node->num_glyphs = num_line_glyphs;
node->glyphs = line_glyphs;
if (last_line) {
last_line->next = node;
} else {
first_line = node;
first_line_top_offset = top_offset;
}
last_line = node;
last_line_bottom_offset = bottom_offset;
widest_line = MaxF32(widest_line, line_width);
++num_lines;
}
string_codepoint_iter_end(&iter);
}
/* ========================== *
* Determine text bounds
* ========================== */
Rect bounds = ZI;
bounds.x = params.pos.x;
bounds.y = params.pos.y;
bounds.width = widest_line;
bounds.height = num_lines * line_spacing + first_line_top_offset + last_line_bottom_offset;
/* Offset bounds X */
switch (params.offset_x) {
case DRAW_TEXT_OFFSET_X_LEFT: break;
case DRAW_TEXT_OFFSET_X_CENTER:
{
bounds.x -= bounds.width / 2.f;
} break;
case DRAW_TEXT_OFFSET_X_RIGHT:
{
bounds.x -= bounds.width;
} break;
}
/* Offset bounds Y */
switch (params.offset_y) {
case DRAW_TEXT_OFFSET_Y_TOP: break;
case DRAW_TEXT_OFFSET_Y_CENTER:
{
bounds.y -= bounds.height / 2.f;
} break;
case DRAW_TEXT_OFFSET_Y_BOTTOM:
{
if (last_line) {
bounds.y -= bounds.height + last_line_bottom_offset;
}
} break;
}
/* ========================== *
* Draw lines
* ========================== */
u64 line_number = 0;
for (struct drawable_line *line = first_line; line; line = line->next) {
Vec2 draw_pos = bounds.pos;
draw_pos.y += line_number * line_spacing - first_line_top_offset;
/* Alignment */
switch (params.alignment) {
case DRAW_TEXT_ALIGNMENT_LEFT: break;
case DRAW_TEXT_ALIGNMENT_CENTER:
{
draw_pos.x += (bounds.width - line->line_width) / 2.f;
} break;
case DRAW_TEXT_ALIGNMENT_RIGHT:
{
draw_pos.x += bounds.width - line->line_width;
} break;
}
/* Draw glyphs */
for (u64 i = 0; i < line->num_glyphs; ++i) {
struct drawable_glyph *tg = &line->glyphs[i];
Vec2 pos = VEC2(draw_pos.x + tg->off_x, draw_pos.y + tg->off_y);
Vec2 size = VEC2(tg->width, tg->height);
Xform xf = XformFromRect(RectFromVec2(pos, size));
draw_ui_rect(sig, DRAW_UI_RECT_PARAMS(.xf = xf, .texture = params.font->texture, .tint = params.color, .clip = tg->clip));
draw_pos.x += tg->advance;
}
++line_number;
}
EndScratch(scratch);
return bounds;
}