451 lines
15 KiB
C
451 lines
15 KiB
C
#include "draw.h"
|
|
#include "gpu.h"
|
|
#include "math.h"
|
|
#include "font.h"
|
|
#include "scratch.h"
|
|
#include "sprite.h"
|
|
#include "collider.h"
|
|
|
|
GLOBAL struct {
|
|
struct gpu_texture solid_white;
|
|
} G = ZI, DEBUG_ALIAS(G, G_draw);
|
|
|
|
/* ========================== *
|
|
* Startup
|
|
* ========================== */
|
|
|
|
struct draw_startup_receipt draw_startup(struct gpu_startup_receipt *gpu_sr,
|
|
struct font_startup_receipt *font_sr)
|
|
{
|
|
(UNUSED)gpu_sr;
|
|
(UNUSED)font_sr;
|
|
u32 pixel_white = 0xFFFFFFFF;
|
|
G.solid_white = gpu_texture_alloc(GPU_TEXTURE_FORMAT_R8G8B8A8_UNORM, 0, V2I32(1, 1), &pixel_white);
|
|
return (struct draw_startup_receipt) { 0 };
|
|
}
|
|
|
|
/* ========================== *
|
|
* Texture
|
|
* ========================== */
|
|
|
|
void draw_texture(struct gpu_cmd_store store, struct draw_texture_params params)
|
|
{
|
|
struct gpu_cmd_params cmd = ZI;
|
|
cmd.kind = GPU_CMD_KIND_DRAW_TEXTURE;
|
|
cmd.texture.xf = params.xf;
|
|
cmd.texture.sprite = params.sprite;
|
|
cmd.texture.texture = params.texture;
|
|
cmd.texture.clip = params.clip;
|
|
cmd.texture.tint = params.tint;
|
|
gpu_push_cmd(store, cmd);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Fill shapes
|
|
* ========================== */
|
|
|
|
void draw_poly_ex(struct gpu_cmd_store store, struct v2_array vertices, struct gpu_indices indices, u32 color)
|
|
{
|
|
struct gpu_cmd_params cmd = ZI;
|
|
cmd.kind = GPU_CMD_KIND_DRAW_MESH;
|
|
cmd.mesh.vertices = vertices;
|
|
cmd.mesh.indices = indices;
|
|
cmd.mesh.color = color;
|
|
gpu_push_cmd(store, cmd);
|
|
}
|
|
|
|
/* Draws a filled polygon using triangles in a fan pattern */
|
|
void draw_poly(struct gpu_cmd_store store, struct v2_array vertices, u32 color)
|
|
{
|
|
if (vertices.count >= 3) {
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
|
|
u32 num_tris = vertices.count - 2;
|
|
u32 num_indices = num_tris * 3;
|
|
|
|
/* Generate indices in a fan pattern */
|
|
struct gpu_indices indices = {
|
|
.count = num_indices,
|
|
.indices = arena_push_array_no_zero(scratch.arena, u16, 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(store, vertices, indices, color);
|
|
|
|
scratch_end(scratch);
|
|
}
|
|
}
|
|
|
|
void draw_circle(struct gpu_cmd_store store, struct v2 pos, f32 radius, u32 color, u32 detail)
|
|
{
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
|
|
struct v2 *points = arena_push_array_no_zero(scratch.arena, struct v2, detail);
|
|
for(u32 i = 0; i < detail; ++i) {
|
|
struct v2 p = V2(
|
|
radius * math_cos(i * (PI * 2.f) / detail),
|
|
radius * math_sin(i * (PI * 2.f) / detail)
|
|
);
|
|
points[i] = v2_add(pos, p);
|
|
}
|
|
|
|
struct v2_array vertices = {
|
|
.points = points,
|
|
.count = detail
|
|
};
|
|
draw_poly(store, vertices, color);
|
|
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
void draw_quad(struct gpu_cmd_store store, struct quad quad, u32 color)
|
|
{
|
|
LOCAL_PERSIST u16 indices_array[6] = {
|
|
0, 1, 2,
|
|
0, 2, 3
|
|
};
|
|
struct v2_array vertices = { .count = 4, .points = quad.e };
|
|
struct gpu_indices indices = { .count = 6, .indices = indices_array };
|
|
draw_poly_ex(store, vertices, indices, color);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Line shapes
|
|
* ========================== */
|
|
|
|
void draw_gradient_line(struct gpu_cmd_store store, struct v2 start, struct v2 end, f32 thickness, u32 start_color, u32 end_color)
|
|
{
|
|
#if 0
|
|
struct quad quad = quad_from_line(start, end, thickness);
|
|
draw_texture(store, DRAW_TEXTURE_PARAMS(.texture = G.solid_white, .tint0 = start_color, .tint1 = end_color, .quad = quad));
|
|
#else
|
|
/* Placeholder */
|
|
(UNUSED)end_color;
|
|
struct quad quad = quad_from_line(start, end, thickness);
|
|
draw_quad(store, quad, start_color);
|
|
#endif
|
|
}
|
|
|
|
void draw_line(struct gpu_cmd_store store, struct v2 start, struct v2 end, f32 thickness, u32 color)
|
|
{
|
|
struct quad quad = quad_from_line(start, end, thickness);
|
|
draw_quad(store, quad, color);
|
|
}
|
|
|
|
void draw_ray(struct gpu_cmd_store store, struct v2 pos, struct v2 rel, f32 thickness, u32 color)
|
|
{
|
|
struct quad quad = quad_from_ray(pos, rel, thickness);
|
|
draw_quad(store, quad, color);
|
|
}
|
|
|
|
void draw_poly_line(struct gpu_cmd_store store, struct v2_array points, b32 loop, f32 thickness, u32 color)
|
|
{
|
|
if (points.count >= 2) {
|
|
for (u64 i = 1; i < points.count; ++i) {
|
|
struct v2 p1 = points.points[i - 1];
|
|
struct v2 p2 = points.points[i];
|
|
struct quad q = quad_from_line(p1, p2, thickness);
|
|
draw_quad(store, q, color);
|
|
}
|
|
if (loop && points.count > 2) {
|
|
struct v2 p1 = points.points[points.count - 1];
|
|
struct v2 p2 = points.points[0];
|
|
struct quad q = quad_from_line(p1, p2, thickness);
|
|
draw_quad(store, q, color);
|
|
}
|
|
}
|
|
}
|
|
|
|
void draw_circle_line(struct gpu_cmd_store store, struct v2 pos, f32 radius, f32 thickness, u32 color, u32 detail)
|
|
{
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
|
|
struct v2 *points = arena_push_array_no_zero(scratch.arena, struct v2, detail);
|
|
for (u32 i = 0; i < detail; ++i) {
|
|
struct v2 p = V2(
|
|
radius * math_cos(i * (PI * 2.f) / detail),
|
|
radius * math_sin(i * (PI * 2.f) / detail)
|
|
);
|
|
points[i] = v2_add(pos, p);
|
|
}
|
|
|
|
struct v2_array a = {
|
|
.points = points,
|
|
.count = detail
|
|
};
|
|
draw_poly_line(store, a, true, thickness, color);
|
|
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
void draw_quad_line(struct gpu_cmd_store store, struct quad quad, f32 thickness, u32 color)
|
|
{
|
|
struct v2 points[] = { quad.p0, quad.p1, quad.p2, quad.p3 };
|
|
struct v2_array a = { .points = points, .count = ARRAY_COUNT(points) };
|
|
draw_poly_line(store, a, true, thickness, color);
|
|
}
|
|
|
|
void draw_arrow_line(struct gpu_cmd_store store, struct v2 start, struct v2 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 = min_f32(arrowhead_height, v2_len(v2_sub(end, start)) * max_height_to_line_ratio);
|
|
|
|
struct v2 head_start_dir = v2_sub(start, end);
|
|
head_start_dir = v2_norm(head_start_dir);
|
|
head_start_dir = v2_mul(head_start_dir, arrowhead_height);
|
|
|
|
struct v2 head_start = v2_add(end, head_start_dir);
|
|
|
|
struct v2 head_p1_dir = v2_perp_mul(head_start_dir, head_width_ratio);
|
|
struct v2 head_p2_dir = v2_neg(head_p1_dir);
|
|
|
|
struct v2 head_p1 = v2_add(head_start, head_p1_dir);
|
|
struct v2 head_p2 = v2_add(head_start, head_p2_dir);
|
|
|
|
struct v2 head_points[] = { end, head_p1, head_p2 };
|
|
struct v2_array head_points_v2_array = {
|
|
.points = head_points,
|
|
.count = ARRAY_COUNT(head_points)
|
|
};
|
|
draw_poly(store, head_points_v2_array, color);
|
|
|
|
struct quad line_quad = quad_from_line(start, head_start, thickness);
|
|
draw_quad(store, line_quad, color);
|
|
}
|
|
|
|
void draw_arrow_ray(struct gpu_cmd_store store, struct v2 pos, struct v2 rel, f32 thickness, f32 arrowhead_height, u32 color)
|
|
{
|
|
struct v2 end = v2_add(pos, rel);
|
|
draw_arrow_line(store, pos, end, thickness, arrowhead_height, color);
|
|
}
|
|
|
|
void draw_collider_line(struct gpu_cmd_store store, struct xform draw_xf, struct collider_shape shape, struct xform shape_xf, f32 thickness, u32 color, u32 detail)
|
|
{
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
|
|
struct v2 *points = arena_push_array_no_zero(scratch.arena, struct v2, detail);
|
|
for (u32 i = 0; i < detail; ++i) {
|
|
f32 angle = ((f32)i / (f32)detail) * (2 * PI);
|
|
struct v2 dir = V2(math_cos(angle), math_sin(angle));
|
|
struct v2 p = collider_get_support_point(&shape, shape_xf, dir).p;
|
|
p = xform_mul_v2(draw_xf, p);
|
|
points[i] = p;
|
|
}
|
|
|
|
struct v2_array poly = { .points = points, .count = detail };
|
|
draw_poly_line(store, poly, true, thickness, color);
|
|
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Grid
|
|
* ========================== */
|
|
|
|
void draw_grid(struct gpu_cmd_store store, struct xform xf, u32 bg0_color, u32 bg1_color, u32 line_color, u32 x_color, u32 y_color, f32 thickness, f32 spacing, struct v2 offset)
|
|
{
|
|
struct gpu_cmd_params cmd = ZI;
|
|
cmd.kind = GPU_CMD_KIND_DRAW_GRID;
|
|
cmd.grid.xf = xf;
|
|
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;
|
|
gpu_push_cmd(store, cmd);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Text
|
|
* ========================== */
|
|
|
|
/* Returns the rect of the text area */
|
|
struct rect draw_text(struct gpu_cmd_store store, struct draw_text_params params)
|
|
{
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
|
|
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;
|
|
struct clip_rect 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 = NULL;
|
|
struct drawable_line *last_line = NULL;
|
|
f32 first_line_top_offset = 0;
|
|
f32 last_line_bottom_offset = 0;
|
|
|
|
if (params.str.len > 0) {
|
|
b32 string_done = false;
|
|
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 = arena_dry_push(scratch.arena, struct drawable_glyph);
|
|
|
|
b32 line_done = false;
|
|
while (!line_done) {
|
|
string_done = !string_codepoint_iter_next(&iter);
|
|
if (string_done) {
|
|
line_done = true;
|
|
} else {
|
|
u32 codepoint = iter.codepoint;
|
|
if (codepoint == '\n') {
|
|
line_done = true;
|
|
} else {
|
|
struct drawable_glyph *tg = arena_push(scratch.arena, struct drawable_glyph);
|
|
++num_line_glyphs;
|
|
struct font_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;
|
|
struct rect glyph_atlas_rect = glyph->atlas_rect;
|
|
tg->clip = (struct clip_rect) {
|
|
{
|
|
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 = min_f32(top_offset, tg->off_y);
|
|
bottom_offset = max_f32(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 = arena_push(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 = max_f32(widest_line, line_width);
|
|
++num_lines;
|
|
}
|
|
string_codepoint_iter_end(&iter);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Determine text bounds
|
|
* ========================== */
|
|
|
|
struct 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) {
|
|
struct v2 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];
|
|
struct v2 pos = V2(draw_pos.x + tg->off_x, draw_pos.y + tg->off_y);
|
|
struct v2 size = V2(tg->width, tg->height);
|
|
struct xform xf = xform_from_rect(RECT_FROM_V2(pos, size));
|
|
draw_texture(store, DRAW_TEXTURE_PARAMS(.xf = xf, .texture = params.font->texture, .tint = params.color, .clip = tg->clip));
|
|
draw_pos.x += tg->advance;
|
|
|
|
}
|
|
++line_number;
|
|
}
|
|
|
|
scratch_end(scratch);
|
|
return bounds;
|
|
}
|