You've already forked flecs_tests
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:
1831
diff_output.patch
Normal file
1831
diff_output.patch
Normal file
File diff suppressed because it is too large
Load Diff
15
src/api.h
15
src/api.h
@@ -27,6 +27,21 @@
|
|||||||
|
|
||||||
#include "generated/sprite.h"
|
#include "generated/sprite.h"
|
||||||
#include "generated/shape.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 "util.h"
|
||||||
#include "shape.h"
|
#include "shape.h"
|
||||||
|
|||||||
103
src/draw.h
103
src/draw.h
@@ -9,20 +9,109 @@ static void draw_shapes(userdata_t *ud)
|
|||||||
if (g_shape_pool_dirty)
|
if (g_shape_pool_dirty)
|
||||||
shape_pool_rebuild(&ud->shapes);
|
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_pipeline(shape_pipeline);
|
||||||
sg_apply_bindings(&(sg_bindings){
|
|
||||||
.vertex_buffers[0] = g_shape_vbuf,
|
int base = 0;
|
||||||
.index_buffer = g_shape_ibuf,
|
while (base < n) {
|
||||||
});
|
shape_t *s = (shape_t*) vec_get(&ud->shapes, imap[base]);
|
||||||
for (int i = 0; i < ud->shapes.count; i++) {
|
uint32_t ne = s->num_elements;
|
||||||
shape_draw((shape_t*) vec_get(&ud->shapes, i), &ud->renderer.uniform.mvp);
|
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)
|
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) {
|
if (has_overlay) {
|
||||||
shape_uniform_t u;
|
shape_uniform_t u;
|
||||||
glm_mat4_identity(u.transform);
|
glm_mat4_identity(u.transform);
|
||||||
|
|||||||
91
src/generated/overlay.h
Normal file
91
src/generated/overlay.h
Normal 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;
|
||||||
@@ -2,90 +2,109 @@ unsigned char src_shaders_shape_wgsl[] = {
|
|||||||
0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x56, 0x73, 0x55, 0x6e, 0x69,
|
0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x56, 0x73, 0x55, 0x6e, 0x69,
|
||||||
0x66, 0x6f, 0x72, 0x6d, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d,
|
0x66, 0x6f, 0x72, 0x6d, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d,
|
||||||
0x76, 0x70, 0x3a, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x78, 0x34, 0x66, 0x2c,
|
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,
|
0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20,
|
||||||
0x53, 0x68, 0x61, 0x70, 0x65, 0x55, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d,
|
0x53, 0x68, 0x61, 0x70, 0x65, 0x44, 0x61, 0x74, 0x61, 0x20, 0x7b, 0x0a,
|
||||||
0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73,
|
0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72,
|
||||||
0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x78, 0x34,
|
0x6d, 0x3a, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x78, 0x34, 0x66, 0x2c, 0x0a,
|
||||||
0x66, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65,
|
0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x3a, 0x20, 0x75,
|
||||||
0x3a, 0x20, 0x75, 0x33, 0x32, 0x2c, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73,
|
0x33, 0x32, 0x2c, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x40, 0x62, 0x69, 0x6e,
|
||||||
0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x56, 0x73, 0x49, 0x6e, 0x20, 0x7b,
|
0x64, 0x69, 0x6e, 0x67, 0x28, 0x30, 0x29, 0x20, 0x40, 0x67, 0x72, 0x6f,
|
||||||
0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69,
|
0x75, 0x70, 0x28, 0x30, 0x29, 0x20, 0x76, 0x61, 0x72, 0x3c, 0x75, 0x6e,
|
||||||
0x6f, 0x6e, 0x28, 0x30, 0x29, 0x20, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69,
|
0x69, 0x66, 0x6f, 0x72, 0x6d, 0x3e, 0x20, 0x76, 0x73, 0x5f, 0x75, 0x6e,
|
||||||
0x6f, 0x6e, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x2c, 0x0a, 0x7d,
|
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,
|
0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x56, 0x73,
|
||||||
0x32, 0x46, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x62,
|
0x49, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x62, 0x75,
|
||||||
0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x28, 0x70, 0x6f, 0x73, 0x69, 0x74,
|
0x69, 0x6c, 0x74, 0x69, 0x6e, 0x28, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e,
|
||||||
0x69, 0x6f, 0x6e, 0x29, 0x20, 0x70, 0x6f, 0x73, 0x3a, 0x20, 0x76, 0x65,
|
0x63, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x29, 0x20, 0x69, 0x6e,
|
||||||
0x63, 0x34, 0x66, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x6c, 0x6f,
|
0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x78, 0x3a, 0x20,
|
||||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x30, 0x29, 0x20, 0x40, 0x69,
|
0x75, 0x33, 0x32, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x6c, 0x6f,
|
||||||
0x6e, 0x74, 0x65, 0x72, 0x70, 0x6f, 0x6c, 0x61, 0x74, 0x65, 0x28, 0x6c,
|
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x30, 0x29, 0x20, 0x70, 0x6f,
|
||||||
0x69, 0x6e, 0x65, 0x61, 0x72, 0x29, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72,
|
0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32,
|
||||||
0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x2c, 0x0a, 0x7d, 0x3b, 0x0a,
|
0x66, 0x2c, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63,
|
||||||
0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x46, 0x73, 0x4f, 0x75,
|
0x74, 0x20, 0x56, 0x73, 0x32, 0x46, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20,
|
||||||
0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x6c, 0x6f, 0x63,
|
0x20, 0x20, 0x40, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x28, 0x70,
|
||||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x30, 0x29, 0x20, 0x63, 0x6f, 0x6c,
|
0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x29, 0x20, 0x70, 0x6f, 0x73,
|
||||||
0x6f, 0x72, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x2c, 0x0a, 0x7d,
|
0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x2c, 0x0a, 0x20, 0x20, 0x20,
|
||||||
0x3b, 0x0a, 0x0a, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28,
|
0x20, 0x40, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x30,
|
||||||
0x30, 0x29, 0x20, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x30, 0x29,
|
0x29, 0x20, 0x40, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x6f, 0x6c, 0x61,
|
||||||
0x20, 0x76, 0x61, 0x72, 0x3c, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d,
|
0x74, 0x65, 0x28, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x29, 0x20, 0x63,
|
||||||
0x3e, 0x20, 0x76, 0x73, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d,
|
0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x2c,
|
||||||
0x73, 0x3a, 0x20, 0x56, 0x73, 0x55, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d,
|
0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20,
|
||||||
0x3b, 0x0a, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, 0x31,
|
0x46, 0x73, 0x4f, 0x75, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
|
||||||
0x29, 0x20, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x30, 0x29, 0x20,
|
0x40, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x30, 0x29,
|
||||||
0x76, 0x61, 0x72, 0x3c, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x3e,
|
0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x34,
|
||||||
0x20, 0x73, 0x68, 0x61, 0x70, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f,
|
0x66, 0x2c, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x40, 0x76, 0x65, 0x72, 0x74,
|
||||||
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,
|
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, 0x28, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x3a, 0x20, 0x56, 0x73, 0x49,
|
||||||
0x6e, 0x29, 0x20, 0x2d, 0x3e, 0x20, 0x56, 0x73, 0x32, 0x46, 0x73, 0x20,
|
0x6e, 0x29, 0x20, 0x2d, 0x3e, 0x20, 0x56, 0x73, 0x32, 0x46, 0x73, 0x20,
|
||||||
0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x6f, 0x75,
|
0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x6f, 0x75,
|
||||||
0x74, 0x70, 0x75, 0x74, 0x3a, 0x20, 0x56, 0x73, 0x32, 0x46, 0x73, 0x3b,
|
0x74, 0x70, 0x75, 0x74, 0x3a, 0x20, 0x56, 0x73, 0x32, 0x46, 0x73, 0x3b,
|
||||||
0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x77, 0x6f, 0x72,
|
0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x73, 0x68, 0x61,
|
||||||
0x6c, 0x64, 0x5f, 0x70, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x73, 0x68, 0x61,
|
0x70, 0x65, 0x5f, 0x69, 0x64, 0x78, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x73,
|
||||||
0x70, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x2e, 0x74,
|
0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6d, 0x61, 0x70, 0x5b, 0x76, 0x73,
|
||||||
0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x2a, 0x20, 0x76,
|
0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x2e, 0x69, 0x6e,
|
||||||
0x65, 0x63, 0x34, 0x66, 0x28, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x70,
|
0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x20,
|
||||||
0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x78, 0x2c, 0x20, 0x69,
|
0x2b, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x69, 0x6e, 0x73, 0x74,
|
||||||
0x6e, 0x70, 0x75, 0x74, 0x2e, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f,
|
0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x78, 0x5d, 0x3b, 0x0a, 0x20,
|
||||||
0x6e, 0x2e, 0x79, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e,
|
0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x73, 0x68, 0x61, 0x70, 0x65,
|
||||||
0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x75, 0x74, 0x70,
|
0x20, 0x3d, 0x20, 0x73, 0x68, 0x61, 0x70, 0x65, 0x5f, 0x64, 0x61, 0x74,
|
||||||
0x75, 0x74, 0x2e, 0x70, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x76, 0x73, 0x5f,
|
0x61, 0x5b, 0x73, 0x68, 0x61, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x78, 0x5d,
|
||||||
0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x2e, 0x6d, 0x76, 0x70,
|
0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x77, 0x6f,
|
||||||
0x20, 0x2a, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x5f, 0x70, 0x6f, 0x73,
|
0x72, 0x6c, 0x64, 0x5f, 0x70, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x73, 0x68,
|
||||||
0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x73, 0x68,
|
0x61, 0x70, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72,
|
||||||
0x61, 0x70, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x2e,
|
0x6d, 0x20, 0x2a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 0x69, 0x6e,
|
||||||
0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x75, 0x29,
|
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,
|
0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f,
|
||||||
0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20,
|
0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20,
|
||||||
0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 0x31, 0x2e, 0x30, 0x2c,
|
0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 0x30, 0x2e, 0x35, 0x2c,
|
||||||
0x20, 0x30, 0x2e, 0x38, 0x34, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20,
|
0x20, 0x30, 0x2e, 0x36, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x31,
|
||||||
0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x20,
|
0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x20, 0x65,
|
||||||
0x65, 0x6c, 0x73, 0x65, 0x20, 0x69, 0x66, 0x20, 0x28, 0x73, 0x68, 0x61,
|
0x6c, 0x73, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||||
0x70, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x2e, 0x73,
|
0x20, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c,
|
||||||
0x74, 0x61, 0x74, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x75, 0x29, 0x20,
|
0x6f, 0x72, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 0x30,
|
||||||
0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x75,
|
0x2e, 0x38, 0x2c, 0x20, 0x30, 0x2e, 0x38, 0x2c, 0x20, 0x30, 0x2e, 0x38,
|
||||||
0x74, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d,
|
0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
|
||||||
0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 0x30, 0x2e, 0x35, 0x2c, 0x20,
|
0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e,
|
||||||
0x30, 0x2e, 0x36, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e,
|
0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a,
|
||||||
0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x20, 0x65, 0x6c,
|
0x40, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x66, 0x6e,
|
||||||
0x73, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
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,
|
0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c, 0x6f,
|
||||||
0x72, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 0x30, 0x2e,
|
0x72, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f,
|
||||||
0x38, 0x2c, 0x20, 0x30, 0x2e, 0x38, 0x2c, 0x20, 0x30, 0x2e, 0x38, 0x2c,
|
0x6c, 0x6f, 0x72, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74,
|
||||||
0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d,
|
0x75, 0x72, 0x6e, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3b, 0x0a,
|
||||||
0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20,
|
0x7d, 0x0a
|
||||||
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_shape_wgsl_len = 1045;
|
unsigned int src_shaders_shape_wgsl_len = 1274;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
#include "api.h"
|
#include "api.h"
|
||||||
|
|
||||||
|
#define HISTORY_MAX_DEPTH 256
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
HIST_POSITION,
|
HIST_POSITION,
|
||||||
HIST_SCALE,
|
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;
|
*((hist_entry_t*) vec_push(&h->entries)) = entry;
|
||||||
h->current = h->entries.count - 1;
|
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,
|
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.
|
// Used for HIST_CREATE and HIST_DELETE entries.
|
||||||
// old_val = { kind, cx, cy, num_verts }
|
// old_val = { kind, cx, cy, num_verts }
|
||||||
// new_val = { sx, sy, rotation, group_id }
|
// 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) {
|
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[0] = (float)s->kind;
|
||||||
c->old_val[1] = s->cx;
|
c->old_val[1] = s->cx;
|
||||||
c->old_val[2] = s->cy;
|
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[1] = s->sy;
|
||||||
c->new_val[2] = s->rotation;
|
c->new_val[2] = s->rotation;
|
||||||
c->new_val[3] = (float)s->group_id;
|
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.
|
// 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.
|
// 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) {
|
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;
|
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));
|
memset(&s, 0, sizeof(s));
|
||||||
s.kind = (int)c->old_val[0];
|
s.kind = (int)c->old_val[0];
|
||||||
s.cx = c->old_val[1];
|
s.cx = cx;
|
||||||
s.cy = c->old_val[2];
|
s.cy = cy;
|
||||||
s.num_verts = (uint32_t)c->old_val[3];
|
s.num_verts = (uint32_t)c->old_val[3];
|
||||||
s.num_elements = (uint32_t)c->vertex_count;
|
s.num_elements = (uint32_t)c->vertex_count;
|
||||||
s.sx = c->new_val[0];
|
s.sx = sx;
|
||||||
s.sy = c->new_val[1];
|
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;
|
int n = c->vertex_count;
|
||||||
s.verts = (shape_vertex_t*) ALLOC((size_t)n * sizeof(shape_vertex_t));
|
s.verts = (shape_vertex_t*) ALLOC((size_t)n * sizeof(shape_vertex_t));
|
||||||
s.indices = (uint16_t*) ALLOC((size_t)n * sizeof(uint16_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.verts, c->vertex_data, (size_t)n * sizeof(shape_vertex_t));
|
||||||
memcpy(s.indices, c->index_data, (size_t)n * sizeof(uint16_t));
|
memcpy(s.indices, c->index_data, (size_t)n * sizeof(uint16_t));
|
||||||
|
|
||||||
shape_init_common(&s);
|
shape_init_common(&s);
|
||||||
shape_build_transform(&s);
|
shape_build_transform(&s);
|
||||||
shape_make_buffers(&s);
|
shape_make_buffers(&s);
|
||||||
return 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) {
|
static void history_apply_entry(hist_entry_t *entry, vector_t *shapes, bool forward) {
|
||||||
bool has_shape_ops = false;
|
bool has_shape_ops = false;
|
||||||
|
|||||||
273
src/input.h
273
src/input.h
@@ -55,7 +55,7 @@ static void handle_left_down_resize_begin(userdata_t *ud, float wx, float wy, in
|
|||||||
int sel_n = 0;
|
int sel_n = 0;
|
||||||
for (int i = 0; i < ud->shapes.count; i++) {
|
for (int i = 0; i < ud->shapes.count; i++) {
|
||||||
shape_t *s = (shape_t*) vec_get(&ud->shapes, 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);
|
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++) {
|
for (int i = 0; i < ud->shapes.count; i++) {
|
||||||
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
|
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
|
||||||
if (s->selected) {
|
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 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 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;
|
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,
|
wy - ud->interact.rotate.center_y,
|
||||||
wx - ud->interact.rotate.center_x);
|
wx - ud->interact.rotate.center_x);
|
||||||
ud->interact.rotate.total_delta = 0.0f;
|
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)
|
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.start_wy = wy;
|
||||||
ud->interact.move.total_dx = 0;
|
ud->interact.move.total_dx = 0;
|
||||||
ud->interact.move.total_dy = 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)
|
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)
|
static void handle_rotate_end(userdata_t *ud)
|
||||||
{
|
{
|
||||||
if (ud->interact.rotate.total_delta != 0.0f) {
|
int n = ud->interact.drag_indices.count;
|
||||||
int sel_count = 0;
|
if (n > 0 && ud->interact.rotate.total_delta != 0.0f) {
|
||||||
for (int i = 0; i < ud->shapes.count; i++) {
|
|
||||||
if (((shape_t*) vec_get(&ud->shapes, i))->selected) sel_count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
float cos_b = cosf(-ud->interact.rotate.total_delta);
|
float cos_b = cosf(-ud->interact.rotate.total_delta);
|
||||||
float sin_b = sinf(-ud->interact.rotate.total_delta);
|
float sin_b = sinf(-ud->interact.rotate.total_delta);
|
||||||
float cx = ud->interact.rotate.center_x;
|
float cx = ud->interact.rotate.center_x;
|
||||||
float cy = ud->interact.rotate.center_y;
|
float cy = ud->interact.rotate.center_y;
|
||||||
|
|
||||||
hist_batch_t batch;
|
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);
|
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
|
||||||
if (s->selected) {
|
|
||||||
float dx = s->cx - cx;
|
float dx = s->cx - cx;
|
||||||
float dy = s->cy - cy;
|
float dy = s->cy - cy;
|
||||||
float old_cx = cx + dx * cos_b - dy * sin_b;
|
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 - ud->interact.rotate.total_delta },
|
||||||
(float[4]){ s->rotation });
|
(float[4]){ s->rotation });
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
history_batch_commit(&batch, &ud->history);
|
history_batch_commit(&batch, &ud->history);
|
||||||
}
|
}
|
||||||
|
|
||||||
ud->interact.rotate.dragging = false;
|
ud->interact.rotate.dragging = false;
|
||||||
|
ud->interact.drag_indices.count = 0;
|
||||||
update_shape_states(ud);
|
update_shape_states(ud);
|
||||||
spatial_mark_dirty(&ud->spatial_grid);
|
spatial_mark_dirty(&ud->spatial_grid);
|
||||||
ud->interact.aabb_cached = false;
|
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)
|
static void handle_move_end(userdata_t *ud)
|
||||||
{
|
{
|
||||||
if (ud->interact.move.total_dx != 0.0f || ud->interact.move.total_dy != 0.0f) {
|
int n = ud->interact.drag_indices.count;
|
||||||
int sel_count = 0;
|
if (n > 0 && (ud->interact.move.total_dx != 0.0f || ud->interact.move.total_dy != 0.0f)) {
|
||||||
for (int i = 0; i < ud->shapes.count; i++) {
|
|
||||||
if (((shape_t*) vec_get(&ud->shapes, i))->selected) sel_count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
hist_batch_t batch;
|
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);
|
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
|
||||||
if (s->selected) {
|
|
||||||
history_batch_add(&batch, i, HIST_POSITION,
|
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 - ud->interact.move.total_dx, s->cy - ud->interact.move.total_dy },
|
||||||
(float[4]){ s->cx, s->cy });
|
(float[4]){ s->cx, s->cy });
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
history_batch_commit(&batch, &ud->history);
|
history_batch_commit(&batch, &ud->history);
|
||||||
}
|
}
|
||||||
|
|
||||||
ud->interact.move.dragging = false;
|
ud->interact.move.dragging = false;
|
||||||
|
ud->interact.drag_indices.count = 0;
|
||||||
update_shape_states(ud);
|
update_shape_states(ud);
|
||||||
spatial_mark_dirty(&ud->spatial_grid);
|
spatial_mark_dirty(&ud->spatial_grid);
|
||||||
ud->interact.aabb_cached = false;
|
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];
|
resize_init_t *ini = &ud->interact.resize.init[j];
|
||||||
shape_t *s = (shape_t*) vec_get(&ud->shapes, ini->idx);
|
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 mlx = (wx - ini->init_cx) * sc + (wy - ini->init_cy) * ss;
|
||||||
float mly = -(wx - ini->init_cx) * ss + (wy - ini->init_cy) * sc;
|
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 cx = ud->interact.rotate.center_x;
|
||||||
float cy = ud->interact.rotate.center_y;
|
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);
|
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
|
||||||
if (s->selected) {
|
|
||||||
float dx = s->cx - cx;
|
float dx = s->cx - cx;
|
||||||
float dy = s->cy - cy;
|
float dy = s->cy - cy;
|
||||||
s->cx = cx + dx * cos_a - dy * sin_a;
|
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_build_transform(s);
|
||||||
shape_set_state(s, false, true);
|
shape_set_state(s, false, true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ud->interact.rotate.total_delta = delta;
|
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_x = dx - ud->interact.move.total_dx;
|
||||||
float delta_y = dy - ud->interact.move.total_dy;
|
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);
|
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
|
||||||
if (s->selected) {
|
|
||||||
s->cx += delta_x;
|
s->cx += delta_x;
|
||||||
s->cy += delta_y;
|
s->cy += delta_y;
|
||||||
shape_build_transform(s);
|
shape_retranslate(s);
|
||||||
shape_set_state(s, false, true);
|
shape_set_state(s, false, true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ud->interact.move.total_dx = dx;
|
ud->interact.move.total_dx = dx;
|
||||||
ud->interact.move.total_dy = dy;
|
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,
|
&ud->spatial_grid, &ud->shapes,
|
||||||
min_x, min_y, max_x, max_y);
|
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++) {
|
for (int i = 0; i < ud->shapes.count; i++) {
|
||||||
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
|
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
|
||||||
shape_set_state(s, false, s->selected);
|
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) {
|
if (hovered >= 0) {
|
||||||
shape_t *hs = (shape_t*) vec_get(&ud->shapes, hovered);
|
shape_t *hs = (shape_t*) vec_get(&ud->shapes, hovered);
|
||||||
hovered_gid = hs->group_id;
|
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++) {
|
for (int i = 0; i < ud->shapes.count; i++) {
|
||||||
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
|
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
|
||||||
bool in_group = (ud->interact.focused_group_id == 0 &&
|
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);
|
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 --
|
// -- public event handlers --
|
||||||
|
|
||||||
static bool handle_key_down(userdata_t *ud, const sapp_event *event)
|
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)) {
|
if (history_undo(&ud->history, &ud->shapes)) {
|
||||||
rebuild_groups_from_shapes(&ud->groups, &ud->shapes);
|
rebuild_groups_from_shapes(&ud->groups, &ud->shapes);
|
||||||
ud->interact.hovered_shape = -1;
|
ud->interact.hovered_shape = -1;
|
||||||
|
g_shape_pool_dirty = true;
|
||||||
spatial_mark_dirty(&ud->spatial_grid);
|
spatial_mark_dirty(&ud->spatial_grid);
|
||||||
ud->interact.aabb_cached = false;
|
ud->interact.aabb_cached = false;
|
||||||
ud->overlay_upload_needed = true;
|
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)) {
|
if (history_redo(&ud->history, &ud->shapes)) {
|
||||||
rebuild_groups_from_shapes(&ud->groups, &ud->shapes);
|
rebuild_groups_from_shapes(&ud->groups, &ud->shapes);
|
||||||
ud->interact.hovered_shape = -1;
|
ud->interact.hovered_shape = -1;
|
||||||
|
g_shape_pool_dirty = true;
|
||||||
spatial_mark_dirty(&ud->spatial_grid);
|
spatial_mark_dirty(&ud->spatial_grid);
|
||||||
ud->interact.aabb_cached = false;
|
ud->interact.aabb_cached = false;
|
||||||
ud->overlay_upload_needed = true;
|
ud->overlay_upload_needed = true;
|
||||||
}
|
}
|
||||||
return 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->key_code == SAPP_KEYCODE_G) {
|
||||||
if (event->modifiers & SAPP_MODIFIER_SHIFT) {
|
if (event->modifiers & SAPP_MODIFIER_SHIFT) {
|
||||||
// Ungroup: collect unique group IDs of selected shapes
|
// 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(parents);
|
||||||
FREE(gids);
|
FREE(gids);
|
||||||
ud->ui.list_last_shape = -1;
|
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 new_grp = { .id = gid, .parent_id = 0 };
|
||||||
*((group_t*) vec_push(&ud->groups)) = new_grp;
|
*((group_t*) vec_push(&ud->groups)) = new_grp;
|
||||||
|
group_index_rebuild(&ud->groups);
|
||||||
|
|
||||||
for (int j = 0; j < n_full; j++) {
|
for (int j = 0; j < n_full; j++) {
|
||||||
for (int g = 0; g < ud->groups.count; g++) {
|
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);
|
shape_shutdown(s);
|
||||||
vec_remove_ordered(&ud->shapes, indices[j]);
|
vec_remove_ordered(&ud->shapes, indices[j]);
|
||||||
}
|
}
|
||||||
|
g_shape_pool_dirty = true;
|
||||||
|
|
||||||
FREE(indices);
|
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)
|
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) {
|
if (ud->camera.pan_state.dragging) {
|
||||||
handle_pan_drag(ud, event);
|
handle_pan_drag(ud, event);
|
||||||
} else if (ud->interact.resize.dragging) {
|
} else if (ud->interact.resize.dragging) {
|
||||||
|
|||||||
@@ -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++) {
|
for (int i = 0; i < ud->shapes.count; i++) {
|
||||||
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
|
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
|
||||||
if (!s->selected) continue;
|
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++) {
|
for (uint32_t v = 0; v < s->num_verts; v++) {
|
||||||
float lx = s->verts[v].x * s->sx;
|
float lx = s->verts[v].x * s->sx;
|
||||||
float ly = s->verts[v].y * s->sy;
|
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);
|
if (saved) FREE(saved);
|
||||||
|
group_index_rebuild(groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int hit_test_resize_handles(userdata_t *ud, float wx, float wy, float tol)
|
static int hit_test_resize_handles(userdata_t *ud, float wx, float wy, float tol)
|
||||||
|
|||||||
26
src/main.c
26
src/main.c
@@ -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;
|
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)
|
static void meter_fps(userdata_t *ud)
|
||||||
{
|
{
|
||||||
float dt = (float)sapp_frame_duration();
|
float dt = (float)sapp_frame_duration();
|
||||||
@@ -102,10 +112,14 @@ static void init(void* _userdata)
|
|||||||
|
|
||||||
userdata_t* ud = (userdata_t*) _userdata;
|
userdata_t* ud = (userdata_t*) _userdata;
|
||||||
|
|
||||||
|
g_panel_log_fn = panel_log_impl;
|
||||||
|
g_panel_log_ud = ud;
|
||||||
|
|
||||||
sg_desc sgdesc = {
|
sg_desc sgdesc = {
|
||||||
.environment = sglue_environment(),
|
.environment = sglue_environment(),
|
||||||
.logger.func = log_capture,
|
.logger.func = log_capture,
|
||||||
.logger.user_data = ud,
|
.logger.user_data = ud,
|
||||||
|
.uniform_buffer_size = 16 * 1024 * 1024,
|
||||||
};
|
};
|
||||||
sg_setup(&sgdesc);
|
sg_setup(&sgdesc);
|
||||||
if (!sg_isvalid()) {
|
if (!sg_isvalid()) {
|
||||||
@@ -216,6 +230,7 @@ static void init(void* _userdata)
|
|||||||
|
|
||||||
vec_init(&ud->shapes, sizeof(shape_t));
|
vec_init(&ud->shapes, sizeof(shape_t));
|
||||||
vec_init(&ud->groups, sizeof(group_t));
|
vec_init(&ud->groups, sizeof(group_t));
|
||||||
|
vec_init(&ud->interact.drag_indices, sizeof(int));
|
||||||
spatial_init(&ud->spatial_grid);
|
spatial_init(&ud->spatial_grid);
|
||||||
ud->interact.selected_count = 0;
|
ud->interact.selected_count = 0;
|
||||||
ud->interact.hovered_shape = -1;
|
ud->interact.hovered_shape = -1;
|
||||||
@@ -296,7 +311,7 @@ static void init(void* _userdata)
|
|||||||
EM_ASM({
|
EM_ASM({
|
||||||
window.addEventListener('keydown', function(e) {
|
window.addEventListener('keydown', function(e) {
|
||||||
if (e.ctrlKey && !e.altKey && !e.metaKey) {
|
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();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,6 +331,8 @@ static void cleanup(void* _userdata)
|
|||||||
spatial_destroy(&ud->spatial_grid);
|
spatial_destroy(&ud->spatial_grid);
|
||||||
vec_free(&ud->shapes);
|
vec_free(&ud->shapes);
|
||||||
vec_free(&ud->groups);
|
vec_free(&ud->groups);
|
||||||
|
vec_free(&ud->interact.drag_indices);
|
||||||
|
group_index_shutdown();
|
||||||
history_destroy(&ud->history);
|
history_destroy(&ud->history);
|
||||||
if (ud->interact.resize.init) FREE(ud->interact.resize.init);
|
if (ud->interact.resize.init) FREE(ud->interact.resize.init);
|
||||||
sg_destroy_buffer(ud->rect_vbuf);
|
sg_destroy_buffer(ud->rect_vbuf);
|
||||||
@@ -329,6 +346,13 @@ static void cleanup(void* _userdata)
|
|||||||
shape_pool_shutdown();
|
shape_pool_shutdown();
|
||||||
shape_shutdown_pipeline();
|
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);
|
FREE(ud);
|
||||||
|
|
||||||
simgui_shutdown();
|
simgui_shutdown();
|
||||||
|
|||||||
@@ -27,7 +27,26 @@ static void compute_overlay_geometry(userdata_t *ud,
|
|||||||
overlay_verts[4] = (shape_vertex_t){x1, y1};
|
overlay_verts[4] = (shape_vertex_t){x1, y1};
|
||||||
*has_overlay = true;
|
*has_overlay = true;
|
||||||
} else if (ud->interact.selected_count >= 1) {
|
} 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++) {
|
for (int i = 0; i < ud->shapes.count; i++) {
|
||||||
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
|
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
|
||||||
if (!s->selected) continue;
|
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++) {
|
for (int i = 0; i < ud->shapes.count; i++) {
|
||||||
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
|
shape_t *s = (shape_t*) vec_get(&ud->shapes, i);
|
||||||
if (!s->selected) continue;
|
if (!s->selected) continue;
|
||||||
sum_sin += sinf(s->rotation);
|
sum_sin += s->sin_r;
|
||||||
sum_cos += cosf(s->rotation);
|
sum_cos += s->cos_r;
|
||||||
}
|
}
|
||||||
selected_aabb(ud, &omin[0], &omin[1], &omax[0], &omax[1]);
|
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];
|
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;
|
ud->interact.rotate.handle_radius = radius;
|
||||||
|
|
||||||
const int n = HANDLE_CIRCLE_SEGMENTS + 1;
|
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++) {
|
for (int i = 0; i < n; i++) {
|
||||||
float a = (float)i / (float)HANDLE_CIRCLE_SEGMENTS * 2.0f * GLM_PIf;
|
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)
|
if (need_upload)
|
||||||
sg_update_buffer(ud->handle_vbuf, &(sg_range){hv, sizeof(hv)});
|
sg_update_buffer(ud->handle_vbuf, &(sg_range){hv, sizeof(hv)});
|
||||||
|
|||||||
73
src/render.h
73
src/render.h
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
static sg_pipeline shape_pipeline;
|
static sg_pipeline shape_pipeline;
|
||||||
static sg_shader shape_shader;
|
static sg_shader shape_shader;
|
||||||
|
static sg_pipeline overlay_pipeline;
|
||||||
|
static sg_shader overlay_shader;
|
||||||
static int g_shape_frame_id;
|
static int g_shape_frame_id;
|
||||||
|
|
||||||
static void shape_begin_frame(void)
|
static void shape_begin_frame(void)
|
||||||
@@ -14,13 +16,14 @@ static void shape_begin_frame(void)
|
|||||||
|
|
||||||
static void shape_init_pipeline(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 = {
|
.vertex_func = {
|
||||||
.source = (const char*) src_shaders_shape_wgsl,
|
.source = (const char*) src_shaders_overlay_wgsl,
|
||||||
.entry = "vs_main",
|
.entry = "vs_main",
|
||||||
},
|
},
|
||||||
.fragment_func = {
|
.fragment_func = {
|
||||||
.source = (const char*) src_shaders_shape_wgsl,
|
.source = (const char*) src_shaders_overlay_wgsl,
|
||||||
.entry = "fs_main",
|
.entry = "fs_main",
|
||||||
},
|
},
|
||||||
.uniform_blocks = {
|
.uniform_blocks = {
|
||||||
@@ -38,31 +41,79 @@ static void shape_init_pipeline(void)
|
|||||||
.attrs = {
|
.attrs = {
|
||||||
[0] = { .base_type = SG_SHADERATTRBASETYPE_FLOAT },
|
[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",
|
.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) {
|
shape_pipeline = sg_make_pipeline(&(sg_pipeline_desc) {
|
||||||
.shader = shape_shader,
|
.shader = shape_shader,
|
||||||
.index_type = SG_INDEXTYPE_UINT16,
|
.index_type = SG_INDEXTYPE_NONE,
|
||||||
.primitive_type = SG_PRIMITIVETYPE_LINE_STRIP,
|
.primitive_type = SG_PRIMITIVETYPE_LINE_STRIP,
|
||||||
.layout.attrs = {
|
.layout.attrs = {
|
||||||
[0].format = SG_VERTEXFORMAT_FLOAT2,
|
[0].format = SG_VERTEXFORMAT_FLOAT2,
|
||||||
},
|
},
|
||||||
.label = "Shape pipeline",
|
.label = "Shape pipeline",
|
||||||
});
|
});
|
||||||
|
panel_log(3, "[shapes] pipeline id=%d valid=%d", shape_pipeline.id, sg_isvalid());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void shape_shutdown_pipeline(void)
|
static void shape_shutdown_pipeline(void)
|
||||||
{
|
{
|
||||||
sg_destroy_pipeline(shape_pipeline);
|
sg_destroy_pipeline(shape_pipeline);
|
||||||
sg_destroy_shader(shape_shader);
|
sg_destroy_shader(shape_shader);
|
||||||
}
|
sg_destroy_pipeline(overlay_pipeline);
|
||||||
|
sg_destroy_shader(overlay_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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
44
src/shaders/overlay.wgsl
Normal file
44
src/shaders/overlay.wgsl
Normal 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;
|
||||||
|
}
|
||||||
@@ -1,13 +1,20 @@
|
|||||||
struct VsUniform {
|
struct VsUniform {
|
||||||
mvp: mat4x4f,
|
mvp: mat4x4f,
|
||||||
|
instance_base: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ShapeUniform {
|
struct ShapeData {
|
||||||
transform: mat4x4f,
|
transform: mat4x4f,
|
||||||
state: u32,
|
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 {
|
struct VsIn {
|
||||||
|
@builtin(instance_index) instance_idx: u32,
|
||||||
@location(0) position: vec2f,
|
@location(0) position: vec2f,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,16 +27,15 @@ struct FsOut {
|
|||||||
@location(0) color: vec4f,
|
@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 {
|
@vertex fn vs_main(input: VsIn) -> Vs2Fs {
|
||||||
var output: 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;
|
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);
|
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);
|
output.color = vec4f(0.5, 0.6, 1.0, 1.0);
|
||||||
} else {
|
} else {
|
||||||
output.color = vec4f(0.8, 0.8, 0.8, 1.0);
|
output.color = vec4f(0.8, 0.8, 0.8, 1.0);
|
||||||
|
|||||||
282
src/shape.h
282
src/shape.h
@@ -20,6 +20,12 @@ typedef struct shape_uniform_t {
|
|||||||
uint8_t _pad[12];
|
uint8_t _pad[12];
|
||||||
} shape_uniform_t;
|
} shape_uniform_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
mat4 transform;
|
||||||
|
uint32_t state;
|
||||||
|
uint8_t _pad[12];
|
||||||
|
} shape_gpu_data_t;
|
||||||
|
|
||||||
typedef struct shape_t {
|
typedef struct shape_t {
|
||||||
shape_vertex_t *verts;
|
shape_vertex_t *verts;
|
||||||
uint16_t *indices;
|
uint16_t *indices;
|
||||||
@@ -32,11 +38,9 @@ typedef struct shape_t {
|
|||||||
float cx, cy;
|
float cx, cy;
|
||||||
float sx, sy;
|
float sx, sy;
|
||||||
float rotation;
|
float rotation;
|
||||||
|
float cos_r, sin_r;
|
||||||
int kind;
|
int kind;
|
||||||
|
|
||||||
uint32_t vertex_base;
|
|
||||||
uint32_t index_base;
|
|
||||||
|
|
||||||
int group_id;
|
int group_id;
|
||||||
} shape_t;
|
} shape_t;
|
||||||
|
|
||||||
@@ -47,15 +51,55 @@ typedef struct {
|
|||||||
int parent_id; // 0 = top-level group
|
int parent_id; // 0 = top-level group
|
||||||
} group_t;
|
} 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++) {
|
for (int i = 0; i < groups->count; i++) {
|
||||||
group_t *g = (group_t*) vec_get(groups, 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) {
|
static int get_topmost_group(vector_t *groups, int gid) {
|
||||||
|
(void)groups;
|
||||||
while (gid != 0) {
|
while (gid != 0) {
|
||||||
group_t *g = find_group(groups, gid);
|
group_t *g = find_group(groups, gid);
|
||||||
if (!g || g->parent_id == 0) return 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) {
|
static bool is_shape_in_group_hierarchy(int shape_gid, int target_gid, vector_t *groups) {
|
||||||
|
(void)groups;
|
||||||
int cur = shape_gid;
|
int cur = shape_gid;
|
||||||
while (cur != 0) {
|
while (cur != 0) {
|
||||||
if (cur == target_gid) return true;
|
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;
|
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) --
|
// -- shared geometry buffers (one vbuf + one ibuf for all shapes) --
|
||||||
|
|
||||||
static sg_buffer g_shape_vbuf = {0};
|
static sg_buffer g_shape_data_sbuf = {0};
|
||||||
static sg_buffer g_shape_ibuf = {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 bool g_shape_pool_dirty;
|
||||||
static uint32_t g_shape_vert_count;
|
static bool g_shape_data_dirty;
|
||||||
static uint32_t g_shape_idx_count;
|
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)
|
static void shape_pool_rebuild(vector_t *shapes)
|
||||||
{
|
{
|
||||||
// count total vertices / indices (line strips: num_elements == num_verts + 1)
|
int n = shapes->count;
|
||||||
uint32_t total_verts = 0, total_indices = 0;
|
|
||||||
for (int i = 0; i < shapes->count; i++) {
|
// Destroy old groups
|
||||||
shape_t *s = (shape_t*) vec_get(shapes, i);
|
for (int i = 0; i < g_shape_group_count; i++) {
|
||||||
total_verts += s->num_elements;
|
if (g_shape_groups[i].vbuf.id) sg_destroy_buffer(g_shape_groups[i].vbuf);
|
||||||
total_indices += s->num_elements;
|
|
||||||
}
|
}
|
||||||
|
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_data_sbuf.id) { sg_destroy_buffer(g_shape_data_sbuf); g_shape_data_sbuf.id = 0; }
|
||||||
if (g_shape_ibuf.id) { sg_destroy_buffer(g_shape_ibuf); g_shape_ibuf.id = 0; }
|
if (g_shape_data_view.id) { sg_destroy_view(g_shape_data_view); g_shape_data_view.id = 0; }
|
||||||
g_shape_vert_count = 0;
|
|
||||||
g_shape_idx_count = 0;
|
|
||||||
|
|
||||||
if (total_verts == 0) {
|
if (n == 0) {
|
||||||
g_shape_pool_dirty = false;
|
g_shape_pool_dirty = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
shape_vertex_t *all_v = (shape_vertex_t*) ALLOC((size_t)total_verts * sizeof(shape_vertex_t));
|
g_shape_data_buf_size = (size_t)n * sizeof(shape_gpu_data_t);
|
||||||
uint16_t *all_i = (uint16_t*) ALLOC((size_t)total_indices * sizeof(uint16_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;
|
// Count unique num_elements
|
||||||
for (int i = 0; i < shapes->count; i++) {
|
uint32_t max_ne = 0;
|
||||||
shape_t *s = (shape_t*) vec_get(shapes, i);
|
for (int i = 0; i < n; i++) {
|
||||||
uint32_t n = s->num_elements;
|
uint32_t ne = ((shape_t*) vec_get(shapes, i))->num_elements;
|
||||||
memcpy(&all_v[voff], s->verts, (size_t)n * sizeof(shape_vertex_t));
|
if (ne > max_ne) max_ne = ne;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g_shape_vbuf = sg_make_buffer(&(sg_buffer_desc){
|
int *ne_seen = (int*) ALLOC((size_t)(max_ne + 1) * sizeof(int));
|
||||||
.data = { all_v, (size_t)total_verts * sizeof(shape_vertex_t) },
|
memset(ne_seen, 0, (size_t)(max_ne + 1) * sizeof(int));
|
||||||
.label = "Shape verts (shared)",
|
for (int i = 0; i < n; i++) {
|
||||||
});
|
uint32_t ne = ((shape_t*) vec_get(shapes, i))->num_elements;
|
||||||
g_shape_ibuf = sg_make_buffer(&(sg_buffer_desc){
|
ne_seen[ne] = 1;
|
||||||
.data = { all_i, (size_t)total_indices * sizeof(uint16_t) },
|
}
|
||||||
.usage = { .index_buffer = true },
|
int group_count = 0;
|
||||||
.label = "Shape indices (shared)",
|
for (uint32_t ne = 0; ne <= max_ne; ne++)
|
||||||
});
|
if (ne_seen[ne]) group_count++;
|
||||||
|
|
||||||
FREE(all_v);
|
// Create per-group vertex buffers (one copy of vertex data per unique num_elements)
|
||||||
FREE(all_i);
|
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;
|
g_shape_pool_dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void shape_pool_shutdown(void)
|
static void shape_pool_shutdown(void)
|
||||||
{
|
{
|
||||||
if (g_shape_vbuf.id) { sg_destroy_buffer(g_shape_vbuf); g_shape_vbuf.id = 0; }
|
for (int i = 0; i < g_shape_group_count; i++) {
|
||||||
if (g_shape_ibuf.id) { sg_destroy_buffer(g_shape_ibuf); g_shape_ibuf.id = 0; }
|
if (g_shape_groups[i].vbuf.id) sg_destroy_buffer(g_shape_groups[i].vbuf);
|
||||||
g_shape_vert_count = 0;
|
}
|
||||||
g_shape_idx_count = 0;
|
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
|
#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_scale_make(S, (vec3){s->sx, s->sy, 1.0f});
|
||||||
glm_mat4_mul(R, S, RS);
|
glm_mat4_mul(R, S, RS);
|
||||||
glm_mat4_mul(T, RS, s->uniform.transform);
|
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)
|
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;
|
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)
|
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->hovered = hovered;
|
||||||
s->selected = selected;
|
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)
|
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)
|
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 dx = wx - s->cx, dy = wy - s->cy;
|
||||||
float lx = (dx * sc + dy * ss) / s->sx;
|
float lx = (dx * sc + dy * ss) / s->sx;
|
||||||
float ly = (-dx * ss + dy * sc) / s->sy;
|
float ly = (-dx * ss + dy * sc) / s->sy;
|
||||||
|
|||||||
@@ -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,
|
static void spatial_compute_aabb(shape_t *s, float *min_x, float *min_y,
|
||||||
float *max_x, float *max_y)
|
float *max_x, float *max_y)
|
||||||
{
|
{
|
||||||
float cos_r = cosf(s->rotation);
|
float cos_r = s->cos_r;
|
||||||
float sin_r = sinf(s->rotation);
|
float sin_r = s->sin_r;
|
||||||
float hx = fabsf(cos_r) * s->sx + fabsf(sin_r) * s->sy;
|
float hx = fabsf(cos_r) * s->sx + fabsf(sin_r) * s->sy;
|
||||||
float hy = fabsf(sin_r) * s->sx + fabsf(cos_r) * s->sy;
|
float hy = fabsf(sin_r) * s->sx + fabsf(cos_r) * s->sy;
|
||||||
*min_x = s->cx - hx;
|
*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 selected_count = 0;
|
||||||
|
|
||||||
int min_cx = (int) floorf(min_x / SPATIAL_CELL_SIZE);
|
for (int s = 0; s < SPATIAL_HASH_SIZE; s++) {
|
||||||
int min_cy = (int) floorf(min_y / SPATIAL_CELL_SIZE);
|
if (!grid->slots[s].occupied) continue;
|
||||||
int max_cx = (int) floorf(max_x / SPATIAL_CELL_SIZE);
|
for (int e = 0; e < grid->slots[s].count; e++) {
|
||||||
int max_cy = (int) floorf(max_y / SPATIAL_CELL_SIZE);
|
spatial_entry_t *entry = &grid->slots[s].entries[e];
|
||||||
|
|
||||||
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];
|
|
||||||
|
|
||||||
if (entry->max_x < min_x || entry->min_x > max_x ||
|
if (entry->max_x < min_x || entry->min_x > max_x ||
|
||||||
entry->max_y < min_y || entry->min_y > max_y)
|
entry->max_y < min_y || entry->min_y > max_y)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
shape_t *s = (shape_t*) vec_get(shapes, entry->shape_idx);
|
shape_t *shape = (shape_t*) vec_get(shapes, entry->shape_idx);
|
||||||
if (s->selected) continue;
|
if (shape->selected) continue;
|
||||||
|
|
||||||
bool hit = (s->cx >= min_x && s->cx <= max_x &&
|
bool hit = (shape->cx >= min_x && shape->cx <= max_x &&
|
||||||
s->cy >= min_y && s->cy <= max_y);
|
shape->cy >= min_y && shape->cy <= max_y);
|
||||||
float sc = cosf(s->rotation), ss = sinf(s->rotation);
|
float sc = shape->cos_r, ss = shape->sin_r;
|
||||||
for (uint32_t v = 0; !hit && v < s->num_verts; v++) {
|
for (uint32_t v = 0; !hit && v < shape->num_verts; v++) {
|
||||||
float lx = s->verts[v].x * s->sx;
|
float lx = shape->verts[v].x * shape->sx;
|
||||||
float ly = s->verts[v].y * s->sy;
|
float ly = shape->verts[v].y * shape->sy;
|
||||||
float wx = s->cx + lx * sc - ly * ss;
|
float wx = shape->cx + lx * sc - ly * ss;
|
||||||
float wy = s->cy + lx * ss + ly * sc;
|
float wy = shape->cy + lx * ss + ly * sc;
|
||||||
if (wx >= min_x && wx <= max_x &&
|
if (wx >= min_x && wx <= max_x &&
|
||||||
wy >= min_y && wy <= max_y)
|
wy >= min_y && wy <= max_y)
|
||||||
hit = true;
|
hit = true;
|
||||||
}
|
}
|
||||||
if (hit) {
|
if (hit) {
|
||||||
s->selected = true;
|
shape->selected = true;
|
||||||
selected_count++;
|
selected_count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
idx = (idx + 1) & (SPATIAL_HASH_SIZE - 1);
|
|
||||||
} while (idx != probe_start);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return selected_count;
|
return selected_count;
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/types.h
11
src/types.h
@@ -89,6 +89,8 @@ typedef struct {
|
|||||||
int focused_group_id;
|
int focused_group_id;
|
||||||
double last_click_time;
|
double last_click_time;
|
||||||
int last_click_shape_idx;
|
int last_click_shape_idx;
|
||||||
|
|
||||||
|
vector_t drag_indices;
|
||||||
} interact_state_t;
|
} interact_state_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -112,6 +114,13 @@ typedef struct {
|
|||||||
int list_prev_count;
|
int list_prev_count;
|
||||||
} ui_state_t;
|
} ui_state_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
shape_t *shapes;
|
||||||
|
int shape_count;
|
||||||
|
group_t *groups;
|
||||||
|
int group_count;
|
||||||
|
} clipboard_t;
|
||||||
|
|
||||||
typedef struct userdata_t {
|
typedef struct userdata_t {
|
||||||
camera_t camera;
|
camera_t camera;
|
||||||
renderer_t renderer;
|
renderer_t renderer;
|
||||||
@@ -127,6 +136,8 @@ typedef struct userdata_t {
|
|||||||
sg_buffer corner_vbuf, corner_ibuf;
|
sg_buffer corner_vbuf, corner_ibuf;
|
||||||
int next_group_id;
|
int next_group_id;
|
||||||
vector_t groups;
|
vector_t groups;
|
||||||
|
clipboard_t clipboard;
|
||||||
|
float mouse_x, mouse_y;
|
||||||
double time;
|
double time;
|
||||||
} userdata_t;
|
} userdata_t;
|
||||||
|
|
||||||
|
|||||||
@@ -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)[2][0], (*m)[2][1], (*m)[2][2], (*m)[2][3],
|
||||||
(*m)[3][0], (*m)[3][1], (*m)[3][2], (*m)[3][3],
|
(*m)[3][0], (*m)[3][1], (*m)[3][2], (*m)[3][3],
|
||||||
s->verts[0].x, s->verts[0].y,
|
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->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 * sinf(s->rotation) + s->verts[0].y * s->sy * cosf(s->rotation),
|
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);
|
s->cx, s->cy, s->sx, s->sy, s->rotation);
|
||||||
if (igButton("Copy Debug", (ImVec2){0, 0}))
|
if (igButton("Copy Debug", (ImVec2){0, 0}))
|
||||||
sapp_set_clipboard_string(dbg);
|
sapp_set_clipboard_string(dbg);
|
||||||
@@ -389,7 +389,9 @@ static void draw_log_panel(userdata_t *ud)
|
|||||||
int idx = (start + i) % LOG_RING_SIZE;
|
int idx = (start + i) % LOG_RING_SIZE;
|
||||||
off += snprintf(buf + off, (size_t)(cap - off), "%s\n", ud->ui.log_ring[idx].text);
|
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);
|
FREE(buf);
|
||||||
}
|
}
|
||||||
igSameLine(0.0f, 10.0f);
|
igSameLine(0.0f, 10.0f);
|
||||||
|
|||||||
Reference in New Issue
Block a user