Add shapes and basic selective actions

This commit is contained in:
2026-04-27 18:26:02 +02:00
parent 21476a3b95
commit 5881a7dafc
12 changed files with 1060 additions and 423 deletions

315
src/shape.h Normal file
View File

@@ -0,0 +1,315 @@
#ifndef SHAPE_H
#define SHAPE_H
#include "api.h"
typedef struct shape_vertex_t {
float x, y;
} shape_vertex_t;
typedef struct shape_uniform_t {
mat4 transform;
float base_color[4];
uint32_t state;
uint8_t _pad[12];
} shape_uniform_t;
typedef enum shape_kind_t {
SHAPE_CIRCLE,
SHAPE_STAR,
} shape_kind_t;
typedef struct shape_t {
shape_vertex_t *verts;
uint16_t *indices;
uint32_t num_indices;
uint32_t num_verts;
sg_buffer vbuf;
sg_buffer ibuf;
shape_uniform_t uniform;
bool hovered;
bool selected;
shape_kind_t kind;
float cx, cy;
float sx, sy;
float rotation;
int star_points;
float star_inner_ratio;
} shape_t;
#define SHAPE_HOVER_PX 6.0f
static sg_pipeline shape_pipeline;
static sg_pipeline overlay_pipeline;
static sg_shader shape_shader;
static void shape_init_pipeline(void)
{
shape_shader = sg_make_shader(&(sg_shader_desc) {
.vertex_func = {
.source = (const char*) src_shaders_shape_wgsl,
.entry = "vs_main",
},
.fragment_func = {
.source = (const char*) src_shaders_shape_wgsl,
.entry = "fs_main",
},
.uniform_blocks = {
[0] = {
.size = sizeof(mat4),
.stage = SG_SHADERSTAGE_VERTEX,
.wgsl_group0_binding_n = 0,
},
[1] = {
.size = sizeof(shape_uniform_t),
.stage = SG_SHADERSTAGE_VERTEX,
.wgsl_group0_binding_n = 1,
},
},
.attrs = {
[0] = { .base_type = SG_SHADERATTRBASETYPE_FLOAT },
},
.label = "Shape shader",
});
shape_pipeline = sg_make_pipeline(&(sg_pipeline_desc) {
.shader = shape_shader,
.index_type = SG_INDEXTYPE_UINT16,
.primitive_type = SG_PRIMITIVETYPE_LINE_STRIP,
.layout.attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT2,
},
.label = "Shape pipeline",
});
overlay_pipeline = sg_make_pipeline(&(sg_pipeline_desc) {
.shader = shape_shader,
.index_type = SG_INDEXTYPE_UINT16,
.primitive_type = SG_PRIMITIVETYPE_TRIANGLES,
.layout.attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT2,
},
.label = "Overlay pipeline",
});
}
static void shape_shutdown_pipeline(void)
{
sg_destroy_pipeline(shape_pipeline);
sg_destroy_pipeline(overlay_pipeline);
sg_destroy_shader(shape_shader);
}
static int shape_calc_segments(float r)
{
int n = (int)(fabsf(r) * 0.5f) + 16;
if (n < 8) n = 8;
if (n > 128) n = 128;
return n;
}
static void shape_init_common(shape_t *s, const float color[4])
{
s->hovered = false;
s->selected = false;
glm_mat4_identity(s->uniform.transform);
memcpy(s->uniform.base_color, color, sizeof(float[4]));
s->uniform.state = 0;
memset(s->uniform._pad, 0, sizeof(s->uniform._pad));
}
static void shape_make_buffers(shape_t *s)
{
s->vbuf = sg_make_buffer(&(sg_buffer_desc) {
.data = { s->verts, s->num_indices * sizeof(shape_vertex_t) },
.label = "Shape vertices",
});
s->ibuf = sg_make_buffer(&(sg_buffer_desc) {
.usage = { .index_buffer = true },
.data = { s->indices, s->num_indices * sizeof(uint16_t) },
.label = "Shape indices",
});
}
static void shape_shutdown(shape_t *s)
{
sg_destroy_buffer(s->vbuf);
sg_destroy_buffer(s->ibuf);
FREE(s->verts);
FREE(s->indices);
}
static void shape_regenerate(shape_t *s)
{
sg_destroy_buffer(s->vbuf);
sg_destroy_buffer(s->ibuf);
int n, count;
if (s->kind == SHAPE_CIRCLE) {
int segs = shape_calc_segments(s->sx);
n = segs;
count = segs + 1;
if (s->num_indices != (uint32_t)count) {
FREE(s->verts);
FREE(s->indices);
s->verts = (shape_vertex_t*) ALLOC(count * sizeof(shape_vertex_t));
s->indices = (uint16_t*) ALLOC(count * sizeof(uint16_t));
}
for (int i = 0; i < segs; i++) {
float a = (float)i / (float)segs * 2.0f * GLM_PIf - GLM_PI_2f + s->rotation;
s->verts[i] = (shape_vertex_t) {
s->cx + cosf(a) * s->sx,
s->cy + sinf(a) * s->sy,
};
}
s->verts[segs] = s->verts[0];
} else {
n = s->star_points * 2;
count = n + 1;
if (s->num_indices != (uint32_t)count) {
FREE(s->verts);
FREE(s->indices);
s->verts = (shape_vertex_t*) ALLOC(count * sizeof(shape_vertex_t));
s->indices = (uint16_t*) ALLOC(count * sizeof(uint16_t));
}
for (int i = 0; i < n; i++) {
float a = (float)i / (float)n * 2.0f * GLM_PIf - GLM_PI_2f + s->rotation;
float r = (i & 1) ? s->star_inner_ratio * s->sx : s->sx;
s->verts[i] = (shape_vertex_t) {
s->cx + cosf(a) * r,
s->cy + sinf(a) * r,
};
}
s->verts[n] = s->verts[0];
}
s->num_indices = (uint32_t)count;
s->num_verts = (uint32_t)n;
for (int i = 0; i <= n; i++) s->indices[i] = (uint16_t)i;
shape_make_buffers(s);
}
static void shape_set_state(shape_t *s, bool hovered, bool selected)
{
s->hovered = hovered;
s->selected = selected;
s->uniform.state = selected ? 2u : (hovered ? 1u : 0u);
}
static bool point_in_polygon(float px, float py, shape_vertex_t *verts, uint32_t n)
{
bool inside = false;
for (uint32_t i = 0, j = n - 1; i < n; j = i++) {
float xi = verts[i].x, yi = verts[i].y;
float xj = verts[j].x, yj = verts[j].y;
if ((yi > py) != (yj > py) && px < (xj - xi) * (py - yi) / (yj - yi) + xi)
inside = !inside;
}
return inside;
}
static bool shape_hit_test(shape_t *s, float wx, float wy, float world_tol)
{
float tol_sq = world_tol * world_tol;
if (point_in_polygon(wx, wy, s->verts, s->num_verts))
return true;
for (uint32_t i = 0, j = s->num_verts - 1; i < s->num_verts; j = i++) {
float ax = s->verts[i].x, ay = s->verts[i].y;
float bx = s->verts[j].x, by = s->verts[j].y;
float abx = bx - ax, aby = by - ay;
float len_sq = abx * abx + aby * aby;
if (len_sq < 0.0001f) continue;
float t = ((wx - ax) * abx + (wy - ay) * aby) / len_sq;
t = fmaxf(0.0f, fminf(1.0f, t));
float cx = ax + t * abx, cy = ay + t * aby;
float dx = wx - cx, dy = wy - cy;
if (dx * dx + dy * dy <= tol_sq) return true;
}
return false;
}
static void shape_draw(shape_t *s, const mat4 *mvp)
{
sg_apply_pipeline(shape_pipeline);
sg_apply_uniforms(0, &SG_RANGE(*mvp));
sg_apply_uniforms(1, &SG_RANGE(s->uniform));
sg_apply_bindings(&(sg_bindings) {
.vertex_buffers[0] = s->vbuf,
.index_buffer = s->ibuf,
});
sg_draw(0, s->num_indices, 1);
}
static shape_t shape_circle(float x, float y, float r, const float color[4])
{
shape_t s;
s.kind = SHAPE_CIRCLE;
s.cx = x; s.cy = y;
s.sx = r; s.sy = r;
s.rotation = 0.0f;
int segs = shape_calc_segments(r);
int count = segs + 1;
s.verts = (shape_vertex_t*) ALLOC(count * sizeof(shape_vertex_t));
s.indices = (uint16_t*) ALLOC(count * sizeof(uint16_t));
for (int i = 0; i < segs; i++) {
float a = (float)i / (float)segs * 2.0f * GLM_PIf - GLM_PI_2f;
s.verts[i] = (shape_vertex_t) {
x + cosf(a) * r,
y + sinf(a) * r,
};
}
s.verts[segs] = s.verts[0];
for (int i = 0; i <= segs; i++) s.indices[i] = (uint16_t)i;
s.num_indices = (uint32_t)count;
s.num_verts = (uint32_t)segs;
shape_init_common(&s, color);
shape_make_buffers(&s);
return s;
}
static shape_t shape_star(float x, float y, float outer_r, float inner_r,
int points, const float color[4])
{
shape_t s;
s.kind = SHAPE_STAR;
s.cx = x; s.cy = y;
s.sx = outer_r; s.sy = outer_r;
s.rotation = 0.0f;
s.star_points = points;
s.star_inner_ratio = inner_r / outer_r;
int n = points * 2;
int count = n + 1;
s.verts = (shape_vertex_t*) ALLOC(count * sizeof(shape_vertex_t));
s.indices = (uint16_t*) ALLOC(count * sizeof(uint16_t));
for (int i = 0; i < n; i++) {
float a = (float)i / (float)n * 2.0f * GLM_PIf - GLM_PI_2f;
float r = (i & 1) ? inner_r : outer_r;
s.verts[i] = (shape_vertex_t) {
x + cosf(a) * r,
y + sinf(a) * r,
};
}
s.verts[n] = s.verts[0];
for (int i = 0; i <= n; i++) s.indices[i] = (uint16_t)i;
s.num_indices = (uint32_t)count;
s.num_verts = (uint32_t)n;
shape_init_common(&s, color);
shape_make_buffers(&s);
return s;
}
#endif