History, spatial grid and optimizations

This commit is contained in:
2026-04-28 19:02:00 +02:00
parent 5881a7dafc
commit 81616f8a5d
8 changed files with 1630 additions and 171 deletions

View File

@@ -36,6 +36,7 @@ typedef struct shape_t {
float rotation;
int star_points;
float star_inner_ratio;
int last_update_frame;
} shape_t;
#define SHAPE_HOVER_PX 6.0f
@@ -43,7 +44,17 @@ typedef struct shape_t {
static sg_pipeline shape_pipeline;
static sg_pipeline overlay_pipeline;
static sg_shader shape_shader;
static int g_shape_frame_id;
static void shape_begin_frame(void)
{
g_shape_frame_id++;
}
/**
* Create the shape shader, shape pipeline (line strip), and overlay pipeline
* (triangles). Call once during app init before drawing any shapes.
*/
static void shape_init_pipeline(void)
{
shape_shader = sg_make_shader(&(sg_shader_desc) {
@@ -94,6 +105,9 @@ static void shape_init_pipeline(void)
});
}
/**
* Destroy the shape shader and both pipelines. Call during app shutdown.
*/
static void shape_shutdown_pipeline(void)
{
sg_destroy_pipeline(shape_pipeline);
@@ -101,6 +115,13 @@ static void shape_shutdown_pipeline(void)
sg_destroy_shader(shape_shader);
}
/**
* Return the number of line segments for a circle of the given radius.
* Clamped to [8, 128]; scales roughly with circumference.
*
* @param r circle radius in world units
* @return segment count
*/
static int shape_calc_segments(float r)
{
int n = (int)(fabsf(r) * 0.5f) + 16;
@@ -109,22 +130,50 @@ static int shape_calc_segments(float r)
return n;
}
/**
* Set default state for a newly created shape: identity transform, base color,
* not hovered, not selected.
*
* @param s shape to initialize
* @param color RGBA base color (copied)
*/
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));
}
/**
* Build the per-shape transform matrix from cx, cy, rotation.
* Uses R(-angle) so the shader's row-vector convention matches the existing
* world-space vertex computation.
*/
static void shape_build_transform(shape_t *s)
{
mat4 T, R, S, RS;
glm_translate_make(T, (vec3){s->cx, s->cy, 0.0f});
glm_rotate_make(R, -s->rotation, (vec3){0.0f, 0.0f, 1.0f});
glm_scale_make(S, (vec3){s->sx, s->sy, 1.0f});
glm_mat4_mul(R, S, RS);
glm_mat4_mul(T, RS, s->uniform.transform);
}
/**
* Create GPU vertex and index buffers from the shape's current verts/indices.
*
* @param s shape (must have verts and indices allocated)
*/
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) },
.size = s->num_indices * sizeof(shape_vertex_t),
.usage = { .stream_update = true },
.label = "Shape vertices",
});
sg_update_buffer(s->vbuf, &(sg_range){s->verts, s->num_indices * sizeof(shape_vertex_t)});
s->ibuf = sg_make_buffer(&(sg_buffer_desc) {
.usage = { .index_buffer = true },
.data = { s->indices, s->num_indices * sizeof(uint16_t) },
@@ -132,6 +181,11 @@ static void shape_make_buffers(shape_t *s)
});
}
/**
* Destroy GPU buffers and free vertex/index arrays for a single shape.
*
* @param s shape to tear down
*/
static void shape_shutdown(shape_t *s)
{
sg_destroy_buffer(s->vbuf);
@@ -140,50 +194,47 @@ static void shape_shutdown(shape_t *s)
FREE(s->indices);
}
/**
* Rebuild vertex and index data from the shape's current parameters (position,
* scale, rotation, kind), then recreate GPU buffers. Call after any parameter
* change.
*
* @param s shape to regenerate
*/
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));
bool resized = ((uint32_t)count != s->num_indices);
if (resized) {
sg_destroy_buffer(s->vbuf);
sg_destroy_buffer(s->ibuf);
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));
}
if (s->kind == SHAPE_CIRCLE) {
int segs = n;
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) { cosf(a), sinf(a) };
}
s->verts[segs] = s->verts[0];
} else {
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,
};
float a = (float)i / (float)n * 2.0f * GLM_PIf - GLM_PI_2f;
float r = (i & 1) ? s->star_inner_ratio : 1.0f;
s->verts[i] = (shape_vertex_t) { cosf(a) * r, sinf(a) * r };
}
s->verts[n] = s->verts[0];
}
@@ -192,9 +243,25 @@ static void shape_regenerate(shape_t *s)
s->num_verts = (uint32_t)n;
for (int i = 0; i <= n; i++) s->indices[i] = (uint16_t)i;
shape_make_buffers(s);
shape_build_transform(s);
if (resized) {
shape_make_buffers(s);
s->last_update_frame = g_shape_frame_id;
} else if (s->last_update_frame != g_shape_frame_id) {
sg_update_buffer(s->vbuf, &(sg_range){s->verts, (size_t)count * sizeof(shape_vertex_t)});
s->last_update_frame = g_shape_frame_id;
}
}
/**
* Update hovered/selected flags and the shader uniform state.
* State is 0=normal, 1=hovered (brightened), 2=selected (green).
*
* @param s shape to update
* @param hovered true if cursor is over the shape
* @param selected true if shape is in the selection set
*/
static void shape_set_state(shape_t *s, bool hovered, bool selected)
{
s->hovered = hovered;
@@ -202,6 +269,16 @@ static void shape_set_state(shape_t *s, bool hovered, bool selected)
s->uniform.state = selected ? 2u : (hovered ? 1u : 0u);
}
/**
* Ray-casting point-in-polygon test. Handles arbitrary non-self-intersecting
* polygons.
*
* @param px point X in world space
* @param py point Y in world space
* @param verts polygon vertices
* @param n vertex count
* @return true if the point is inside the polygon
*/
static bool point_in_polygon(float px, float py, shape_vertex_t *verts, uint32_t n)
{
bool inside = false;
@@ -214,11 +291,28 @@ static bool point_in_polygon(float px, float py, shape_vertex_t *verts, uint32_t
return inside;
}
/**
* Test whether a world-space point hits this shape. Transforms the query
* to local space (verts are now stored relative to origin), then tests
* polygon containment and edge proximity.
*
* @param s shape to test
* @param wx point X in world space
* @param wy point Y in world space
* @param world_tol hit tolerance in world units
* @return true if the point hits the shape
*/
static bool shape_hit_test(shape_t *s, float wx, float wy, float world_tol)
{
float tol_sq = world_tol * world_tol;
float sc = cosf(s->rotation), ss = sinf(s->rotation);
float dx = wx - s->cx, dy = wy - s->cy;
float lx = (dx * sc + dy * ss) / s->sx;
float ly = (-dx * ss + dy * sc) / s->sy;
float min_scale = fminf(fabsf(s->sx), fabsf(s->sy));
float local_tol = world_tol / (min_scale > 0.0001f ? min_scale : 1.0f);
float tol_sq = local_tol * local_tol;
if (point_in_polygon(wx, wy, s->verts, s->num_verts))
if (point_in_polygon(lx, ly, s->verts, s->num_verts))
return true;
for (uint32_t i = 0, j = s->num_verts - 1; i < s->num_verts; j = i++) {
@@ -227,18 +321,23 @@ static bool shape_hit_test(shape_t *s, float wx, float wy, float world_tol)
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;
float t = ((lx - ax) * abx + (ly - 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;
float ddx = lx - cx, ddy = ly - cy;
if (ddx * ddx + ddy * ddy <= tol_sq) return true;
}
return false;
}
/**
* Issue a draw call for this shape using the shape line-strip pipeline.
*
* @param s shape to draw
* @param mvp model-view-projection matrix (from compute_mvp)
*/
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) {
@@ -248,6 +347,16 @@ static void shape_draw(shape_t *s, const mat4 *mvp)
sg_draw(0, s->num_indices, 1);
}
/**
* Create a circle shape (returned by value). Allocates verts/indices and GPU
* buffers. The number of line segments adapts to radius.
*
* @param x center X in world space
* @param y center Y in world space
* @param r radius in world units
* @param color RGBA base color
* @return fully initialized shape_t
*/
static shape_t shape_circle(float x, float y, float r, const float color[4])
{
shape_t s;
@@ -263,10 +372,7 @@ static shape_t shape_circle(float x, float y, float r, const float color[4])
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[i] = (shape_vertex_t) { cosf(a), sinf(a) };
}
s.verts[segs] = s.verts[0];
for (int i = 0; i <= segs; i++) s.indices[i] = (uint16_t)i;
@@ -274,10 +380,24 @@ static shape_t shape_circle(float x, float y, float r, const float color[4])
s.num_verts = (uint32_t)segs;
shape_init_common(&s, color);
shape_build_transform(&s);
shape_make_buffers(&s);
return s;
}
/**
* Create a star shape (returned by value). Alternates between outer_r and
* inner_r at each vertex, producing a star with the given number of points.
* Allocates verts/indices and GPU buffers.
*
* @param x center X in world space
* @param y center Y in world space
* @param outer_r outer radius in world units
* @param inner_r inner radius in world units
* @param points number of star points
* @param color RGBA base color
* @return fully initialized shape_t
*/
static shape_t shape_star(float x, float y, float outer_r, float inner_r,
int points, const float color[4])
{
@@ -296,11 +416,8 @@ static shape_t shape_star(float x, float y, float outer_r, float inner_r,
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,
};
float r = (i & 1) ? s.star_inner_ratio : 1.0f;
s.verts[i] = (shape_vertex_t) { cosf(a) * r, sinf(a) * r };
}
s.verts[n] = s.verts[0];
for (int i = 0; i <= n; i++) s.indices[i] = (uint16_t)i;
@@ -308,6 +425,7 @@ static shape_t shape_star(float x, float y, float outer_r, float inner_r,
s.num_verts = (uint32_t)n;
shape_init_common(&s, color);
shape_build_transform(&s);
shape_make_buffers(&s);
return s;
}