Add copy/paste, rewrite rendering pipeline with instanced draw calls and add a lot of caching to minimize overhead on huge edits.

This commit is contained in:
2026-05-01 00:10:19 +02:00
parent e71641c094
commit c4d657043c
17 changed files with 2917 additions and 289 deletions

1831
diff_output.patch Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -27,6 +27,21 @@
#include "generated/sprite.h"
#include "generated/shape.h"
#include "generated/overlay.h"
// Log-to-panel infrastructure
static void (*g_panel_log_fn)(void*, int, const char*) = NULL;
static void *g_panel_log_ud = NULL;
static void panel_log(int level, const char *fmt, ...) {
if (!g_panel_log_fn) return;
char buf[256];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
g_panel_log_fn(g_panel_log_ud, level, buf);
}
#include "util.h"
#include "shape.h"

View File

@@ -9,20 +9,109 @@ static void draw_shapes(userdata_t *ud)
if (g_shape_pool_dirty)
shape_pool_rebuild(&ud->shapes);
if (ud->shapes.count == 0) return;
int n = ud->shapes.count;
if (n == 0) return;
if (g_shape_data_dirty) {
shape_upload_data(&ud->shapes);
g_shape_data_dirty = false;
}
panel_log(3, "[shapes] draw_shapes: n=%d pipeline=%d", n, shape_pipeline.id);
static uint32_t *imap = NULL;
static int imap_cap = 0;
static uint32_t *ne_counts = NULL;
static uint32_t *ne_starts = NULL;
static int ne_cap = 0;
if (n > imap_cap) {
if (imap) FREE(imap);
imap = (uint32_t*) ALLOC((size_t)n * sizeof(uint32_t));
imap_cap = n;
}
// Group shapes by num_elements using counting sort
uint32_t max_ne = 0;
for (int i = 0; i < n; i++) {
uint32_t ne = ((shape_t*) vec_get(&ud->shapes, i))->num_elements;
if (ne > max_ne) max_ne = ne;
}
int ne_size = (int)(max_ne + 1);
if (ne_size > ne_cap) {
if (ne_counts) FREE(ne_counts);
if (ne_starts) FREE(ne_starts);
ne_counts = (uint32_t*) ALLOC((size_t)ne_size * sizeof(uint32_t));
ne_starts = (uint32_t*) ALLOC((size_t)ne_size * sizeof(uint32_t));
ne_cap = ne_size;
}
memset(ne_counts, 0, (size_t)ne_size * sizeof(uint32_t));
for (int i = 0; i < n; i++) {
uint32_t ne = ((shape_t*) vec_get(&ud->shapes, i))->num_elements;
ne_counts[ne]++;
}
uint32_t pos = 0;
for (uint32_t ne = 0; ne <= max_ne; ne++) {
ne_starts[ne] = pos;
pos += ne_counts[ne];
ne_counts[ne] = 0;
}
for (int i = 0; i < n; i++) {
uint32_t ne = ((shape_t*) vec_get(&ud->shapes, i))->num_elements;
imap[ne_starts[ne] + ne_counts[ne]++] = (uint32_t)i;
}
shape_upload_instance_map(imap, n);
sg_apply_pipeline(shape_pipeline);
sg_apply_bindings(&(sg_bindings){
.vertex_buffers[0] = g_shape_vbuf,
.index_buffer = g_shape_ibuf,
});
for (int i = 0; i < ud->shapes.count; i++) {
shape_draw((shape_t*) vec_get(&ud->shapes, i), &ud->renderer.uniform.mvp);
int base = 0;
while (base < n) {
shape_t *s = (shape_t*) vec_get(&ud->shapes, imap[base]);
uint32_t ne = s->num_elements;
int count = 1;
while (base + count < n &&
((shape_t*) vec_get(&ud->shapes, imap[base + count]))->num_elements == ne)
count++;
// Find the vertex buffer for this num_elements
sg_buffer group_vbuf = {0};
for (int gi = 0; gi < g_shape_group_count; gi++) {
if (g_shape_groups[gi].num_elements == ne) {
group_vbuf = g_shape_groups[gi].vbuf;
break;
}
}
struct { mat4 mvp; uint32_t base; uint8_t _pad[12]; } vs_u;
memcpy(vs_u.mvp, ud->renderer.uniform.mvp, sizeof(mat4));
vs_u.base = (uint32_t)base;
memset(vs_u._pad, 0, 12);
sg_apply_uniforms(0, &SG_RANGE(vs_u));
sg_apply_bindings(&(sg_bindings){
.vertex_buffers[0] = group_vbuf,
.views[0] = g_shape_data_view,
.views[1] = g_instance_map_view,
});
panel_log(3, "[shapes] draw group: ne=%u count=%d base=%d vbuf=%d data_view=%d imap_view=%d",
ne, count, base, group_vbuf.id, g_shape_data_view.id, g_instance_map_view.id);
sg_draw(0, (int)ne, count);
base += count;
}
}
static void draw_overlay_and_handles(userdata_t *ud, bool has_overlay, bool show_handle)
{
sg_apply_pipeline(overlay_pipeline);
panel_log(3, "[shapes] draw_overlay: pipeline=%d has_ov=%d show_h=%d",
overlay_pipeline.id, has_overlay, show_handle);
if (has_overlay) {
shape_uniform_t u;
glm_mat4_identity(u.transform);

91
src/generated/overlay.h Normal file
View File

@@ -0,0 +1,91 @@
unsigned char src_shaders_overlay_wgsl[] = {
0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x56, 0x73, 0x55, 0x6e, 0x69,
0x66, 0x6f, 0x72, 0x6d, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d,
0x76, 0x70, 0x3a, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x78, 0x34, 0x66, 0x2c,
0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20,
0x53, 0x68, 0x61, 0x70, 0x65, 0x55, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d,
0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73,
0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x78, 0x34,
0x66, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65,
0x3a, 0x20, 0x75, 0x33, 0x32, 0x2c, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73,
0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x56, 0x73, 0x49, 0x6e, 0x20, 0x7b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x28, 0x30, 0x29, 0x20, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69,
0x6f, 0x6e, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x2c, 0x0a, 0x7d,
0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x56, 0x73,
0x32, 0x46, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x62,
0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x28, 0x70, 0x6f, 0x73, 0x69, 0x74,
0x69, 0x6f, 0x6e, 0x29, 0x20, 0x70, 0x6f, 0x73, 0x3a, 0x20, 0x76, 0x65,
0x63, 0x34, 0x66, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x6c, 0x6f,
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x30, 0x29, 0x20, 0x40, 0x69,
0x6e, 0x74, 0x65, 0x72, 0x70, 0x6f, 0x6c, 0x61, 0x74, 0x65, 0x28, 0x6c,
0x69, 0x6e, 0x65, 0x61, 0x72, 0x29, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72,
0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x2c, 0x0a, 0x7d, 0x3b, 0x0a,
0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x46, 0x73, 0x4f, 0x75,
0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x6c, 0x6f, 0x63,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x30, 0x29, 0x20, 0x63, 0x6f, 0x6c,
0x6f, 0x72, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x2c, 0x0a, 0x7d,
0x3b, 0x0a, 0x0a, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28,
0x30, 0x29, 0x20, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x30, 0x29,
0x20, 0x76, 0x61, 0x72, 0x3c, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d,
0x3e, 0x20, 0x76, 0x73, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d,
0x73, 0x3a, 0x20, 0x56, 0x73, 0x55, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d,
0x3b, 0x0a, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, 0x31,
0x29, 0x20, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x30, 0x29, 0x20,
0x76, 0x61, 0x72, 0x3c, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x3e,
0x20, 0x73, 0x68, 0x61, 0x70, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f,
0x72, 0x6d, 0x3a, 0x20, 0x53, 0x68, 0x61, 0x70, 0x65, 0x55, 0x6e, 0x69,
0x66, 0x6f, 0x72, 0x6d, 0x3b, 0x0a, 0x0a, 0x40, 0x76, 0x65, 0x72, 0x74,
0x65, 0x78, 0x20, 0x66, 0x6e, 0x20, 0x76, 0x73, 0x5f, 0x6d, 0x61, 0x69,
0x6e, 0x28, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x3a, 0x20, 0x56, 0x73, 0x49,
0x6e, 0x29, 0x20, 0x2d, 0x3e, 0x20, 0x56, 0x73, 0x32, 0x46, 0x73, 0x20,
0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x6f, 0x75,
0x74, 0x70, 0x75, 0x74, 0x3a, 0x20, 0x56, 0x73, 0x32, 0x46, 0x73, 0x3b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x77, 0x6f, 0x72,
0x6c, 0x64, 0x5f, 0x70, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x73, 0x68, 0x61,
0x70, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x2e, 0x74,
0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x2a, 0x20, 0x76,
0x65, 0x63, 0x34, 0x66, 0x28, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x70,
0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x78, 0x2c, 0x20, 0x69,
0x6e, 0x70, 0x75, 0x74, 0x2e, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f,
0x6e, 0x2e, 0x79, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e,
0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x75, 0x74, 0x70,
0x75, 0x74, 0x2e, 0x70, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x76, 0x73, 0x5f,
0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x2e, 0x6d, 0x76, 0x70,
0x20, 0x2a, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x5f, 0x70, 0x6f, 0x73,
0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x73, 0x68,
0x61, 0x70, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x2e,
0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x75, 0x29,
0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f,
0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20,
0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 0x31, 0x2e, 0x30, 0x2c,
0x20, 0x30, 0x2e, 0x38, 0x34, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20,
0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x20,
0x65, 0x6c, 0x73, 0x65, 0x20, 0x69, 0x66, 0x20, 0x28, 0x73, 0x68, 0x61,
0x70, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x2e, 0x73,
0x74, 0x61, 0x74, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x75, 0x29, 0x20,
0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x75,
0x74, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d,
0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 0x30, 0x2e, 0x35, 0x2c, 0x20,
0x30, 0x2e, 0x36, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e,
0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x20, 0x65, 0x6c,
0x73, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 0x30, 0x2e,
0x38, 0x2c, 0x20, 0x30, 0x2e, 0x38, 0x2c, 0x20, 0x30, 0x2e, 0x38, 0x2c,
0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20,
0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x40,
0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x66, 0x6e, 0x20,
0x66, 0x73, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x3a, 0x20, 0x56, 0x73, 0x32, 0x46, 0x73, 0x29, 0x20, 0x2d, 0x3e,
0x20, 0x46, 0x73, 0x4f, 0x75, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x76, 0x61, 0x72, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3a,
0x20, 0x46, 0x73, 0x4f, 0x75, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c, 0x6f, 0x72,
0x20, 0x3d, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c,
0x6f, 0x72, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75,
0x72, 0x6e, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3b, 0x0a, 0x7d,
0x0a
};
unsigned int src_shaders_overlay_wgsl_len = 1045;

View File

@@ -2,90 +2,109 @@ unsigned char src_shaders_shape_wgsl[] = {
0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x56, 0x73, 0x55, 0x6e, 0x69,
0x66, 0x6f, 0x72, 0x6d, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d,
0x76, 0x70, 0x3a, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x78, 0x34, 0x66, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63,
0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x3a, 0x20, 0x75, 0x33, 0x32, 0x2c,
0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20,
0x53, 0x68, 0x61, 0x70, 0x65, 0x55, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d,
0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73,
0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x78, 0x34,
0x66, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65,
0x3a, 0x20, 0x75, 0x33, 0x32, 0x2c, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73,
0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x56, 0x73, 0x49, 0x6e, 0x20, 0x7b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x28, 0x30, 0x29, 0x20, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69,
0x6f, 0x6e, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x2c, 0x0a, 0x7d,
0x53, 0x68, 0x61, 0x70, 0x65, 0x44, 0x61, 0x74, 0x61, 0x20, 0x7b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72,
0x6d, 0x3a, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x78, 0x34, 0x66, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x3a, 0x20, 0x75,
0x33, 0x32, 0x2c, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x40, 0x62, 0x69, 0x6e,
0x64, 0x69, 0x6e, 0x67, 0x28, 0x30, 0x29, 0x20, 0x40, 0x67, 0x72, 0x6f,
0x75, 0x70, 0x28, 0x30, 0x29, 0x20, 0x76, 0x61, 0x72, 0x3c, 0x75, 0x6e,
0x69, 0x66, 0x6f, 0x72, 0x6d, 0x3e, 0x20, 0x76, 0x73, 0x5f, 0x75, 0x6e,
0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x3a, 0x20, 0x56, 0x73, 0x55, 0x6e,
0x69, 0x66, 0x6f, 0x72, 0x6d, 0x3b, 0x0a, 0x0a, 0x40, 0x62, 0x69, 0x6e,
0x64, 0x69, 0x6e, 0x67, 0x28, 0x30, 0x29, 0x20, 0x40, 0x67, 0x72, 0x6f,
0x75, 0x70, 0x28, 0x31, 0x29, 0x20, 0x76, 0x61, 0x72, 0x3c, 0x73, 0x74,
0x6f, 0x72, 0x61, 0x67, 0x65, 0x3e, 0x20, 0x73, 0x68, 0x61, 0x70, 0x65,
0x5f, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79,
0x3c, 0x53, 0x68, 0x61, 0x70, 0x65, 0x44, 0x61, 0x74, 0x61, 0x3e, 0x3b,
0x0a, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, 0x31, 0x29,
0x20, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x31, 0x29, 0x20, 0x76,
0x61, 0x72, 0x3c, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x3e, 0x20,
0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6d, 0x61, 0x70,
0x3a, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x75, 0x33, 0x32, 0x3e,
0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x56, 0x73,
0x32, 0x46, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x62,
0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x28, 0x70, 0x6f, 0x73, 0x69, 0x74,
0x69, 0x6f, 0x6e, 0x29, 0x20, 0x70, 0x6f, 0x73, 0x3a, 0x20, 0x76, 0x65,
0x63, 0x34, 0x66, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x6c, 0x6f,
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x30, 0x29, 0x20, 0x40, 0x69,
0x6e, 0x74, 0x65, 0x72, 0x70, 0x6f, 0x6c, 0x61, 0x74, 0x65, 0x28, 0x6c,
0x69, 0x6e, 0x65, 0x61, 0x72, 0x29, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72,
0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x2c, 0x0a, 0x7d, 0x3b, 0x0a,
0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x46, 0x73, 0x4f, 0x75,
0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x6c, 0x6f, 0x63,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x30, 0x29, 0x20, 0x63, 0x6f, 0x6c,
0x6f, 0x72, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x2c, 0x0a, 0x7d,
0x3b, 0x0a, 0x0a, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28,
0x30, 0x29, 0x20, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x30, 0x29,
0x20, 0x76, 0x61, 0x72, 0x3c, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d,
0x3e, 0x20, 0x76, 0x73, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d,
0x73, 0x3a, 0x20, 0x56, 0x73, 0x55, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d,
0x3b, 0x0a, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, 0x31,
0x29, 0x20, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x30, 0x29, 0x20,
0x76, 0x61, 0x72, 0x3c, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x3e,
0x20, 0x73, 0x68, 0x61, 0x70, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f,
0x72, 0x6d, 0x3a, 0x20, 0x53, 0x68, 0x61, 0x70, 0x65, 0x55, 0x6e, 0x69,
0x66, 0x6f, 0x72, 0x6d, 0x3b, 0x0a, 0x0a, 0x40, 0x76, 0x65, 0x72, 0x74,
0x49, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x62, 0x75,
0x69, 0x6c, 0x74, 0x69, 0x6e, 0x28, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e,
0x63, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x29, 0x20, 0x69, 0x6e,
0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x78, 0x3a, 0x20,
0x75, 0x33, 0x32, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x6c, 0x6f,
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x30, 0x29, 0x20, 0x70, 0x6f,
0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32,
0x66, 0x2c, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63,
0x74, 0x20, 0x56, 0x73, 0x32, 0x46, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x40, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x28, 0x70,
0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x29, 0x20, 0x70, 0x6f, 0x73,
0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x40, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x30,
0x29, 0x20, 0x40, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x6f, 0x6c, 0x61,
0x74, 0x65, 0x28, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x29, 0x20, 0x63,
0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x2c,
0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20,
0x46, 0x73, 0x4f, 0x75, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x40, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x30, 0x29,
0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x34,
0x66, 0x2c, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x40, 0x76, 0x65, 0x72, 0x74,
0x65, 0x78, 0x20, 0x66, 0x6e, 0x20, 0x76, 0x73, 0x5f, 0x6d, 0x61, 0x69,
0x6e, 0x28, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x3a, 0x20, 0x56, 0x73, 0x49,
0x6e, 0x29, 0x20, 0x2d, 0x3e, 0x20, 0x56, 0x73, 0x32, 0x46, 0x73, 0x20,
0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x6f, 0x75,
0x74, 0x70, 0x75, 0x74, 0x3a, 0x20, 0x56, 0x73, 0x32, 0x46, 0x73, 0x3b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x77, 0x6f, 0x72,
0x6c, 0x64, 0x5f, 0x70, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x73, 0x68, 0x61,
0x70, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x2e, 0x74,
0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x2a, 0x20, 0x76,
0x65, 0x63, 0x34, 0x66, 0x28, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x70,
0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x78, 0x2c, 0x20, 0x69,
0x6e, 0x70, 0x75, 0x74, 0x2e, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f,
0x6e, 0x2e, 0x79, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e,
0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x75, 0x74, 0x70,
0x75, 0x74, 0x2e, 0x70, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x76, 0x73, 0x5f,
0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x2e, 0x6d, 0x76, 0x70,
0x20, 0x2a, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x5f, 0x70, 0x6f, 0x73,
0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x73, 0x68,
0x61, 0x70, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x2e,
0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x75, 0x29,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x73, 0x68, 0x61,
0x70, 0x65, 0x5f, 0x69, 0x64, 0x78, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x73,
0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6d, 0x61, 0x70, 0x5b, 0x76, 0x73,
0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x2e, 0x69, 0x6e,
0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x20,
0x2b, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x69, 0x6e, 0x73, 0x74,
0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x78, 0x5d, 0x3b, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x73, 0x68, 0x61, 0x70, 0x65,
0x20, 0x3d, 0x20, 0x73, 0x68, 0x61, 0x70, 0x65, 0x5f, 0x64, 0x61, 0x74,
0x61, 0x5b, 0x73, 0x68, 0x61, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x78, 0x5d,
0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x77, 0x6f,
0x72, 0x6c, 0x64, 0x5f, 0x70, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x73, 0x68,
0x61, 0x70, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72,
0x6d, 0x20, 0x2a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 0x69, 0x6e,
0x70, 0x75, 0x74, 0x2e, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e,
0x2e, 0x78, 0x2c, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x70, 0x6f,
0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x79, 0x2c, 0x20, 0x30, 0x2e,
0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x70, 0x6f, 0x73, 0x20,
0x3d, 0x20, 0x76, 0x73, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d,
0x73, 0x2e, 0x6d, 0x76, 0x70, 0x20, 0x2a, 0x20, 0x77, 0x6f, 0x72, 0x6c,
0x64, 0x5f, 0x70, 0x6f, 0x73, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69,
0x66, 0x20, 0x28, 0x73, 0x68, 0x61, 0x70, 0x65, 0x2e, 0x73, 0x74, 0x61,
0x74, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x75, 0x29, 0x20, 0x7b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x75, 0x74, 0x70,
0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x76,
0x65, 0x63, 0x34, 0x66, 0x28, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x30, 0x2e,
0x38, 0x34, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30,
0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x20, 0x65, 0x6c, 0x73,
0x65, 0x20, 0x69, 0x66, 0x20, 0x28, 0x73, 0x68, 0x61, 0x70, 0x65, 0x2e,
0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x75, 0x29,
0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f,
0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20,
0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 0x31, 0x2e, 0x30, 0x2c,
0x20, 0x30, 0x2e, 0x38, 0x34, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20,
0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x20,
0x65, 0x6c, 0x73, 0x65, 0x20, 0x69, 0x66, 0x20, 0x28, 0x73, 0x68, 0x61,
0x70, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x2e, 0x73,
0x74, 0x61, 0x74, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x75, 0x29, 0x20,
0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x75,
0x74, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d,
0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 0x30, 0x2e, 0x35, 0x2c, 0x20,
0x30, 0x2e, 0x36, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e,
0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x20, 0x65, 0x6c,
0x73, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 0x30, 0x2e, 0x35, 0x2c,
0x20, 0x30, 0x2e, 0x36, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x31,
0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x20, 0x65,
0x6c, 0x73, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c,
0x6f, 0x72, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 0x30,
0x2e, 0x38, 0x2c, 0x20, 0x30, 0x2e, 0x38, 0x2c, 0x20, 0x30, 0x2e, 0x38,
0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e,
0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a,
0x40, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x66, 0x6e,
0x20, 0x66, 0x73, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x69, 0x6e, 0x70,
0x75, 0x74, 0x3a, 0x20, 0x56, 0x73, 0x32, 0x46, 0x73, 0x29, 0x20, 0x2d,
0x3e, 0x20, 0x46, 0x73, 0x4f, 0x75, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74,
0x3a, 0x20, 0x46, 0x73, 0x4f, 0x75, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 0x30, 0x2e,
0x38, 0x2c, 0x20, 0x30, 0x2e, 0x38, 0x2c, 0x20, 0x30, 0x2e, 0x38, 0x2c,
0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20,
0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x40,
0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x66, 0x6e, 0x20,
0x66, 0x73, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x3a, 0x20, 0x56, 0x73, 0x32, 0x46, 0x73, 0x29, 0x20, 0x2d, 0x3e,
0x20, 0x46, 0x73, 0x4f, 0x75, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x76, 0x61, 0x72, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3a,
0x20, 0x46, 0x73, 0x4f, 0x75, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c, 0x6f, 0x72,
0x20, 0x3d, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c,
0x6f, 0x72, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75,
0x72, 0x6e, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3b, 0x0a, 0x7d,
0x0a
0x72, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f,
0x6c, 0x6f, 0x72, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74,
0x75, 0x72, 0x6e, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3b, 0x0a,
0x7d, 0x0a
};
unsigned int src_shaders_shape_wgsl_len = 1045;
unsigned int src_shaders_shape_wgsl_len = 1274;

View File

@@ -3,6 +3,8 @@
#include "api.h"
#define HISTORY_MAX_DEPTH 256
typedef enum {
HIST_POSITION,
HIST_SCALE,
@@ -103,6 +105,17 @@ static void history_push_entry(history_t *h, hist_entry_t entry) {
*((hist_entry_t*) vec_push(&h->entries)) = entry;
h->current = h->entries.count - 1;
while (h->entries.count > HISTORY_MAX_DEPTH) {
hist_entry_t *e = (hist_entry_t*) vec_get(&h->entries, 0);
if (e->changes) {
for (int j = 0; j < e->count; j++)
hist_free_change(&e->changes[j]);
FREE(e->changes);
}
vec_remove_ordered(&h->entries, 0);
h->current--;
}
}
static void history_begin_edit(history_t *h, vector_t *shapes,
@@ -180,14 +193,9 @@ static void history_batch_add(hist_batch_t *batch, int shape_index, hist_prop_t
// Used for HIST_CREATE and HIST_DELETE entries.
// old_val = { kind, cx, cy, num_verts }
// new_val = { sx, sy, rotation, group_id }
// For procedural shapes (CIRCLE, STAR, RECTANGLE), vertices are reconstructed
// from parameters instead of deep-copied. For STAR, new_val[1] stores inner_r.
static void hist_snapshot_shape_verts(hist_change_t *c, shape_t *s) {
int n = (int)s->num_elements;
c->vertex_count = n;
c->index_count = n;
c->vertex_data = (shape_vertex_t*) ALLOC((size_t)n * sizeof(shape_vertex_t));
c->index_data = (uint16_t*) ALLOC((size_t)n * sizeof(uint16_t));
memcpy(c->vertex_data, s->verts, (size_t)n * sizeof(shape_vertex_t));
memcpy(c->index_data, s->indices, (size_t)n * sizeof(uint16_t));
c->old_val[0] = (float)s->kind;
c->old_val[1] = s->cx;
c->old_val[2] = s->cy;
@@ -196,6 +204,28 @@ static void hist_snapshot_shape_verts(hist_change_t *c, shape_t *s) {
c->new_val[1] = s->sy;
c->new_val[2] = s->rotation;
c->new_val[3] = (float)s->group_id;
if (s->kind == SHAPE_GENERIC) {
int n = (int)s->num_elements;
c->vertex_count = n;
c->index_count = n;
c->vertex_data = (shape_vertex_t*) ALLOC((size_t)n * sizeof(shape_vertex_t));
c->index_data = (uint16_t*) ALLOC((size_t)n * sizeof(uint16_t));
memcpy(c->vertex_data, s->verts, (size_t)n * sizeof(shape_vertex_t));
memcpy(c->index_data, s->indices, (size_t)n * sizeof(uint16_t));
} else if (s->kind == SHAPE_STAR) {
float inner_ratio = sqrtf(s->verts[1].x * s->verts[1].x + s->verts[1].y * s->verts[1].y);
c->new_val[1] = inner_ratio * s->sx;
c->vertex_count = 0;
c->index_count = 0;
c->vertex_data = NULL;
c->index_data = NULL;
} else {
c->vertex_count = 0;
c->index_count = 0;
c->vertex_data = NULL;
c->index_data = NULL;
}
}
// Append a CREATE or DELETE entry to a batch, snapshotting the shape's vertex data.
@@ -214,29 +244,48 @@ static void history_batch_commit(hist_batch_t *batch, history_t *h) {
// Reconstruct a shape_t from a HIST_CREATE / HIST_DELETE change snapshot.
static shape_t hist_rebuild_shape_from_snapshot(const hist_change_t *c) {
float cx = c->old_val[1], cy = c->old_val[2];
float sx = c->new_val[0], rot = c->new_val[2];
int gid = (int)c->new_val[3];
shape_t s;
switch ((int)c->old_val[0]) {
case SHAPE_CIRCLE:
s = shape_circle(cx, cy, sx);
break;
case SHAPE_RECTANGLE:
s = shape_rectangle(cx, cy, sx * 2.0f, c->new_val[1] * 2.0f);
break;
case SHAPE_STAR: {
int points = (int)c->old_val[3] / 2;
s = shape_star(cx, cy, sx, c->new_val[1], points);
break;
}
default: {
memset(&s, 0, sizeof(s));
s.kind = (int)c->old_val[0];
s.cx = c->old_val[1];
s.cy = c->old_val[2];
s.cx = cx;
s.cy = cy;
s.num_verts = (uint32_t)c->old_val[3];
s.num_elements = (uint32_t)c->vertex_count;
s.sx = c->new_val[0];
s.sx = sx;
s.sy = c->new_val[1];
s.rotation = c->new_val[2];
s.group_id = (int)c->new_val[3];
int n = c->vertex_count;
s.verts = (shape_vertex_t*) ALLOC((size_t)n * sizeof(shape_vertex_t));
s.indices = (uint16_t*) ALLOC((size_t)n * sizeof(uint16_t));
memcpy(s.verts, c->vertex_data, (size_t)n * sizeof(shape_vertex_t));
memcpy(s.indices, c->index_data, (size_t)n * sizeof(uint16_t));
shape_init_common(&s);
shape_build_transform(&s);
shape_make_buffers(&s);
return s;
}
}
s.rotation = rot;
s.group_id = gid;
shape_build_transform(&s);
return s;
}
static void history_apply_entry(hist_entry_t *entry, vector_t *shapes, bool forward) {
bool has_shape_ops = false;

View File

@@ -55,7 +55,7 @@ static void handle_left_down_resize_begin(userdata_t *ud, float wx, float wy, in
int sel_n = 0;
for (int i = 0; i < ud->shapes.count; i++) {
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
if (s->selected) { sum_sin += sinf(s->rotation); sum_cos += cosf(s->rotation); sel_n++; }
if (s->selected) { sum_sin += s->sin_r; sum_cos += s->cos_r; sel_n++; }
}
ud->interact.resize.angle = atan2f(sum_sin, sum_cos);
@@ -65,7 +65,7 @@ static void handle_left_down_resize_begin(userdata_t *ud, float wx, float wy, in
for (int i = 0; i < ud->shapes.count; i++) {
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
if (s->selected) {
float sc = cosf(s->rotation), ss = sinf(s->rotation);
float sc = s->cos_r, ss = s->sin_r;
float hlx = (ud->interact.resize.start_wx - s->cx) * sc + (ud->interact.resize.start_wy - s->cy) * ss;
float hly = -(ud->interact.resize.start_wx - s->cx) * ss + (ud->interact.resize.start_wy - s->cy) * sc;
float plx = (ud->interact.resize.pivot_x - s->cx) * sc + (ud->interact.resize.pivot_y - s->cy) * ss;
@@ -92,6 +92,12 @@ static void handle_left_down_rotate_begin(userdata_t *ud, float wx, float wy)
wy - ud->interact.rotate.center_y,
wx - ud->interact.rotate.center_x);
ud->interact.rotate.total_delta = 0.0f;
ud->interact.drag_indices.count = 0;
for (int i = 0; i < ud->shapes.count; i++) {
if (((shape_t*) vec_get(&ud->shapes, i))->selected)
*(int*) vec_push(&ud->interact.drag_indices) = i;
}
}
static void handle_left_down_move_begin(userdata_t *ud, float wx, float wy)
@@ -101,6 +107,12 @@ static void handle_left_down_move_begin(userdata_t *ud, float wx, float wy)
ud->interact.move.start_wy = wy;
ud->interact.move.total_dx = 0;
ud->interact.move.total_dy = 0;
ud->interact.drag_indices.count = 0;
for (int i = 0; i < ud->shapes.count; i++) {
if (((shape_t*) vec_get(&ud->shapes, i))->selected)
*(int*) vec_push(&ud->interact.drag_indices) = i;
}
}
static void handle_left_down_select_or_marquee(userdata_t *ud, const sapp_event *event, float wx, float wy, float tol)
@@ -170,23 +182,19 @@ static void handle_resize_end(userdata_t *ud)
static void handle_rotate_end(userdata_t *ud)
{
if (ud->interact.rotate.total_delta != 0.0f) {
int sel_count = 0;
for (int i = 0; i < ud->shapes.count; i++) {
if (((shape_t*) vec_get(&ud->shapes, i))->selected) sel_count++;
}
int n = ud->interact.drag_indices.count;
if (n > 0 && ud->interact.rotate.total_delta != 0.0f) {
float cos_b = cosf(-ud->interact.rotate.total_delta);
float sin_b = sinf(-ud->interact.rotate.total_delta);
float cx = ud->interact.rotate.center_x;
float cy = ud->interact.rotate.center_y;
hist_batch_t batch;
history_batch_init(&batch, sel_count * 2);
history_batch_init(&batch, n * 2);
for (int i = 0; i < ud->shapes.count; i++) {
for (int j = 0; j < n; j++) {
int i = *(int*) vec_get(&ud->interact.drag_indices, j);
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
if (s->selected) {
float dx = s->cx - cx;
float dy = s->cy - cy;
float old_cx = cx + dx * cos_b - dy * sin_b;
@@ -199,12 +207,12 @@ static void handle_rotate_end(userdata_t *ud)
(float[4]){ s->rotation - ud->interact.rotate.total_delta },
(float[4]){ s->rotation });
}
}
history_batch_commit(&batch, &ud->history);
}
ud->interact.rotate.dragging = false;
ud->interact.drag_indices.count = 0;
update_shape_states(ud);
spatial_mark_dirty(&ud->spatial_grid);
ud->interact.aabb_cached = false;
@@ -213,28 +221,24 @@ static void handle_rotate_end(userdata_t *ud)
static void handle_move_end(userdata_t *ud)
{
if (ud->interact.move.total_dx != 0.0f || ud->interact.move.total_dy != 0.0f) {
int sel_count = 0;
for (int i = 0; i < ud->shapes.count; i++) {
if (((shape_t*) vec_get(&ud->shapes, i))->selected) sel_count++;
}
int n = ud->interact.drag_indices.count;
if (n > 0 && (ud->interact.move.total_dx != 0.0f || ud->interact.move.total_dy != 0.0f)) {
hist_batch_t batch;
history_batch_init(&batch, sel_count);
history_batch_init(&batch, n);
for (int i = 0; i < ud->shapes.count; i++) {
for (int j = 0; j < n; j++) {
int i = *(int*) vec_get(&ud->interact.drag_indices, j);
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
if (s->selected) {
history_batch_add(&batch, i, HIST_POSITION,
(float[4]){ s->cx - ud->interact.move.total_dx, s->cy - ud->interact.move.total_dy },
(float[4]){ s->cx, s->cy });
}
}
history_batch_commit(&batch, &ud->history);
}
ud->interact.move.dragging = false;
ud->interact.drag_indices.count = 0;
update_shape_states(ud);
spatial_mark_dirty(&ud->spatial_grid);
ud->interact.aabb_cached = false;
@@ -306,7 +310,7 @@ static void handle_resize_drag(userdata_t *ud, const sapp_event *event)
resize_init_t *ini = &ud->interact.resize.init[j];
shape_t *s = (shape_t*) vec_get(&ud->shapes, ini->idx);
float sc = cosf(s->rotation), ss = sinf(s->rotation);
float sc = s->cos_r, ss = s->sin_r;
float mlx = (wx - ini->init_cx) * sc + (wy - ini->init_cy) * ss;
float mly = -(wx - ini->init_cx) * ss + (wy - ini->init_cy) * sc;
@@ -351,9 +355,9 @@ static void handle_rotate_drag(userdata_t *ud, const sapp_event *event)
float cx = ud->interact.rotate.center_x;
float cy = ud->interact.rotate.center_y;
for (int i = 0; i < ud->shapes.count; i++) {
for (int j = 0; j < ud->interact.drag_indices.count; j++) {
int i = *(int*) vec_get(&ud->interact.drag_indices, j);
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
if (s->selected) {
float dx = s->cx - cx;
float dy = s->cy - cy;
s->cx = cx + dx * cos_a - dy * sin_a;
@@ -362,7 +366,6 @@ static void handle_rotate_drag(userdata_t *ud, const sapp_event *event)
shape_build_transform(s);
shape_set_state(s, false, true);
}
}
ud->interact.rotate.total_delta = delta;
}
@@ -376,15 +379,14 @@ static void handle_move_drag(userdata_t *ud, const sapp_event *event)
float delta_x = dx - ud->interact.move.total_dx;
float delta_y = dy - ud->interact.move.total_dy;
for (int i = 0; i < ud->shapes.count; i++) {
for (int j = 0; j < ud->interact.drag_indices.count; j++) {
int i = *(int*) vec_get(&ud->interact.drag_indices, j);
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
if (s->selected) {
s->cx += delta_x;
s->cy += delta_y;
shape_build_transform(s);
shape_retranslate(s);
shape_set_state(s, false, true);
}
}
ud->interact.move.total_dx = dx;
ud->interact.move.total_dy = dy;
@@ -411,6 +413,35 @@ static void handle_marquee_drag(userdata_t *ud, const sapp_event *event)
&ud->spatial_grid, &ud->shapes,
min_x, min_y, max_x, max_y);
if (ud->interact.focused_group_id == 0) {
int cap = ud->shapes.count;
int *gids = (int*) ALLOC((size_t)cap * sizeof(int));
int n_gids = 0;
for (int i = 0; i < ud->shapes.count; i++) {
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
if (!s->selected || s->group_id == 0) continue;
int topmost = get_topmost_group(&ud->groups, s->group_id);
bool found = false;
for (int j = 0; j < n_gids; j++) {
if (gids[j] == topmost) { found = true; break; }
}
if (!found) gids[n_gids++] = topmost;
}
for (int j = 0; j < n_gids; j++) {
for (int i = 0; i < ud->shapes.count; i++) {
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
if (is_shape_in_group_hierarchy(s->group_id, gids[j], &ud->groups))
s->selected = true;
}
}
FREE(gids);
ud->interact.selected_count = 0;
for (int i = 0; i < ud->shapes.count; i++) {
if (((shape_t*) vec_get(&ud->shapes, i))->selected)
ud->interact.selected_count++;
}
}
for (int i = 0; i < ud->shapes.count; i++) {
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
shape_set_state(s, false, s->selected);
@@ -435,16 +466,179 @@ static void handle_hover(userdata_t *ud, const sapp_event *event)
if (hovered >= 0) {
shape_t *hs = (shape_t*) vec_get(&ud->shapes, hovered);
hovered_gid = hs->group_id;
if (hovered_gid != 0 && ud->interact.focused_group_id == 0)
hovered_gid = get_topmost_group(&ud->groups, hovered_gid);
}
for (int i = 0; i < ud->shapes.count; i++) {
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
bool in_group = (ud->interact.focused_group_id == 0 &&
hovered_gid != 0 && s->group_id == hovered_gid);
hovered_gid != 0 &&
is_shape_in_group_hierarchy(s->group_id, hovered_gid, &ud->groups));
shape_set_state(s, (i == hovered || in_group), s->selected);
}
}
// -- clipboard --
static shape_t clipboard_deep_copy_shape(const shape_t *src)
{
shape_t dst = *src;
dst.verts = (shape_vertex_t*) ALLOC((size_t)src->num_elements * sizeof(shape_vertex_t));
dst.indices = (uint16_t*) ALLOC((size_t)src->num_elements * sizeof(uint16_t));
memcpy(dst.verts, src->verts, (size_t)src->num_elements * sizeof(shape_vertex_t));
memcpy(dst.indices, src->indices, (size_t)src->num_elements * sizeof(uint16_t));
return dst;
}
static void clipboard_clear(clipboard_t *cb)
{
for (int i = 0; i < cb->shape_count; i++) {
FREE(cb->shapes[i].verts);
FREE(cb->shapes[i].indices);
}
FREE(cb->shapes);
FREE(cb->groups);
memset(cb, 0, sizeof(*cb));
}
static int clipboard_lookup_gid(int old_id, const int *map, int map_count)
{
for (int j = 0; j < map_count; j++) {
if (map[j * 2] == old_id) return map[j * 2 + 1];
}
return 0;
}
static void handle_copy(userdata_t *ud)
{
if (ud->interact.selected_count == 0) return;
clipboard_clear(&ud->clipboard);
int n = ud->shapes.count;
int sel = 0;
for (int i = 0; i < n; i++) {
if (((shape_t*) vec_get(&ud->shapes, i))->selected) sel++;
}
ud->clipboard.shapes = (shape_t*) ALLOC((size_t)sel * sizeof(shape_t));
ud->clipboard.shape_count = 0;
int *gids = (int*) ALLOC((size_t)n * sizeof(int));
int n_gids = 0;
for (int i = 0; i < n; i++) {
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
if (!s->selected) continue;
ud->clipboard.shapes[ud->clipboard.shape_count++] = clipboard_deep_copy_shape(s);
int gid = s->group_id;
while (gid != 0) {
bool found = false;
for (int j = 0; j < n_gids; j++) {
if (gids[j] == gid) { found = true; break; }
}
if (!found) gids[n_gids++] = gid;
group_t *g = find_group(&ud->groups, gid);
gid = g ? g->parent_id : 0;
}
}
ud->clipboard.groups = (group_t*) ALLOC((size_t)n_gids * sizeof(group_t));
ud->clipboard.group_count = n_gids;
for (int j = 0; j < n_gids; j++) {
group_t *src = find_group(&ud->groups, gids[j]);
ud->clipboard.groups[j] = *src;
}
FREE(gids);
}
static void handle_paste(userdata_t *ud)
{
clipboard_t *cb = &ud->clipboard;
if (cb->shape_count == 0) return;
int gc = cb->group_count;
int *gid_map = NULL;
if (gc > 0) {
gid_map = (int*) ALLOC((size_t)gc * 2 * sizeof(int));
for (int j = 0; j < gc; j++) {
gid_map[j * 2] = cb->groups[j].id;
gid_map[j * 2 + 1] = ud->next_group_id++;
}
for (int j = 0; j < gc; j++) {
group_t g = cb->groups[j];
g.id = clipboard_lookup_gid(g.id, gid_map, gc);
g.parent_id = clipboard_lookup_gid(g.parent_id, gid_map, gc);
*((group_t*) vec_push(&ud->groups)) = g;
}
group_index_rebuild(&ud->groups);
}
for (int i = 0; i < ud->shapes.count; i++)
((shape_t*) vec_get(&ud->shapes, i))->selected = false;
ud->interact.selected_count = 0;
float cx, cy;
screen_to_world(&ud->camera, ud->mouse_x, ud->mouse_y, &cx, &cy);
float cb_min_x = 0, cb_min_y = 0, cb_max_x = 0, cb_max_y = 0;
for (int i = 0; i < cb->shape_count; i++) {
shape_t *s = &cb->shapes[i];
float sc = s->cos_r, ss = s->sin_r;
for (uint32_t v = 0; v < s->num_verts; v++) {
float lx = s->verts[v].x * s->sx;
float ly = s->verts[v].y * s->sy;
float wx = s->cx + lx * sc - ly * ss;
float wy = s->cy + lx * ss + ly * sc;
if (i == 0 && v == 0) {
cb_min_x = cb_max_x = wx;
cb_min_y = cb_max_y = wy;
} else {
if (wx < cb_min_x) cb_min_x = wx;
if (wx > cb_max_x) cb_max_x = wx;
if (wy < cb_min_y) cb_min_y = wy;
if (wy > cb_max_y) cb_max_y = wy;
}
}
}
float cb_cx = (cb_min_x + cb_max_x) * 0.5f;
float cb_cy = (cb_min_y + cb_max_y) * 0.5f;
int sc = cb->shape_count;
hist_batch_t batch;
history_batch_init(&batch, sc);
for (int i = 0; i < sc; i++) {
shape_t s = clipboard_deep_copy_shape(&cb->shapes[i]);
s.cx += cx - cb_cx;
s.cy += cy - cb_cy;
if (gc > 0)
s.group_id = clipboard_lookup_gid(s.group_id, gid_map, gc);
else
s.group_id = 0;
s.selected = true;
ud->interact.selected_count++;
*((shape_t*) vec_push(&ud->shapes)) = s;
g_shape_pool_dirty = true;
history_batch_add_shape(&batch, ud->shapes.count - 1, HIST_CREATE,
(shape_t*) vec_get(&ud->shapes, ud->shapes.count - 1));
}
history_batch_commit(&batch, &ud->history);
FREE(gid_map);
spatial_mark_dirty(&ud->spatial_grid);
ud->interact.aabb_cached = false;
ud->interact.focused_group_id = 0;
ud->overlay_upload_needed = true;
update_shape_states(ud);
}
// -- public event handlers --
static bool handle_key_down(userdata_t *ud, const sapp_event *event)
@@ -454,6 +648,7 @@ static bool handle_key_down(userdata_t *ud, const sapp_event *event)
if (history_undo(&ud->history, &ud->shapes)) {
rebuild_groups_from_shapes(&ud->groups, &ud->shapes);
ud->interact.hovered_shape = -1;
g_shape_pool_dirty = true;
spatial_mark_dirty(&ud->spatial_grid);
ud->interact.aabb_cached = false;
ud->overlay_upload_needed = true;
@@ -464,12 +659,21 @@ static bool handle_key_down(userdata_t *ud, const sapp_event *event)
if (history_redo(&ud->history, &ud->shapes)) {
rebuild_groups_from_shapes(&ud->groups, &ud->shapes);
ud->interact.hovered_shape = -1;
g_shape_pool_dirty = true;
spatial_mark_dirty(&ud->spatial_grid);
ud->interact.aabb_cached = false;
ud->overlay_upload_needed = true;
}
return true;
}
if (event->key_code == SAPP_KEYCODE_C) {
handle_copy(ud);
return true;
}
if (event->key_code == SAPP_KEYCODE_V) {
handle_paste(ud);
return true;
}
if (event->key_code == SAPP_KEYCODE_G) {
if (event->modifiers & SAPP_MODIFIER_SHIFT) {
// Ungroup: collect unique group IDs of selected shapes
@@ -549,6 +753,8 @@ static bool handle_key_down(userdata_t *ud, const sapp_event *event)
}
}
group_index_rebuild(&ud->groups);
FREE(parents);
FREE(gids);
ud->ui.list_last_shape = -1;
@@ -624,6 +830,7 @@ static bool handle_key_down(userdata_t *ud, const sapp_event *event)
group_t new_grp = { .id = gid, .parent_id = 0 };
*((group_t*) vec_push(&ud->groups)) = new_grp;
group_index_rebuild(&ud->groups);
for (int j = 0; j < n_full; j++) {
for (int g = 0; g < ud->groups.count; g++) {
@@ -683,6 +890,7 @@ static bool handle_key_down(userdata_t *ud, const sapp_event *event)
shape_shutdown(s);
vec_remove_ordered(&ud->shapes, indices[j]);
}
g_shape_pool_dirty = true;
FREE(indices);
@@ -853,6 +1061,9 @@ static void handle_mouse_up(userdata_t *ud, const sapp_event *event)
static void handle_mouse_move(userdata_t *ud, const sapp_event *event)
{
ud->mouse_x = event->mouse_x;
ud->mouse_y = event->mouse_y;
if (ud->camera.pan_state.dragging) {
handle_pan_drag(ud, event);
} else if (ud->interact.resize.dragging) {

View File

@@ -11,7 +11,7 @@ static void selected_aabb(userdata_t *ud, float *min_x, float *min_y,
for (int i = 0; i < ud->shapes.count; i++) {
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
if (!s->selected) continue;
float sc = cosf(s->rotation), ss = sinf(s->rotation);
float sc = s->cos_r, ss = s->sin_r;
for (uint32_t v = 0; v < s->num_verts; v++) {
float lx = s->verts[v].x * s->sx;
float ly = s->verts[v].y * s->sy;
@@ -117,6 +117,7 @@ static void rebuild_groups_from_shapes(vector_t *groups, vector_t *shapes)
}
if (saved) FREE(saved);
group_index_rebuild(groups);
}
static int hit_test_resize_handles(userdata_t *ud, float wx, float wy, float tol)

View File

@@ -34,6 +34,16 @@ static void log_capture(const char* tag, uint32_t log_level, uint32_t log_item,
if (log_level <= 1) ud->ui.log_show = true;
}
static void panel_log_impl(void *ud_v, int level, const char *msg) {
userdata_t *ud = (userdata_t*)ud_v;
int idx = ud->ui.log_head;
strncpy(ud->ui.log_ring[idx].text, msg, 255);
ud->ui.log_ring[idx].text[255] = 0;
ud->ui.log_ring[idx].level = (uint32_t)level;
ud->ui.log_head = (idx + 1) % LOG_RING_SIZE;
if (ud->ui.log_count < LOG_RING_SIZE) ud->ui.log_count++;
}
static void meter_fps(userdata_t *ud)
{
float dt = (float)sapp_frame_duration();
@@ -102,10 +112,14 @@ static void init(void* _userdata)
userdata_t* ud = (userdata_t*) _userdata;
g_panel_log_fn = panel_log_impl;
g_panel_log_ud = ud;
sg_desc sgdesc = {
.environment = sglue_environment(),
.logger.func = log_capture,
.logger.user_data = ud,
.uniform_buffer_size = 16 * 1024 * 1024,
};
sg_setup(&sgdesc);
if (!sg_isvalid()) {
@@ -216,6 +230,7 @@ static void init(void* _userdata)
vec_init(&ud->shapes, sizeof(shape_t));
vec_init(&ud->groups, sizeof(group_t));
vec_init(&ud->interact.drag_indices, sizeof(int));
spatial_init(&ud->spatial_grid);
ud->interact.selected_count = 0;
ud->interact.hovered_shape = -1;
@@ -296,7 +311,7 @@ static void init(void* _userdata)
EM_ASM({
window.addEventListener('keydown', function(e) {
if (e.ctrlKey && !e.altKey && !e.metaKey) {
if (e.key === 'z' || e.key === 'y') {
if (e.key === 'z' || e.key === 'y' || e.key === 'c' || e.key === 'v') {
e.preventDefault();
}
}
@@ -316,6 +331,8 @@ static void cleanup(void* _userdata)
spatial_destroy(&ud->spatial_grid);
vec_free(&ud->shapes);
vec_free(&ud->groups);
vec_free(&ud->interact.drag_indices);
group_index_shutdown();
history_destroy(&ud->history);
if (ud->interact.resize.init) FREE(ud->interact.resize.init);
sg_destroy_buffer(ud->rect_vbuf);
@@ -329,6 +346,13 @@ static void cleanup(void* _userdata)
shape_pool_shutdown();
shape_shutdown_pipeline();
for (int i = 0; i < ud->clipboard.shape_count; i++) {
FREE(ud->clipboard.shapes[i].verts);
FREE(ud->clipboard.shapes[i].indices);
}
FREE(ud->clipboard.shapes);
FREE(ud->clipboard.groups);
FREE(ud);
simgui_shutdown();

View File

@@ -27,7 +27,26 @@ static void compute_overlay_geometry(userdata_t *ud,
overlay_verts[4] = (shape_vertex_t){x1, y1};
*has_overlay = true;
} else if (ud->interact.selected_count >= 1) {
if (ud->interact.selected_count == 1) {
if (ud->interact.move.dragging && ud->interact.aabb_cached) {
float dx = ud->interact.move.total_dx;
float dy = ud->interact.move.total_dy;
float omin_x = ud->interact.cached_aabb[0] + dx;
float omin_y = ud->interact.cached_aabb[1] + dy;
float omax_x = ud->interact.cached_aabb[2] + dx;
float omax_y = ud->interact.cached_aabb[3] + dy;
float pad = 8.0f / ud->camera.zoom;
overlay_verts[0] = (shape_vertex_t){omin_x - pad, omin_y - pad};
overlay_verts[1] = (shape_vertex_t){omax_x + pad, omin_y - pad};
overlay_verts[2] = (shape_vertex_t){omax_x + pad, omax_y + pad};
overlay_verts[3] = (shape_vertex_t){omin_x - pad, omax_y + pad};
overlay_verts[4] = overlay_verts[0];
*sel_cx = (omin_x + omax_x) * 0.5f;
*sel_cy = (omin_y + omax_y) * 0.5f;
*sel_hw = (omax_x - omin_x) * 0.5f + pad;
*sel_hh = (omax_y - omin_y) * 0.5f + pad;
*sel_angle = 0;
*has_overlay = true;
} else if (ud->interact.selected_count == 1) {
for (int i = 0; i < ud->shapes.count; i++) {
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
if (!s->selected) continue;
@@ -52,8 +71,8 @@ static void compute_overlay_geometry(userdata_t *ud,
for (int i = 0; i < ud->shapes.count; i++) {
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
if (!s->selected) continue;
sum_sin += sinf(s->rotation);
sum_cos += cosf(s->rotation);
sum_sin += s->sin_r;
sum_cos += s->cos_r;
}
selected_aabb(ud, &omin[0], &omin[1], &omax[0], &omax[1]);
ud->interact.cached_aabb[0] = omin[0]; ud->interact.cached_aabb[1] = omin[1];
@@ -102,10 +121,19 @@ static void upload_overlay_buffers(userdata_t *ud,
ud->interact.rotate.handle_radius = radius;
const int n = HANDLE_CIRCLE_SEGMENTS + 1;
shape_vertex_t hv[HANDLE_CIRCLE_SEGMENTS + 1];
static shape_vertex_t unit_circle[HANDLE_CIRCLE_SEGMENTS + 1];
static bool unit_circle_ready = false;
if (!unit_circle_ready) {
for (int i = 0; i < n; i++) {
float a = (float)i / (float)HANDLE_CIRCLE_SEGMENTS * 2.0f * GLM_PIf;
hv[i] = (shape_vertex_t){sel_cx + cosf(a) * radius, sel_cy + sinf(a) * radius};
unit_circle[i] = (shape_vertex_t){cosf(a), sinf(a)};
}
unit_circle_ready = true;
}
shape_vertex_t hv[HANDLE_CIRCLE_SEGMENTS + 1];
for (int i = 0; i < n; i++) {
hv[i] = (shape_vertex_t){sel_cx + unit_circle[i].x * radius,
sel_cy + unit_circle[i].y * radius};
}
if (need_upload)
sg_update_buffer(ud->handle_vbuf, &(sg_range){hv, sizeof(hv)});

View File

@@ -5,6 +5,8 @@
static sg_pipeline shape_pipeline;
static sg_shader shape_shader;
static sg_pipeline overlay_pipeline;
static sg_shader overlay_shader;
static int g_shape_frame_id;
static void shape_begin_frame(void)
@@ -14,13 +16,14 @@ static void shape_begin_frame(void)
static void shape_init_pipeline(void)
{
shape_shader = sg_make_shader(&(sg_shader_desc) {
// Overlay shader/pipeline (simple, no storage buffers)
overlay_shader = sg_make_shader(&(sg_shader_desc) {
.vertex_func = {
.source = (const char*) src_shaders_shape_wgsl,
.source = (const char*) src_shaders_overlay_wgsl,
.entry = "vs_main",
},
.fragment_func = {
.source = (const char*) src_shaders_shape_wgsl,
.source = (const char*) src_shaders_overlay_wgsl,
.entry = "fs_main",
},
.uniform_blocks = {
@@ -38,31 +41,79 @@ static void shape_init_pipeline(void)
.attrs = {
[0] = { .base_type = SG_SHADERATTRBASETYPE_FLOAT },
},
.label = "Overlay shader",
});
panel_log(3, "[shapes] overlay shader id=%d valid=%d", overlay_shader.id, sg_isvalid());
overlay_pipeline = sg_make_pipeline(&(sg_pipeline_desc) {
.shader = overlay_shader,
.index_type = SG_INDEXTYPE_UINT16,
.primitive_type = SG_PRIMITIVETYPE_LINE_STRIP,
.layout.attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT2,
},
.label = "Overlay pipeline",
});
panel_log(3, "[shapes] overlay pipeline id=%d valid=%d", overlay_pipeline.id, sg_isvalid());
// Shape shader/pipeline (storage buffers, instanced)
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 = 80,
.stage = SG_SHADERSTAGE_VERTEX,
.wgsl_group0_binding_n = 0,
},
},
.views = {
[0] = {
.storage_buffer = {
.stage = SG_SHADERSTAGE_VERTEX,
.readonly = true,
.wgsl_group1_binding_n = 0,
},
},
[1] = {
.storage_buffer = {
.stage = SG_SHADERSTAGE_VERTEX,
.readonly = true,
.wgsl_group1_binding_n = 1,
},
},
},
.attrs = {
[0] = { .base_type = SG_SHADERATTRBASETYPE_FLOAT },
},
.label = "Shape shader",
});
panel_log(3, "[shapes] shader id=%d valid=%d", shape_shader.id, sg_isvalid());
shape_pipeline = sg_make_pipeline(&(sg_pipeline_desc) {
.shader = shape_shader,
.index_type = SG_INDEXTYPE_UINT16,
.index_type = SG_INDEXTYPE_NONE,
.primitive_type = SG_PRIMITIVETYPE_LINE_STRIP,
.layout.attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT2,
},
.label = "Shape pipeline",
});
panel_log(3, "[shapes] pipeline id=%d valid=%d", shape_pipeline.id, sg_isvalid());
}
static void shape_shutdown_pipeline(void)
{
sg_destroy_pipeline(shape_pipeline);
sg_destroy_shader(shape_shader);
}
static void shape_draw(shape_t *s, const mat4 *mvp)
{
sg_apply_uniforms(0, &SG_RANGE(*mvp));
sg_apply_uniforms(1, &SG_RANGE(s->uniform));
sg_draw((int)s->index_base, (int)s->num_elements, 1);
sg_destroy_pipeline(overlay_pipeline);
sg_destroy_shader(overlay_shader);
}
#endif

44
src/shaders/overlay.wgsl Normal file
View File

@@ -0,0 +1,44 @@
struct VsUniform {
mvp: mat4x4f,
};
struct ShapeUniform {
transform: mat4x4f,
state: u32,
};
struct VsIn {
@location(0) position: vec2f,
};
struct Vs2Fs {
@builtin(position) pos: vec4f,
@location(0) @interpolate(linear) color: vec4f,
};
struct FsOut {
@location(0) color: vec4f,
};
@binding(0) @group(0) var<uniform> vs_uniforms: VsUniform;
@binding(1) @group(0) var<uniform> shape_uniform: ShapeUniform;
@vertex fn vs_main(input: VsIn) -> Vs2Fs {
var output: Vs2Fs;
let world_pos = shape_uniform.transform * vec4f(input.position.x, input.position.y, 0.0, 1.0);
output.pos = vs_uniforms.mvp * world_pos;
if (shape_uniform.state == 2u) {
output.color = vec4f(1.0, 0.84, 0.0, 1.0);
} else if (shape_uniform.state == 1u) {
output.color = vec4f(0.5, 0.6, 1.0, 1.0);
} else {
output.color = vec4f(0.8, 0.8, 0.8, 1.0);
}
return output;
}
@fragment fn fs_main(input: Vs2Fs) -> FsOut {
var output: FsOut;
output.color = input.color;
return output;
}

View File

@@ -1,13 +1,20 @@
struct VsUniform {
mvp: mat4x4f,
instance_base: u32,
};
struct ShapeUniform {
struct ShapeData {
transform: mat4x4f,
state: u32,
};
@binding(0) @group(0) var<uniform> vs_uniforms: VsUniform;
@binding(0) @group(1) var<storage> shape_data: array<ShapeData>;
@binding(1) @group(1) var<storage> instance_map: array<u32>;
struct VsIn {
@builtin(instance_index) instance_idx: u32,
@location(0) position: vec2f,
};
@@ -20,16 +27,15 @@ struct FsOut {
@location(0) color: vec4f,
};
@binding(0) @group(0) var<uniform> vs_uniforms: VsUniform;
@binding(1) @group(0) var<uniform> shape_uniform: ShapeUniform;
@vertex fn vs_main(input: VsIn) -> Vs2Fs {
var output: Vs2Fs;
let world_pos = shape_uniform.transform * vec4f(input.position.x, input.position.y, 0.0, 1.0);
let shape_idx = instance_map[vs_uniforms.instance_base + input.instance_idx];
let shape = shape_data[shape_idx];
let world_pos = shape.transform * vec4f(input.position.x, input.position.y, 0.0, 1.0);
output.pos = vs_uniforms.mvp * world_pos;
if (shape_uniform.state == 2u) {
if (shape.state == 2u) {
output.color = vec4f(1.0, 0.84, 0.0, 1.0);
} else if (shape_uniform.state == 1u) {
} else if (shape.state == 1u) {
output.color = vec4f(0.5, 0.6, 1.0, 1.0);
} else {
output.color = vec4f(0.8, 0.8, 0.8, 1.0);

View File

@@ -20,6 +20,12 @@ typedef struct shape_uniform_t {
uint8_t _pad[12];
} shape_uniform_t;
typedef struct {
mat4 transform;
uint32_t state;
uint8_t _pad[12];
} shape_gpu_data_t;
typedef struct shape_t {
shape_vertex_t *verts;
uint16_t *indices;
@@ -32,11 +38,9 @@ typedef struct shape_t {
float cx, cy;
float sx, sy;
float rotation;
float cos_r, sin_r;
int kind;
uint32_t vertex_base;
uint32_t index_base;
int group_id;
} shape_t;
@@ -47,15 +51,55 @@ typedef struct {
int parent_id; // 0 = top-level group
} group_t;
static group_t* find_group(vector_t *groups, int id) {
static group_t **g_group_by_id = NULL;
static int g_group_by_id_cap = 0;
static void group_index_rebuild(vector_t *groups)
{
int max_id = 0;
for (int i = 0; i < groups->count; i++) {
int gid = ((group_t*) vec_get(groups, i))->id;
if (gid > max_id) max_id = gid;
}
if (max_id >= g_group_by_id_cap) {
if (g_group_by_id) FREE(g_group_by_id);
int new_cap = max_id + 64;
g_group_by_id = (group_t**) ALLOC((size_t)new_cap * sizeof(group_t*));
memset(g_group_by_id, 0, (size_t)new_cap * sizeof(group_t*));
g_group_by_id_cap = new_cap;
} else {
for (int i = 0; i <= max_id; i++) g_group_by_id[i] = NULL;
}
for (int i = 0; i < groups->count; i++) {
group_t *g = (group_t*) vec_get(groups, i);
if (g->id == id) return g;
g_group_by_id[g->id] = g;
}
return NULL;
}
static void group_index_ensure_cap(int max_id)
{
if (max_id >= g_group_by_id_cap) {
int new_cap = max_id + 64;
group_t **old = g_group_by_id;
g_group_by_id = (group_t**) ALLOC((size_t)new_cap * sizeof(group_t*));
if (old) {
memcpy(g_group_by_id, old, (size_t)g_group_by_id_cap * sizeof(group_t*));
FREE(old);
}
memset(g_group_by_id + g_group_by_id_cap, 0,
(size_t)(new_cap - g_group_by_id_cap) * sizeof(group_t*));
g_group_by_id_cap = new_cap;
}
}
static group_t* find_group(vector_t *groups, int id) {
(void)groups;
if (id <= 0 || id >= g_group_by_id_cap) return NULL;
return g_group_by_id[id];
}
static int get_topmost_group(vector_t *groups, int gid) {
(void)groups;
while (gid != 0) {
group_t *g = find_group(groups, gid);
if (!g || g->parent_id == 0) return gid;
@@ -65,6 +109,7 @@ static int get_topmost_group(vector_t *groups, int gid) {
}
static bool is_shape_in_group_hierarchy(int shape_gid, int target_gid, vector_t *groups) {
(void)groups;
int cur = shape_gid;
while (cur != 0) {
if (cur == target_gid) return true;
@@ -75,74 +120,189 @@ static bool is_shape_in_group_hierarchy(int shape_gid, int target_gid, vector_t
return false;
}
static void group_index_shutdown(void)
{
if (g_group_by_id) FREE(g_group_by_id);
g_group_by_id = NULL;
g_group_by_id_cap = 0;
}
// -- shared geometry buffers (one vbuf + one ibuf for all shapes) --
static sg_buffer g_shape_vbuf = {0};
static sg_buffer g_shape_ibuf = {0};
static sg_buffer g_shape_data_sbuf = {0};
static sg_buffer g_instance_map_sbuf = {0};
static sg_view g_shape_data_view = {0};
static sg_view g_instance_map_view = {0};
// Per-group vertex buffers: one per unique num_elements
typedef struct {
uint32_t num_elements;
sg_buffer vbuf;
} shape_group_buf_t;
static shape_group_buf_t *g_shape_groups = NULL;
static int g_shape_group_count = 0;
static bool g_shape_pool_dirty;
static uint32_t g_shape_vert_count;
static uint32_t g_shape_idx_count;
static bool g_shape_data_dirty;
static size_t g_shape_data_buf_size = 0;
static int g_instance_map_capacity = 0;
static void shape_make_view_for_buffer(sg_view *view, sg_buffer buf)
{
if (view->id) sg_destroy_view(*view);
*view = sg_make_view(&(sg_view_desc){
.storage_buffer = { .buffer = buf },
});
}
static shape_gpu_data_t *g_upload_buf = NULL;
static int g_upload_buf_cap = 0;
static void shape_upload_data(vector_t *shapes)
{
int n = shapes->count;
if (n == 0 || !g_shape_data_sbuf.id) return;
size_t need = (size_t)n * sizeof(shape_gpu_data_t);
if (need > g_shape_data_buf_size) {
panel_log(2, "[shapes] upload_data: buffer too small (%zu < %zu), forcing rebuild",
g_shape_data_buf_size, need);
g_shape_pool_dirty = true;
return;
}
if (n > g_upload_buf_cap) {
if (g_upload_buf) FREE(g_upload_buf);
g_upload_buf = (shape_gpu_data_t*) ALLOC(need);
g_upload_buf_cap = n;
}
for (int i = 0; i < n; i++) {
shape_t *s = (shape_t*) vec_get(shapes, i);
memcpy(g_upload_buf[i].transform, s->uniform.transform, sizeof(mat4));
g_upload_buf[i].state = s->uniform.state;
memset(g_upload_buf[i]._pad, 0, sizeof(g_upload_buf[i]._pad));
}
sg_update_buffer(g_shape_data_sbuf, &(sg_range){g_upload_buf, need});
}
static void shape_upload_instance_map(const uint32_t *map, int count)
{
if (count > g_instance_map_capacity) {
if (g_instance_map_sbuf.id) sg_destroy_buffer(g_instance_map_sbuf);
g_instance_map_sbuf = sg_make_buffer(&(sg_buffer_desc){
.size = (size_t)count * sizeof(uint32_t),
.usage = { .storage_buffer = true, .stream_update = true },
.label = "Instance map",
});
g_instance_map_capacity = count;
shape_make_view_for_buffer(&g_instance_map_view, g_instance_map_sbuf);
}
sg_update_buffer(g_instance_map_sbuf, &(sg_range){map, (size_t)count * sizeof(uint32_t)});
panel_log(3, "[shapes] upload_instance_map: count=%d buf=%d view=%d",
count, g_instance_map_sbuf.id, g_instance_map_view.id);
}
static void shape_pool_rebuild(vector_t *shapes)
{
// count total vertices / indices (line strips: num_elements == num_verts + 1)
uint32_t total_verts = 0, total_indices = 0;
for (int i = 0; i < shapes->count; i++) {
shape_t *s = (shape_t*) vec_get(shapes, i);
total_verts += s->num_elements;
total_indices += s->num_elements;
int n = shapes->count;
// Destroy old groups
for (int i = 0; i < g_shape_group_count; i++) {
if (g_shape_groups[i].vbuf.id) sg_destroy_buffer(g_shape_groups[i].vbuf);
}
FREE(g_shape_groups);
g_shape_groups = NULL;
g_shape_group_count = 0;
if (g_shape_vbuf.id) { sg_destroy_buffer(g_shape_vbuf); g_shape_vbuf.id = 0; }
if (g_shape_ibuf.id) { sg_destroy_buffer(g_shape_ibuf); g_shape_ibuf.id = 0; }
g_shape_vert_count = 0;
g_shape_idx_count = 0;
if (g_shape_data_sbuf.id) { sg_destroy_buffer(g_shape_data_sbuf); g_shape_data_sbuf.id = 0; }
if (g_shape_data_view.id) { sg_destroy_view(g_shape_data_view); g_shape_data_view.id = 0; }
if (total_verts == 0) {
if (n == 0) {
g_shape_pool_dirty = false;
return;
}
shape_vertex_t *all_v = (shape_vertex_t*) ALLOC((size_t)total_verts * sizeof(shape_vertex_t));
uint16_t *all_i = (uint16_t*) ALLOC((size_t)total_indices * sizeof(uint16_t));
g_shape_data_buf_size = (size_t)n * sizeof(shape_gpu_data_t);
g_shape_data_sbuf = sg_make_buffer(&(sg_buffer_desc){
.size = g_shape_data_buf_size,
.usage = { .storage_buffer = true, .stream_update = true },
.label = "Shape data",
});
shape_make_view_for_buffer(&g_shape_data_view, g_shape_data_sbuf);
// Data filled by shape_upload_data() in draw_shapes
uint32_t voff = 0, ioff = 0;
for (int i = 0; i < shapes->count; i++) {
shape_t *s = (shape_t*) vec_get(shapes, i);
uint32_t n = s->num_elements;
memcpy(&all_v[voff], s->verts, (size_t)n * sizeof(shape_vertex_t));
for (uint32_t j = 0; j < n; j++)
all_i[ioff + j] = (uint16_t)(voff + s->indices[j]);
s->vertex_base = voff;
s->index_base = ioff;
voff += n;
ioff += n;
// Count unique num_elements
uint32_t max_ne = 0;
for (int i = 0; i < n; i++) {
uint32_t ne = ((shape_t*) vec_get(shapes, i))->num_elements;
if (ne > max_ne) max_ne = ne;
}
g_shape_vbuf = sg_make_buffer(&(sg_buffer_desc){
.data = { all_v, (size_t)total_verts * sizeof(shape_vertex_t) },
.label = "Shape verts (shared)",
});
g_shape_ibuf = sg_make_buffer(&(sg_buffer_desc){
.data = { all_i, (size_t)total_indices * sizeof(uint16_t) },
.usage = { .index_buffer = true },
.label = "Shape indices (shared)",
});
int *ne_seen = (int*) ALLOC((size_t)(max_ne + 1) * sizeof(int));
memset(ne_seen, 0, (size_t)(max_ne + 1) * sizeof(int));
for (int i = 0; i < n; i++) {
uint32_t ne = ((shape_t*) vec_get(shapes, i))->num_elements;
ne_seen[ne] = 1;
}
int group_count = 0;
for (uint32_t ne = 0; ne <= max_ne; ne++)
if (ne_seen[ne]) group_count++;
FREE(all_v);
FREE(all_i);
// Create per-group vertex buffers (one copy of vertex data per unique num_elements)
g_shape_groups = (shape_group_buf_t*) ALLOC((size_t)group_count * sizeof(shape_group_buf_t));
memset(g_shape_groups, 0, (size_t)group_count * sizeof(shape_group_buf_t));
int gi = 0;
for (uint32_t ne = 0; ne <= max_ne; ne++) {
if (!ne_seen[ne]) continue;
// Find first shape with this num_elements to use as vertex template
shape_t *ref = NULL;
for (int i = 0; i < n; i++) {
if (((shape_t*) vec_get(shapes, i))->num_elements == ne) {
ref = (shape_t*) vec_get(shapes, i);
break;
}
}
g_shape_groups[gi].num_elements = ne;
g_shape_groups[gi].vbuf = sg_make_buffer(&(sg_buffer_desc){
.data = { ref->verts, (size_t)ne * sizeof(shape_vertex_t) },
.label = "Shape group verts",
});
gi++;
}
g_shape_group_count = group_count;
FREE(ne_seen);
panel_log(3, "[shapes] pool_rebuild: %d shapes, %d groups, data_buf=%d data_view=%d",
n, group_count, g_shape_data_sbuf.id, g_shape_data_view.id);
for (int gi = 0; gi < group_count; gi++) {
panel_log(3, "[shapes] group[%d]: ne=%u vbuf=%d",
gi, g_shape_groups[gi].num_elements, g_shape_groups[gi].vbuf.id);
}
g_shape_vert_count = total_verts;
g_shape_idx_count = total_indices;
g_shape_pool_dirty = false;
}
static void shape_pool_shutdown(void)
{
if (g_shape_vbuf.id) { sg_destroy_buffer(g_shape_vbuf); g_shape_vbuf.id = 0; }
if (g_shape_ibuf.id) { sg_destroy_buffer(g_shape_ibuf); g_shape_ibuf.id = 0; }
g_shape_vert_count = 0;
g_shape_idx_count = 0;
for (int i = 0; i < g_shape_group_count; i++) {
if (g_shape_groups[i].vbuf.id) sg_destroy_buffer(g_shape_groups[i].vbuf);
}
FREE(g_shape_groups);
g_shape_groups = NULL;
g_shape_group_count = 0;
if (g_shape_data_view.id) { sg_destroy_view(g_shape_data_view); g_shape_data_view.id = 0; }
if (g_instance_map_view.id) { sg_destroy_view(g_instance_map_view); g_instance_map_view.id = 0; }
if (g_shape_data_sbuf.id) { sg_destroy_buffer(g_shape_data_sbuf); g_shape_data_sbuf.id = 0; }
if (g_instance_map_sbuf.id) { sg_destroy_buffer(g_instance_map_sbuf); g_instance_map_sbuf.id = 0; }
g_instance_map_capacity = 0;
if (g_upload_buf) { FREE(g_upload_buf); g_upload_buf = NULL; }
g_upload_buf_cap = 0;
}
#define SHAPE_HOVER_PX 6.0f
@@ -172,11 +332,23 @@ static void shape_build_transform(shape_t *s)
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);
s->cos_r = R[0][0];
s->sin_r = R[0][1];
g_shape_data_dirty = true;
}
static void shape_retranslate(shape_t *s)
{
s->uniform.transform[3][0] = s->cx;
s->uniform.transform[3][1] = s->cy;
g_shape_data_dirty = true;
}
static void shape_make_buffers(shape_t *s)
{
(void)s;
for (int i = 0; i < g_shape_group_count; i++) {
if (g_shape_groups[i].num_elements == s->num_elements) return;
}
g_shape_pool_dirty = true;
}
@@ -194,9 +366,11 @@ static void shape_regenerate(shape_t *s)
static void shape_set_state(shape_t *s, bool hovered, bool selected)
{
uint32_t new_state = selected ? 2u : (hovered ? 1u : 0u);
if (s->uniform.state != new_state) g_shape_data_dirty = true;
s->hovered = hovered;
s->selected = selected;
s->uniform.state = selected ? 2u : (hovered ? 1u : 0u);
s->uniform.state = new_state;
}
static bool point_in_polygon(float px, float py, shape_vertex_t *verts, uint32_t n)
@@ -213,7 +387,7 @@ static bool point_in_polygon(float px, float py, shape_vertex_t *verts, uint32_t
static bool shape_hit_test(shape_t *s, float wx, float wy, float world_tol)
{
float sc = cosf(s->rotation), ss = sinf(s->rotation);
float sc = s->cos_r, ss = s->sin_r;
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;

View File

@@ -35,8 +35,8 @@ static int spatial_hash(int cx, int cy)
static void spatial_compute_aabb(shape_t *s, float *min_x, float *min_y,
float *max_x, float *max_y)
{
float cos_r = cosf(s->rotation);
float sin_r = sinf(s->rotation);
float cos_r = s->cos_r;
float sin_r = s->sin_r;
float hx = fabsf(cos_r) * s->sx + fabsf(sin_r) * s->sy;
float hy = fabsf(sin_r) * s->sx + fabsf(cos_r) * s->sy;
*min_x = s->cx - hx;
@@ -184,53 +184,35 @@ static int spatial_query_rect_select(spatial_grid_t *grid, vector_t *shapes,
}
int selected_count = 0;
int min_cx = (int) floorf(min_x / SPATIAL_CELL_SIZE);
int min_cy = (int) floorf(min_y / SPATIAL_CELL_SIZE);
int max_cx = (int) floorf(max_x / SPATIAL_CELL_SIZE);
int max_cy = (int) floorf(max_y / SPATIAL_CELL_SIZE);
for (int cell_x = min_cx; cell_x <= max_cx; cell_x++) {
for (int cell_y = min_cy; cell_y <= max_cy; cell_y++) {
int idx = spatial_hash(cell_x, cell_y) & (SPATIAL_HASH_SIZE - 1);
int probe_start = idx;
do {
if (!grid->slots[idx].occupied) break;
if (grid->slots[idx].cx == cell_x && grid->slots[idx].cy == cell_y) {
for (int e = 0; e < grid->slots[idx].count; e++) {
spatial_entry_t *entry = &grid->slots[idx].entries[e];
for (int s = 0; s < SPATIAL_HASH_SIZE; s++) {
if (!grid->slots[s].occupied) continue;
for (int e = 0; e < grid->slots[s].count; e++) {
spatial_entry_t *entry = &grid->slots[s].entries[e];
if (entry->max_x < min_x || entry->min_x > max_x ||
entry->max_y < min_y || entry->min_y > max_y)
continue;
shape_t *s = (shape_t*) vec_get(shapes, entry->shape_idx);
if (s->selected) continue;
shape_t *shape = (shape_t*) vec_get(shapes, entry->shape_idx);
if (shape->selected) continue;
bool hit = (s->cx >= min_x && s->cx <= max_x &&
s->cy >= min_y && s->cy <= max_y);
float sc = cosf(s->rotation), ss = sinf(s->rotation);
for (uint32_t v = 0; !hit && v < s->num_verts; v++) {
float lx = s->verts[v].x * s->sx;
float ly = s->verts[v].y * s->sy;
float wx = s->cx + lx * sc - ly * ss;
float wy = s->cy + lx * ss + ly * sc;
bool hit = (shape->cx >= min_x && shape->cx <= max_x &&
shape->cy >= min_y && shape->cy <= max_y);
float sc = shape->cos_r, ss = shape->sin_r;
for (uint32_t v = 0; !hit && v < shape->num_verts; v++) {
float lx = shape->verts[v].x * shape->sx;
float ly = shape->verts[v].y * shape->sy;
float wx = shape->cx + lx * sc - ly * ss;
float wy = shape->cy + lx * ss + ly * sc;
if (wx >= min_x && wx <= max_x &&
wy >= min_y && wy <= max_y)
hit = true;
}
if (hit) {
s->selected = true;
shape->selected = true;
selected_count++;
}
}
break;
}
idx = (idx + 1) & (SPATIAL_HASH_SIZE - 1);
} while (idx != probe_start);
}
}
return selected_count;
}

View File

@@ -89,6 +89,8 @@ typedef struct {
int focused_group_id;
double last_click_time;
int last_click_shape_idx;
vector_t drag_indices;
} interact_state_t;
typedef struct {
@@ -112,6 +114,13 @@ typedef struct {
int list_prev_count;
} ui_state_t;
typedef struct {
shape_t *shapes;
int shape_count;
group_t *groups;
int group_count;
} clipboard_t;
typedef struct userdata_t {
camera_t camera;
renderer_t renderer;
@@ -127,6 +136,8 @@ typedef struct userdata_t {
sg_buffer corner_vbuf, corner_ibuf;
int next_group_id;
vector_t groups;
clipboard_t clipboard;
float mouse_x, mouse_y;
double time;
} userdata_t;

View File

@@ -352,8 +352,8 @@ static void draw_properties_panel(userdata_t *ud)
(*m)[2][0], (*m)[2][1], (*m)[2][2], (*m)[2][3],
(*m)[3][0], (*m)[3][1], (*m)[3][2], (*m)[3][3],
s->verts[0].x, s->verts[0].y,
s->cx + s->verts[0].x * s->sx * cosf(s->rotation) - s->verts[0].y * s->sy * sinf(s->rotation),
s->cy + s->verts[0].x * s->sx * sinf(s->rotation) + s->verts[0].y * s->sy * cosf(s->rotation),
s->cx + s->verts[0].x * s->sx * s->cos_r - s->verts[0].y * s->sy * s->sin_r,
s->cy + s->verts[0].x * s->sx * s->sin_r + s->verts[0].y * s->sy * s->cos_r,
s->cx, s->cy, s->sx, s->sy, s->rotation);
if (igButton("Copy Debug", (ImVec2){0, 0}))
sapp_set_clipboard_string(dbg);
@@ -389,7 +389,9 @@ static void draw_log_panel(userdata_t *ud)
int idx = (start + i) % LOG_RING_SIZE;
off += snprintf(buf + off, (size_t)(cap - off), "%s\n", ud->ui.log_ring[idx].text);
}
igSetClipboardText(buf);
EM_ASM({
navigator.clipboard.writeText(UTF8ToString($0));
}, buf);
FREE(buf);
}
igSameLine(0.0f, 10.0f);