diff --git a/fetch_libs.sh b/fetch_libs.sh index 20ddc2c..3a06499 100644 --- a/fetch_libs.sh +++ b/fetch_libs.sh @@ -5,12 +5,12 @@ LIB_DIR="lib" echo "=== Fetching library dependencies ===" -# 1. Sokol (single-file headers) mkdir -p "$LIB_DIR/sokol" mkdir -p "$LIB_DIR/imgui" mkdir -p "$LIB_DIR/util" mkdir -p "$LIB_DIR/cglm" +# 1. Sokol (single-file headers) if [ ! -f "$LIB_DIR/sokol/sokol_gfx.h" ]; then echo " > Fetching sokol (pinned to emdawnwebgpu-compatible version)..." git clone --depth 500 https://github.com/floooh/sokol.git "$LIB_DIR/sokol_tmp" @@ -59,4 +59,14 @@ else echo " > cglm already present" fi +# 5. sokol_gp.h +if [ ! -f "$LIB_DIR/sokol/sokol_gp.h" ]; then + echo " > Fetching cglm..." + git clone --depth 1 https://github.com/edubart/sokol_gp.git "$LIB_DIR/sokol_gp_tmp" + cp -r "$LIB_DIR/sokol_gp_tmp/sokol_gp.h" "$LIB_DIR/sokol/sokol_gp.h" + rm -rf "$LIB_DIR/sokol_gp_tmp" +else + echo " > sokol_gp.h already present" +fi + echo "=== Done ===" diff --git a/makefile b/makefile index 4a04ca6..e209cc2 100644 --- a/makefile +++ b/makefile @@ -31,7 +31,8 @@ EMCC_FLAGS = --use-port=emdawnwebgpu \ -sALLOW_MEMORY_GROWTH \ -msimd128 \ -sFILESYSTEM=0 \ - -flto + -flto \ + -Rpass=loop-vectorize # Shell template SHELL_FILE = shell.html diff --git a/src/api.h b/src/api.h index 698117d..31d2daa 100644 --- a/src/api.h +++ b/src/api.h @@ -6,67 +6,12 @@ #define SOKOL_IMPL #define SOKOL_WGPU #define SOKOL_IMGUI_IMPL -#define SOKOL_VALIDATE_NON_FATAL #include "sokol_gfx.h" #include "sokol_app.h" #include "sokol_glue.h" -#include "sokol_log.h" #include "cimgui.h" #include "sokol_imgui.h" -#include "sokol_memtrack.h" -#include "sokol_shape.h" - -#define ALLOC(arg) smemtrack_alloc(arg, NULL) -#define FREE(arg) smemtrack_free(arg, NULL) - -#include "cglm/cglm.h" - -#include "rand.h" -#include "camera.h" - -#include "generated/sprite.h" -#include "generated/shape.h" -#include "generated/overlay.h" - -// Log-to-panel infrastructure — ctx pointer passed explicitly. -// The panel_log function writes into the panel's ring buffer through the -// callback registered in panel_log_ctx_t. This avoids a global function -// pointer that would restrict the codebase to a single compilation unit. -typedef struct { - void (*fn)(void*, int, const char*); - void *ud; -} panel_log_ctx_t; - -static void panel_log(panel_log_ctx_t *pl, int level, const char *fmt, ...) { - if (!pl || !pl->fn) return; - char buf[256]; - va_list ap; - va_start(ap, fmt); - vsnprintf(buf, sizeof(buf), fmt, ap); - va_end(ap); - pl->fn(pl->ud, level, buf); -} - -// Debug-level log calls are stripped in release builds. All panel_log(3, ...) -// calls should use this macro so they compile to nothing with -O3. -#ifdef NDEBUG -#define panel_log_debug(pl, fmt, ...) ((void)0) -#else -#define panel_log_debug(pl, fmt, ...) panel_log(pl, 3, fmt, ##__VA_ARGS__) -#endif - -#include "util.h" -#include "shape.h" -#include "render.h" -#include "spatial.h" -#include "history.h" -#include "types.h" -#include "interact.h" -#include "overlay.h" -#include "draw.h" -#include "input.h" -#include "ui_panels.h" #include #include @@ -74,4 +19,8 @@ static void panel_log(panel_log_ctx_t *pl, int level, const char *fmt, ...) { #include #include +#include "shape.h" +#include "generated/compute.h" +#include "generated/display.h" + #endif \ No newline at end of file diff --git a/src/camera.h b/src/camera.h deleted file mode 100644 index f563240..0000000 --- a/src/camera.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef CAMERA_H -#define CAMERA_H - -#include "api.h" - -typedef struct { - bool dragging; - float origin_x, origin_y; -} pan_state_t; - -typedef struct { - int width, height; - float half_width, half_height; - vec2 pan; - float zoom; - float hover_tol; - pan_state_t pan_state; -} camera_t; - -// Build a view-projection matrix that maps world coordinates to clip space. -// Uses glm_ortho rather than manual element assignment — the previous -// hand-rolled version was equivalent but obscured the intent. -static void compute_mvp(camera_t *cam, mat4 *mvp) -{ - float l = (-cam->pan[0] - cam->half_width) / cam->zoom; - float r = (-cam->pan[0] + cam->half_width) / cam->zoom; - float b = (-cam->pan[1] - cam->half_height) / cam->zoom; - float t = (-cam->pan[1] + cam->half_height) / cam->zoom; - glm_ortho(l, r, b, t, -1.0f, 1.0f, *mvp); -} - -static void screen_to_world(camera_t *cam, float mx, float my, float *wx, float *wy) -{ - const float sx = mx - cam->half_width; - const float sy = cam->half_height - my; - *wx = (sx - cam->pan[0]) / cam->zoom; - *wy = (sy - cam->pan[1]) / cam->zoom; -} - -#endif diff --git a/src/draw.h b/src/draw.h deleted file mode 100644 index 1da220f..0000000 --- a/src/draw.h +++ /dev/null @@ -1,353 +0,0 @@ -#ifndef DRAW_H -#define DRAW_H - -#include "api.h" -#include "types.h" - -static void draw_shapes(userdata_t *ud) -{ - bool pool_was_dirty = ud->shape_pool.pool_dirty; - if (ud->shape_pool.pool_dirty) { - shape_pool_rebuild(&ud->shape_pool, &ud->panel_log_ctx, &ud->shapes); - ud->shape_pool.data_dirty = true; - } - - int n = ud->shapes.count; - if (n == 0) return; - - if (ud->shape_pool.states_dirty) { - for (int i = 0; i < n; i++) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, i); - shape_set_state(&ud->shape_pool, s, s->hovered, s->selected); - } - ud->shape_pool.states_dirty = false; - } - - if (ud->shape_pool.data_dirty) { - shape_upload_data(&ud->shape_pool, &ud->shapes); - ud->shape_pool.data_dirty = false; - } - - // -- frustum culling: viewport world bounds -- - float vp_min_x = -(ud->camera.pan[0] + ud->camera.half_width) / ud->camera.zoom; - float vp_max_x = (ud->camera.half_width - ud->camera.pan[0]) / ud->camera.zoom; - float vp_min_y = -(ud->camera.half_height + ud->camera.pan[1]) / ud->camera.zoom; - float vp_max_y = (ud->camera.half_height - ud->camera.pan[1]) / ud->camera.zoom; - float margin = FRUSTUM_CULL_MARGIN; - vp_min_x -= margin; vp_max_x += margin; - vp_min_y -= margin; vp_max_y += margin; - - static uint32_t *imap = NULL; - static int imap_cap = 0; - static uint32_t *gi_counts = NULL; - static uint32_t *gi_starts = NULL; - static int gi_cap = 0; - static bool imap_valid = false; - static int *visible = NULL; - static int visible_cap = 0; - - int draw_count; - bool any_drag = ud->interact.move.dragging || ud->interact.rotate.dragging || - ud->interact.resize.dragging; - bool use_culling = !any_drag; - - if (use_culling) { - if (n > visible_cap) { - if (visible) FREE(visible); - visible = (int*) ALLOC((size_t)n * sizeof(int)); - visible_cap = n; - } - - int cell_min_x = (int) floorf(vp_min_x / SPATIAL_CELL_SIZE); - int cell_max_x = (int) floorf(vp_max_x / SPATIAL_CELL_SIZE); - int cell_min_y = (int) floorf(vp_min_y / SPATIAL_CELL_SIZE); - int cell_max_y = (int) floorf(vp_max_y / SPATIAL_CELL_SIZE); - int cell_count = (cell_max_x - cell_min_x + 1) * (cell_max_y - cell_min_y + 1); - - if (cell_count <= SPATIAL_HASH_SIZE) { - draw_count = spatial_query_viewport(&ud->spatial_grid, - vp_min_x, vp_min_y, vp_max_x, vp_max_y, - visible, n); - } else { - // Viewport too large for cell iteration — linear scan - draw_count = 0; - for (int i = 0; i < n; i++) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, i); - if (s->cx + s->aabb_hx < vp_min_x || s->cx - s->aabb_hx > vp_max_x || - s->cy + s->aabb_hy < vp_min_y || s->cy - s->aabb_hy > vp_max_y) - continue; - visible[draw_count++] = i; - } - } - imap_valid = false; - } else { - draw_count = n; - if (pool_was_dirty) imap_valid = false; - } - - if (draw_count == 0) return; - - // Shrink draw buffers when usage drops far below peak for 60+ frames. - // Prevents WASM memory from being held at a transient high-water mark - // (e.g. after a large paste that was then undone). - { - static int peak_draw_count = 0; - static int peak_gi_cap = 0; - static int peak_visible_cap = 0; - static int frames_at_peak = 0; - - int cur_gi_size = ud->shape_pool.group_count; - - if (draw_count > peak_draw_count || cur_gi_size > peak_gi_cap || n > peak_visible_cap) { - peak_draw_count = draw_count; - peak_gi_cap = cur_gi_size; - peak_visible_cap = use_culling ? n : peak_visible_cap; - frames_at_peak = 0; - } else { - frames_at_peak++; - } - - if (frames_at_peak > 60) { - // Halve buffers when peak is more than 4× current usage - if (imap_cap > 64 && imap_cap > draw_count * 4) { - int new_cap = imap_cap / 2; - if (new_cap < draw_count) new_cap = draw_count; - uint32_t *new_imap = (uint32_t*) ALLOC((size_t)new_cap * sizeof(uint32_t)); - memcpy(new_imap, imap, (size_t)draw_count * sizeof(uint32_t)); - FREE(imap); - imap = new_imap; - imap_cap = new_cap; - imap_valid = false; - peak_draw_count = draw_count; - } - if (gi_cap > 64 && gi_cap > cur_gi_size * 4) { - int new_cap = gi_cap / 2; - if (new_cap < cur_gi_size) new_cap = cur_gi_size; - uint32_t *new_gc = (uint32_t*) ALLOC((size_t)new_cap * sizeof(uint32_t)); - uint32_t *new_gs = (uint32_t*) ALLOC((size_t)new_cap * sizeof(uint32_t)); - if (cur_gi_size > 0) memcpy(new_gc, gi_counts, (size_t)cur_gi_size * sizeof(uint32_t)); - FREE(gi_counts); - FREE(gi_starts); - gi_counts = new_gc; - gi_starts = new_gs; - gi_cap = new_cap; - peak_gi_cap = cur_gi_size; - } - if (visible_cap > 64 && visible_cap > n * 4) { - int new_cap = visible_cap / 2; - if (new_cap < n) new_cap = n; - int *new_vis = (int*) ALLOC((size_t)new_cap * sizeof(int)); - FREE(visible); - visible = new_vis; - visible_cap = new_cap; - peak_visible_cap = n; - } - frames_at_peak = 0; - } - } - - if (draw_count > imap_cap) { - if (imap) FREE(imap); - imap = (uint32_t*) ALLOC((size_t)draw_count * sizeof(uint32_t)); - imap_cap = draw_count; - imap_valid = false; - } - - if (!imap_valid) { - // Sort visible shape indices by group_index so that shapes sharing - // the same vertex buffer are consecutive. This lets the draw loop - // issue one instanced draw call per group run rather than per shape. - int n_groups = ud->shape_pool.group_count; - int max_gi = n_groups - 1; - - int gi_size = max_gi >= 0 ? max_gi + 1 : 0; - if (gi_size > gi_cap) { - if (gi_counts) FREE(gi_counts); - if (gi_starts) FREE(gi_starts); - gi_counts = (uint32_t*) ALLOC((size_t)gi_size * sizeof(uint32_t)); - gi_starts = (uint32_t*) ALLOC((size_t)gi_size * sizeof(uint32_t)); - gi_cap = gi_size; - } - memset(gi_counts, 0, (size_t)gi_size * sizeof(uint32_t)); - - for (int i = 0; i < draw_count; i++) { - int si = use_culling ? visible[i] : i; - int gi = ((shape_t*) vec_get(&ud->shapes, si))->group_index; - if (gi < 0 || gi >= gi_size) gi = 0; - gi_counts[gi]++; - } - - uint32_t pos = 0; - for (int gi = 0; gi < gi_size; gi++) { - gi_starts[gi] = pos; - pos += gi_counts[gi]; - gi_counts[gi] = 0; - } - - for (int i = 0; i < draw_count; i++) { - int si = use_culling ? visible[i] : i; - int gi = ((shape_t*) vec_get(&ud->shapes, si))->group_index; - if (gi < 0 || gi >= gi_size) gi = 0; - imap[gi_starts[gi] + gi_counts[gi]++] = (uint32_t)si; - } - - shape_upload_instance_map(&ud->shape_pool, imap, draw_count); - imap_valid = true; - } - - sg_apply_pipeline(ud->pipelines.shape_pipeline); - - int base = 0; - while (base < draw_count) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, imap[base]); - int gi = s->group_index; - if (gi < 0 || gi >= ud->shape_pool.group_count) gi = 0; - uint32_t ne = s->num_elements; - int count = 1; - while (base + count < draw_count) { - shape_t *ns = (shape_t*) vec_get(&ud->shapes, imap[base + count]); - int ngi = ns->group_index; - if (ngi < 0 || ngi >= ud->shape_pool.group_count) ngi = 0; - if (ngi != gi) break; - count++; - } - - sg_buffer group_vbuf = ud->shape_pool.groups[gi].vbuf; - - 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] = ud->shape_pool.data_view, - .views[1] = ud->shape_pool.instance_map_view, - }); - sg_draw(0, (int)ne, count); - - base += count; - } -} - -static void draw_overlay_and_handles(userdata_t *ud, bool has_overlay, bool show_handle) -{ - sg_apply_pipeline(ud->pipelines.overlay_pipeline); - panel_log_debug(&ud->panel_log_ctx, "[shapes] draw_overlay: pipeline=%d has_ov=%d show_h=%d", - ud->pipelines.overlay_pipeline.id, has_overlay, show_handle); - - if (has_overlay) { - shape_uniform_t u; - glm_mat4_identity(u.transform); - u.state = 0; - memset(u._pad, 0, sizeof(u._pad)); - - sg_apply_uniforms(0, &SG_RANGE(ud->renderer.uniform.mvp)); - sg_apply_uniforms(1, &SG_RANGE(u)); - sg_apply_bindings(&(sg_bindings){ - .vertex_buffers[0] = ud->rect_vbuf, - .index_buffer = ud->rect_ibuf, - }); - sg_draw(0, 5, 1); - } - - if (show_handle) { - shape_uniform_t hu; - glm_mat4_identity(hu.transform); - hu.state = 0; - memset(hu._pad, 0, sizeof(hu._pad)); - - sg_apply_uniforms(0, &SG_RANGE(ud->renderer.uniform.mvp)); - sg_apply_uniforms(1, &SG_RANGE(hu)); - sg_apply_bindings(&(sg_bindings){ - .vertex_buffers[0] = ud->handle_vbuf, - .index_buffer = ud->handle_ibuf, - }); - sg_draw(0, HANDLE_CIRCLE_SEGMENTS + 1, 1); - - { - shape_uniform_t cu; - glm_mat4_identity(cu.transform); - cu.state = 0; - memset(cu._pad, 0, sizeof(cu._pad)); - - sg_apply_uniforms(0, &SG_RANGE(ud->renderer.uniform.mvp)); - sg_apply_uniforms(1, &SG_RANGE(cu)); - sg_apply_bindings(&(sg_bindings){ - .vertex_buffers[0] = ud->corner_vbuf, - .index_buffer = ud->corner_ibuf, - }); - for (int h = 0; h < 8; h++) sg_draw(h * 5, 5, 1); - } - } - - // Pen preview - if (ud->pen.drawing && ud->pen.preview_count >= 2) { - sg_update_buffer(ud->pen_vbuf, &(sg_range){ - ud->pen.preview_verts, - (size_t)ud->pen.preview_count * sizeof(shape_vertex_t) - }); - - shape_uniform_t pu; - glm_mat4_identity(pu.transform); - pu.state = 0; - memset(pu._pad, 0, sizeof(pu._pad)); - - sg_apply_uniforms(0, &SG_RANGE(ud->renderer.uniform.mvp)); - sg_apply_uniforms(1, &SG_RANGE(pu)); - sg_apply_bindings(&(sg_bindings){ - .vertex_buffers[0] = ud->pen_vbuf, - .index_buffer = ud->pen_ibuf, - }); - sg_draw(0, ud->pen.preview_count, 1); - } - - // Edit mode overlays - if (ud->interact.editing_shape_idx >= 0) { - shape_uniform_t eu; - glm_mat4_identity(eu.transform); - - // Handle lines (anchor → handle) — drawn as separate 2-vert segments - if (ud->ed_handle_line_count >= 2) { - eu.state = 0; - memset(eu._pad, 0, sizeof(eu._pad)); - sg_apply_uniforms(0, &SG_RANGE(ud->renderer.uniform.mvp)); - sg_apply_uniforms(1, &SG_RANGE(eu)); - sg_apply_bindings(&(sg_bindings){ - .vertex_buffers[0] = ud->ed_handle_line_vbuf, - .index_buffer = ud->ed_shared_ibuf, - }); - int n_lines = ud->ed_handle_line_count / 2; - for (int i = 0; i < n_lines; i++) sg_draw(i * 2, 2, 1); - } - - // Handle squares - if (ud->ed_handle_count > 0) { - eu.state = 0; - memset(eu._pad, 0, sizeof(eu._pad)); - sg_apply_uniforms(0, &SG_RANGE(ud->renderer.uniform.mvp)); - sg_apply_uniforms(1, &SG_RANGE(eu)); - sg_apply_bindings(&(sg_bindings){ - .vertex_buffers[0] = ud->ed_handle_vbuf, - .index_buffer = ud->ed_shared_ibuf, - }); - for (int h = 0; h < ud->ed_handle_count; h++) sg_draw(h * 5, 5, 1); - } - - // Anchor squares (drawn last so they're on top) - if (ud->ed_anchor_count > 0) { - eu.state = 0; - memset(eu._pad, 0, sizeof(eu._pad)); - sg_apply_uniforms(0, &SG_RANGE(ud->renderer.uniform.mvp)); - sg_apply_uniforms(1, &SG_RANGE(eu)); - sg_apply_bindings(&(sg_bindings){ - .vertex_buffers[0] = ud->ed_anchor_vbuf, - .index_buffer = ud->ed_shared_ibuf, - }); - for (int h = 0; h < ud->ed_anchor_count; h++) sg_draw(h * 5, 5, 1); - } - } -} - -#endif diff --git a/src/generated/compute.h b/src/generated/compute.h new file mode 100644 index 0000000..e698fc2 --- /dev/null +++ b/src/generated/compute.h @@ -0,0 +1,627 @@ +unsigned char src_shaders_compute_wgsl[] = { + 0x2f, 0x2f, 0x20, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x20, 0x42, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x20, 0x28, + 0x65, 0x78, 0x61, 0x63, 0x74, 0x6c, 0x79, 0x20, 0x61, 0x73, 0x20, 0x79, + 0x6f, 0x75, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x29, 0x20, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0d, 0x0a, + 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x53, 0x65, 0x67, 0x6d, 0x65, + 0x6e, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x30, + 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x2c, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x70, 0x31, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x2c, + 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x32, 0x3a, 0x20, 0x76, 0x65, + 0x63, 0x32, 0x66, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x33, + 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x2c, 0x0d, 0x0a, 0x7d, 0x0d, + 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x53, 0x68, 0x61, 0x70, + 0x65, 0x4d, 0x65, 0x74, 0x61, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x73, 0x65, 0x67, 0x6d, 0x65, + 0x6e, 0x74, 0x3a, 0x20, 0x75, 0x33, 0x32, 0x2c, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x3a, 0x20, 0x75, 0x33, 0x32, 0x2c, 0x0d, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x62, 0x62, 0x6f, 0x78, 0x5f, 0x6d, 0x69, 0x6e, 0x3a, + 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x62, 0x62, 0x6f, 0x78, 0x5f, 0x6d, 0x61, 0x78, 0x3a, 0x20, 0x76, + 0x65, 0x63, 0x32, 0x66, 0x2c, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, + 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x31, 0x29, 0x20, 0x40, 0x62, + 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, 0x30, 0x29, 0x20, 0x76, 0x61, + 0x72, 0x3c, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x3e, 0x20, 0x73, + 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x3a, 0x20, 0x61, 0x72, 0x72, + 0x61, 0x79, 0x3c, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x3e, 0x3b, + 0x0d, 0x0a, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x31, 0x29, 0x20, + 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, 0x31, 0x29, 0x20, + 0x76, 0x61, 0x72, 0x3c, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x3e, + 0x20, 0x73, 0x68, 0x61, 0x70, 0x65, 0x73, 0x3a, 0x20, 0x61, 0x72, 0x72, + 0x61, 0x79, 0x3c, 0x53, 0x68, 0x61, 0x70, 0x65, 0x4d, 0x65, 0x74, 0x61, + 0x3e, 0x3b, 0x0d, 0x0a, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x31, + 0x29, 0x20, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, 0x32, + 0x29, 0x20, 0x76, 0x61, 0x72, 0x3c, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x2c, 0x20, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x77, 0x72, 0x69, 0x74, + 0x65, 0x3e, 0x20, 0x73, 0x64, 0x66, 0x5f, 0x62, 0x75, 0x66, 0x66, 0x65, + 0x72, 0x3a, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x66, 0x33, 0x32, + 0x3e, 0x3b, 0x0d, 0x0a, 0x0d, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, + 0x20, 0x55, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x20, 0x7b, 0x0d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x6e, 0x3a, 0x20, 0x76, 0x65, + 0x63, 0x32, 0x66, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7a, 0x6f, + 0x6f, 0x6d, 0x3a, 0x20, 0x66, 0x33, 0x32, 0x2c, 0x0d, 0x0a, 0x7d, 0x3b, + 0x0d, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x54, 0x69, 0x6c, + 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x20, 0x7b, 0x0d, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x64, 0x3a, 0x20, 0x75, 0x33, 0x32, 0x2c, + 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x69, 0x6c, 0x65, 0x5f, 0x73, + 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x66, 0x33, 0x32, 0x2c, 0x0d, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x5f, 0x6d, 0x69, 0x6e, + 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x2c, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x78, 0x3a, + 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x2c, 0x0d, 0x0a, 0x7d, 0x3b, 0x0d, + 0x0a, 0x0d, 0x0a, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x30, 0x29, + 0x20, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, 0x30, 0x29, + 0x20, 0x76, 0x61, 0x72, 0x3c, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, + 0x3e, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x3a, 0x20, + 0x55, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x3b, 0x0d, 0x0a, 0x40, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x30, 0x29, 0x20, 0x40, 0x62, 0x69, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, 0x31, 0x29, 0x20, 0x76, 0x61, 0x72, + 0x3c, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x3e, 0x20, 0x74, 0x69, + 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x3a, 0x20, 0x54, + 0x69, 0x6c, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x3b, 0x0d, 0x0a, + 0x0d, 0x0a, 0x0d, 0x0a, 0x2f, 0x2f, 0x20, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x20, 0x43, 0x75, 0x62, 0x69, 0x63, 0x20, + 0x42, 0xc3, 0xa9, 0x7a, 0x69, 0x65, 0x72, 0x20, 0x68, 0x65, 0x6c, 0x70, + 0x65, 0x72, 0x73, 0x20, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x0d, 0x0a, 0x66, 0x6e, 0x20, 0x65, 0x76, 0x61, 0x6c, 0x5f, + 0x63, 0x75, 0x62, 0x69, 0x63, 0x28, 0x74, 0x3a, 0x20, 0x66, 0x33, 0x32, + 0x2c, 0x20, 0x70, 0x30, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x2c, + 0x20, 0x70, 0x31, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x2c, 0x20, + 0x70, 0x32, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x2c, 0x20, 0x70, + 0x33, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x29, 0x20, 0x2d, 0x3e, + 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x6d, 0x74, 0x20, 0x3d, 0x20, 0x31, + 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x6c, 0x65, 0x74, 0x20, 0x6d, 0x74, 0x32, 0x20, 0x3d, 0x20, 0x6d, + 0x74, 0x20, 0x2a, 0x20, 0x6d, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x6c, 0x65, 0x74, 0x20, 0x74, 0x32, 0x20, 0x3d, 0x20, 0x74, 0x20, + 0x2a, 0x20, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, + 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6d, 0x74, 0x32, 0x20, 0x2a, 0x20, 0x6d, + 0x74, 0x20, 0x2a, 0x20, 0x70, 0x30, 0x20, 0x2b, 0x20, 0x33, 0x2e, 0x30, + 0x20, 0x2a, 0x20, 0x6d, 0x74, 0x32, 0x20, 0x2a, 0x20, 0x74, 0x20, 0x2a, + 0x20, 0x70, 0x31, 0x20, 0x2b, 0x20, 0x33, 0x2e, 0x30, 0x20, 0x2a, 0x20, + 0x6d, 0x74, 0x20, 0x2a, 0x20, 0x74, 0x32, 0x20, 0x2a, 0x20, 0x70, 0x32, + 0x20, 0x2b, 0x20, 0x74, 0x32, 0x20, 0x2a, 0x20, 0x74, 0x20, 0x2a, 0x20, + 0x70, 0x33, 0x3b, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x66, 0x6e, + 0x20, 0x65, 0x76, 0x61, 0x6c, 0x5f, 0x63, 0x75, 0x62, 0x69, 0x63, 0x5f, + 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x76, 0x65, 0x28, 0x74, + 0x3a, 0x20, 0x66, 0x33, 0x32, 0x2c, 0x20, 0x70, 0x30, 0x3a, 0x20, 0x76, + 0x65, 0x63, 0x32, 0x66, 0x2c, 0x20, 0x70, 0x31, 0x3a, 0x20, 0x76, 0x65, + 0x63, 0x32, 0x66, 0x2c, 0x20, 0x70, 0x32, 0x3a, 0x20, 0x76, 0x65, 0x63, + 0x32, 0x66, 0x2c, 0x20, 0x70, 0x33, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, + 0x66, 0x29, 0x20, 0x2d, 0x3e, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x20, + 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x6d, + 0x74, 0x20, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x74, 0x3b, + 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, + 0x20, 0x33, 0x2e, 0x30, 0x20, 0x2a, 0x20, 0x6d, 0x74, 0x20, 0x2a, 0x20, + 0x6d, 0x74, 0x20, 0x2a, 0x20, 0x28, 0x70, 0x31, 0x20, 0x2d, 0x20, 0x70, + 0x30, 0x29, 0x20, 0x2b, 0x20, 0x36, 0x2e, 0x30, 0x20, 0x2a, 0x20, 0x6d, + 0x74, 0x20, 0x2a, 0x20, 0x74, 0x20, 0x2a, 0x20, 0x28, 0x70, 0x32, 0x20, + 0x2d, 0x20, 0x70, 0x31, 0x29, 0x20, 0x2b, 0x20, 0x33, 0x2e, 0x30, 0x20, + 0x2a, 0x20, 0x74, 0x20, 0x2a, 0x20, 0x74, 0x20, 0x2a, 0x20, 0x28, 0x70, + 0x33, 0x20, 0x2d, 0x20, 0x70, 0x32, 0x29, 0x3b, 0x0d, 0x0a, 0x7d, 0x0d, + 0x0a, 0x0d, 0x0a, 0x2f, 0x2f, 0x20, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x20, 0x44, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x63, 0x75, 0x62, 0x69, 0x63, + 0x20, 0x42, 0xc3, 0xa9, 0x7a, 0x69, 0x65, 0x72, 0x20, 0x28, 0x73, 0x61, + 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x20, 0x2b, 0x20, 0x4e, 0x65, 0x77, + 0x74, 0x6f, 0x6e, 0x20, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x29, 0x20, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x0d, 0x0a, 0x66, 0x6e, 0x20, 0x64, 0x69, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x75, 0x62, 0x69, 0x63, + 0x28, 0x70, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x2c, 0x20, 0x73, + 0x65, 0x67, 0x3a, 0x20, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x29, + 0x20, 0x2d, 0x3e, 0x20, 0x66, 0x33, 0x32, 0x20, 0x7b, 0x0d, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x70, 0x30, 0x20, 0x3d, 0x20, + 0x73, 0x65, 0x67, 0x2e, 0x70, 0x30, 0x3b, 0x20, 0x6c, 0x65, 0x74, 0x20, + 0x70, 0x31, 0x20, 0x3d, 0x20, 0x73, 0x65, 0x67, 0x2e, 0x70, 0x31, 0x3b, + 0x20, 0x6c, 0x65, 0x74, 0x20, 0x70, 0x32, 0x20, 0x3d, 0x20, 0x73, 0x65, + 0x67, 0x2e, 0x70, 0x32, 0x3b, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x70, 0x33, + 0x20, 0x3d, 0x20, 0x73, 0x65, 0x67, 0x2e, 0x70, 0x33, 0x3b, 0x0d, 0x0a, + 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x55, 0x6e, 0x69, + 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, + 0x67, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x69, 0x6e, 0x64, 0x20, 0x61, 0x20, + 0x67, 0x6f, 0x6f, 0x64, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x69, 0x6e, + 0x67, 0x20, 0x74, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, + 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x31, + 0x36, 0x75, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, + 0x20, 0x62, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x20, 0x3d, 0x20, 0x30, 0x2e, + 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, + 0x62, 0x65, 0x73, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x74, 0x20, 0x3d, 0x20, + 0x31, 0x65, 0x32, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6f, 0x72, 0x20, 0x28, 0x76, 0x61, 0x72, 0x20, 0x69, 0x20, 0x3d, 0x20, + 0x30, 0x75, 0x3b, 0x20, 0x69, 0x20, 0x3c, 0x20, 0x73, 0x61, 0x6d, 0x70, + 0x6c, 0x65, 0x73, 0x3b, 0x20, 0x69, 0x2b, 0x2b, 0x29, 0x20, 0x7b, 0x0d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, + 0x20, 0x74, 0x20, 0x3d, 0x20, 0x66, 0x33, 0x32, 0x28, 0x69, 0x29, 0x20, + 0x2f, 0x20, 0x66, 0x33, 0x32, 0x28, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x73, 0x20, 0x2d, 0x20, 0x31, 0x75, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x71, 0x20, + 0x3d, 0x20, 0x65, 0x76, 0x61, 0x6c, 0x5f, 0x63, 0x75, 0x62, 0x69, 0x63, + 0x28, 0x74, 0x2c, 0x20, 0x70, 0x30, 0x2c, 0x20, 0x70, 0x31, 0x2c, 0x20, + 0x70, 0x32, 0x2c, 0x20, 0x70, 0x33, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x64, 0x20, + 0x3d, 0x20, 0x64, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x28, 0x70, + 0x2c, 0x20, 0x71, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x64, 0x20, 0x3c, 0x20, 0x62, 0x65, + 0x73, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, + 0x65, 0x73, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x74, 0x20, 0x3d, 0x20, 0x64, + 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x62, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x20, 0x3d, 0x20, + 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x4e, 0x65, 0x77, 0x74, 0x6f, + 0x6e, 0x20, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x71, 0x75, 0x61, + 0x72, 0x65, 0x64, 0x20, 0x64, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x20, 0x28, 0x6d, 0x61, 0x78, 0x20, 0x34, 0x20, 0x69, 0x74, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x29, 0x0d, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x76, 0x61, 0x72, 0x20, 0x74, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x61, + 0x6d, 0x70, 0x28, 0x62, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x2c, 0x20, 0x30, + 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0d, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x28, 0x76, 0x61, 0x72, 0x20, + 0x69, 0x74, 0x65, 0x72, 0x20, 0x3d, 0x20, 0x30, 0x75, 0x3b, 0x20, 0x69, + 0x74, 0x65, 0x72, 0x20, 0x3c, 0x20, 0x34, 0x75, 0x3b, 0x20, 0x69, 0x74, + 0x65, 0x72, 0x2b, 0x2b, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x42, 0x20, 0x3d, + 0x20, 0x65, 0x76, 0x61, 0x6c, 0x5f, 0x63, 0x75, 0x62, 0x69, 0x63, 0x28, + 0x74, 0x2c, 0x20, 0x70, 0x30, 0x2c, 0x20, 0x70, 0x31, 0x2c, 0x20, 0x70, + 0x32, 0x2c, 0x20, 0x70, 0x33, 0x29, 0x20, 0x2d, 0x20, 0x70, 0x3b, 0x0d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, + 0x20, 0x42, 0x70, 0x20, 0x3d, 0x20, 0x65, 0x76, 0x61, 0x6c, 0x5f, 0x63, + 0x75, 0x62, 0x69, 0x63, 0x5f, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, + 0x69, 0x76, 0x65, 0x28, 0x74, 0x2c, 0x20, 0x70, 0x30, 0x2c, 0x20, 0x70, + 0x31, 0x2c, 0x20, 0x70, 0x32, 0x2c, 0x20, 0x70, 0x33, 0x29, 0x3b, 0x0d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, + 0x20, 0x66, 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x74, 0x28, 0x42, 0x2c, 0x20, + 0x42, 0x70, 0x29, 0x3b, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0xc2, 0xbd, + 0x20, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, + 0x6f, 0x66, 0x20, 0x7c, 0x42, 0x7c, 0xc2, 0xb2, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x64, 0x66, + 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x74, 0x28, 0x42, 0x70, 0x2c, 0x20, 0x42, + 0x70, 0x29, 0x20, 0x2b, 0x20, 0x64, 0x6f, 0x74, 0x28, 0x42, 0x2c, 0x20, + 0x33, 0x2e, 0x30, 0x20, 0x2a, 0x20, 0x28, 0x70, 0x32, 0x20, 0x2d, 0x20, + 0x70, 0x31, 0x20, 0x2b, 0x20, 0x28, 0x70, 0x33, 0x20, 0x2d, 0x20, 0x70, + 0x32, 0x20, 0x2d, 0x20, 0x70, 0x32, 0x20, 0x2b, 0x20, 0x70, 0x31, 0x29, + 0x20, 0x2a, 0x20, 0x32, 0x2e, 0x30, 0x20, 0x2a, 0x20, 0x74, 0x29, 0x29, + 0x3b, 0x20, 0x2f, 0x2f, 0x20, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, + 0x69, 0x65, 0x64, 0x20, 0x32, 0x6e, 0x64, 0x20, 0x64, 0x65, 0x72, 0x69, + 0x76, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, + 0x66, 0x20, 0x61, 0x62, 0x73, 0x28, 0x64, 0x66, 0x29, 0x20, 0x3c, 0x20, + 0x31, 0x65, 0x2d, 0x31, 0x32, 0x20, 0x7b, 0x20, 0x62, 0x72, 0x65, 0x61, + 0x6b, 0x3b, 0x20, 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x73, 0x74, 0x65, 0x70, 0x20, 0x3d, + 0x20, 0x66, 0x20, 0x2f, 0x20, 0x64, 0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x20, 0x3d, 0x20, 0x63, 0x6c, + 0x61, 0x6d, 0x70, 0x28, 0x74, 0x20, 0x2d, 0x20, 0x73, 0x74, 0x65, 0x70, + 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, + 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, + 0x20, 0x61, 0x62, 0x73, 0x28, 0x73, 0x74, 0x65, 0x70, 0x29, 0x20, 0x3c, + 0x20, 0x31, 0x65, 0x2d, 0x36, 0x20, 0x7b, 0x20, 0x62, 0x72, 0x65, 0x61, + 0x6b, 0x3b, 0x20, 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x71, 0x5f, 0x66, + 0x69, 0x6e, 0x61, 0x6c, 0x20, 0x3d, 0x20, 0x65, 0x76, 0x61, 0x6c, 0x5f, + 0x63, 0x75, 0x62, 0x69, 0x63, 0x28, 0x74, 0x2c, 0x20, 0x70, 0x30, 0x2c, + 0x20, 0x70, 0x31, 0x2c, 0x20, 0x70, 0x32, 0x2c, 0x20, 0x70, 0x33, 0x29, + 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, + 0x6e, 0x20, 0x64, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x28, 0x70, + 0x2c, 0x20, 0x71, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x29, 0x3b, 0x0d, + 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x2f, 0x2f, 0x20, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x20, 0x41, 0x6e, 0x61, 0x6c, + 0x79, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x20, 0x63, 0x75, 0x62, 0x69, 0x63, + 0x20, 0x72, 0x6f, 0x6f, 0x74, 0x20, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, + 0x20, 0x28, 0x66, 0x6f, 0x72, 0x20, 0x79, 0x2d, 0x63, 0x72, 0x6f, 0x73, + 0x73, 0x69, 0x6e, 0x67, 0x29, 0x20, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x0d, 0x0a, 0x2f, 0x2f, 0x20, 0x52, 0x65, 0x74, + 0x75, 0x72, 0x6e, 0x73, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, + 0x6f, 0x66, 0x20, 0x72, 0x65, 0x61, 0x6c, 0x20, 0x72, 0x6f, 0x6f, 0x74, + 0x73, 0x20, 0x69, 0x6e, 0x20, 0x5b, 0x30, 0x2c, 0x31, 0x5d, 0x2c, 0x20, + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x72, 0x6f, + 0x6f, 0x74, 0x73, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x20, 0x28, 0x75, + 0x70, 0x20, 0x74, 0x6f, 0x20, 0x33, 0x29, 0x2e, 0x0d, 0x0a, 0x66, 0x6e, + 0x20, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x5f, 0x63, 0x75, 0x62, 0x69, 0x63, + 0x5f, 0x69, 0x6e, 0x5f, 0x30, 0x31, 0x28, 0x63, 0x33, 0x3a, 0x20, 0x66, + 0x33, 0x32, 0x2c, 0x20, 0x63, 0x32, 0x3a, 0x20, 0x66, 0x33, 0x32, 0x2c, + 0x20, 0x63, 0x31, 0x3a, 0x20, 0x66, 0x33, 0x32, 0x2c, 0x20, 0x63, 0x30, + 0x3a, 0x20, 0x66, 0x33, 0x32, 0x2c, 0x20, 0x72, 0x6f, 0x6f, 0x74, 0x73, + 0x3a, 0x20, 0x70, 0x74, 0x72, 0x3c, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x2c, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x66, 0x33, + 0x32, 0x2c, 0x20, 0x33, 0x3e, 0x3e, 0x29, 0x20, 0x2d, 0x3e, 0x20, 0x75, + 0x33, 0x32, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, + 0x20, 0x4e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x73, 0x65, 0x3a, 0x20, + 0x63, 0x33, 0x20, 0x2a, 0x20, 0x74, 0x5e, 0x33, 0x20, 0x2b, 0x20, 0x63, + 0x32, 0x20, 0x2a, 0x20, 0x74, 0x5e, 0x32, 0x20, 0x2b, 0x20, 0x63, 0x31, + 0x20, 0x2a, 0x20, 0x74, 0x20, 0x2b, 0x20, 0x63, 0x30, 0x20, 0x3d, 0x20, + 0x30, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x61, 0x62, + 0x73, 0x28, 0x63, 0x33, 0x29, 0x20, 0x3c, 0x20, 0x31, 0x65, 0x2d, 0x39, + 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x2f, 0x2f, 0x20, 0x51, 0x75, 0x61, 0x64, 0x72, 0x61, 0x74, 0x69, 0x63, + 0x20, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x0d, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x61, 0x62, + 0x73, 0x28, 0x63, 0x32, 0x29, 0x20, 0x3c, 0x20, 0x31, 0x65, 0x2d, 0x39, + 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x61, 0x62, 0x73, 0x28, 0x63, + 0x31, 0x29, 0x20, 0x3c, 0x20, 0x31, 0x65, 0x2d, 0x39, 0x20, 0x7b, 0x20, + 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x30, 0x75, 0x3b, 0x20, 0x7d, + 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x74, 0x20, 0x3d, 0x20, 0x2d, 0x63, + 0x30, 0x20, 0x2f, 0x20, 0x63, 0x31, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, + 0x74, 0x20, 0x3e, 0x3d, 0x20, 0x30, 0x2e, 0x30, 0x20, 0x26, 0x26, 0x20, + 0x74, 0x20, 0x3c, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x20, 0x7b, 0x0d, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x28, 0x2a, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x29, + 0x5b, 0x30, 0x5d, 0x20, 0x3d, 0x20, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x31, 0x75, 0x3b, + 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, + 0x30, 0x75, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x6c, 0x65, 0x74, 0x20, 0x64, 0x69, 0x73, 0x63, 0x20, 0x3d, 0x20, 0x63, + 0x31, 0x20, 0x2a, 0x20, 0x63, 0x31, 0x20, 0x2d, 0x20, 0x34, 0x2e, 0x30, + 0x20, 0x2a, 0x20, 0x63, 0x32, 0x20, 0x2a, 0x20, 0x63, 0x30, 0x3b, 0x0d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, + 0x64, 0x69, 0x73, 0x63, 0x20, 0x3c, 0x20, 0x30, 0x2e, 0x30, 0x20, 0x7b, + 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x30, 0x75, 0x3b, 0x20, + 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, + 0x65, 0x74, 0x20, 0x73, 0x64, 0x20, 0x3d, 0x20, 0x73, 0x71, 0x72, 0x74, + 0x28, 0x64, 0x69, 0x73, 0x63, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x74, 0x30, 0x20, + 0x3d, 0x20, 0x28, 0x2d, 0x63, 0x31, 0x20, 0x2b, 0x20, 0x73, 0x64, 0x29, + 0x20, 0x2f, 0x20, 0x28, 0x32, 0x2e, 0x30, 0x20, 0x2a, 0x20, 0x63, 0x32, + 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x6c, 0x65, 0x74, 0x20, 0x74, 0x31, 0x20, 0x3d, 0x20, 0x28, 0x2d, 0x63, + 0x31, 0x20, 0x2d, 0x20, 0x73, 0x64, 0x29, 0x20, 0x2f, 0x20, 0x28, 0x32, + 0x2e, 0x30, 0x20, 0x2a, 0x20, 0x63, 0x32, 0x29, 0x3b, 0x0d, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x30, 0x75, 0x3b, 0x0d, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x74, + 0x30, 0x20, 0x3e, 0x3d, 0x20, 0x30, 0x2e, 0x30, 0x20, 0x26, 0x26, 0x20, + 0x74, 0x30, 0x20, 0x3c, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x20, 0x7b, 0x20, + 0x28, 0x2a, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x29, 0x5b, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x5d, 0x20, 0x3d, 0x20, 0x74, 0x30, 0x3b, 0x20, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x2b, 0x2b, 0x3b, 0x20, 0x7d, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x74, 0x31, 0x20, + 0x3e, 0x3d, 0x20, 0x30, 0x2e, 0x30, 0x20, 0x26, 0x26, 0x20, 0x74, 0x31, + 0x20, 0x3c, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x20, 0x7b, 0x20, 0x28, 0x2a, + 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x29, 0x5b, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x5d, 0x20, 0x3d, 0x20, 0x74, 0x31, 0x3b, 0x20, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x2b, 0x2b, 0x3b, 0x20, 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, + 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x44, + 0x65, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x20, 0x63, 0x75, 0x62, + 0x69, 0x63, 0x3a, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x74, 0x20, 0x3d, 0x20, + 0x78, 0x20, 0x2d, 0x20, 0x63, 0x32, 0x2f, 0x28, 0x33, 0x2a, 0x63, 0x33, + 0x29, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x61, + 0x20, 0x3d, 0x20, 0x63, 0x32, 0x20, 0x2f, 0x20, 0x63, 0x33, 0x3b, 0x0d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x62, 0x20, 0x3d, + 0x20, 0x63, 0x31, 0x20, 0x2f, 0x20, 0x63, 0x33, 0x3b, 0x0d, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x63, 0x20, 0x3d, 0x20, 0x63, + 0x30, 0x20, 0x2f, 0x20, 0x63, 0x33, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x6c, 0x65, 0x74, 0x20, 0x70, 0x20, 0x3d, 0x20, 0x62, 0x20, 0x2d, + 0x20, 0x61, 0x20, 0x2a, 0x20, 0x61, 0x20, 0x2f, 0x20, 0x33, 0x2e, 0x30, + 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x71, + 0x20, 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x20, 0x2a, 0x20, 0x61, 0x20, 0x2a, + 0x20, 0x61, 0x20, 0x2a, 0x20, 0x61, 0x20, 0x2f, 0x20, 0x32, 0x37, 0x2e, + 0x30, 0x20, 0x2d, 0x20, 0x61, 0x20, 0x2a, 0x20, 0x62, 0x20, 0x2f, 0x20, + 0x33, 0x2e, 0x30, 0x20, 0x2b, 0x20, 0x63, 0x3b, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x64, 0x69, 0x73, 0x63, 0x20, 0x3d, + 0x20, 0x71, 0x20, 0x2a, 0x20, 0x71, 0x20, 0x2f, 0x20, 0x34, 0x2e, 0x30, + 0x20, 0x2b, 0x20, 0x70, 0x20, 0x2a, 0x20, 0x70, 0x20, 0x2a, 0x20, 0x70, + 0x20, 0x2f, 0x20, 0x32, 0x37, 0x2e, 0x30, 0x3b, 0x0d, 0x0a, 0x0d, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x20, 0x3d, 0x20, 0x30, 0x75, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x6c, 0x65, 0x74, 0x20, 0x73, 0x68, 0x69, 0x66, 0x74, 0x20, 0x3d, + 0x20, 0x61, 0x20, 0x2f, 0x20, 0x33, 0x2e, 0x30, 0x3b, 0x0d, 0x0a, 0x0d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x64, 0x69, 0x73, 0x63, + 0x20, 0x3e, 0x20, 0x30, 0x2e, 0x30, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x73, 0x64, + 0x20, 0x3d, 0x20, 0x73, 0x71, 0x72, 0x74, 0x28, 0x64, 0x69, 0x73, 0x63, + 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x6c, 0x65, 0x74, 0x20, 0x75, 0x20, 0x3d, 0x20, 0x73, 0x69, 0x67, 0x6e, + 0x28, 0x2d, 0x71, 0x2f, 0x32, 0x2e, 0x30, 0x20, 0x2b, 0x20, 0x73, 0x64, + 0x29, 0x20, 0x2a, 0x20, 0x70, 0x6f, 0x77, 0x28, 0x61, 0x62, 0x73, 0x28, + 0x2d, 0x71, 0x2f, 0x32, 0x2e, 0x30, 0x20, 0x2b, 0x20, 0x73, 0x64, 0x29, + 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x2f, 0x33, 0x2e, 0x30, 0x29, 0x3b, 0x0d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, + 0x20, 0x76, 0x20, 0x3d, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x28, 0x2d, 0x71, + 0x2f, 0x32, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x73, 0x64, 0x29, 0x20, 0x2a, + 0x20, 0x70, 0x6f, 0x77, 0x28, 0x61, 0x62, 0x73, 0x28, 0x2d, 0x71, 0x2f, + 0x32, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x73, 0x64, 0x29, 0x2c, 0x20, 0x31, + 0x2e, 0x30, 0x2f, 0x33, 0x2e, 0x30, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x78, 0x31, + 0x20, 0x3d, 0x20, 0x75, 0x20, 0x2b, 0x20, 0x76, 0x3b, 0x0d, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x74, + 0x20, 0x3d, 0x20, 0x78, 0x31, 0x20, 0x2d, 0x20, 0x73, 0x68, 0x69, 0x66, + 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x69, 0x66, 0x20, 0x74, 0x20, 0x3e, 0x3d, 0x20, 0x30, 0x2e, 0x30, 0x20, + 0x26, 0x26, 0x20, 0x74, 0x20, 0x3c, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x20, + 0x7b, 0x20, 0x28, 0x2a, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x29, 0x5b, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x5d, 0x20, 0x3d, 0x20, 0x74, 0x3b, 0x20, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x2b, 0x2b, 0x3b, 0x20, 0x7d, 0x0d, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x7d, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x20, 0x69, 0x66, + 0x20, 0x64, 0x69, 0x73, 0x63, 0x20, 0x3d, 0x3d, 0x20, 0x30, 0x2e, 0x30, + 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x6c, 0x65, 0x74, 0x20, 0x75, 0x20, 0x3d, 0x20, 0x73, 0x69, 0x67, 0x6e, + 0x28, 0x2d, 0x71, 0x2f, 0x32, 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x70, + 0x6f, 0x77, 0x28, 0x61, 0x62, 0x73, 0x28, 0x2d, 0x71, 0x2f, 0x32, 0x2e, + 0x30, 0x29, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x2f, 0x33, 0x2e, 0x30, 0x29, + 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, + 0x65, 0x74, 0x20, 0x78, 0x31, 0x20, 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x20, + 0x2a, 0x20, 0x75, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x78, 0x32, 0x20, 0x3d, 0x20, 0x2d, + 0x75, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x6c, 0x65, 0x74, 0x20, 0x74, 0x31, 0x20, 0x3d, 0x20, 0x78, 0x31, 0x20, + 0x2d, 0x20, 0x73, 0x68, 0x69, 0x66, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x74, 0x32, + 0x20, 0x3d, 0x20, 0x78, 0x32, 0x20, 0x2d, 0x20, 0x73, 0x68, 0x69, 0x66, + 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x69, 0x66, 0x20, 0x74, 0x31, 0x20, 0x3e, 0x3d, 0x20, 0x30, 0x2e, 0x30, + 0x20, 0x26, 0x26, 0x20, 0x74, 0x31, 0x20, 0x3c, 0x3d, 0x20, 0x31, 0x2e, + 0x30, 0x20, 0x7b, 0x20, 0x28, 0x2a, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x29, + 0x5b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5d, 0x20, 0x3d, 0x20, 0x74, 0x31, + 0x3b, 0x20, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2b, 0x2b, 0x3b, 0x20, 0x7d, + 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, + 0x20, 0x74, 0x32, 0x20, 0x3e, 0x3d, 0x20, 0x30, 0x2e, 0x30, 0x20, 0x26, + 0x26, 0x20, 0x74, 0x32, 0x20, 0x3c, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x20, + 0x7b, 0x20, 0x28, 0x2a, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x29, 0x5b, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x5d, 0x20, 0x3d, 0x20, 0x74, 0x32, 0x3b, 0x20, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2b, 0x2b, 0x3b, 0x20, 0x7d, 0x0d, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x7d, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x20, 0x7b, + 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, + 0x74, 0x20, 0x70, 0x68, 0x69, 0x20, 0x3d, 0x20, 0x61, 0x63, 0x6f, 0x73, + 0x28, 0x20, 0x2d, 0x71, 0x20, 0x2f, 0x20, 0x28, 0x32, 0x2e, 0x30, 0x20, + 0x2a, 0x20, 0x73, 0x71, 0x72, 0x74, 0x28, 0x2d, 0x70, 0x2a, 0x70, 0x2a, + 0x70, 0x2f, 0x32, 0x37, 0x2e, 0x30, 0x29, 0x29, 0x20, 0x29, 0x3b, 0x0d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, + 0x20, 0x72, 0x20, 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x20, 0x2a, 0x20, 0x73, + 0x71, 0x72, 0x74, 0x28, 0x2d, 0x70, 0x2f, 0x33, 0x2e, 0x30, 0x29, 0x3b, + 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, + 0x72, 0x20, 0x28, 0x76, 0x61, 0x72, 0x20, 0x6b, 0x20, 0x3d, 0x20, 0x30, + 0x75, 0x3b, 0x20, 0x6b, 0x20, 0x3c, 0x20, 0x33, 0x75, 0x3b, 0x20, 0x6b, + 0x2b, 0x2b, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x78, + 0x20, 0x3d, 0x20, 0x72, 0x20, 0x2a, 0x20, 0x63, 0x6f, 0x73, 0x28, 0x28, + 0x70, 0x68, 0x69, 0x20, 0x2b, 0x20, 0x32, 0x2e, 0x30, 0x20, 0x2a, 0x20, + 0x33, 0x2e, 0x31, 0x34, 0x31, 0x35, 0x39, 0x32, 0x36, 0x35, 0x33, 0x35, + 0x20, 0x2a, 0x20, 0x66, 0x33, 0x32, 0x28, 0x6b, 0x29, 0x29, 0x20, 0x2f, + 0x20, 0x33, 0x2e, 0x30, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, + 0x74, 0x20, 0x3d, 0x20, 0x78, 0x20, 0x2d, 0x20, 0x73, 0x68, 0x69, 0x66, + 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x74, 0x20, 0x3e, 0x3d, 0x20, + 0x30, 0x2e, 0x30, 0x20, 0x26, 0x26, 0x20, 0x74, 0x20, 0x3c, 0x3d, 0x20, + 0x31, 0x2e, 0x30, 0x20, 0x7b, 0x20, 0x28, 0x2a, 0x72, 0x6f, 0x6f, 0x74, + 0x73, 0x29, 0x5b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5d, 0x20, 0x3d, 0x20, + 0x74, 0x3b, 0x20, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2b, 0x2b, 0x3b, 0x20, + 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, + 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x3b, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x2f, 0x2f, 0x20, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x20, 0x57, + 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, + 0x72, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x6f, 0x6e, 0x65, 0x20, + 0x63, 0x75, 0x62, 0x69, 0x63, 0x20, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, + 0x74, 0x20, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x0d, 0x0a, 0x66, 0x6e, 0x20, 0x72, 0x61, 0x79, 0x5f, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x63, + 0x75, 0x62, 0x69, 0x63, 0x28, 0x70, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, + 0x66, 0x2c, 0x20, 0x73, 0x65, 0x67, 0x3a, 0x20, 0x53, 0x65, 0x67, 0x6d, + 0x65, 0x6e, 0x74, 0x29, 0x20, 0x2d, 0x3e, 0x20, 0x69, 0x33, 0x32, 0x20, + 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x57, 0x65, + 0x20, 0x63, 0x61, 0x73, 0x74, 0x20, 0x61, 0x20, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x6f, 0x6e, 0x74, 0x61, 0x6c, 0x20, 0x72, 0x61, 0x79, 0x20, 0x66, + 0x72, 0x6f, 0x6d, 0x20, 0x70, 0x20, 0x74, 0x6f, 0x20, 0x2b, 0x78, 0x3b, + 0x20, 0x66, 0x69, 0x6e, 0x64, 0x20, 0x74, 0x20, 0x77, 0x68, 0x65, 0x72, + 0x65, 0x20, 0x79, 0x28, 0x74, 0x29, 0x20, 0x3d, 0x20, 0x70, 0x2e, 0x79, + 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x79, 0x30, + 0x20, 0x3d, 0x20, 0x73, 0x65, 0x67, 0x2e, 0x70, 0x30, 0x2e, 0x79, 0x20, + 0x2d, 0x20, 0x70, 0x2e, 0x79, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x6c, 0x65, 0x74, 0x20, 0x79, 0x31, 0x20, 0x3d, 0x20, 0x73, 0x65, 0x67, + 0x2e, 0x70, 0x31, 0x2e, 0x79, 0x20, 0x2d, 0x20, 0x70, 0x2e, 0x79, 0x3b, + 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x79, 0x32, + 0x20, 0x3d, 0x20, 0x73, 0x65, 0x67, 0x2e, 0x70, 0x32, 0x2e, 0x79, 0x20, + 0x2d, 0x20, 0x70, 0x2e, 0x79, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x6c, 0x65, 0x74, 0x20, 0x79, 0x33, 0x20, 0x3d, 0x20, 0x73, 0x65, 0x67, + 0x2e, 0x70, 0x33, 0x2e, 0x79, 0x20, 0x2d, 0x20, 0x70, 0x2e, 0x79, 0x3b, + 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x79, + 0x28, 0x74, 0x29, 0x20, 0x3d, 0x20, 0x28, 0x31, 0x2d, 0x74, 0x29, 0x5e, + 0x33, 0x2a, 0x79, 0x30, 0x20, 0x2b, 0x20, 0x33, 0x28, 0x31, 0x2d, 0x74, + 0x29, 0x5e, 0x32, 0x20, 0x74, 0x20, 0x2a, 0x20, 0x79, 0x31, 0x20, 0x2b, + 0x20, 0x33, 0x28, 0x31, 0x2d, 0x74, 0x29, 0x20, 0x74, 0x5e, 0x32, 0x20, + 0x2a, 0x20, 0x79, 0x32, 0x20, 0x2b, 0x20, 0x74, 0x5e, 0x33, 0x20, 0x2a, + 0x20, 0x79, 0x33, 0x20, 0x3d, 0x20, 0x30, 0x0d, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x2f, 0x2f, 0x20, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x20, 0x70, + 0x6f, 0x6c, 0x79, 0x6e, 0x6f, 0x6d, 0x69, 0x61, 0x6c, 0x3a, 0x20, 0x63, + 0x33, 0x20, 0x74, 0x5e, 0x33, 0x20, 0x2b, 0x20, 0x63, 0x32, 0x20, 0x74, + 0x5e, 0x32, 0x20, 0x2b, 0x20, 0x63, 0x31, 0x20, 0x74, 0x20, 0x2b, 0x20, + 0x63, 0x30, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, + 0x63, 0x30, 0x20, 0x3d, 0x20, 0x79, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x63, 0x31, 0x20, 0x3d, 0x20, 0x33, + 0x2e, 0x30, 0x20, 0x2a, 0x20, 0x28, 0x79, 0x31, 0x20, 0x2d, 0x20, 0x79, + 0x30, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, + 0x20, 0x63, 0x32, 0x20, 0x3d, 0x20, 0x33, 0x2e, 0x30, 0x20, 0x2a, 0x20, + 0x28, 0x79, 0x32, 0x20, 0x2d, 0x20, 0x32, 0x2e, 0x30, 0x20, 0x2a, 0x20, + 0x79, 0x31, 0x20, 0x2b, 0x20, 0x79, 0x30, 0x29, 0x3b, 0x0d, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x63, 0x33, 0x20, 0x3d, 0x20, + 0x79, 0x33, 0x20, 0x2d, 0x20, 0x33, 0x2e, 0x30, 0x20, 0x2a, 0x20, 0x79, + 0x32, 0x20, 0x2b, 0x20, 0x33, 0x2e, 0x30, 0x20, 0x2a, 0x20, 0x79, 0x31, + 0x20, 0x2d, 0x20, 0x79, 0x30, 0x3b, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x3a, + 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x66, 0x33, 0x32, 0x2c, 0x20, + 0x33, 0x3e, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, + 0x20, 0x6e, 0x75, 0x6d, 0x20, 0x3d, 0x20, 0x73, 0x6f, 0x6c, 0x76, 0x65, + 0x5f, 0x63, 0x75, 0x62, 0x69, 0x63, 0x5f, 0x69, 0x6e, 0x5f, 0x30, 0x31, + 0x28, 0x63, 0x33, 0x2c, 0x20, 0x63, 0x32, 0x2c, 0x20, 0x63, 0x31, 0x2c, + 0x20, 0x63, 0x30, 0x2c, 0x20, 0x26, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x29, + 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x77, + 0x69, 0x6e, 0x64, 0x20, 0x3d, 0x20, 0x30, 0x69, 0x3b, 0x0d, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x28, 0x76, 0x61, 0x72, 0x20, + 0x69, 0x20, 0x3d, 0x20, 0x30, 0x75, 0x3b, 0x20, 0x69, 0x20, 0x3c, 0x20, + 0x6e, 0x75, 0x6d, 0x3b, 0x20, 0x69, 0x2b, 0x2b, 0x29, 0x20, 0x7b, 0x0d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, + 0x20, 0x74, 0x20, 0x3d, 0x20, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x5b, 0x69, + 0x5d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x6c, 0x65, 0x74, 0x20, 0x78, 0x20, 0x3d, 0x20, 0x65, 0x76, 0x61, 0x6c, + 0x5f, 0x63, 0x75, 0x62, 0x69, 0x63, 0x28, 0x74, 0x2c, 0x20, 0x73, 0x65, + 0x67, 0x2e, 0x70, 0x30, 0x2c, 0x20, 0x73, 0x65, 0x67, 0x2e, 0x70, 0x31, + 0x2c, 0x20, 0x73, 0x65, 0x67, 0x2e, 0x70, 0x32, 0x2c, 0x20, 0x73, 0x65, + 0x67, 0x2e, 0x70, 0x33, 0x29, 0x2e, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x78, 0x20, 0x3e, + 0x20, 0x70, 0x2e, 0x78, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, + 0x64, 0x79, 0x20, 0x3d, 0x20, 0x65, 0x76, 0x61, 0x6c, 0x5f, 0x63, 0x75, + 0x62, 0x69, 0x63, 0x5f, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, + 0x76, 0x65, 0x28, 0x74, 0x2c, 0x20, 0x73, 0x65, 0x67, 0x2e, 0x70, 0x30, + 0x2c, 0x20, 0x73, 0x65, 0x67, 0x2e, 0x70, 0x31, 0x2c, 0x20, 0x73, 0x65, + 0x67, 0x2e, 0x70, 0x32, 0x2c, 0x20, 0x73, 0x65, 0x67, 0x2e, 0x70, 0x33, + 0x29, 0x2e, 0x79, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x64, 0x79, 0x20, + 0x3e, 0x20, 0x30, 0x2e, 0x30, 0x20, 0x7b, 0x20, 0x77, 0x69, 0x6e, 0x64, + 0x20, 0x2b, 0x3d, 0x20, 0x31, 0x69, 0x3b, 0x20, 0x7d, 0x0d, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, + 0x6c, 0x73, 0x65, 0x20, 0x69, 0x66, 0x20, 0x64, 0x79, 0x20, 0x3c, 0x20, + 0x30, 0x2e, 0x30, 0x20, 0x7b, 0x20, 0x77, 0x69, 0x6e, 0x64, 0x20, 0x2d, + 0x3d, 0x20, 0x31, 0x69, 0x3b, 0x20, 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, + 0x6e, 0x20, 0x77, 0x69, 0x6e, 0x64, 0x3b, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, + 0x0d, 0x0a, 0x2f, 0x2f, 0x20, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x20, 0x4d, 0x61, 0x69, 0x6e, 0x20, 0x63, 0x6f, 0x6d, + 0x70, 0x75, 0x74, 0x65, 0x20, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x20, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0d, 0x0a, + 0x40, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x20, 0x40, 0x77, 0x6f, + 0x72, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x73, 0x69, 0x7a, 0x65, + 0x28, 0x38, 0x2c, 0x20, 0x38, 0x29, 0x0d, 0x0a, 0x66, 0x6e, 0x20, 0x6d, + 0x61, 0x69, 0x6e, 0x28, 0x40, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, + 0x28, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x69, 0x6e, 0x76, 0x6f, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x29, 0x20, 0x69, + 0x64, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x33, 0x3c, 0x75, 0x33, 0x32, 0x3e, + 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, + 0x20, 0x74, 0x69, 0x6c, 0x65, 0x5f, 0x64, 0x69, 0x6d, 0x20, 0x3d, 0x20, + 0x76, 0x65, 0x63, 0x32, 0x66, 0x28, 0x74, 0x69, 0x6c, 0x65, 0x5f, 0x70, + 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x74, 0x69, 0x6c, 0x65, 0x5f, 0x73, + 0x69, 0x7a, 0x65, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, + 0x66, 0x20, 0x69, 0x64, 0x2e, 0x78, 0x20, 0x3e, 0x3d, 0x20, 0x75, 0x33, + 0x32, 0x28, 0x74, 0x69, 0x6c, 0x65, 0x5f, 0x64, 0x69, 0x6d, 0x2e, 0x78, + 0x29, 0x20, 0x7c, 0x7c, 0x20, 0x69, 0x64, 0x2e, 0x79, 0x20, 0x3e, 0x3d, + 0x20, 0x75, 0x33, 0x32, 0x28, 0x74, 0x69, 0x6c, 0x65, 0x5f, 0x64, 0x69, + 0x6d, 0x2e, 0x79, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x3b, 0x0d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x20, 0x63, + 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x55, 0x56, 0x2c, 0x20, 0x74, 0x68, + 0x65, 0x6e, 0x20, 0x6d, 0x61, 0x70, 0x20, 0x74, 0x6f, 0x20, 0x77, 0x6f, + 0x72, 0x6c, 0x64, 0x20, 0x73, 0x70, 0x61, 0x63, 0x65, 0x0d, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x75, 0x76, 0x20, 0x3d, 0x20, + 0x28, 0x76, 0x65, 0x63, 0x32, 0x66, 0x28, 0x69, 0x64, 0x2e, 0x78, 0x79, + 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x29, 0x20, 0x2f, 0x20, 0x74, + 0x69, 0x6c, 0x65, 0x5f, 0x64, 0x69, 0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x20, + 0x3d, 0x20, 0x6d, 0x69, 0x78, 0x28, 0x74, 0x69, 0x6c, 0x65, 0x5f, 0x70, + 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x5f, + 0x6d, 0x69, 0x6e, 0x2c, 0x20, 0x74, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, + 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x5f, 0x6d, + 0x61, 0x78, 0x2c, 0x20, 0x75, 0x76, 0x29, 0x3b, 0x0d, 0x0a, 0x0d, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x6d, 0x69, 0x6e, 0x44, + 0x69, 0x73, 0x74, 0x3a, 0x20, 0x66, 0x33, 0x32, 0x20, 0x3d, 0x20, 0x31, + 0x65, 0x32, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, + 0x72, 0x20, 0x77, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x69, + 0x33, 0x32, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x28, 0x76, 0x61, 0x72, 0x20, + 0x73, 0x20, 0x3d, 0x20, 0x30, 0x75, 0x3b, 0x20, 0x73, 0x20, 0x3c, 0x20, + 0x61, 0x72, 0x72, 0x61, 0x79, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x28, + 0x26, 0x73, 0x68, 0x61, 0x70, 0x65, 0x73, 0x29, 0x3b, 0x20, 0x73, 0x2b, + 0x2b, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x73, 0x68, 0x61, 0x70, 0x65, 0x20, + 0x3d, 0x20, 0x73, 0x68, 0x61, 0x70, 0x65, 0x73, 0x5b, 0x73, 0x5d, 0x3b, + 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x2f, 0x2f, 0x20, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x20, 0x62, 0x62, 0x6f, + 0x78, 0x20, 0x63, 0x75, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x6e, + 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x20, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, + 0x74, 0x20, 0x62, 0x62, 0x6f, 0x78, 0x5f, 0x6d, 0x69, 0x6e, 0x20, 0x3d, + 0x20, 0x73, 0x68, 0x61, 0x70, 0x65, 0x2e, 0x62, 0x62, 0x6f, 0x78, 0x5f, + 0x6d, 0x69, 0x6e, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x62, 0x62, 0x6f, 0x78, 0x5f, 0x6d, + 0x61, 0x78, 0x20, 0x3d, 0x20, 0x73, 0x68, 0x61, 0x70, 0x65, 0x2e, 0x62, + 0x62, 0x6f, 0x78, 0x5f, 0x6d, 0x61, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x64, 0x78, + 0x20, 0x3d, 0x20, 0x6d, 0x61, 0x78, 0x28, 0x62, 0x62, 0x6f, 0x78, 0x5f, + 0x6d, 0x69, 0x6e, 0x2e, 0x78, 0x20, 0x2d, 0x20, 0x77, 0x6f, 0x72, 0x6c, + 0x64, 0x2e, 0x78, 0x2c, 0x20, 0x6d, 0x61, 0x78, 0x28, 0x30, 0x2e, 0x30, + 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x78, 0x20, 0x2d, 0x20, + 0x62, 0x62, 0x6f, 0x78, 0x5f, 0x6d, 0x61, 0x78, 0x2e, 0x78, 0x29, 0x29, + 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, + 0x65, 0x74, 0x20, 0x64, 0x79, 0x20, 0x3d, 0x20, 0x6d, 0x61, 0x78, 0x28, + 0x62, 0x62, 0x6f, 0x78, 0x5f, 0x6d, 0x69, 0x6e, 0x2e, 0x79, 0x20, 0x2d, + 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x79, 0x2c, 0x20, 0x6d, 0x61, + 0x78, 0x28, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, + 0x2e, 0x79, 0x20, 0x2d, 0x20, 0x62, 0x62, 0x6f, 0x78, 0x5f, 0x6d, 0x61, + 0x78, 0x2e, 0x79, 0x29, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x64, 0x69, 0x73, 0x74, + 0x5f, 0x74, 0x6f, 0x5f, 0x62, 0x6f, 0x78, 0x20, 0x3d, 0x20, 0x73, 0x71, + 0x72, 0x74, 0x28, 0x64, 0x78, 0x20, 0x2a, 0x20, 0x64, 0x78, 0x20, 0x2b, + 0x20, 0x64, 0x79, 0x20, 0x2a, 0x20, 0x64, 0x79, 0x29, 0x3b, 0x0d, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x64, + 0x69, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x5f, 0x62, 0x6f, 0x78, 0x20, 0x3e, + 0x3d, 0x20, 0x6d, 0x69, 0x6e, 0x44, 0x69, 0x73, 0x74, 0x20, 0x7b, 0x0d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x3b, 0x20, 0x20, + 0x20, 0x2f, 0x2f, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x73, 0x68, 0x61, + 0x70, 0x65, 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x20, 0x69, 0x6d, + 0x70, 0x72, 0x6f, 0x76, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x69, + 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x50, 0x72, 0x6f, 0x63, + 0x65, 0x73, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x73, 0x65, 0x67, 0x6d, + 0x65, 0x6e, 0x74, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x73, 0x68, 0x61, 0x70, 0x65, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x28, 0x76, 0x61, 0x72, 0x20, + 0x69, 0x20, 0x3d, 0x20, 0x73, 0x68, 0x61, 0x70, 0x65, 0x2e, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x5f, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x3b, + 0x20, 0x69, 0x20, 0x3c, 0x20, 0x73, 0x68, 0x61, 0x70, 0x65, 0x2e, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x5f, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, + 0x20, 0x2b, 0x20, 0x73, 0x68, 0x61, 0x70, 0x65, 0x2e, 0x73, 0x65, 0x67, + 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x3b, 0x20, + 0x69, 0x2b, 0x2b, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, + 0x73, 0x65, 0x67, 0x20, 0x3d, 0x20, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x5b, 0x69, 0x5d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, + 0x64, 0x20, 0x3d, 0x20, 0x64, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x75, 0x62, 0x69, 0x63, 0x28, 0x77, 0x6f, + 0x72, 0x6c, 0x64, 0x2c, 0x20, 0x73, 0x65, 0x67, 0x29, 0x3b, 0x0d, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x6d, 0x69, 0x6e, 0x44, 0x69, 0x73, 0x74, 0x20, 0x3d, 0x20, 0x6d, 0x69, + 0x6e, 0x28, 0x6d, 0x69, 0x6e, 0x44, 0x69, 0x73, 0x74, 0x2c, 0x20, 0x64, + 0x29, 0x3b, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x20, 0x2b, 0x3d, 0x20, 0x72, 0x61, 0x79, 0x5f, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x63, + 0x75, 0x62, 0x69, 0x63, 0x28, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2c, 0x20, + 0x73, 0x65, 0x67, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, + 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x73, + 0x69, 0x67, 0x6e, 0x20, 0x3d, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x28, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x2d, 0x31, 0x2e, 0x30, 0x2c, 0x20, + 0x77, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x21, 0x3d, 0x20, 0x30, + 0x69, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, + 0x20, 0x73, 0x64, 0x66, 0x20, 0x3d, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x20, + 0x2a, 0x20, 0x6d, 0x69, 0x6e, 0x44, 0x69, 0x73, 0x74, 0x3b, 0x0d, 0x0a, + 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x64, 0x66, 0x5f, 0x62, 0x75, + 0x66, 0x66, 0x65, 0x72, 0x5b, 0x69, 0x64, 0x2e, 0x79, 0x20, 0x2a, 0x20, + 0x75, 0x33, 0x32, 0x28, 0x74, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x72, + 0x61, 0x6d, 0x73, 0x2e, 0x74, 0x69, 0x6c, 0x65, 0x5f, 0x73, 0x69, 0x7a, + 0x65, 0x29, 0x20, 0x2b, 0x20, 0x69, 0x64, 0x2e, 0x78, 0x5d, 0x20, 0x3d, + 0x20, 0x73, 0x64, 0x66, 0x3b, 0x0d, 0x0a, 0x7d +}; +unsigned int src_shaders_compute_wgsl_len = 7484; diff --git a/src/generated/display.h b/src/generated/display.h new file mode 100644 index 0000000..03b5571 --- /dev/null +++ b/src/generated/display.h @@ -0,0 +1,53 @@ +unsigned char src_shaders_display_wgsl[] = { + 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x55, 0x6e, 0x69, 0x66, 0x6f, + 0x72, 0x6d, 0x73, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, + 0x61, 0x6e, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x2c, 0x0d, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x7a, 0x6f, 0x6f, 0x6d, 0x3a, 0x20, 0x66, 0x33, + 0x32, 0x2c, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, + 0x74, 0x20, 0x54, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, + 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x64, 0x3a, + 0x20, 0x75, 0x33, 0x32, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, + 0x69, 0x6c, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x66, 0x33, + 0x32, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x6f, 0x72, 0x6c, + 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, + 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, + 0x5f, 0x6d, 0x61, 0x78, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x2c, + 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x40, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x28, 0x30, 0x29, 0x20, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x28, 0x30, 0x29, 0x20, 0x76, 0x61, 0x72, 0x3c, 0x75, 0x6e, 0x69, + 0x66, 0x6f, 0x72, 0x6d, 0x3e, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, + 0x6d, 0x73, 0x3a, 0x20, 0x55, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73, + 0x3b, 0x0d, 0x0a, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x30, 0x29, + 0x20, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, 0x31, 0x29, + 0x20, 0x76, 0x61, 0x72, 0x3c, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, + 0x3e, 0x20, 0x74, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, + 0x73, 0x3a, 0x20, 0x54, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, + 0x73, 0x3b, 0x0d, 0x0a, 0x0d, 0x0a, 0x40, 0x76, 0x65, 0x72, 0x74, 0x65, + 0x78, 0x20, 0x66, 0x6e, 0x20, 0x76, 0x73, 0x5f, 0x6d, 0x61, 0x69, 0x6e, + 0x28, 0x40, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x28, 0x76, 0x65, + 0x72, 0x74, 0x65, 0x78, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x29, 0x20, + 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x20, 0x3a, 0x20, 0x75, 0x33, 0x32, 0x29, 0x20, 0x2d, 0x3e, 0x20, 0x40, + 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x28, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x29, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x20, + 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x70, + 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x28, 0x0d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x20, 0x30, + 0x2e, 0x30, 0x2c, 0x20, 0x20, 0x30, 0x2e, 0x35, 0x29, 0x2c, 0x0d, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x2d, 0x30, 0x2e, + 0x35, 0x2c, 0x20, 0x2d, 0x30, 0x2e, 0x35, 0x29, 0x2c, 0x0d, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x20, 0x30, 0x2e, 0x35, + 0x2c, 0x20, 0x2d, 0x30, 0x2e, 0x35, 0x29, 0x0d, 0x0a, 0x20, 0x20, 0x29, + 0x3b, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, + 0x6e, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 0x70, 0x6f, 0x73, 0x5b, + 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x5d, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x7d, + 0x0d, 0x0a, 0x0d, 0x0a, 0x40, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, + 0x74, 0x20, 0x66, 0x6e, 0x20, 0x66, 0x73, 0x5f, 0x6d, 0x61, 0x69, 0x6e, + 0x28, 0x29, 0x20, 0x2d, 0x3e, 0x20, 0x40, 0x6c, 0x6f, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x28, 0x30, 0x29, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, + 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, + 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x31, 0x2c, 0x20, 0x30, 0x2c, 0x20, + 0x30, 0x2c, 0x20, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a +}; +unsigned int src_shaders_display_wgsl_len = 599; diff --git a/src/generated/overlay.h b/src/generated/overlay.h deleted file mode 100644 index 15114c2..0000000 --- a/src/generated/overlay.h +++ /dev/null @@ -1,91 +0,0 @@ -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; diff --git a/src/generated/shape.h b/src/generated/shape.h deleted file mode 100644 index a35c0b1..0000000 --- a/src/generated/shape.h +++ /dev/null @@ -1,110 +0,0 @@ -unsigned char src_shaders_shape_wgsl[] = { - 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x56, 0x73, 0x55, 0x6e, 0x69, - 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, - 0x76, 0x70, 0x3a, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x78, 0x34, 0x66, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x3a, 0x20, 0x75, 0x33, 0x32, 0x2c, - 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, - 0x53, 0x68, 0x61, 0x70, 0x65, 0x44, 0x61, 0x74, 0x61, 0x20, 0x7b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, - 0x6d, 0x3a, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x78, 0x34, 0x66, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x3a, 0x20, 0x75, - 0x33, 0x32, 0x2c, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x40, 0x62, 0x69, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x28, 0x30, 0x29, 0x20, 0x40, 0x67, 0x72, 0x6f, - 0x75, 0x70, 0x28, 0x30, 0x29, 0x20, 0x76, 0x61, 0x72, 0x3c, 0x75, 0x6e, - 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x3e, 0x20, 0x76, 0x73, 0x5f, 0x75, 0x6e, - 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x3a, 0x20, 0x56, 0x73, 0x55, 0x6e, - 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x3b, 0x0a, 0x0a, 0x40, 0x62, 0x69, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x28, 0x30, 0x29, 0x20, 0x40, 0x67, 0x72, 0x6f, - 0x75, 0x70, 0x28, 0x31, 0x29, 0x20, 0x76, 0x61, 0x72, 0x3c, 0x73, 0x74, - 0x6f, 0x72, 0x61, 0x67, 0x65, 0x3e, 0x20, 0x73, 0x68, 0x61, 0x70, 0x65, - 0x5f, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, - 0x3c, 0x53, 0x68, 0x61, 0x70, 0x65, 0x44, 0x61, 0x74, 0x61, 0x3e, 0x3b, - 0x0a, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, 0x31, 0x29, - 0x20, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x31, 0x29, 0x20, 0x76, - 0x61, 0x72, 0x3c, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x3e, 0x20, - 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6d, 0x61, 0x70, - 0x3a, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x75, 0x33, 0x32, 0x3e, - 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x56, 0x73, - 0x49, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x62, 0x75, - 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x28, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, - 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x29, 0x20, 0x69, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x78, 0x3a, 0x20, - 0x75, 0x33, 0x32, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x6c, 0x6f, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x30, 0x29, 0x20, 0x70, 0x6f, - 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, - 0x66, 0x2c, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, - 0x74, 0x20, 0x56, 0x73, 0x32, 0x46, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x40, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x28, 0x70, - 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x29, 0x20, 0x70, 0x6f, 0x73, - 0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x40, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x30, - 0x29, 0x20, 0x40, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x6f, 0x6c, 0x61, - 0x74, 0x65, 0x28, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x29, 0x20, 0x63, - 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x2c, - 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, - 0x46, 0x73, 0x4f, 0x75, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x40, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x30, 0x29, - 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, - 0x66, 0x2c, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x40, 0x76, 0x65, 0x72, 0x74, - 0x65, 0x78, 0x20, 0x66, 0x6e, 0x20, 0x76, 0x73, 0x5f, 0x6d, 0x61, 0x69, - 0x6e, 0x28, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x3a, 0x20, 0x56, 0x73, 0x49, - 0x6e, 0x29, 0x20, 0x2d, 0x3e, 0x20, 0x56, 0x73, 0x32, 0x46, 0x73, 0x20, - 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x6f, 0x75, - 0x74, 0x70, 0x75, 0x74, 0x3a, 0x20, 0x56, 0x73, 0x32, 0x46, 0x73, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x73, 0x68, 0x61, - 0x70, 0x65, 0x5f, 0x69, 0x64, 0x78, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x73, - 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6d, 0x61, 0x70, 0x5b, 0x76, 0x73, - 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x2e, 0x69, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x20, - 0x2b, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x69, 0x6e, 0x73, 0x74, - 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x78, 0x5d, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x73, 0x68, 0x61, 0x70, 0x65, - 0x20, 0x3d, 0x20, 0x73, 0x68, 0x61, 0x70, 0x65, 0x5f, 0x64, 0x61, 0x74, - 0x61, 0x5b, 0x73, 0x68, 0x61, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x78, 0x5d, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x77, 0x6f, - 0x72, 0x6c, 0x64, 0x5f, 0x70, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x73, 0x68, - 0x61, 0x70, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, - 0x6d, 0x20, 0x2a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 0x69, 0x6e, - 0x70, 0x75, 0x74, 0x2e, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x2e, 0x78, 0x2c, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x70, 0x6f, - 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x79, 0x2c, 0x20, 0x30, 0x2e, - 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x70, 0x6f, 0x73, 0x20, - 0x3d, 0x20, 0x76, 0x73, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, - 0x73, 0x2e, 0x6d, 0x76, 0x70, 0x20, 0x2a, 0x20, 0x77, 0x6f, 0x72, 0x6c, - 0x64, 0x5f, 0x70, 0x6f, 0x73, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, - 0x66, 0x20, 0x28, 0x73, 0x68, 0x61, 0x70, 0x65, 0x2e, 0x73, 0x74, 0x61, - 0x74, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x75, 0x29, 0x20, 0x7b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x76, - 0x65, 0x63, 0x34, 0x66, 0x28, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x30, 0x2e, - 0x38, 0x34, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, - 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x20, 0x65, 0x6c, 0x73, - 0x65, 0x20, 0x69, 0x66, 0x20, 0x28, 0x73, 0x68, 0x61, 0x70, 0x65, 0x2e, - 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x75, 0x29, - 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, - 0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, - 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 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_shape_wgsl_len = 1274; diff --git a/src/generated/sprite.h b/src/generated/sprite.h deleted file mode 100644 index 912fb68..0000000 --- a/src/generated/sprite.h +++ /dev/null @@ -1,92 +0,0 @@ -unsigned char src_shaders_sprite_wgsl[] = { - 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x53, 0x70, 0x72, 0x69, 0x74, - 0x65, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x74, - 0x72, 0x69, 0x78, 0x3a, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x78, 0x34, 0x66, - 0x2c, 0x0d, 0x0a, 0x7d, 0x3b, 0x0d, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, - 0x74, 0x20, 0x56, 0x73, 0x55, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, - 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x76, 0x70, 0x3a, 0x20, - 0x6d, 0x61, 0x74, 0x34, 0x78, 0x34, 0x66, 0x2c, 0x0d, 0x0a, 0x7d, 0x3b, - 0x0d, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x56, 0x73, 0x49, - 0x20, 0x7b, 0x20, 0x2f, 0x2f, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x20, - 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, - 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x40, 0x62, 0x75, 0x69, 0x6c, 0x74, - 0x69, 0x6e, 0x28, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, - 0x69, 0x6e, 0x64, 0x65, 0x78, 0x29, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, - 0x6e, 0x63, 0x65, 0x3a, 0x20, 0x75, 0x33, 0x32, 0x2c, 0x0d, 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, 0x0d, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x40, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, - 0x31, 0x29, 0x20, 0x75, 0x76, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, - 0x2c, 0x0d, 0x0a, 0x7d, 0x3b, 0x0d, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, - 0x74, 0x20, 0x56, 0x73, 0x32, 0x46, 0x73, 0x20, 0x7b, 0x20, 0x2f, 0x2f, - 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x20, 0x73, 0x68, 0x61, 0x64, 0x65, - 0x72, 0x20, 0x74, 0x6f, 0x20, 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, - 0x74, 0x20, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x0d, 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, 0x0d, 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, - 0x75, 0x76, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x2c, 0x0d, 0x0a, - 0x7d, 0x3b, 0x0d, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x46, - 0x73, 0x4f, 0x20, 0x7b, 0x20, 0x2f, 0x2f, 0x46, 0x72, 0x61, 0x67, 0x6d, - 0x65, 0x6e, 0x74, 0x20, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x6f, - 0x75, 0x74, 0x70, 0x75, 0x74, 0x0d, 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, 0x0d, 0x0a, 0x7d, 0x3b, 0x0d, 0x0a, 0x0d, 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, 0x0d, 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, 0x20, 0x74, - 0x65, 0x78, 0x3a, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x5f, - 0x32, 0x64, 0x3c, 0x66, 0x33, 0x32, 0x3e, 0x3b, 0x0d, 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, 0x20, - 0x73, 0x61, 0x6d, 0x70, 0x3a, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, - 0x72, 0x3b, 0x0d, 0x0a, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x28, 0x32, 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, 0x70, 0x72, 0x69, 0x74, 0x65, 0x73, 0x3a, 0x20, - 0x61, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x53, 0x70, 0x72, 0x69, 0x74, 0x65, - 0x3e, 0x3b, 0x0d, 0x0a, 0x0d, 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, 0x29, - 0x20, 0x2d, 0x3e, 0x20, 0x56, 0x73, 0x32, 0x46, 0x73, 0x20, 0x7b, 0x0d, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x6f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x3a, 0x20, 0x56, 0x73, 0x32, 0x46, 0x73, 0x3b, 0x0d, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, - 0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x70, 0x6f, 0x73, 0x20, 0x3d, 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, 0x2c, 0x20, 0x30, 0x29, 0x20, - 0x2a, 0x20, 0x73, 0x70, 0x72, 0x69, 0x74, 0x65, 0x73, 0x5b, 0x69, 0x6e, - 0x70, 0x75, 0x74, 0x2e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x5d, 0x2e, 0x6d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x20, 0x2a, 0x20, 0x76, - 0x73, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x2e, 0x6d, - 0x76, 0x70, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x2e, 0x75, 0x76, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x70, - 0x75, 0x74, 0x2e, 0x75, 0x76, 0x3b, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x3b, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 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, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x76, 0x61, 0x72, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3a, 0x20, - 0x46, 0x73, 0x4f, 0x3b, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x63, 0x6f, 0x6c, 0x6f, 0x72, - 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x53, 0x61, - 0x6d, 0x70, 0x6c, 0x65, 0x28, 0x74, 0x65, 0x78, 0x2c, 0x20, 0x73, 0x61, - 0x6d, 0x70, 0x2c, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x75, 0x76, - 0x29, 0x3b, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, - 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x3b, - 0x0d, 0x0a, 0x7d -}; -unsigned int src_shaders_sprite_wgsl_len = 1059; diff --git a/src/globals.h b/src/globals.h deleted file mode 100644 index 9e655dc..0000000 --- a/src/globals.h +++ /dev/null @@ -1,14 +0,0 @@ -// Context struct declarations are now defined alongside the code they -// belong to rather than in a single header: -// -// panel_log_ctx_t — api.h -// shape_pool_ctx_t — shape.h (after shape_t / group_t) -// group_index_ctx_t — shape.h -// pipeline_ctx_t — render.h -// shape_group_buf_t — shape.h -// -// This header exists only to avoid breaking includes elsewhere and can -// be removed once all consumers are updated. -#ifndef GLOBALS_H -#define GLOBALS_H -#endif diff --git a/src/history.h b/src/history.h deleted file mode 100644 index 23caeeb..0000000 --- a/src/history.h +++ /dev/null @@ -1,499 +0,0 @@ -#ifndef HISTORY_H -#define HISTORY_H - -#include "api.h" - -#define HISTORY_MAX_DEPTH 256 - -typedef enum { - HIST_POSITION, - HIST_SCALE, - HIST_ROTATION, - HIST_CREATE, - HIST_DELETE, - HIST_GROUP, - HIST_GROUP_CREATE, - HIST_GROUP_DELETE, - HIST_GROUP_REPARENT, - HIST_EDIT, -} hist_prop_t; - -typedef struct hist_change_t { - int shape_index; - hist_prop_t prop; - float old_val[4]; - float new_val[4]; - - // Owned vertex+index buffer snapshot — only used for HIST_CREATE / HIST_DELETE - // when the shape has no control points (pen paths). - shape_vertex_t *vertex_data; - uint16_t *index_data; - int vertex_count; - int index_count; - - // Control point snapshot — used for HIST_CREATE / HIST_DELETE / HIST_EDIT. - // For HIST_CREATE / HIST_DELETE, ctrl_* holds the saved shape data. - // For HIST_EDIT, ctrl_* holds the old (pre-edit) state and - // new_ctrl_* holds the new (post-edit) state. - char name[64]; - shape_vertex_t *ctrl_points; - shape_vertex_t *ctrl_handle_in; - shape_vertex_t *ctrl_handle_out; - int ctrl_count; - bool closed; - // Post-edit control point state (only used for HIST_EDIT) - shape_vertex_t *new_ctrl_points; - shape_vertex_t *new_ctrl_handle_in; - shape_vertex_t *new_ctrl_handle_out; - int new_ctrl_count; -} hist_change_t; - -typedef struct hist_entry_t { - hist_change_t *changes; - int count; -} hist_entry_t; - -typedef struct history_t { - vector_t entries; - int current; - - bool capturing; - int pending_shape_idx; - hist_prop_t pending_prop; - float pending_old[4]; -} history_t; - -// -- helpers -- - -static void hist_read_prop(shape_t *s, hist_prop_t prop, float out[4]) { - memset(out, 0, sizeof(float[4])); - switch (prop) { - case HIST_POSITION: out[0] = s->cx; out[1] = s->cy; break; - case HIST_SCALE: out[0] = s->sx; out[1] = s->sy; break; - case HIST_ROTATION: out[0] = s->rotation; break; - case HIST_GROUP: out[0] = (float)s->group_id; break; - default: break; - } -} - -static void hist_apply_prop(shape_t *s, hist_prop_t prop, const float val[4]) { - switch (prop) { - case HIST_POSITION: s->cx = val[0]; s->cy = val[1]; break; - case HIST_SCALE: s->sx = val[0]; s->sy = val[1]; break; - case HIST_ROTATION: s->rotation = val[0]; break; - case HIST_GROUP: s->group_id = (int)val[0]; break; - default: break; - } -} - -static void hist_free_change(hist_change_t *c) { - if (c->vertex_data) FREE(c->vertex_data); - if (c->index_data) FREE(c->index_data); - if (c->ctrl_points) FREE(c->ctrl_points); - if (c->ctrl_handle_in) FREE(c->ctrl_handle_in); - if (c->ctrl_handle_out) FREE(c->ctrl_handle_out); - if (c->new_ctrl_points) FREE(c->new_ctrl_points); - if (c->new_ctrl_handle_in) FREE(c->new_ctrl_handle_in); - if (c->new_ctrl_handle_out) FREE(c->new_ctrl_handle_out); - memset(c, 0, sizeof(*c)); -} - -static void history_init(history_t *h) { - memset(h, 0, sizeof(*h)); - vec_init(&h->entries, sizeof(hist_entry_t)); - h->current = -1; -} - -static void history_destroy(history_t *h) { - for (int i = 0; i < h->entries.count; i++) { - hist_entry_t *e = (hist_entry_t*) vec_get(&h->entries, i); - if (e->changes) { - for (int j = 0; j < e->count; j++) - hist_free_change(&e->changes[j]); - FREE(e->changes); - } - } - vec_free(&h->entries); - memset(h, 0, sizeof(*h)); - h->current = -1; -} - -static void history_push_entry(history_t *h, hist_entry_t entry) { - while (h->entries.count > h->current + 1) { - hist_entry_t *e = (hist_entry_t*) vec_get(&h->entries, h->entries.count - 1); - if (e->changes) { - for (int j = 0; j < e->count; j++) - hist_free_change(&e->changes[j]); - FREE(e->changes); - } - vec_pop(&h->entries); - } - - *((hist_entry_t*) vec_push(&h->entries)) = entry; - h->current = h->entries.count - 1; - - while (h->entries.count > HISTORY_MAX_DEPTH) { - hist_entry_t *e = (hist_entry_t*) vec_get(&h->entries, 0); - if (e->changes) { - for (int j = 0; j < e->count; j++) - hist_free_change(&e->changes[j]); - FREE(e->changes); - } - vec_remove_ordered(&h->entries, 0); - h->current--; - } -} - -static void history_begin_edit(history_t *h, vector_t *shapes, - int shape_idx, hist_prop_t prop) { - if (h->capturing) { - shape_t *s = (shape_t*) vec_get(shapes, h->pending_shape_idx); - float new_val[4]; - hist_read_prop(s, h->pending_prop, new_val); - if (memcmp(h->pending_old, new_val, sizeof(float[4])) != 0) { - hist_entry_t entry = { .changes = NULL, .count = 1 }; - entry.changes = (hist_change_t*) ALLOC(sizeof(hist_change_t)); - memset(entry.changes, 0, sizeof(hist_change_t)); - entry.changes->shape_index = h->pending_shape_idx; - entry.changes->prop = h->pending_prop; - memcpy(entry.changes->old_val, h->pending_old, sizeof(float[4])); - memcpy(entry.changes->new_val, new_val, sizeof(float[4])); - history_push_entry(h, entry); - } - h->capturing = false; - } - - h->capturing = true; - h->pending_shape_idx = shape_idx; - h->pending_prop = prop; - shape_t *s = (shape_t*) vec_get(shapes, shape_idx); - hist_read_prop(s, prop, h->pending_old); -} - -static void history_end_edit(history_t *h, vector_t *shapes) { - if (!h->capturing) return; - - shape_t *s = (shape_t*) vec_get(shapes, h->pending_shape_idx); - float new_val[4]; - hist_read_prop(s, h->pending_prop, new_val); - - if (memcmp(h->pending_old, new_val, sizeof(float[4])) != 0) { - hist_entry_t entry = { .changes = NULL, .count = 1 }; - entry.changes = (hist_change_t*) ALLOC(sizeof(hist_change_t)); - memset(entry.changes, 0, sizeof(hist_change_t)); - entry.changes->shape_index = h->pending_shape_idx; - entry.changes->prop = h->pending_prop; - memcpy(entry.changes->old_val, h->pending_old, sizeof(float[4])); - memcpy(entry.changes->new_val, new_val, sizeof(float[4])); - history_push_entry(h, entry); - } - h->capturing = false; -} - -// -- batch API -- - -typedef struct { - hist_change_t *changes; - int count; - int capacity; -} hist_batch_t; - -static void history_batch_init(hist_batch_t *batch, int count) { - batch->changes = (hist_change_t*) ALLOC((size_t)count * sizeof(hist_change_t)); - memset(batch->changes, 0, (size_t)count * sizeof(hist_change_t)); - batch->count = 0; - batch->capacity = count; -} - -// For property changes (POSITION, SCALE, ROTATION, GROUP) -static void history_batch_add(hist_batch_t *batch, int shape_index, hist_prop_t prop, - const float old_val[4], const float new_val[4]) { - hist_change_t *c = &batch->changes[batch->count++]; - c->shape_index = shape_index; - c->prop = prop; - memcpy(c->old_val, old_val, sizeof(float[4])); - memcpy(c->new_val, new_val, sizeof(float[4])); -} - -// Snapshot a shape's full data into a change entry. -// old_val = { cx, cy, num_elements, 0 } -// new_val = { sx, sy, rotation, group_id } -// For procedural shapes (ctrl_count > 0), control points are deep-copied. -// For pen paths (ctrl_count == 0), raw vertex/index data is deep-copied. -static void hist_snapshot_shape_verts(hist_change_t *c, shape_t *s) { - c->old_val[0] = s->cx; - c->old_val[1] = s->cy; - c->old_val[2] = (float)(int)s->num_elements; - c->old_val[3] = 0; - c->new_val[0] = s->sx; - c->new_val[1] = s->sy; - c->new_val[2] = s->rotation; - c->new_val[3] = (float)s->group_id; - - strncpy(c->name, s->name, sizeof(c->name) - 1); - c->name[sizeof(c->name) - 1] = '\0'; - c->closed = s->closed; - - if (s->ctrl_count > 0) { - c->ctrl_count = s->ctrl_count; - c->ctrl_points = (shape_vertex_t*) ALLOC((size_t)c->ctrl_count * sizeof(shape_vertex_t)); - c->ctrl_handle_in = (shape_vertex_t*) ALLOC((size_t)c->ctrl_count * sizeof(shape_vertex_t)); - c->ctrl_handle_out = (shape_vertex_t*) ALLOC((size_t)c->ctrl_count * sizeof(shape_vertex_t)); - memcpy(c->ctrl_points, s->ctrl_points, (size_t)c->ctrl_count * sizeof(shape_vertex_t)); - memcpy(c->ctrl_handle_in, s->ctrl_handle_in, (size_t)c->ctrl_count * sizeof(shape_vertex_t)); - memcpy(c->ctrl_handle_out, s->ctrl_handle_out, (size_t)c->ctrl_count * sizeof(shape_vertex_t)); - } else { - 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)); - } -} - -// Append a CREATE or DELETE entry to a batch, snapshotting the shape's data. -static void history_batch_add_shape(hist_batch_t *batch, int shape_index, - hist_prop_t prop, shape_t *s) { - hist_change_t *c = &batch->changes[batch->count++]; - c->shape_index = shape_index; - c->prop = prop; - hist_snapshot_shape_verts(c, s); -} - -// Snapshot both old and new control point states for a HIST_EDIT change. -static void history_batch_add_edit(hist_batch_t *batch, int shape_index, shape_t *s, - const shape_vertex_t *old_pts, - const shape_vertex_t *old_hin, - const shape_vertex_t *old_hout, - int old_count) { - hist_change_t *c = &batch->changes[batch->count++]; - c->shape_index = shape_index; - c->prop = HIST_EDIT; - // Store shape metadata - c->old_val[0] = s->cx; - c->old_val[1] = s->cy; - c->old_val[2] = (float)(int)s->num_elements; - c->old_val[3] = 0; - c->new_val[0] = s->sx; - c->new_val[1] = s->sy; - c->new_val[2] = s->rotation; - c->new_val[3] = (float)s->group_id; - strncpy(c->name, s->name, sizeof(c->name) - 1); - c->name[sizeof(c->name) - 1] = '\0'; - c->closed = s->closed; - // Snapshot old ctrl state - c->ctrl_count = old_count; - c->ctrl_points = (shape_vertex_t*) ALLOC((size_t)old_count * sizeof(shape_vertex_t)); - c->ctrl_handle_in = (shape_vertex_t*) ALLOC((size_t)old_count * sizeof(shape_vertex_t)); - c->ctrl_handle_out = (shape_vertex_t*) ALLOC((size_t)old_count * sizeof(shape_vertex_t)); - memcpy(c->ctrl_points, old_pts, (size_t)old_count * sizeof(shape_vertex_t)); - memcpy(c->ctrl_handle_in, old_hin, (size_t)old_count * sizeof(shape_vertex_t)); - memcpy(c->ctrl_handle_out, old_hout, (size_t)old_count * sizeof(shape_vertex_t)); - // Snapshot new ctrl state - c->new_ctrl_count = s->ctrl_count; - c->new_ctrl_points = (shape_vertex_t*) ALLOC((size_t)s->ctrl_count * sizeof(shape_vertex_t)); - c->new_ctrl_handle_in = (shape_vertex_t*) ALLOC((size_t)s->ctrl_count * sizeof(shape_vertex_t)); - c->new_ctrl_handle_out = (shape_vertex_t*) ALLOC((size_t)s->ctrl_count * sizeof(shape_vertex_t)); - memcpy(c->new_ctrl_points, s->ctrl_points, (size_t)s->ctrl_count * sizeof(shape_vertex_t)); - memcpy(c->new_ctrl_handle_in, s->ctrl_handle_in, (size_t)s->ctrl_count * sizeof(shape_vertex_t)); - memcpy(c->new_ctrl_handle_out, s->ctrl_handle_out, (size_t)s->ctrl_count * sizeof(shape_vertex_t)); -} - -static void history_batch_commit(hist_batch_t *batch, history_t *h) { - hist_entry_t entry = { .changes = batch->changes, .count = batch->count }; - history_push_entry(h, entry); -} - -// Reconstruct a shape_t from a HIST_CREATE / HIST_DELETE change snapshot. -static shape_t hist_rebuild_shape_from_snapshot(shape_pool_ctx_t *sp, const hist_change_t *c) { - float cx = c->old_val[0], cy = c->old_val[1]; - float sx = c->new_val[0], sy = c->new_val[1]; - float rot = c->new_val[2]; - int gid = (int)c->new_val[3]; - shape_t s; - - if (c->ctrl_count > 0) { - memset(&s, 0, sizeof(s)); - s.cx = cx; - s.cy = cy; - s.sx = sx; - s.sy = sy; - s.rotation = rot; - shape_init_common(&s); - strncpy(s.name, c->name, sizeof(s.name) - 1); - s.closed = c->closed; - s.ctrl_count = c->ctrl_count; - s.ctrl_points = (shape_vertex_t*) ALLOC((size_t)c->ctrl_count * sizeof(shape_vertex_t)); - s.ctrl_handle_in = (shape_vertex_t*) ALLOC((size_t)c->ctrl_count * sizeof(shape_vertex_t)); - s.ctrl_handle_out = (shape_vertex_t*) ALLOC((size_t)c->ctrl_count * sizeof(shape_vertex_t)); - memcpy(s.ctrl_points, c->ctrl_points, (size_t)c->ctrl_count * sizeof(shape_vertex_t)); - memcpy(s.ctrl_handle_in, c->ctrl_handle_in, (size_t)c->ctrl_count * sizeof(shape_vertex_t)); - memcpy(s.ctrl_handle_out, c->ctrl_handle_out, (size_t)c->ctrl_count * sizeof(shape_vertex_t)); - shape_regenerate_from_ctrl(sp, &s); - } else { - memset(&s, 0, sizeof(s)); - s.cx = cx; - s.cy = cy; - s.rotation = rot; - s.num_verts = (uint32_t)c->vertex_count; - s.num_elements = (uint32_t)c->vertex_count; - s.sx = sx; - s.sy = sy; - int n = c->vertex_count; - s.verts = (shape_vertex_t*) ALLOC((size_t)n * sizeof(shape_vertex_t)); - s.indices = (uint16_t*) ALLOC((size_t)n * sizeof(uint16_t)); - memcpy(s.verts, c->vertex_data, (size_t)n * sizeof(shape_vertex_t)); - memcpy(s.indices, c->index_data, (size_t)n * sizeof(uint16_t)); - shape_init_common(&s); - strncpy(s.name, c->name, sizeof(s.name) - 1); - s.vertex_hash = hash_vertex_data(s.verts, s.num_elements); - shape_build_transform(sp, &s); - shape_update_aabb(&s); - shape_make_buffers(sp, &s); - } - s.rotation = rot; - s.group_id = gid; - return s; -} - -static void history_apply_entry(history_t *h, vector_t *shapes, - shape_pool_ctx_t *sp, vector_t *groups, - group_index_ctx_t *gi, bool forward) { - (void)h; - hist_entry_t *entry = (hist_entry_t*) vec_get(&h->entries, h->current); - bool has_shape_ops = false; - for (int i = 0; i < entry->count; i++) { - hist_prop_t p = entry->changes[i].prop; - if (p == HIST_CREATE || p == HIST_DELETE || - p == HIST_GROUP_CREATE || p == HIST_GROUP_DELETE) { has_shape_ops = true; break; } - } - - int start = 0, end = entry->count, step = 1; - if (has_shape_ops && !forward) { - start = entry->count - 1; - end = -1; - step = -1; - } - - for (int i = start; i != end; i += step) { - hist_change_t *c = &entry->changes[i]; - - if (c->prop == HIST_GROUP_CREATE) { - int gid = (int)c->new_val[0]; - int parent_id = (int)c->new_val[1]; - if (forward) { - group_t g = { .id = gid, .parent_id = parent_id, .collapsed = false }; - *((group_t*) vec_push(groups)) = g; - } else { - for (int g = 0; g < groups->count; g++) { - group_t *grp = (group_t*) vec_get(groups, g); - if (grp->id == gid) { - if (grp->member_indices) FREE(grp->member_indices); - vec_remove_ordered(groups, g); - break; - } - } - } - gi->dirty = true; - continue; - } - - if (c->prop == HIST_GROUP_DELETE) { - int gid = (int)c->old_val[0]; - int parent_id = (int)c->old_val[1]; - if (forward) { - for (int g = 0; g < groups->count; g++) { - group_t *grp = (group_t*) vec_get(groups, g); - if (grp->id == gid) { - if (grp->member_indices) FREE(grp->member_indices); - vec_remove_ordered(groups, g); - break; - } - } - } else { - group_t g = { .id = gid, .parent_id = parent_id, .collapsed = false }; - *((group_t*) vec_push(groups)) = g; - } - gi->dirty = true; - continue; - } - - if (c->prop == HIST_GROUP_REPARENT) { - int gid = (int)c->new_val[0]; - int new_pid = forward ? (int)c->new_val[1] : (int)c->old_val[1]; - for (int g = 0; g < groups->count; g++) { - group_t *grp = (group_t*) vec_get(groups, g); - if (grp->id == gid) { - grp->parent_id = new_pid; - break; - } - } - continue; - } - - if (c->prop == HIST_CREATE || c->prop == HIST_DELETE) { - bool adding = (c->prop == HIST_CREATE) ? forward : !forward; - if (adding) { - if (c->shape_index < 0 || c->shape_index > shapes->count) continue; - shape_t s = hist_rebuild_shape_from_snapshot(sp, c); - *((shape_t*) vec_insert(shapes, c->shape_index)) = s; - } else { - if (c->shape_index < shapes->count) { - shape_t *s = (shape_t*) vec_get(shapes, c->shape_index); - shape_shutdown(sp, s); - vec_remove_ordered(shapes, c->shape_index); - } - } - continue; - } - - if (c->prop == HIST_EDIT) { - if (c->shape_index >= shapes->count) continue; - shape_t *s = (shape_t*) vec_get(shapes, c->shape_index); - shape_vertex_t *pts = forward ? c->new_ctrl_points : c->ctrl_points; - shape_vertex_t *hin = forward ? c->new_ctrl_handle_in : c->ctrl_handle_in; - shape_vertex_t *hout = forward ? c->new_ctrl_handle_out : c->ctrl_handle_out; - int cc = forward ? c->new_ctrl_count : c->ctrl_count; - if (!pts || cc <= 0) continue; - FREE(s->ctrl_points); - FREE(s->ctrl_handle_in); - FREE(s->ctrl_handle_out); - s->ctrl_count = cc; - s->ctrl_points = (shape_vertex_t*) ALLOC((size_t)cc * sizeof(shape_vertex_t)); - s->ctrl_handle_in = (shape_vertex_t*) ALLOC((size_t)cc * sizeof(shape_vertex_t)); - s->ctrl_handle_out = (shape_vertex_t*) ALLOC((size_t)cc * sizeof(shape_vertex_t)); - memcpy(s->ctrl_points, pts, (size_t)cc * sizeof(shape_vertex_t)); - memcpy(s->ctrl_handle_in, hin, (size_t)cc * sizeof(shape_vertex_t)); - memcpy(s->ctrl_handle_out, hout, (size_t)cc * sizeof(shape_vertex_t)); - shape_regenerate_from_ctrl(sp, s); - shape_set_state(sp, s, s->hovered, s->selected); - continue; - } - - if (c->shape_index >= shapes->count) continue; - shape_t *s = (shape_t*) vec_get(shapes, c->shape_index); - hist_apply_prop(s, c->prop, forward ? c->new_val : c->old_val); - shape_regenerate(sp, s); - shape_set_state(sp, s, s->hovered, s->selected); - } -} - -static bool history_undo(history_t *h, vector_t *shapes, shape_pool_ctx_t *sp, - vector_t *groups, group_index_ctx_t *gi) { - if (h->current < 0 || h->current >= h->entries.count) return false; - history_apply_entry(h, shapes, sp, groups, gi, false); - h->current--; - return true; -} - -static bool history_redo(history_t *h, vector_t *shapes, shape_pool_ctx_t *sp, - vector_t *groups, group_index_ctx_t *gi) { - if (h->current + 1 >= h->entries.count) return false; - h->current++; - history_apply_entry(h, shapes, sp, groups, gi, true); - return true; -} - -#endif diff --git a/src/input.h b/src/input.h deleted file mode 100644 index 8a88846..0000000 --- a/src/input.h +++ /dev/null @@ -1,1248 +0,0 @@ -#ifndef INPUT_H -#define INPUT_H - -#include "api.h" -#include "types.h" -#include "interact.h" -#include "overlay.h" - -// -- edit mode helpers -- - -static int hit_test_edit_point(userdata_t *ud, float wx, float wy, - int *anchor_idx, bool *is_handle, bool *is_in) { - *anchor_idx = -1; - *is_handle = false; - *is_in = false; - int idx = ud->interact.editing_shape_idx; - if (idx < 0 || idx >= ud->shapes.count) return -1; - shape_t *s = (shape_t*) vec_get(&ud->shapes, idx); - if (s->ctrl_count <= 0) return -1; - - float as = EDIT_ANCHOR_SIZE_PX / ud->camera.zoom; - float hs = EDIT_HANDLE_SIZE_PX / ud->camera.zoom; - - // Hit test anchors - for (int i = 0; i < s->ctrl_count; i++) { - shape_vertex_t wp = local_to_world(s, s->ctrl_points[i].x, s->ctrl_points[i].y); - if (fabsf(wx - wp.x) <= as && fabsf(wy - wp.y) <= as) { - *anchor_idx = i; - return 0; // hit anchor - } - } - - // Hit test handles - for (int i = 0; i < s->ctrl_count; i++) { - shape_vertex_t wh_in = local_to_world(s, s->ctrl_handle_in[i].x, s->ctrl_handle_in[i].y); - shape_vertex_t wh_out = local_to_world(s, s->ctrl_handle_out[i].x, s->ctrl_handle_out[i].y); - if (fabsf(wx - wh_in.x) <= hs && fabsf(wy - wh_in.y) <= hs) { - *anchor_idx = i; - *is_handle = true; - *is_in = true; - return 1; // hit in-handle - } - if (fabsf(wx - wh_out.x) <= hs && fabsf(wy - wh_out.y) <= hs) { - *anchor_idx = i; - *is_handle = true; - *is_in = false; - return 1; // hit out-handle - } - } - - return -1; // no hit -} - -// -- pen tool helpers -- - -// Project world-space mouse position to local shape space (used when dragging -// edit anchors/handles within the edit mode shape). -static void world_to_local_shape(shape_t *s, float wx, float wy, float *lx, float *ly) { - float dx = wx - s->cx, dy = wy - s->cy; - *lx = (dx * s->cos_r + dy * s->sin_r) / s->sx; - *ly = (-dx * s->sin_r + dy * s->cos_r) / s->sy; -} - -// Generate a Catmull-Rom preview curve through the pen tool anchor points. -static void pen_update_preview(userdata_t *ud) { - int n = ud->pen.point_count; - if (n < 2) { - ud->pen.preview_count = 0; - return; - } - - shape_vertex_t *pts = ud->pen.points; - int subdivisions = 8; - int max_out = (n - 1) * subdivisions + 1; - if (max_out > PEN_PREVIEW_MAX_VERTS) { - subdivisions = (PEN_PREVIEW_MAX_VERTS - 1) / (n - 1); - if (subdivisions < 1) subdivisions = 1; - max_out = (n - 1) * subdivisions + 1; - } - - for (int seg = 0; seg < n - 1; seg++) { - shape_vertex_t p0 = pts[seg > 0 ? seg - 1 : 0]; - shape_vertex_t p1 = pts[seg]; - shape_vertex_t p2 = pts[seg + 1]; - shape_vertex_t p3 = pts[seg + 2 < n ? seg + 2 : n - 1]; - - int steps = subdivisions; - if (seg == n - 2) steps = max_out - seg * subdivisions; - for (int s = 0; s < steps; s++) { - float t = (float)s / (float)steps; - float t2 = t * t, t3 = t2 * t; - ud->pen.preview_verts[seg * subdivisions + s] = (shape_vertex_t){ - 0.5f * ((2.0f * p1.x) + - (-p0.x + p2.x) * t + - (2.0f * p0.x - 5.0f * p1.x + 4.0f * p2.x - p3.x) * t2 + - (-p0.x + 3.0f * p1.x - 3.0f * p2.x + p3.x) * t3), - 0.5f * ((2.0f * p1.y) + - (-p0.y + p2.y) * t + - (2.0f * p0.y - 5.0f * p1.y + 4.0f * p2.y - p3.y) * t2 + - (-p0.y + 3.0f * p1.y - 3.0f * p2.y + p3.y) * t3) - }; - } - } - ud->pen.preview_verts[max_out - 1] = pts[n - 1]; - ud->pen.preview_count = max_out; -} - -// Finish the pen tool: create a shape from collected world-space anchor points, -// setting up Bezier control points so the shape can be edited later. -static void pen_finish_shape(userdata_t *ud) { - int n = ud->pen.point_count; - if (n < 2) { ud->pen.drawing = false; ud->pen.point_count = 0; return; } - - shape_vertex_t *wpts = ud->pen.points; - - // Compute world-space AABB - float min_x = wpts[0].x, min_y = wpts[0].y; - float max_x = min_x, max_y = min_y; - for (int i = 1; i < n; i++) { - if (wpts[i].x < min_x) min_x = wpts[i].x; - if (wpts[i].y < min_y) min_y = wpts[i].y; - if (wpts[i].x > max_x) max_x = wpts[i].x; - if (wpts[i].y > max_y) max_y = wpts[i].y; - } - float hx = (max_x - min_x) * 0.5f; - float hy = (max_y - min_y) * 0.5f; - if (hx < 0.0001f) hx = 1.0f; - if (hy < 0.0001f) hy = 1.0f; - float cx = (min_x + max_x) * 0.5f; - float cy = (min_y + max_y) * 0.5f; - - shape_t s; - memset(&s, 0, sizeof(s)); - s.cx = cx; - s.cy = cy; - s.sx = hx; - s.sy = hy; - s.rotation = 0.0f; - shape_init_common(&s); - s.closed = true; - s.ctrl_count = n; - s.ctrl_points = (shape_vertex_t*) ALLOC((size_t)n * sizeof(shape_vertex_t)); - s.ctrl_handle_in = (shape_vertex_t*) ALLOC((size_t)n * sizeof(shape_vertex_t)); - s.ctrl_handle_out = (shape_vertex_t*) ALLOC((size_t)n * sizeof(shape_vertex_t)); - - // Convert world-space points to local space, set handles - for (int i = 0; i < n; i++) { - float lx = (wpts[i].x - cx) / hx; - float ly = (wpts[i].y - cy) / hy; - s.ctrl_points[i] = (shape_vertex_t){lx, ly}; - - // Sharp corners by default: handles at anchor position - s.ctrl_handle_in[i] = (shape_vertex_t){lx, ly}; - s.ctrl_handle_out[i] = (shape_vertex_t){lx, ly}; - } - - strncpy(s.name, "Path", sizeof(s.name) - 1); - shape_regenerate_from_ctrl(&ud->shape_pool, &s); - *((shape_t*) vec_push(&ud->shapes)) = s; - - // History - { - int idx = ud->shapes.count - 1; - shape_t *sp = (shape_t*) vec_get(&ud->shapes, idx); - hist_batch_t batch; - history_batch_init(&batch, 1); - history_batch_add_shape(&batch, idx, HIST_CREATE, sp); - history_batch_commit(&batch, &ud->history); - } - - ud->pen.drawing = false; - ud->pen.point_count = 0; - ud->pen.preview_count = 0; - spatial_mark_dirty(&ud->spatial_grid); - overlay_invalidate(ud); -} - -static void handle_left_down_ctrl_click(userdata_t *ud, float wx, float wy, float tol) -{ - int i = spatial_query_point(&ud->spatial_grid, &ud->shapes, wx, wy, tol); - if (i >= 0) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, i); - s->selected = !s->selected; - ud->interact.selected_count += s->selected ? 1 : -1; - overlay_invalidate(ud); - update_shape_states(ud); - } -} - -static void handle_left_down_resize_begin(userdata_t *ud, float wx, float wy, int resize_hit) -{ - float omin[2], omax[2]; - if (ud->interact.aabb_cached) { - omin[0] = ud->interact.cached_aabb[0]; omin[1] = ud->interact.cached_aabb[1]; - omax[0] = ud->interact.cached_aabb[2]; omax[1] = ud->interact.cached_aabb[3]; - } else { - selected_aabb(ud, &omin[0], &omin[1], &omax[0], &omax[1]); - } - float mid_x = (omin[0] + omax[0]) * 0.5f; - float mid_y = (omin[1] + omax[1]) * 0.5f; - float px[8] = {omax[0], mid_x, omin[0], omin[0], omin[0], mid_x, omax[0], omax[0]}; - float py[8] = {omax[1], omax[1], omax[1], mid_y, omin[1], omin[1], omin[1], mid_y}; - ud->interact.resize.pivot_x = px[resize_hit]; - ud->interact.resize.pivot_y = py[resize_hit]; - ud->interact.resize.start_wx = wx; - ud->interact.resize.start_wy = wy; - ud->interact.resize.total_scale_x = 1.0f; - ud->interact.resize.total_scale_y = 1.0f; - ud->interact.resize.mask_x = (resize_hit == 3 || resize_hit == 7 || (resize_hit & 1) == 0) ? 1.0f : 0.0f; - ud->interact.resize.mask_y = (resize_hit == 1 || resize_hit == 5 || (resize_hit & 1) == 0) ? 1.0f : 0.0f; - ud->interact.resize.dragging = true; - - float sum_sin = 0, sum_cos = 0; - int sel_n = 0; - for (int i = 0; i < ud->shapes.count; i++) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, i); - if (s->selected) { sum_sin += s->sin_r; sum_cos += s->cos_r; sel_n++; } - } - ud->interact.resize.angle = atan2f(sum_sin, sum_cos); - - ud->interact.resize.init = (resize_init_t*) ALLOC((size_t)sel_n * sizeof(resize_init_t)); - ud->interact.resize.init_count = sel_n; - int j = 0; - for (int i = 0; i < ud->shapes.count; i++) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, i); - if (s->selected) { - float sc = s->cos_r, ss = s->sin_r; - float hlx = (ud->interact.resize.start_wx - s->cx) * sc + (ud->interact.resize.start_wy - s->cy) * ss; - float hly = -(ud->interact.resize.start_wx - s->cx) * ss + (ud->interact.resize.start_wy - s->cy) * sc; - float plx = (ud->interact.resize.pivot_x - s->cx) * sc + (ud->interact.resize.pivot_y - s->cy) * ss; - float ply = -(ud->interact.resize.pivot_x - s->cx) * ss + (ud->interact.resize.pivot_y - s->cy) * sc; - - ud->interact.resize.init[j].idx = i; - ud->interact.resize.init[j].init_sx = s->sx; - ud->interact.resize.init[j].init_sy = s->sy; - ud->interact.resize.init[j].init_cx = s->cx; - ud->interact.resize.init[j].init_cy = s->cy; - ud->interact.resize.init[j].ext_x = hlx - plx; - ud->interact.resize.init[j].ext_y = hly - ply; - ud->interact.resize.init[j].lpi_x = plx; - ud->interact.resize.init[j].lpi_y = ply; - j++; - } - } -} - -static void handle_left_down_rotate_begin(userdata_t *ud, float wx, float wy) -{ - ud->interact.rotate.dragging = true; - ud->interact.rotate.start_angle = atan2f( - wy - ud->interact.rotate.center_y, - wx - ud->interact.rotate.center_x); - ud->interact.rotate.total_delta = 0.0f; - - ud->interact.drag_indices.count = 0; - for (int i = 0; i < ud->shapes.count; i++) { - if (((shape_t*) vec_get(&ud->shapes, i))->selected) - *(int*) vec_push(&ud->interact.drag_indices) = i; - } -} - -static void handle_left_down_move_begin(userdata_t *ud, float wx, float wy) -{ - ud->interact.move.dragging = true; - ud->interact.move.start_wx = wx; - ud->interact.move.start_wy = wy; - ud->interact.move.total_dx = 0; - ud->interact.move.total_dy = 0; - - ud->interact.drag_indices.count = 0; - for (int i = 0; i < ud->shapes.count; i++) { - if (((shape_t*) vec_get(&ud->shapes, i))->selected) - *(int*) vec_push(&ud->interact.drag_indices) = i; - } -} - -static void handle_left_down_select_or_marquee(userdata_t *ud, const sapp_event *event, float wx, float wy, float tol) -{ - ud->interact.select.active = true; - ud->interact.select.dragging = false; - ud->interact.select.start_x = event->mouse_x; - ud->interact.select.start_y = event->mouse_y; - ud->interact.select.current_x = event->mouse_x; - ud->interact.select.current_y = event->mouse_y; - - ud->interact.select.clicked_shape = - spatial_query_point(&ud->spatial_grid, &ud->shapes, wx, wy, tol); -} - -static void handle_right_down_pan_begin(userdata_t *ud, const sapp_event *event) -{ - ud->camera.pan_state.dragging = true; - ud->camera.pan_state.origin_x = event->mouse_x; - ud->camera.pan_state.origin_y = event->mouse_y; -} - -static void handle_resize_end(userdata_t *ud) -{ - int n = ud->interact.resize.init_count; - bool changed = false; - for (int j = 0; j < n; j++) { - resize_init_t *ini = &ud->interact.resize.init[j]; - shape_t *s = (shape_t*) vec_get(&ud->shapes, ini->idx); - if (s->sx != ini->init_sx || s->sy != ini->init_sy || - s->cx != ini->init_cx || s->cy != ini->init_cy) - changed = true; - } - if (changed) { - hist_batch_t batch; - history_batch_init(&batch, n * 2); - - for (int j = 0; j < n; j++) { - resize_init_t *ini = &ud->interact.resize.init[j]; - shape_t *s = (shape_t*) vec_get(&ud->shapes, ini->idx); - history_batch_add(&batch, ini->idx, HIST_POSITION, - (float[4]){ ini->init_cx, ini->init_cy }, - (float[4]){ s->cx, s->cy }); - history_batch_add(&batch, ini->idx, HIST_SCALE, - (float[4]){ ini->init_sx, ini->init_sy }, - (float[4]){ s->sx, s->sy }); - } - - history_batch_commit(&batch, &ud->history); - } - - FREE(ud->interact.resize.init); - ud->interact.resize.init = NULL; - ud->interact.resize.init_count = 0; - ud->interact.resize.dragging = false; - - update_shape_states(ud); - spatial_mark_dirty(&ud->spatial_grid); - ud->interact.aabb_cached = false; - overlay_invalidate(ud); -} - -static void handle_rotate_end(userdata_t *ud) -{ - int n = ud->interact.drag_indices.count; - if (n > 0 && ud->interact.rotate.total_delta != 0.0f) { - float cos_b = cosf(-ud->interact.rotate.total_delta); - float sin_b = sinf(-ud->interact.rotate.total_delta); - float cx = ud->interact.rotate.center_x; - float cy = ud->interact.rotate.center_y; - - hist_batch_t batch; - history_batch_init(&batch, n * 2); - - 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); - float dx = s->cx - cx; - float dy = s->cy - cy; - float old_cx = cx + dx * cos_b - dy * sin_b; - float old_cy = cy + dx * sin_b + dy * cos_b; - - history_batch_add(&batch, i, HIST_POSITION, - (float[4]){ old_cx, old_cy }, - (float[4]){ s->cx, s->cy }); - history_batch_add(&batch, i, HIST_ROTATION, - (float[4]){ s->rotation - ud->interact.rotate.total_delta }, - (float[4]){ s->rotation }); - } - - history_batch_commit(&batch, &ud->history); - } - - ud->interact.rotate.dragging = false; - ud->interact.drag_indices.count = 0; - update_shape_states(ud); - spatial_mark_dirty(&ud->spatial_grid); - ud->interact.aabb_cached = false; - overlay_invalidate(ud); -} - -static void handle_move_end(userdata_t *ud) -{ - int n = ud->interact.drag_indices.count; - if (n > 0 && (ud->interact.move.total_dx != 0.0f || ud->interact.move.total_dy != 0.0f)) { - hist_batch_t batch; - history_batch_init(&batch, n); - - 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); - history_batch_add(&batch, i, HIST_POSITION, - (float[4]){ s->cx - ud->interact.move.total_dx, s->cy - ud->interact.move.total_dy }, - (float[4]){ s->cx, s->cy }); - } - - history_batch_commit(&batch, &ud->history); - } - - ud->interact.move.dragging = false; - ud->interact.drag_indices.count = 0; - update_shape_states(ud); - spatial_mark_dirty(&ud->spatial_grid); - ud->interact.aabb_cached = false; - overlay_invalidate(ud); -} - -static void handle_select_end(userdata_t *ud) -{ - if (!ud->interact.select.dragging) { - if (ud->interact.select.clicked_shape >= 0) { - for (int i = 0; i < ud->shapes.count; i++) - ((shape_t*) vec_get(&ud->shapes, i))->selected = false; - ud->interact.selected_count = 0; - shape_t *clicked = (shape_t*) vec_get(&ud->shapes, ud->interact.select.clicked_shape); - clicked->selected = true; - ud->interact.selected_count = 1; - } else { - for (int i = 0; i < ud->shapes.count; i++) - ((shape_t*) vec_get(&ud->shapes, i))->selected = false; - ud->interact.selected_count = 0; - } - } - - ud->interact.select.active = false; - ud->interact.select.dragging = false; - update_shape_states(ud); - overlay_invalidate(ud); -} - -static void handle_pan_drag(userdata_t *ud, const sapp_event *event) -{ - ud->camera.pan[0] += event->mouse_dx; - ud->camera.pan[1] -= event->mouse_dy; - compute_mvp(&ud->camera, &ud->renderer.uniform.mvp); -} - -static void handle_resize_drag(userdata_t *ud, const sapp_event *event) -{ - float wx, wy; - screen_to_world(&ud->camera, event->mouse_x, event->mouse_y, &wx, &wy); - - float sx_total = 1.0f, sy_total = 1.0f; - - for (int j = 0; j < ud->interact.resize.init_count; j++) { - resize_init_t *ini = &ud->interact.resize.init[j]; - shape_t *s = (shape_t*) vec_get(&ud->shapes, ini->idx); - - float sc = s->cos_r, ss = s->sin_r; - float mlx = (wx - ini->init_cx) * sc + (wy - ini->init_cy) * ss; - float mly = -(wx - ini->init_cx) * ss + (wy - ini->init_cy) * sc; - - float cex = mlx - ini->lpi_x; - float cey = mly - ini->lpi_y; - - float scale_x = 1.0f, scale_y = 1.0f; - if (ud->interact.resize.mask_x && fabsf(ini->ext_x) >= 0.0001f) - scale_x = cex / ini->ext_x; - if (ud->interact.resize.mask_y && fabsf(ini->ext_y) >= 0.0001f) - scale_y = cey / ini->ext_y; - - s->sx = ini->init_sx * scale_x; - s->sy = ini->init_sy * scale_y; - s->cx = ini->init_cx - ini->lpi_x * (scale_x - 1.0f) * sc + ini->lpi_y * (scale_y - 1.0f) * ss; - s->cy = ini->init_cy - ini->lpi_x * (scale_x - 1.0f) * ss - ini->lpi_y * (scale_y - 1.0f) * sc; - - shape_regenerate(&ud->shape_pool, s); - shape_set_state(&ud->shape_pool, s, false, true); - - sx_total = scale_x; - sy_total = scale_y; - } - - ud->interact.resize.total_scale_x = sx_total; - ud->interact.resize.total_scale_y = sy_total; -} - -static void handle_rotate_drag(userdata_t *ud, const sapp_event *event) -{ - float wx, wy; - screen_to_world(&ud->camera, event->mouse_x, event->mouse_y, &wx, &wy); - float angle = atan2f(wy - ud->interact.rotate.center_y, - wx - ud->interact.rotate.center_x); - float delta = angle - ud->interact.rotate.start_angle; - if (delta > GLM_PIf) delta -= 2.0f * GLM_PIf; - else if (delta < -GLM_PIf) delta += 2.0f * GLM_PIf; - float inc = delta - ud->interact.rotate.total_delta; - - float cos_a = cosf(inc); - float sin_a = sinf(inc); - float cx = ud->interact.rotate.center_x; - float cy = ud->interact.rotate.center_y; - - 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); - float dx = s->cx - cx; - float dy = s->cy - cy; - s->cx = cx + dx * cos_a - dy * sin_a; - s->cy = cy + dx * sin_a + dy * cos_a; - s->rotation += inc; - shape_regenerate(&ud->shape_pool, s); - shape_set_state(&ud->shape_pool, s, false, true); - } - - ud->interact.rotate.total_delta = delta; -} - -static void handle_move_drag(userdata_t *ud, const sapp_event *event) -{ - float wx, wy; - screen_to_world(&ud->camera, event->mouse_x, event->mouse_y, &wx, &wy); - float dx = wx - ud->interact.move.start_wx; - float dy = wy - ud->interact.move.start_wy; - float delta_x = dx - ud->interact.move.total_dx; - float delta_y = dy - ud->interact.move.total_dy; - - 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); - s->cx += delta_x; - s->cy += delta_y; - shape_retranslate(&ud->shape_pool, s); - shape_set_state(&ud->shape_pool, s, false, true); - } - - ud->interact.move.total_dx = dx; - ud->interact.move.total_dy = dy; -} - -static void handle_marquee_drag(userdata_t *ud, const sapp_event *event) -{ - ud->interact.select.current_x = event->mouse_x; - ud->interact.select.current_y = event->mouse_y; - float dx = ud->interact.select.current_x - ud->interact.select.start_x; - float dy = ud->interact.select.current_y - ud->interact.select.start_y; - if (dx * dx + dy * dy > DRAG_THRESHOLD_SQ) { - ud->interact.select.dragging = true; - } - - if (ud->interact.select.dragging) { - float wx1, wy1, wx2, wy2; - screen_to_world(&ud->camera, ud->interact.select.start_x, ud->interact.select.start_y, &wx1, &wy1); - screen_to_world(&ud->camera, ud->interact.select.current_x, ud->interact.select.current_y, &wx2, &wy2); - float min_x = fminf(wx1, wx2), min_y = fminf(wy1, wy2); - float max_x = fmaxf(wx1, wx2), max_y = fmaxf(wy1, wy2); - - ud->interact.selected_count = spatial_query_rect_select( - &ud->spatial_grid, &ud->shapes, - min_x, min_y, max_x, max_y); - - for (int i = 0; i < ud->shapes.count; i++) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, i); - shape_set_state(&ud->shape_pool, s, false, s->selected); - } - } -} - -static void handle_hover(userdata_t *ud, const sapp_event *event) -{ - float wx, wy; - screen_to_world(&ud->camera, event->mouse_x, event->mouse_y, &wx, &wy); - const float tol = ud->camera.hover_tol; - - int hovered = spatial_query_point(&ud->spatial_grid, - &ud->shapes, wx, wy, tol); - - if (hovered != ud->interact.hovered_shape) { - ud->interact.hovered_shape = hovered; - EM_ASM({ Module._cartograph_canvas.style.cursor = $0 ? 'pointer' : 'default'; }, hovered >= 0); - } - - for (int i = 0; i < ud->shapes.count; i++) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, i); - shape_set_state(&ud->shape_pool, s, (i == hovered), s->selected); - } -} - -// -- clipboard -- - -static shape_t clipboard_deep_copy_shape(const shape_t *src) -{ - shape_t dst = *src; - dst.ctrl_points = NULL; - dst.ctrl_handle_in = NULL; - dst.ctrl_handle_out = NULL; - 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)); - if (src->ctrl_count > 0) { - dst.ctrl_points = (shape_vertex_t*) ALLOC((size_t)src->ctrl_count * sizeof(shape_vertex_t)); - dst.ctrl_handle_in = (shape_vertex_t*) ALLOC((size_t)src->ctrl_count * sizeof(shape_vertex_t)); - dst.ctrl_handle_out = (shape_vertex_t*) ALLOC((size_t)src->ctrl_count * sizeof(shape_vertex_t)); - memcpy(dst.ctrl_points, src->ctrl_points, (size_t)src->ctrl_count * sizeof(shape_vertex_t)); - memcpy(dst.ctrl_handle_in, src->ctrl_handle_in, (size_t)src->ctrl_count * sizeof(shape_vertex_t)); - memcpy(dst.ctrl_handle_out, src->ctrl_handle_out, (size_t)src->ctrl_count * sizeof(shape_vertex_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[i].ctrl_points); - FREE(cb->shapes[i].ctrl_handle_in); - FREE(cb->shapes[i].ctrl_handle_out); - } - FREE(cb->shapes); - memset(cb, 0, sizeof(*cb)); -} - -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; - - 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); - } -} - -static void handle_paste(userdata_t *ud) -{ - clipboard_t *cb = &ud->clipboard; - if (cb->shape_count == 0) return; - - 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; - s.group_id = 0; - s.selected = true; - ud->interact.selected_count++; - - shape_retranslate(&ud->shape_pool, &s); - *((shape_t*) vec_push(&ud->shapes)) = s; - ud->shape_pool.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); - - group_rebuild_members(&ud->group_idx, &ud->groups, &ud->shapes); - spatial_mark_dirty(&ud->spatial_grid); - ud->interact.aabb_cached = false; - overlay_invalidate(ud); - update_shape_states(ud); -} - -// -- public event handlers -- - -static bool handle_key_down(userdata_t *ud, const sapp_event *event) -{ - if (event->modifiers & SAPP_MODIFIER_CTRL) { - if (event->key_code == SAPP_KEYCODE_Z || event->key_code == SAPP_KEYCODE_W) { - if (history_undo(&ud->history, &ud->shapes, &ud->shape_pool, - &ud->groups, &ud->group_idx)) { - group_rebuild_members(&ud->group_idx, &ud->groups, &ud->shapes); - interact_structural_change(ud); - } - return true; - } - if (event->key_code == SAPP_KEYCODE_Y) { - if (history_redo(&ud->history, &ud->shapes, &ud->shape_pool, - &ud->groups, &ud->group_idx)) { - group_rebuild_members(&ud->group_idx, &ud->groups, &ud->shapes); - interact_structural_change(ud); - } - return true; - } - if (event->key_code == SAPP_KEYCODE_C) { - handle_copy(ud); - return true; - } - if (event->key_code == SAPP_KEYCODE_V) { - handle_paste(ud); - return true; - } - if (event->key_code == SAPP_KEYCODE_G) { - if (event->modifiers & SAPP_MODIFIER_SHIFT) { - // Ungroup: collect unique group IDs of selected shapes - if (ud->interact.selected_count == 0) return true; - 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; - bool found = false; - for (int j = 0; j < n_gids; j++) { - if (gids[j] == s->group_id) { found = true; break; } - } - if (!found) gids[n_gids++] = s->group_id; - } - if (n_gids == 0) { FREE(gids); return true; } - - int *parents = (int*) ALLOC((size_t)n_gids * sizeof(int)); - for (int j = 0; j < n_gids; j++) { - group_t *grp = find_group(&ud->group_idx, &ud->groups, gids[j]); - parents[j] = grp ? grp->parent_id : 0; - } - - int touched = 0; - for (int i = 0; i < ud->shapes.count; i++) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, i); - if (s->group_id == 0) continue; - for (int j = 0; j < n_gids; j++) { - if (s->group_id == gids[j]) { touched++; break; } - } - } - hist_batch_t batch; - history_batch_init(&batch, touched + n_gids); - for (int i = 0; i < ud->shapes.count; i++) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, i); - if (s->group_id == 0) continue; - int old_gid = s->group_id; - int parent = 0; - for (int j = 0; j < n_gids; j++) { - if (old_gid == gids[j]) { parent = parents[j]; break; } - } - bool in_touched = false; - for (int j = 0; j < n_gids; j++) { - if (old_gid == gids[j]) { in_touched = true; break; } - } - if (!in_touched) continue; - history_batch_add(&batch, i, HIST_GROUP, - (float[4]){ (float)old_gid }, - (float[4]){ (float)parent }); - s->group_id = parent; - } - for (int j = 0; j < n_gids; j++) { - hist_change_t *gc = &batch.changes[batch.count++]; - gc->shape_index = -1; - gc->prop = HIST_GROUP_DELETE; - gc->old_val[0] = (float)gids[j]; - gc->old_val[1] = (float)parents[j]; - } - history_batch_commit(&batch, &ud->history); - - for (int j = 0; j < n_gids; j++) { - for (int g = 0; g < ud->groups.count; g++) { - group_t *grp = (group_t*) vec_get(&ud->groups, g); - if (grp->parent_id == gids[j]) - grp->parent_id = parents[j]; - } - } - - for (int j = n_gids - 1; j >= 0; j--) { - for (int g = 0; g < ud->groups.count; g++) { - group_t *grp = (group_t*) vec_get(&ud->groups, g); - if (grp->id == gids[j]) { - if (grp->member_indices) FREE(grp->member_indices); - vec_remove_ordered(&ud->groups, g); - break; - } - } - } - - group_index_rebuild(&ud->group_idx, &ud->groups); - group_rebuild_members(&ud->group_idx, &ud->groups, &ud->shapes); - - FREE(parents); - FREE(gids); - ud->ui.list_last_shape = -1; - overlay_invalidate(ud); - update_shape_states(ud); - } else { - // Group selected shapes - if (ud->interact.selected_count < 2) return true; - int gid = ud->next_group_id++; - int n = ud->shapes.count; - - int touched = 0; - for (int i = 0; i < n; i++) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, i); - if (s->selected) touched++; - } - - hist_batch_t batch; - history_batch_init(&batch, touched + 1); - for (int i = 0; i < n; i++) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, i); - if (!s->selected) continue; - history_batch_add(&batch, i, HIST_GROUP, - (float[4]){ (float)s->group_id }, - (float[4]){ (float)gid }); - s->group_id = gid; - } - { - hist_change_t *gc = &batch.changes[batch.count++]; - gc->shape_index = -1; - gc->prop = HIST_GROUP_CREATE; - gc->new_val[0] = (float)gid; - gc->new_val[1] = 0.0f; - } - history_batch_commit(&batch, &ud->history); - - group_t new_grp = { .id = gid, .parent_id = 0, .collapsed = false }; - *((group_t*) vec_push(&ud->groups)) = new_grp; - group_index_rebuild(&ud->group_idx, &ud->groups); - group_rebuild_members(&ud->group_idx, &ud->groups, &ud->shapes); - - ud->ui.list_last_shape = -1; - overlay_invalidate(ud); - update_shape_states(ud); - } - return true; - } - } - if (event->key_code == SAPP_KEYCODE_GRAVE_ACCENT) { - ud->ui.log_show = !ud->ui.log_show; - return true; - } - if (event->key_code == SAPP_KEYCODE_DELETE || event->key_code == SAPP_KEYCODE_BACKSPACE) { - if (ud->interact.selected_count > 0) { - int cap = ud->shapes.count; - int *indices = (int*) ALLOC((size_t)cap * sizeof(int)); - int collected = 0; - - for (int i = 0; i < ud->shapes.count; i++) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, i); - if (!s->selected) continue; - indices[collected++] = i; - } - - hist_batch_t batch; - history_batch_init(&batch, collected); - for (int j = collected - 1; j >= 0; j--) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, indices[j]); - history_batch_add_shape(&batch, indices[j], HIST_DELETE, s); - } - history_batch_commit(&batch, &ud->history); - - for (int j = collected - 1; j >= 0; j--) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, indices[j]); - shape_shutdown(&ud->shape_pool, s); - vec_remove_ordered(&ud->shapes, indices[j]); - } - ud->shape_pool.pool_dirty = true; - - FREE(indices); - - ud->interact.selected_count = 0; - - for (int g = ud->groups.count - 1; g >= 0; g--) { - group_t *grp = (group_t*) vec_get(&ud->groups, g); - int members = 0; - for (int i = 0; i < ud->shapes.count; i++) { - if (((shape_t*) vec_get(&ud->shapes, i))->group_id == grp->id) - members++; - } - if (members == 0) { - if (grp->member_indices) FREE(grp->member_indices); - vec_remove_ordered(&ud->groups, g); - } - } - group_index_rebuild(&ud->group_idx, &ud->groups); - group_rebuild_members(&ud->group_idx, &ud->groups, &ud->shapes); - interact_structural_change(ud); - update_shape_states(ud); - } - return true; - } - - if (event->key_code == SAPP_KEYCODE_ESCAPE) { - if (ud->pen.drawing) { - ud->pen.drawing = false; - ud->pen.point_count = 0; - ud->pen.preview_count = 0; - overlay_invalidate(ud); - return true; - } - if (ud->interact.editing_shape_idx >= 0) { - ud->interact.editing_shape_idx = -1; - overlay_invalidate(ud); - return true; - } - return true; - } - - if (event->key_code == SAPP_KEYCODE_ENTER) { - if (ud->pen.drawing && ud->pen.point_count >= 2) { - pen_finish_shape(ud); - return true; - } - return true; - } - - return false; -} - -static void handle_resize(userdata_t *ud, const sapp_event *event) -{ - (void)event; - ud->camera.width = sapp_width(); - ud->camera.height = sapp_height(); - ud->camera.half_width = ud->camera.width * 0.5f; - ud->camera.half_height = ud->camera.height * 0.5f; - compute_mvp(&ud->camera, &ud->renderer.uniform.mvp); -} - -static void handle_mouse_down(userdata_t *ud, const sapp_event *event) -{ - if (event->mouse_button == SAPP_MOUSEBUTTON_LEFT) { - float wx, wy; - screen_to_world(&ud->camera, event->mouse_x, event->mouse_y, &wx, &wy); - const float tol = 4.0f / ud->camera.zoom; - - // Pen tool: click to add points, close path near first point - if (ud->ui.active_tool == TOOL_PEN && !(event->modifiers & SAPP_MODIFIER_CTRL)) { - if (!ud->pen.drawing) { - ud->pen.drawing = true; - ud->pen.point_count = 0; - ud->pen.points[ud->pen.point_count++] = (shape_vertex_t){wx, wy}; - pen_update_preview(ud); - overlay_invalidate(ud); - } else { - // Check if clicking near the first point to close the path - float dx = wx - ud->pen.points[0].x; - float dy = wy - ud->pen.points[0].y; - float close_dist = PEN_CLOSE_PX / ud->camera.zoom; - if (ud->pen.point_count >= 3 && dx * dx + dy * dy <= close_dist * close_dist) { - pen_finish_shape(ud); - } else if (ud->pen.point_count < PEN_MAX_CONTROL_POINTS) { - ud->pen.points[ud->pen.point_count++] = (shape_vertex_t){wx, wy}; - pen_update_preview(ud); - overlay_invalidate(ud); - } - } - return; - } - - // Edit mode: drag anchors/handles, or click empty space to exit - if (ud->interact.editing_shape_idx >= 0) { - int anchor_idx; - bool is_handle, is_in; - int kind = hit_test_edit_point(ud, wx, wy, &anchor_idx, &is_handle, &is_in); - if (kind >= 0) { - // Snapshot old ctrl state for undo - shape_t *es = (shape_t*) vec_get(&ud->shapes, ud->interact.editing_shape_idx); - int n = es->ctrl_count; - ud->interact.edit_dragging = true; - ud->interact.edit_drag_idx = anchor_idx; - if (is_handle) { - ud->interact.edit_handle_dragging = true; - ud->interact.edit_handle_idx = anchor_idx; - ud->interact.edit_handle_is_in = is_in; - } else { - ud->interact.edit_handle_dragging = false; - } - if (ud->interact.edit_saved_ctrl) { FREE(ud->interact.edit_saved_ctrl); FREE(ud->interact.edit_saved_hin); FREE(ud->interact.edit_saved_hout); } - ud->interact.edit_saved_count = n; - ud->interact.edit_saved_ctrl = (shape_vertex_t*) ALLOC((size_t)n * sizeof(shape_vertex_t)); - ud->interact.edit_saved_hin = (shape_vertex_t*) ALLOC((size_t)n * sizeof(shape_vertex_t)); - ud->interact.edit_saved_hout = (shape_vertex_t*) ALLOC((size_t)n * sizeof(shape_vertex_t)); - memcpy(ud->interact.edit_saved_ctrl, es->ctrl_points, (size_t)n * sizeof(shape_vertex_t)); - memcpy(ud->interact.edit_saved_hin, es->ctrl_handle_in, (size_t)n * sizeof(shape_vertex_t)); - memcpy(ud->interact.edit_saved_hout, es->ctrl_handle_out, (size_t)n * sizeof(shape_vertex_t)); - } else { - // Clicked empty space — exit edit mode - ud->interact.editing_shape_idx = -1; - overlay_invalidate(ud); - } - return; - } - - if (!(event->modifiers & SAPP_MODIFIER_CTRL) && ud->ui.active_tool >= TOOL_CIRCLE) { - shape_t s; - switch (ud->ui.active_tool) { - case TOOL_CIRCLE: - s = shape_circle(&ud->shape_pool, wx, wy, 100.0f); - break; - case TOOL_RECTANGLE: - s = shape_rectangle(&ud->shape_pool, wx, wy, 200.0f, 100.0f); - break; - default: - return; - } - *((shape_t*) vec_push(&ud->shapes)) = s; - spatial_mark_dirty(&ud->spatial_grid); - overlay_invalidate(ud); - - { - int idx = ud->shapes.count - 1; - shape_t *sp = (shape_t*) vec_get(&ud->shapes, idx); - hist_batch_t batch; - history_batch_init(&batch, 1); - history_batch_add_shape(&batch, idx, HIST_CREATE, sp); - history_batch_commit(&batch, &ud->history); - } - return; - } - - // Double-click detection for edit mode - { - int hit_shape = spatial_query_point(&ud->spatial_grid, - &ud->shapes, wx, wy, tol); - - bool dbl = (ud->time - ud->interact.last_click_time < DOUBLE_CLICK_TIME && - hit_shape >= 0 && - hit_shape == ud->interact.last_click_shape_idx); - - ud->interact.last_click_time = ud->time; - ud->interact.last_click_shape_idx = hit_shape; - - if (dbl && hit_shape >= 0) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, hit_shape); - if (s->ctrl_count > 0) { - ud->interact.editing_shape_idx = hit_shape; - overlay_invalidate(ud); - return; - } - } - } - - if (event->modifiers & SAPP_MODIFIER_CTRL) { - handle_left_down_ctrl_click(ud, wx, wy, tol); - } else { - int resize_hit = hit_test_resize_handles(ud, wx, wy, tol); - if (resize_hit >= 0) { - handle_left_down_resize_begin(ud, wx, wy, resize_hit); - } else { - float grip = HANDLE_RADIUS_PX / ud->camera.zoom + tol; - float dcx = wx - ud->interact.rotate.center_x; - float dcy = wy - ud->interact.rotate.center_y; - float dist = sqrtf(dcx * dcx + dcy * dcy); - bool on_handle = (ud->interact.selected_count > 0) && - (fabsf(dist - ud->interact.rotate.handle_radius) <= grip); - - if (on_handle) { - handle_left_down_rotate_begin(ud, wx, wy); - } else { - int hit = spatial_query_point(&ud->spatial_grid, - &ud->shapes, wx, wy, tol); - int clicked_selected = (hit >= 0 && - ((shape_t*) vec_get(&ud->shapes, hit))->selected) ? hit : -1; - - bool in_aabb = false; - if (clicked_selected < 0 && ud->interact.selected_count >= 2) { - float omin[2], omax[2]; - if (ud->interact.aabb_cached) { - omin[0] = ud->interact.cached_aabb[0]; omin[1] = ud->interact.cached_aabb[1]; - omax[0] = ud->interact.cached_aabb[2]; omax[1] = ud->interact.cached_aabb[3]; - } else { - selected_aabb(ud, &omin[0], &omin[1], &omax[0], &omax[1]); - } - float pad = 8.0f / ud->camera.zoom; - omin[0] -= pad; omin[1] -= pad; - omax[0] += pad; omax[1] += pad; - in_aabb = (wx >= omin[0] && wx <= omax[0] && wy >= omin[1] && wy <= omax[1]); - } - - if (clicked_selected >= 0 || in_aabb) { - handle_left_down_move_begin(ud, wx, wy); - } else { - handle_left_down_select_or_marquee(ud, event, wx, wy, tol); - } - } - } - } - - update_shape_states(ud); - } else if (event->modifiers & SAPP_MODIFIER_RMB) { - handle_right_down_pan_begin(ud, event); - } -} - -static void handle_mouse_up(userdata_t *ud, const sapp_event *event) -{ - (void)event; - - // Edit mode drag end — push a history entry for the vertex edit - if (ud->interact.edit_dragging) { - int ei = ud->interact.editing_shape_idx; - if (ei >= 0 && ei < ud->shapes.count && ud->interact.edit_saved_ctrl) { - shape_t *es = (shape_t*) vec_get(&ud->shapes, ei); - hist_batch_t batch; - history_batch_init(&batch, 1); - history_batch_add_edit(&batch, ei, es, - ud->interact.edit_saved_ctrl, - ud->interact.edit_saved_hin, - ud->interact.edit_saved_hout, - ud->interact.edit_saved_count); - history_batch_commit(&batch, &ud->history); - } - FREE(ud->interact.edit_saved_ctrl); - FREE(ud->interact.edit_saved_hin); - FREE(ud->interact.edit_saved_hout); - ud->interact.edit_saved_ctrl = NULL; - ud->interact.edit_saved_hin = NULL; - ud->interact.edit_saved_hout = NULL; - ud->interact.edit_saved_count = 0; - ud->interact.edit_dragging = false; - ud->interact.edit_handle_dragging = false; - overlay_invalidate(ud); - return; - } - - if (ud->interact.resize.dragging) { - handle_resize_end(ud); - } else if (ud->interact.rotate.dragging) { - handle_rotate_end(ud); - } else if (ud->interact.move.dragging) { - handle_move_end(ud); - } else if (ud->interact.select.active) { - handle_select_end(ud); - } - - ud->camera.pan_state.dragging = false; -} - -static void handle_mouse_move(userdata_t *ud, const sapp_event *event) -{ - ud->mouse_x = event->mouse_x; - ud->mouse_y = event->mouse_y; - - // Edit mode drag - if (ud->interact.edit_dragging && ud->interact.editing_shape_idx >= 0) { - int idx = ud->interact.editing_shape_idx; - if (idx < ud->shapes.count) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, idx); - float wx, wy, lx, ly; - screen_to_world(&ud->camera, event->mouse_x, event->mouse_y, &wx, &wy); - world_to_local_shape(s, wx, wy, &lx, &ly); - - int ai = ud->interact.edit_drag_idx; - if (ud->interact.edit_handle_dragging) { - if (ud->interact.edit_handle_is_in) - s->ctrl_handle_in[ai] = (shape_vertex_t){lx, ly}; - else - s->ctrl_handle_out[ai] = (shape_vertex_t){lx, ly}; - } else { - // Move the anchor; handles follow by the same delta - float dlx = lx - s->ctrl_points[ai].x; - float dly = ly - s->ctrl_points[ai].y; - s->ctrl_points[ai] = (shape_vertex_t){lx, ly}; - s->ctrl_handle_in[ai].x += dlx; - s->ctrl_handle_in[ai].y += dly; - s->ctrl_handle_out[ai].x += dlx; - s->ctrl_handle_out[ai].y += dly; - } - shape_regenerate_from_ctrl(&ud->shape_pool, s); - overlay_invalidate(ud); - } - return; - } - - // Pen tool preview - if (ud->pen.drawing) { - // Preview already updated on mouse down; add live preview to last segment - if (ud->pen.point_count >= 1 && ud->pen.point_count < PEN_MAX_CONTROL_POINTS) { - float wx, wy; - screen_to_world(&ud->camera, event->mouse_x, event->mouse_y, &wx, &wy); - // Temporarily add the mouse position as the next point for preview - shape_vertex_t saved = ud->pen.points[ud->pen.point_count]; - ud->pen.points[ud->pen.point_count] = (shape_vertex_t){wx, wy}; - ud->pen.point_count++; - pen_update_preview(ud); - ud->pen.point_count--; - ud->pen.points[ud->pen.point_count] = saved; - overlay_invalidate(ud); - } - return; - } - - if (ud->camera.pan_state.dragging) { - handle_pan_drag(ud, event); - } else if (ud->interact.resize.dragging) { - handle_resize_drag(ud, event); - } else if (ud->interact.rotate.dragging) { - handle_rotate_drag(ud, event); - } else if (ud->interact.move.dragging) { - handle_move_drag(ud, event); - } else if (ud->interact.select.active) { - handle_marquee_drag(ud, event); - } else { - handle_hover(ud, event); - } -} - -static void handle_scroll_zoom(userdata_t *ud, const sapp_event *event) -{ - if ((ud->camera.zoom >= CAMERA_ZOOM_MAX && event->scroll_y > 0.0f) || - (ud->camera.zoom <= CAMERA_ZOOM_MIN && event->scroll_y < 0.0f)) - return; - - float wx, wy; - screen_to_world(&ud->camera, event->mouse_x, event->mouse_y, &wx, &wy); - - const float diff = expf(event->scroll_y * 0.1f); - float new_zoom = ud->camera.zoom * diff; - ud->camera.zoom = new_zoom < CAMERA_ZOOM_MIN ? CAMERA_ZOOM_MIN : (new_zoom > CAMERA_ZOOM_MAX ? CAMERA_ZOOM_MAX : new_zoom); - ud->camera.hover_tol = SHAPE_HOVER_PX / ud->camera.zoom; - - const float sx = event->mouse_x - ud->camera.half_width; - const float sy = ud->camera.half_height - event->mouse_y; - ud->camera.pan[0] = sx - wx * ud->camera.zoom; - ud->camera.pan[1] = sy - wy * ud->camera.zoom; - - overlay_invalidate(ud); - compute_mvp(&ud->camera, &ud->renderer.uniform.mvp); -} - -#endif diff --git a/src/interact.h b/src/interact.h deleted file mode 100644 index ddd5069..0000000 --- a/src/interact.h +++ /dev/null @@ -1,113 +0,0 @@ -#ifndef INTERACT_H -#define INTERACT_H - -#include "api.h" -#include "types.h" - -// Forward-declared: defined in overlay.h (included after interact.h in api.h). -static void overlay_invalidate(userdata_t *ud); - -// Called after any operation that changes shape count or structure (undo, -// redo, delete, paste, group, ungroup). Invalidates cached state so the next -// frame rebuilds the spatial grid, overlay geometry, and GPU instance data. -static void interact_structural_change(userdata_t *ud) -{ - ud->interact.hovered_shape = -1; - ud->shape_pool.pool_dirty = true; - spatial_mark_dirty(&ud->spatial_grid); - ud->interact.aabb_cached = false; - ud->ui.display_cache_dirty = true; - overlay_invalidate(ud); -} - -static void selected_aabb(userdata_t *ud, float *min_x, float *min_y, - float *max_x, float *max_y) -{ - bool first = true; - for (int i = 0; i < ud->shapes.count; i++) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, i); - if (!s->selected) continue; - float smin_x = s->cx - s->aabb_hx; - float smin_y = s->cy - s->aabb_hy; - float smax_x = s->cx + s->aabb_hx; - float smax_y = s->cy + s->aabb_hy; - if (first) { - *min_x = smin_x; *min_y = smin_y; - *max_x = smax_x; *max_y = smax_y; - first = false; - } else { - if (smin_x < *min_x) *min_x = smin_x; - if (smin_y < *min_y) *min_y = smin_y; - if (smax_x > *max_x) *max_x = smax_x; - if (smax_y > *max_y) *max_y = smax_y; - } - } -} - -static void update_shape_states(userdata_t *ud) -{ - ud->shape_pool.states_dirty = true; -} - -static void select_group_recursive(userdata_t *ud, int gid) -{ - 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(&ud->group_idx,s->group_id, gid, &ud->groups)) { - s->selected = true; - ud->interact.selected_count++; - } - } -} - -static void deselect_and_select_group_recursive(userdata_t *ud, int gid) -{ - for (int i = 0; i < ud->shapes.count; i++) - ((shape_t*) vec_get(&ud->shapes, i))->selected = false; - ud->interact.selected_count = 0; - select_group_recursive(ud, gid); -} - -static void toggle_group_recursive(userdata_t *ud, int gid) -{ - int total = 0, sel = 0; - 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(&ud->group_idx,s->group_id, gid, &ud->groups)) { - total++; - if (s->selected) sel++; - } - } - bool all_sel = (sel == total && total > 0); - 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(&ud->group_idx,s->group_id, gid, &ud->groups)) { - s->selected = !all_sel; - ud->interact.selected_count += s->selected ? 1 : -1; - } - } -} - -static int hit_test_resize_handles(userdata_t *ud, float wx, float wy, float tol) -{ - if (ud->interact.selected_count <= 0) return -1; - float omin[2], omax[2]; - if (ud->interact.aabb_cached) { - omin[0] = ud->interact.cached_aabb[0]; omin[1] = ud->interact.cached_aabb[1]; - omax[0] = ud->interact.cached_aabb[2]; omax[1] = ud->interact.cached_aabb[3]; - } else { - selected_aabb(ud, &omin[0], &omin[1], &omax[0], &omax[1]); - } - float hs = CORNER_SIZE_PX / ud->camera.zoom * 0.5f + tol; - float mid_x = (omin[0] + omax[0]) * 0.5f; - float mid_y = (omin[1] + omax[1]) * 0.5f; - float hx[8] = {omin[0], mid_x, omax[0], omax[0], omax[0], mid_x, omin[0], omin[0]}; - float hy[8] = {omin[1], omin[1], omin[1], mid_y, omax[1], omax[1], omax[1], mid_y}; - for (int h = 0; h < 8; h++) { - if (fabsf(wx - hx[h]) <= hs && fabsf(wy - hy[h]) <= hs) - return h; - } - return -1; -} - -#endif diff --git a/src/main.c b/src/main.c index f6dfa7d..bd9cd4d 100644 --- a/src/main.c +++ b/src/main.c @@ -1,120 +1,62 @@ #include "api.h" -static void log_capture(const char* tag, uint32_t log_level, uint32_t log_item, - const char* message, uint32_t line_nr, const char* filename, - void* user_data) +typedef struct uniform_t { + float x, y; + float zoom; +} uniform_t; + +typedef struct renderer_t { + sg_pipeline pipeline; + sg_pass_action clear_pass; + uniform_t uniforms; +} renderer_t; + +typedef struct compute_t { + sg_pipeline pipeline; + sg_bindings bindings; +} compute_t; + +typedef struct userdata_t { + scene_t scene; + renderer_t renderer; + compute_t compute; +} userdata_t; + +static void log_fn(const char* tag, uint32_t log_level, uint32_t log_item_id, const char* message_or_null, uint32_t line_nr, const char* filename_or_null, void* user_data) { - userdata_t* ud = (userdata_t*)user_data; - - const char* level_str; - switch (log_level) { - case 0: level_str = "panic"; break; - case 1: level_str = "error"; break; - case 2: level_str = "warn"; break; - default:level_str = "info"; break; - } - - char buf[256]; - int n = snprintf(buf, sizeof(buf), "[%s][%s][id:%u]", tag, level_str, log_item); - if (filename) { - n += snprintf(buf + n, sizeof(buf) - n, " %s:%u:", filename, line_nr); - } - if (message && n < (int)sizeof(buf) - 2) { - snprintf(buf + n, sizeof(buf) - n, " %s", message); - } - - int idx = ud->ui.log_head; - strncpy(ud->ui.log_ring[idx].text, buf, 255); - ud->ui.log_ring[idx].text[255] = 0; - ud->ui.log_ring[idx].level = log_level; - ud->ui.log_ring[idx].hash = 0; - ud->ui.log_head = (idx + 1) % LOG_RING_SIZE; - if (ud->ui.log_count < LOG_RING_SIZE) ud->ui.log_count++; - - fprintf(stderr, "%s\n", buf); - if (log_level <= 1) ud->ui.log_show = true; + if(log_level < 2) return; + fprintf(stderr, "[%s - %s]: (%d) %s \nat %s:%d", tag, log_level == 0 ? "FATAL" : log_level == 1 ? "ERROR" : "WARNING", log_item_id, message_or_null, filename_or_null, line_nr); } -static uint64_t fnv1a_64(const char *msg) { - uint64_t h = 14695981039346656037ULL; - while (*msg) { - h ^= (uint64_t)(unsigned char)*msg++; - h *= 1099511628211ULL; - } - return h; -} - -static void panel_log_impl(void *ud_v, int level, const char *msg) { - userdata_t *ud = (userdata_t*)ud_v; - - // Use a 64-bit message hash to skip the O(n) strcmp scan for most - // non-matches. Debug-level messages (3) skip dedup entirely — they are - // expected to repeat and the linear scan cost isn't worth it. - if (level < 3) { - uint64_t h = fnv1a_64(msg); - int total = ud->ui.log_count < LOG_RING_SIZE ? ud->ui.log_count : LOG_RING_SIZE; - for (int i = 0; i < total; i++) { - if (ud->ui.log_ring[i].hash == h && - strcmp(ud->ui.log_ring[i].text, msg) == 0) return; - } - ud->ui.log_ring[ud->ui.log_head].hash = h; - } else { - ud->ui.log_ring[ud->ui.log_head].hash = 0; - } - - 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 draw_scene(userdata_t* ud) { - float dt = (float)sapp_frame_duration(); - float instant_fps = dt > 0.0001f ? 1.0f / dt : 0.0f; - ud->debug.fps_immediate += (instant_fps - ud->debug.fps_immediate) * 0.1f; - - int idx = ud->debug.frame_time_head; - if (ud->debug.frame_time_count == 60) { - ud->debug.frame_time_sum -= ud->debug.frame_times[idx]; - } else { - ud->debug.frame_time_count++; + /* if(ud->scene.dirty) + { + //sg_update_buffer(ud->compute.shared_buffer, SG_RANGE(ud->scene)); + ud->scene.dirty = false; } - ud->debug.frame_times[idx] = dt; - ud->debug.frame_time_sum += dt; - ud->debug.frame_time_head = (idx + 1) % 60; - ud->debug.fps_average = ud->debug.frame_time_sum > 0.0001f - ? (float)ud->debug.frame_time_count / ud->debug.frame_time_sum : 0.0f; + + sg_begin_pass(&(sg_pass){ .compute = true, label = "compute-pass" }); + sg_apply_pipeline(ud->compute.pipeline); + sg_apply_uniforms(0, &SG_RANGE(ud->renderer.uniforms)); + sg_apply_bindings(&ud->compute.bindings); + + sg_dispatch(1, 1, 1); + + sg_end_pass(); */ } static void frame(void* _userdata) { userdata_t* ud = (userdata_t*) _userdata; - shape_begin_frame(); - meter_fps(ud); - ud->time += (double)sapp_frame_duration(); - spatial_rebuild(&ud->spatial_grid, &ud->shapes); - - float sel_cx, sel_cy, sel_hw, sel_hh, sel_angle; - shape_vertex_t overlay_verts[5]; - bool has_overlay, show_handle; - compute_overlay_geometry(ud, overlay_verts, &sel_cx, &sel_cy, - &sel_hw, &sel_hh, &sel_angle, &has_overlay, &show_handle); - - upload_overlay_buffers(ud, overlay_verts, sel_cx, sel_cy, - sel_hw, sel_hh, sel_angle, has_overlay, show_handle); + draw_scene(ud); sg_begin_pass(&(sg_pass){ .action = ud->renderer.clear_pass, .swapchain = sglue_swapchain(), }); - draw_shapes(ud); - draw_overlay_and_handles(ud, has_overlay, show_handle); - simgui_new_frame(&(simgui_frame_desc_t){ .width = sapp_width(), .height = sapp_height(), @@ -122,10 +64,10 @@ static void frame(void* _userdata) .dpi_scale = sapp_dpi_scale(), }); - draw_top_panel(ud); - draw_shape_list_panel(ud); - draw_properties_panel(ud); - draw_log_panel(ud); + igBegin("Framerate", (bool*) true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); + igText("%.0f FPS", 1 / sapp_frame_duration()); + igText("%.3fms", sapp_frame_duration() * 1000); + igEnd(); simgui_render(); sg_end_pass(); @@ -136,317 +78,150 @@ static void init(void* _userdata) { userdata_t* ud = (userdata_t*) _userdata; - rand_seed(&ud->rand_ctx, 1); - - ud->panel_log_ctx.fn = panel_log_impl; - ud->panel_log_ctx.ud = ud; - - sg_desc sgdesc = { + sg_setup(&(sg_desc){ .environment = sglue_environment(), - .logger.func = log_capture, - .logger.user_data = ud, - .uniform_buffer_size = 16 * 1024 * 1024, - }; - sg_setup(&sgdesc); - if (!sg_isvalid()) { - fprintf(stderr, "Failed to create Sokol GFX context!\n"); - exit(-1); - } + .logger.func = log_fn, + }); simgui_setup(&(simgui_desc_t){0}); - const vec2 quad[4] = { - {-2.0f, 2.0f}, - {2.0f, 2.0f}, - {2.0f, -2.0f}, - {-2.0f, -2.0f}, - }; - const vec2 uv[4] = { - {0.0f, 1.0f}, - {1.0f, 1.0f}, - {1.0f, 0.0f}, - {0.0f, 0.0f}, - }; - const uint16_t indices[] = { - 0, 1, 2, 0, 2, 3, - }; + ud->scene = (scene_t) {0}; + if(!scene_init(&ud->scene, 128, 1024)) return; - ud->camera.width = sapp_width(); - ud->camera.height = sapp_height(); - ud->camera.half_width = ud->camera.width * 0.5f; - ud->camera.half_height = ud->camera.height * 0.5f; - glm_vec2_zero(ud->camera.pan); - ud->camera.zoom = 0.5f; - ud->camera.hover_tol = SHAPE_HOVER_PX / ud->camera.zoom; - - ud->renderer.shader = sg_make_shader(&(sg_shader_desc) { - .vertex_func = { - .source = (const char*) src_shaders_sprite_wgsl, - .entry = "vs_main", - }, - .fragment_func = { - .source = (const char*) src_shaders_sprite_wgsl, - .entry = "fs_main", - }, - .views = { - [0] = { - .texture = { - .stage = SG_SHADERSTAGE_FRAGMENT, - .image_type = SG_IMAGETYPE_2D, - .wgsl_group1_binding_n = 0, - .sample_type = SG_IMAGESAMPLETYPE_FLOAT, + ud->compute = (compute_t) { + .pipeline = sg_make_pipeline(&(sg_pipeline_desc){ + .compute = true, + .shader = sg_make_shader(&(sg_shader_desc) { + .compute_func = { + .source = (const char*) src_shaders_compute_wgsl, + .entry = "main", }, - }, - [1] = { + .views = { + [0] = { + .storage_buffer = { + .stage = SG_SHADERSTAGE_COMPUTE, + .wgsl_group1_binding_n = 0, + .readonly = true, + } + }, + [1] = { + .storage_buffer = { + .stage = SG_SHADERSTAGE_COMPUTE, + .wgsl_group1_binding_n = 1, + .readonly = true, + } + }, + [2] = { + .storage_buffer = { + .stage = SG_SHADERSTAGE_COMPUTE, + .wgsl_group1_binding_n = 2, + .readonly = false, + } + } + }, + .uniform_blocks = { + [0] = { + .size = sizeof(uniform_t), + .stage = SG_SHADERSTAGE_COMPUTE, + .wgsl_group0_binding_n = 0, + }, + [1] = { + .size = sizeof(tile_ID), + .stage = SG_SHADERSTAGE_COMPUTE, + .wgsl_group0_binding_n = 1, + }, + }, + .label = "SDF Compute Shader", + }), + .label = "SDF Compute Pipeline", + }), + .bindings = (sg_bindings) { + .views[0] = sg_make_view(&(sg_view_desc) { + .label = "Segments view", .storage_buffer = { - .stage = SG_SHADERSTAGE_VERTEX, - .readonly = true, - .wgsl_group1_binding_n = 2, + .buffer = sg_make_buffer(&(sg_buffer_desc) { + .label = "Segment buffer", + .usage = { + .storage_buffer = true, + .stream_update = true, + }, + .size = sizeof(segment_t), + }), }, - }, - }, - .samplers = { - [0] = { - .stage = SG_SHADERSTAGE_FRAGMENT, - .sampler_type = SG_SAMPLERTYPE_FILTERING, - .wgsl_group1_binding_n = 1, - } - }, - .texture_sampler_pairs = { - [0] = { - .stage = SG_SHADERSTAGE_FRAGMENT, - .view_slot = 0, - .sampler_slot = 0, - } - }, - .uniform_blocks = { - [0] = { - .size = sizeof(uniform_t), - .stage = SG_SHADERSTAGE_VERTEX, - .wgsl_group0_binding_n = 0, - }, - }, - .attrs[0] = { - .base_type = SG_SHADERATTRBASETYPE_FLOAT, - }, - .label = "Sprite shader", - }); - - ud->renderer.clear_pass = (sg_pass_action) { - .colors[0] = { .clear_value = { 0.0f, 0.0f, 0.0f, 1.0f }, .load_action = SG_LOADACTION_CLEAR } - }; - ud->renderer.pipeline = sg_make_pipeline(&(sg_pipeline_desc) { - .shader = ud->renderer.shader, - .index_type = SG_INDEXTYPE_UINT16, - .layout.attrs = { - [0].format = SG_VERTEXFORMAT_FLOAT2, - [1].format = SG_VERTEXFORMAT_FLOAT2, - }, - .label = "Sprite pipeline", - }); - ud->renderer.uniform = (uniform_t) { - .mvp = { - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }, + }), + .views[1] = sg_make_view(&(sg_view_desc) { + .label = "Shapes metadata view", + .storage_buffer = { + .buffer = sg_make_buffer(&(sg_buffer_desc) { + .label = "Shapes metadata buffer", + .usage = { + .storage_buffer = true, + .stream_update = true, + }, + .size = sizeof(shape_meta_t), + }), + }, + }), + .views[2] = sg_make_view(&(sg_view_desc) { + .label = "SDF field view", + .storage_buffer = { + .buffer = sg_make_buffer(&(sg_buffer_desc) { + .label = "SDF field buffer", + .size = sizeof(float), + .usage.storage_buffer = true, + }), + }, + }), + } }; - shape_init_pipeline(&ud->pipelines, &ud->panel_log_ctx); - - vec_init(&ud->shapes, sizeof(shape_t)); - vec_init(&ud->groups, sizeof(group_t)); - vec_init(&ud->interact.drag_indices, sizeof(int)); - spatial_init(&ud->spatial_grid); - ud->interact.selected_count = 0; - ud->interact.hovered_shape = -1; - ud->interact.select.active = false; - ud->interact.select.dragging = false; - ud->ui.right_panel_w = 300; - ud->ui.left_panel_w = 220; - ud->ui.list_last_shape = -1; - ud->ui.list_prev_count = -1; - ud->ui.display_cache = NULL; - ud->ui.display_cache_len = 0; - ud->ui.display_cache_dirty = true; - ud->interact.move.dragging = false; - ud->interact.rotate.dragging = false; - ud->interact.resize.dragging = false; - ud->interact.resize.angle = 0.0f; - ud->interact.resize.init = NULL; - overlay_invalidate(ud); - ud->interact.resize.init_count = 0; - ud->next_group_id = 1; - ud->time = 0.0; - ud->interact.last_click_time = 0.0; - ud->interact.last_click_shape_idx = -1; - ud->map_w = 0; - ud->map_h = 0; - ud->ui.log_head = 0; - ud->ui.log_count = 0; - ud->ui.log_show = true; - ud->ui.active_tool = TOOL_SELECT; - - { - ud->rect_vbuf = sg_make_buffer(&(sg_buffer_desc){ - .size = 5 * sizeof(shape_vertex_t), - .usage = { .stream_update = true }, - .label = "Sel rect verts", - }); - uint16_t rect_idx[5] = {0, 1, 2, 3, 4}; - ud->rect_ibuf = sg_make_buffer(&(sg_buffer_desc){ - .usage = {.index_buffer = true}, - .data = {rect_idx, sizeof(rect_idx)}, - .label = "Sel rect indices", - }); - } - - { - const int n = HANDLE_CIRCLE_SEGMENTS + 1; - uint16_t handle_idx[HANDLE_CIRCLE_SEGMENTS + 1]; - for (int i = 0; i < n; i++) handle_idx[i] = (uint16_t)i; - ud->handle_vbuf = sg_make_buffer(&(sg_buffer_desc){ - .size = (size_t)n * sizeof(shape_vertex_t), - .usage = { .stream_update = true }, - .label = "Handle verts", - }); - ud->handle_ibuf = sg_make_buffer(&(sg_buffer_desc){ - .usage = {.index_buffer = true}, - .data = {handle_idx, sizeof(handle_idx)}, - .label = "Handle indices", - }); - } - - { - ud->corner_vbuf = sg_make_buffer(&(sg_buffer_desc){ - .size = 40 * sizeof(shape_vertex_t), - .usage = { .stream_update = true }, - .label = "Corner verts", - }); - uint16_t ci[40]; - for (int i = 0; i < 40; i++) ci[i] = (uint16_t)i; - ud->corner_ibuf = sg_make_buffer(&(sg_buffer_desc){ - .usage = {.index_buffer = true}, - .data = {ci, sizeof(ci)}, - .label = "Corner indices", - }); - } - - *((shape_t*) vec_push(&ud->shapes)) = shape_circle(&ud->shape_pool, 300.0f, 0.0f, 120.0f); - - // Pen tool buffers - { - ud->pen_vbuf = sg_make_buffer(&(sg_buffer_desc){ - .size = PEN_PREVIEW_MAX_VERTS * sizeof(shape_vertex_t), - .usage = { .stream_update = true }, - .label = "Pen preview verts", - }); - uint16_t *pen_idx = (uint16_t*) ALLOC(PEN_PREVIEW_MAX_VERTS * sizeof(uint16_t)); - for (int i = 0; i < PEN_PREVIEW_MAX_VERTS; i++) pen_idx[i] = (uint16_t)i; - ud->pen_ibuf = sg_make_buffer(&(sg_buffer_desc){ - .usage = {.index_buffer = true}, - .data = {pen_idx, (size_t)PEN_PREVIEW_MAX_VERTS * sizeof(uint16_t)}, - .label = "Pen preview indices", - }); - FREE(pen_idx); - memset(&ud->pen, 0, sizeof(ud->pen)); - } - - // Edit mode buffers - { - int amax = PEN_MAX_CONTROL_POINTS * 5; // anchors - int hmax = PEN_MAX_CONTROL_POINTS * 10; // handles (2 per anchor, 5 verts each) - int lmax = PEN_MAX_CONTROL_POINTS * 4; // lines (2 per anchor, 2 verts each) - - ud->ed_anchor_vbuf = sg_make_buffer(&(sg_buffer_desc){ - .size = (size_t)amax * sizeof(shape_vertex_t), - .usage = { .stream_update = true }, - .label = "Edit anchor verts", - }); - ud->ed_handle_vbuf = sg_make_buffer(&(sg_buffer_desc){ - .size = (size_t)hmax * sizeof(shape_vertex_t), - .usage = { .stream_update = true }, - .label = "Edit handle verts", - }); - ud->ed_handle_line_vbuf = sg_make_buffer(&(sg_buffer_desc){ - .size = (size_t)lmax * sizeof(shape_vertex_t), - .usage = { .stream_update = true }, - .label = "Edit handle lines", - }); - - int ibmax = hmax > lmax ? hmax : lmax; - if (amax > ibmax) ibmax = amax; - uint16_t *ed_idx = (uint16_t*) ALLOC((size_t)ibmax * sizeof(uint16_t)); - for (int i = 0; i < ibmax; i++) ed_idx[i] = (uint16_t)i; - ud->ed_shared_ibuf = sg_make_buffer(&(sg_buffer_desc){ - .usage = {.index_buffer = true}, - .data = {ed_idx, (size_t)ibmax * sizeof(uint16_t)}, - .label = "Edit shared indices", - }); - FREE(ed_idx); - - ud->ed_anchor_count = 0; - ud->ed_handle_count = 0; - ud->ed_handle_line_count = 0; - ud->interact.editing_shape_idx = -1; - } - history_init(&ud->history); - - EM_ASM({ - window.addEventListener('keydown', function(e) { - if (e.ctrlKey && !e.altKey && !e.metaKey) { - if (e.key === 'z' || e.key === 'y' || e.key === 'c' || e.key === 'v' || e.key === 'g') { - e.preventDefault(); - } - } - }, true); - Module._cartograph_canvas = document.querySelector('canvas'); - }); - - compute_mvp(&ud->camera, &ud->renderer.uniform.mvp); + ud->renderer = (renderer_t) { + .pipeline = sg_make_pipeline(&(sg_pipeline_desc){ + .shader = sg_make_shader(&(sg_shader_desc) { + .vertex_func = { + .source = (const char*) src_shaders_display_wgsl, + .entry = "vs_main", + }, + .fragment_func = { + .source = (const char*) src_shaders_display_wgsl, + .entry = "fs_main", + }, + .views = { + [0] = { + .storage_buffer = { + .stage = SG_SHADERSTAGE_FRAGMENT, + .wgsl_group1_binding_n = 1, + .readonly = true, + } + } + }, + .uniform_blocks = { + [0] = { + .size = sizeof(uniform_t), + .stage = SG_SHADERSTAGE_VERTEX, + .wgsl_group0_binding_n = 0, + }, + [1] = { + .size = sizeof(tile_ID), + .stage = SG_SHADERSTAGE_VERTEX, + .wgsl_group0_binding_n = 1, + }, + }, + .label = "Display Shader", + }), + .label = "Render pipeline", + }), + .clear_pass = (sg_pass_action) { + .colors[0] = { .clear_value = { 0.0f, 0.0f, 0.0f, 1.0f }, .load_action = SG_LOADACTION_CLEAR } + } + }; } static void cleanup(void* _userdata) { userdata_t* ud = (userdata_t*) _userdata; - for (int i = 0; i < ud->shapes.count; i++) { - shape_shutdown(&ud->shape_pool, (shape_t*) vec_get(&ud->shapes, i)); - } - spatial_destroy(&ud->spatial_grid); - vec_free(&ud->shapes); - group_shutdown_members(&ud->groups); - vec_free(&ud->groups); - vec_free(&ud->interact.drag_indices); - group_index_shutdown(&ud->group_idx); - history_destroy(&ud->history); - if (ud->interact.edit_saved_ctrl) { FREE(ud->interact.edit_saved_ctrl); FREE(ud->interact.edit_saved_hin); FREE(ud->interact.edit_saved_hout); } - if (ud->interact.resize.init) FREE(ud->interact.resize.init); - sg_destroy_buffer(ud->rect_vbuf); - sg_destroy_buffer(ud->rect_ibuf); - sg_destroy_buffer(ud->handle_vbuf); - sg_destroy_buffer(ud->handle_ibuf); - sg_destroy_buffer(ud->corner_vbuf); - sg_destroy_buffer(ud->corner_ibuf); - sg_destroy_buffer(ud->pen_vbuf); - sg_destroy_buffer(ud->pen_ibuf); - sg_destroy_buffer(ud->ed_anchor_vbuf); - sg_destroy_buffer(ud->ed_handle_vbuf); - sg_destroy_buffer(ud->ed_handle_line_vbuf); - sg_destroy_buffer(ud->ed_shared_ibuf); - sg_destroy_pipeline(ud->renderer.pipeline); - sg_destroy_shader(ud->renderer.shader); - shape_pool_shutdown(&ud->shape_pool); - shape_shutdown_pipeline(&ud->pipelines); - - 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->ui.display_cache); - - FREE(ud); + scene_shutdown(&ud->scene); + free(ud); simgui_shutdown(); sg_shutdown(); @@ -457,17 +232,17 @@ static void event(const sapp_event* event, void* _userdata) userdata_t* ud = (userdata_t*) _userdata; if (event->type == SAPP_EVENTTYPE_RESIZED) { - handle_resize(ud, event); + //handle_resize(ud, event); } if (event->type == SAPP_EVENTTYPE_KEY_DOWN) { - if (handle_key_down(ud, event)) return; + //if (handle_key_down(ud, event)) return; } if (simgui_handle_event(event)) return; switch (event->type) { - case SAPP_EVENTTYPE_MOUSE_DOWN: + /* case SAPP_EVENTTYPE_MOUSE_DOWN: handle_mouse_down(ud, event); break; case SAPP_EVENTTYPE_MOUSE_UP: @@ -478,7 +253,7 @@ static void event(const sapp_event* event, void* _userdata) break; case SAPP_EVENTTYPE_MOUSE_SCROLL: handle_scroll_zoom(ud, event); - break; + break; */ default: break; } @@ -486,7 +261,7 @@ static void event(const sapp_event* event, void* _userdata) sapp_desc sokol_main(int argc, char* argv[]) { - userdata_t* userdata = (userdata_t*) ALLOC(sizeof(userdata_t)); + userdata_t* userdata = (userdata_t*) malloc(sizeof(userdata_t)); (void)argc; (void)argv; @@ -497,10 +272,6 @@ sapp_desc sokol_main(int argc, char* argv[]) .cleanup_userdata_cb = cleanup, .event_userdata_cb = event, .window_title = "Sokol", - .logger.func = slog_func, - .allocator = { - .alloc_fn = smemtrack_alloc, - .free_fn = smemtrack_free, - }, + .logger.func = log_fn, }; } diff --git a/src/overlay.h b/src/overlay.h deleted file mode 100644 index 2e0aaeb..0000000 --- a/src/overlay.h +++ /dev/null @@ -1,247 +0,0 @@ -#ifndef OVERLAY_H -#define OVERLAY_H - -#include "api.h" -#include "types.h" -#include "interact.h" - -static void overlay_invalidate(userdata_t *ud) -{ - memset(&ud->overlay_upload, 1, sizeof(ud->overlay_upload)); -} - -static void compute_overlay_geometry(userdata_t *ud, - shape_vertex_t overlay_verts[5], - float *sel_cx, float *sel_cy, float *sel_hw, float *sel_hh, float *sel_angle, - bool *has_overlay, bool *show_handle) -{ - *has_overlay = false; - *sel_cx = *sel_cy = *sel_angle = 0; - *sel_hw = *sel_hh = 0; - - // Suppress selection/resize/rotate overlay during vertex edit mode - if (ud->interact.editing_shape_idx >= 0) { *show_handle = false; return; } - - if (ud->interact.select.active && ud->interact.select.dragging) { - float wx1, wy1, wx2, wy2; - screen_to_world(&ud->camera, ud->interact.select.start_x, ud->interact.select.start_y, &wx1, &wy1); - screen_to_world(&ud->camera, ud->interact.select.current_x, ud->interact.select.current_y, &wx2, &wy2); - float x1 = fminf(wx1, wx2), y1 = fminf(wy1, wy2); - float x2 = fmaxf(wx1, wx2), y2 = fmaxf(wy1, wy2); - overlay_verts[0] = (shape_vertex_t){x1, y1}; - overlay_verts[1] = (shape_vertex_t){x2, y1}; - overlay_verts[2] = (shape_vertex_t){x2, y2}; - overlay_verts[3] = (shape_vertex_t){x1, y2}; - overlay_verts[4] = (shape_vertex_t){x1, y1}; - *has_overlay = true; - } else if (ud->interact.selected_count >= 1) { - if (ud->interact.move.dragging && ud->interact.aabb_cached) { - float dx = ud->interact.move.total_dx; - float dy = ud->interact.move.total_dy; - float omin_x = ud->interact.cached_aabb[0] + dx; - float omin_y = ud->interact.cached_aabb[1] + dy; - float omax_x = ud->interact.cached_aabb[2] + dx; - float omax_y = ud->interact.cached_aabb[3] + dy; - float pad = 8.0f / ud->camera.zoom; - overlay_verts[0] = (shape_vertex_t){omin_x - pad, omin_y - pad}; - overlay_verts[1] = (shape_vertex_t){omax_x + pad, omin_y - pad}; - overlay_verts[2] = (shape_vertex_t){omax_x + pad, omax_y + pad}; - overlay_verts[3] = (shape_vertex_t){omin_x - pad, omax_y + pad}; - overlay_verts[4] = overlay_verts[0]; - *sel_cx = (omin_x + omax_x) * 0.5f; - *sel_cy = (omin_y + omax_y) * 0.5f; - *sel_hw = (omax_x - omin_x) * 0.5f + pad; - *sel_hh = (omax_y - omin_y) * 0.5f + pad; - *sel_angle = 0; - *has_overlay = true; - } else if (ud->interact.selected_count == 1) { - for (int i = 0; i < ud->shapes.count; i++) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, i); - if (!s->selected) continue; - *sel_cx = s->cx; *sel_cy = s->cy; - *sel_angle = s->rotation; - float x1, y1, x2, y2; - selected_aabb(ud, &x1, &y1, &x2, &y2); - *sel_hw = (x2 - x1) * 0.5f; - *sel_hh = (y2 - y1) * 0.5f; - ud->interact.cached_aabb[0] = x1; ud->interact.cached_aabb[1] = y1; - ud->interact.cached_aabb[2] = x2; ud->interact.cached_aabb[3] = y2; - ud->interact.aabb_cached = true; - overlay_verts[0] = (shape_vertex_t){x1, y1}; - overlay_verts[1] = (shape_vertex_t){x2, y1}; - overlay_verts[2] = (shape_vertex_t){x2, y2}; - overlay_verts[3] = (shape_vertex_t){x1, y2}; - overlay_verts[4] = overlay_verts[0]; - break; - } - } else { - float omin[2], omax[2]; - float sum_sin = 0, sum_cos = 0; - for (int i = 0; i < ud->shapes.count; i++) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, i); - if (!s->selected) continue; - sum_sin += s->sin_r; - sum_cos += s->cos_r; - } - selected_aabb(ud, &omin[0], &omin[1], &omax[0], &omax[1]); - ud->interact.cached_aabb[0] = omin[0]; ud->interact.cached_aabb[1] = omin[1]; - ud->interact.cached_aabb[2] = omax[0]; ud->interact.cached_aabb[3] = omax[1]; - ud->interact.aabb_cached = true; - float pad = 8.0f / ud->camera.zoom; - omin[0] -= pad; omin[1] -= pad; - omax[0] += pad; omax[1] += pad; - *sel_cx = (omin[0] + omax[0]) * 0.5f; - *sel_cy = (omin[1] + omax[1]) * 0.5f; - *sel_hw = (omax[0] - omin[0]) * 0.5f; - *sel_hh = (omax[1] - omin[1]) * 0.5f; - *sel_angle = atan2f(sum_sin, sum_cos); - - overlay_verts[0] = (shape_vertex_t){omin[0], omin[1]}; - overlay_verts[1] = (shape_vertex_t){omax[0], omin[1]}; - overlay_verts[2] = (shape_vertex_t){omax[0], omax[1]}; - overlay_verts[3] = (shape_vertex_t){omin[0], omax[1]}; - overlay_verts[4] = overlay_verts[0]; - } - *has_overlay = true; - } - - *show_handle = ud->interact.selected_count > 0 && !ud->interact.select.active; -} - -static void upload_overlay_buffers(userdata_t *ud, - const shape_vertex_t overlay_verts[5], - float sel_cx, float sel_cy, float sel_hw, float sel_hh, float sel_angle, - bool has_overlay, bool show_handle) -{ - bool need_upload = ud->overlay_upload.rect || - ud->interact.move.dragging || ud->interact.rotate.dragging || - ud->interact.resize.dragging || ud->interact.select.active || - (ud->interact.editing_shape_idx >= 0); - - static shape_vertex_t ed_anchor_verts[128 * 5]; - static shape_vertex_t ed_handle_verts[256 * 5]; - static shape_vertex_t ed_line_verts[256 * 2]; - - if (has_overlay && need_upload) { - sg_update_buffer(ud->rect_vbuf, &(sg_range){overlay_verts, (size_t)5 * sizeof(shape_vertex_t)}); - } - - if (show_handle) { - float pad = HANDLE_OFFSET_PX / ud->camera.zoom; - float radius = sqrtf(sel_hw * sel_hw + sel_hh * sel_hh) + pad; - - ud->interact.rotate.center_x = sel_cx; - ud->interact.rotate.center_y = sel_cy; - ud->interact.rotate.handle_radius = radius; - - const int n = HANDLE_CIRCLE_SEGMENTS + 1; - static shape_vertex_t unit_circle[HANDLE_CIRCLE_SEGMENTS + 1]; - static bool unit_circle_ready = false; - if (!unit_circle_ready) { - for (int i = 0; i < n; i++) { - float a = (float)i / (float)HANDLE_CIRCLE_SEGMENTS * 2.0f * GLM_PIf; - unit_circle[i] = (shape_vertex_t){cosf(a), sinf(a)}; - } - unit_circle_ready = true; - } - shape_vertex_t hv[HANDLE_CIRCLE_SEGMENTS + 1]; - for (int i = 0; i < n; i++) { - hv[i] = (shape_vertex_t){sel_cx + unit_circle[i].x * radius, - sel_cy + unit_circle[i].y * radius}; - } - if (need_upload) - sg_update_buffer(ud->handle_vbuf, &(sg_range){hv, sizeof(hv)}); - - { - float hs = CORNER_SIZE_PX / ud->camera.zoom * 0.5f; - float mid_x = (overlay_verts[0].x + overlay_verts[1].x) * 0.5f; - float mid_y = (overlay_verts[0].y + overlay_verts[2].y) * 0.5f; - float handles[8][2] = { - {overlay_verts[0].x, overlay_verts[0].y}, - {mid_x, overlay_verts[0].y}, - {overlay_verts[1].x, overlay_verts[1].y}, - {overlay_verts[1].x, mid_y }, - {overlay_verts[2].x, overlay_verts[2].y}, - {mid_x, overlay_verts[2].y}, - {overlay_verts[3].x, overlay_verts[3].y}, - {overlay_verts[3].x, mid_y }, - }; - shape_vertex_t cv[40]; - for (int h = 0; h < 8; h++) { - float cx = handles[h][0], cy = handles[h][1]; - cv[h*5+0] = (shape_vertex_t){cx - hs, cy - hs}; - cv[h*5+1] = (shape_vertex_t){cx + hs, cy - hs}; - cv[h*5+2] = (shape_vertex_t){cx + hs, cy + hs}; - cv[h*5+3] = (shape_vertex_t){cx - hs, cy + hs}; - cv[h*5+4] = (shape_vertex_t){cx - hs, cy - hs}; - } - if (need_upload) - sg_update_buffer(ud->corner_vbuf, &(sg_range){cv, sizeof(cv)}); - } - } - - // Edit mode overlay - if (ud->interact.editing_shape_idx >= 0 && need_upload) { - shape_t *es = (shape_t*) vec_get(&ud->shapes, ud->interact.editing_shape_idx); - int n = es->ctrl_count; - if (n > 128) n = 128; - - float as = EDIT_ANCHOR_SIZE_PX / ud->camera.zoom * 0.5f; - float hs = EDIT_HANDLE_SIZE_PX / ud->camera.zoom * 0.5f; - - int ac = 0, hc = 0, lc = 0; - for (int i = 0; i < n; i++) { - shape_vertex_t wa = local_to_world(es, es->ctrl_points[i].x, es->ctrl_points[i].y); - // Anchor square - int ba = ac * 5; - ed_anchor_verts[ba+0] = (shape_vertex_t){wa.x - as, wa.y - as}; - ed_anchor_verts[ba+1] = (shape_vertex_t){wa.x + as, wa.y - as}; - ed_anchor_verts[ba+2] = (shape_vertex_t){wa.x + as, wa.y + as}; - ed_anchor_verts[ba+3] = (shape_vertex_t){wa.x - as, wa.y + as}; - ed_anchor_verts[ba+4] = (shape_vertex_t){wa.x - as, wa.y - as}; - ac++; - - shape_vertex_t wh_in = local_to_world(es, es->ctrl_handle_in[i].x, es->ctrl_handle_in[i].y); - shape_vertex_t wh_out = local_to_world(es, es->ctrl_handle_out[i].x, es->ctrl_handle_out[i].y); - - // Handle line: anchor → in handle - ed_line_verts[lc++] = wa; - ed_line_verts[lc++] = wh_in; - // Handle line: anchor → out handle - ed_line_verts[lc++] = wa; - ed_line_verts[lc++] = wh_out; - - // In handle square - int bh = hc * 5; - ed_handle_verts[bh+0] = (shape_vertex_t){wh_in.x - hs, wh_in.y - hs}; - ed_handle_verts[bh+1] = (shape_vertex_t){wh_in.x + hs, wh_in.y - hs}; - ed_handle_verts[bh+2] = (shape_vertex_t){wh_in.x + hs, wh_in.y + hs}; - ed_handle_verts[bh+3] = (shape_vertex_t){wh_in.x - hs, wh_in.y + hs}; - ed_handle_verts[bh+4] = (shape_vertex_t){wh_in.x - hs, wh_in.y - hs}; - hc++; - // Out handle square - bh = hc * 5; - ed_handle_verts[bh+0] = (shape_vertex_t){wh_out.x - hs, wh_out.y - hs}; - ed_handle_verts[bh+1] = (shape_vertex_t){wh_out.x + hs, wh_out.y - hs}; - ed_handle_verts[bh+2] = (shape_vertex_t){wh_out.x + hs, wh_out.y + hs}; - ed_handle_verts[bh+3] = (shape_vertex_t){wh_out.x - hs, wh_out.y + hs}; - ed_handle_verts[bh+4] = (shape_vertex_t){wh_out.x - hs, wh_out.y - hs}; - hc++; - } - - ud->ed_anchor_count = ac; - ud->ed_handle_count = hc; - ud->ed_handle_line_count = lc; - - if (ac > 0) - sg_update_buffer(ud->ed_anchor_vbuf, &(sg_range){ed_anchor_verts, (size_t)(ac * 5) * sizeof(shape_vertex_t)}); - if (hc > 0) - sg_update_buffer(ud->ed_handle_vbuf, &(sg_range){ed_handle_verts, (size_t)(hc * 5) * sizeof(shape_vertex_t)}); - if (lc > 0) - sg_update_buffer(ud->ed_handle_line_vbuf, &(sg_range){ed_line_verts, (size_t)lc * sizeof(shape_vertex_t)}); - } - - memset(&ud->overlay_upload, 0, sizeof(ud->overlay_upload)); -} - -#endif diff --git a/src/rand.h b/src/rand.h deleted file mode 100644 index 3d1a924..0000000 --- a/src/rand.h +++ /dev/null @@ -1,116 +0,0 @@ -#ifndef RAND_H -#define RAND_H - -#include "api.h" - -typedef struct { - uint32_t seed; -} rand_ctx_t; - -static uint32_t xorshift32(rand_ctx_t *r); -static void rand_seed(rand_ctx_t *r, uint32_t _seed); -static uint32_t next_int(rand_ctx_t *r); -static uint32_t next_int_max(rand_ctx_t *r, uint32_t max); -static uint32_t next_int_minmax(rand_ctx_t *r, uint32_t min, uint32_t max); -static float next_float(rand_ctx_t *r); -static float next_float_max(rand_ctx_t *r, float max); -static float next_float_minmax(rand_ctx_t *r, float min, float max); - -/** - * Xorshift32 PRNG core. Advances the seed and returns the new value. - * - * @param r PRNG context - * @return pseudo-random 32-bit integer - */ -static uint32_t xorshift32(rand_ctx_t *r) -{ - r->seed ^= r->seed<<13; - r->seed ^= r->seed>>17; - r->seed ^= r->seed<<5; - return r->seed; -} -/** - * Seed the PRNG state. Zero is ignored (caller should pass a non-zero - * seed). Runs the generator once after seeding to mix the state. - * - * @param r PRNG context - * @param _seed non-zero 32-bit seed value - */ -static void rand_seed(rand_ctx_t *r, uint32_t _seed) -{ - if(_seed == 0) - return; - - r->seed = _seed; - xorshift32(r); -} -/** - * Return a random integer in [0, UINT32_MAX]. - * - * @param r PRNG context - * @return pseudo-random 32-bit integer - */ -static uint32_t next_int(rand_ctx_t *r) -{ - return xorshift32(r); -} -/** - * Return a random integer in [0, max]. - * - * @param r PRNG context - * @param max inclusive upper bound - * @return pseudo-random integer - */ -static uint32_t next_int_max(rand_ctx_t *r, uint32_t max) -{ - return (uint32_t)((double)xorshift32(r) / (double)UINT32_MAX * max); -} -/** - * Return a random integer in [min, max]. - * - * @param r PRNG context - * @param min inclusive lower bound - * @param max inclusive upper bound - * @return pseudo-random integer - */ -static uint32_t next_int_minmax(rand_ctx_t *r, uint32_t min, uint32_t max) -{ - const double x = (double)xorshift32(r) / (double)UINT32_MAX; - return (uint32_t)((1.0 - x) * min + x * max); -} -/** - * Return a random float in [0, 1]. - * - * @param r PRNG context - * @return pseudo-random float - */ -static float next_float(rand_ctx_t *r) -{ - return (float)((double)xorshift32(r) / (double)UINT32_MAX); -} -/** - * Return a random float in [0, max]. - * - * @param r PRNG context - * @param max inclusive upper bound - * @return pseudo-random float - */ -static float next_float_max(rand_ctx_t *r, float max) -{ - return (float)((double)xorshift32(r) / (double)UINT32_MAX * max); -} -/** - * Return a random float in [min, max]. - * - * @param r PRNG context - * @param min inclusive lower bound - * @param max inclusive upper bound - * @return pseudo-random float - */ -static float next_float_minmax(rand_ctx_t *r, float min, float max) -{ - const double x = (double)xorshift32(r) / (double)UINT32_MAX; - return (float)((1.0 - x) * min + x * max); -} - -#endif diff --git a/src/render.h b/src/render.h deleted file mode 100644 index 2d56c23..0000000 --- a/src/render.h +++ /dev/null @@ -1,133 +0,0 @@ -#ifndef RENDER_H -#define RENDER_H - -#include "api.h" - -// Pipeline state — was static globals, now owned by userdata_t. -typedef struct { - sg_pipeline shape_pipeline; - sg_shader shape_shader; - sg_pipeline overlay_pipeline; - sg_shader overlay_shader; -} pipeline_ctx_t; - -static int g_shape_frame_id; - -static void shape_begin_frame(void) -{ - g_shape_frame_id++; -} - -// Pipeline state is owned by pipeline_ctx_t (embedded in userdata_t). -// Previously these were file-scope statics, which prevented multi-TU builds. -static void shape_init_pipeline(pipeline_ctx_t *p, panel_log_ctx_t *pl) -{ - // Overlay shader/pipeline (simple, no storage buffers) - p->overlay_shader = sg_make_shader(&(sg_shader_desc) { - .vertex_func = { - .source = (const char*) src_shaders_overlay_wgsl, - .entry = "vs_main", - }, - .fragment_func = { - .source = (const char*) src_shaders_overlay_wgsl, - .entry = "fs_main", - }, - .uniform_blocks = { - [0] = { - .size = sizeof(mat4), - .stage = SG_SHADERSTAGE_VERTEX, - .wgsl_group0_binding_n = 0, - }, - [1] = { - .size = sizeof(shape_uniform_t), - .stage = SG_SHADERSTAGE_VERTEX, - .wgsl_group0_binding_n = 1, - }, - }, - .attrs = { - [0] = { .base_type = SG_SHADERATTRBASETYPE_FLOAT }, - }, - .label = "Overlay shader", - }); - panel_log_debug(pl, "[shapes] overlay shader id=%d valid=%d", p->overlay_shader.id, sg_isvalid()); - if (p->overlay_shader.id == SG_INVALID_ID) - panel_log(pl, 1, "[shapes] FAILED to create overlay shader"); - - p->overlay_pipeline = sg_make_pipeline(&(sg_pipeline_desc) { - .shader = p->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_debug(pl, "[shapes] overlay pipeline id=%d valid=%d", p->overlay_pipeline.id, sg_isvalid()); - if (p->overlay_pipeline.id == SG_INVALID_ID) - panel_log(pl, 1, "[shapes] FAILED to create overlay pipeline"); - - // Shape shader/pipeline (storage buffers, instanced) - p->shape_shader = sg_make_shader(&(sg_shader_desc) { - .vertex_func = { - .source = (const char*) src_shaders_shape_wgsl, - .entry = "vs_main", - }, - .fragment_func = { - .source = (const char*) src_shaders_shape_wgsl, - .entry = "fs_main", - }, - .uniform_blocks = { - [0] = { - .size = 80, - .stage = SG_SHADERSTAGE_VERTEX, - .wgsl_group0_binding_n = 0, - }, - }, - .views = { - [0] = { - .storage_buffer = { - .stage = SG_SHADERSTAGE_VERTEX, - .readonly = true, - .wgsl_group1_binding_n = 0, - }, - }, - [1] = { - .storage_buffer = { - .stage = SG_SHADERSTAGE_VERTEX, - .readonly = true, - .wgsl_group1_binding_n = 1, - }, - }, - }, - .attrs = { - [0] = { .base_type = SG_SHADERATTRBASETYPE_FLOAT }, - }, - .label = "Shape shader", - }); - panel_log_debug(pl, "[shapes] shader id=%d valid=%d", p->shape_shader.id, sg_isvalid()); - if (p->shape_shader.id == SG_INVALID_ID) - panel_log(pl, 1, "[shapes] FAILED to create shape shader"); - - p->shape_pipeline = sg_make_pipeline(&(sg_pipeline_desc) { - .shader = p->shape_shader, - .index_type = SG_INDEXTYPE_NONE, - .primitive_type = SG_PRIMITIVETYPE_LINE_STRIP, - .layout.attrs = { - [0].format = SG_VERTEXFORMAT_FLOAT2, - }, - .label = "Shape pipeline", - }); - panel_log_debug(pl, "[shapes] pipeline id=%d valid=%d", p->shape_pipeline.id, sg_isvalid()); - if (p->shape_pipeline.id == SG_INVALID_ID) - panel_log(pl, 1, "[shapes] FAILED to create shape pipeline"); -} - -static void shape_shutdown_pipeline(pipeline_ctx_t *p) -{ - sg_destroy_pipeline(p->shape_pipeline); - sg_destroy_shader(p->shape_shader); - sg_destroy_pipeline(p->overlay_pipeline); - sg_destroy_shader(p->overlay_shader); -} - -#endif diff --git a/src/shaders/compute.wgsl b/src/shaders/compute.wgsl new file mode 100644 index 0000000..d46bee8 --- /dev/null +++ b/src/shaders/compute.wgsl @@ -0,0 +1,217 @@ +// ---------- Bindings (exactly as you defined) ---------- +struct Segment { + p0: vec2f, + p1: vec2f, + p2: vec2f, + p3: vec2f, +} +struct ShapeMeta { + start_segment: u32, + segment_count: u32, + bbox_min: vec2f, + bbox_max: vec2f, +} + +@group(1) @binding(0) var segments: array; +@group(1) @binding(1) var shapes: array; +@group(1) @binding(2) var sdf_buffer: array; + +struct Uniforms { + pan: vec2f, + zoom: f32, +}; +struct TileParams { + lod: u32, + tile_size: f32, + world_min: vec2f, + world_max: vec2f, +}; + +@group(0) @binding(0) var uniforms: Uniforms; +@group(0) @binding(1) var tile_params: TileParams; + + +// ---------- Cubic Bézier helpers ---------- +fn eval_cubic(t: f32, p0: vec2f, p1: vec2f, p2: vec2f, p3: vec2f) -> vec2f { + let mt = 1.0 - t; + let mt2 = mt * mt; + let t2 = t * t; + return mt2 * mt * p0 + 3.0 * mt2 * t * p1 + 3.0 * mt * t2 * p2 + t2 * t * p3; +} + +fn eval_cubic_derivative(t: f32, p0: vec2f, p1: vec2f, p2: vec2f, p3: vec2f) -> vec2f { + let mt = 1.0 - t; + return 3.0 * mt * mt * (p1 - p0) + 6.0 * mt * t * (p2 - p1) + 3.0 * t * t * (p3 - p2); +} + +// ---------- Distance to a cubic Bézier (sampling + Newton refinement) ---------- +fn distance_to_cubic(p: vec2f, seg: Segment) -> f32 { + let p0 = seg.p0; let p1 = seg.p1; let p2 = seg.p2; let p3 = seg.p3; + + // Uniform sampling to find a good starting t + let samples = 16u; + var best_t = 0.0; + var best_dist = 1e20; + for (var i = 0u; i < samples; i++) { + let t = f32(i) / f32(samples - 1u); + let q = eval_cubic(t, p0, p1, p2, p3); + let d = distance(p, q); + if d < best_dist { + best_dist = d; + best_t = t; + } + } + + // Newton refinement on the squared distance (max 4 iterations) + var t = clamp(best_t, 0.0, 1.0); + for (var iter = 0u; iter < 4u; iter++) { + let B = eval_cubic(t, p0, p1, p2, p3) - p; + let Bp = eval_cubic_derivative(t, p0, p1, p2, p3); + let f = dot(B, Bp); // ½ derivative of |B|² + let df = dot(Bp, Bp) + dot(B, 3.0 * (p2 - p1 + (p3 - p2 - p2 + p1) * 2.0 * t)); // simplified 2nd deriv + if abs(df) < 1e-12 { break; } + let step = f / df; + t = clamp(t - step, 0.0, 1.0); + if abs(step) < 1e-6 { break; } + } + let q_final = eval_cubic(t, p0, p1, p2, p3); + return distance(p, q_final); +} + +// ---------- Analytical cubic root solver (for y-crossing) ---------- +// Returns number of real roots in [0,1], stored in roots array (up to 3). +fn solve_cubic_in_01(c3: f32, c2: f32, c1: f32, c0: f32, roots: ptr>) -> u32 { + // Normalise: c3 * t^3 + c2 * t^2 + c1 * t + c0 = 0 + if abs(c3) < 1e-9 { + // Quadratic fallback + if abs(c2) < 1e-9 { + if abs(c1) < 1e-9 { return 0u; } + let t = -c0 / c1; + if t >= 0.0 && t <= 1.0 { + (*roots)[0] = t; + return 1u; + } + return 0u; + } + let disc = c1 * c1 - 4.0 * c2 * c0; + if disc < 0.0 { return 0u; } + let sd = sqrt(disc); + let t0 = (-c1 + sd) / (2.0 * c2); + let t1 = (-c1 - sd) / (2.0 * c2); + var count = 0u; + if t0 >= 0.0 && t0 <= 1.0 { (*roots)[count] = t0; count++; } + if t1 >= 0.0 && t1 <= 1.0 { (*roots)[count] = t1; count++; } + return count; + } + + // Depressed cubic: let t = x - c2/(3*c3) + let a = c2 / c3; + let b = c1 / c3; + let c = c0 / c3; + let p = b - a * a / 3.0; + let q = 2.0 * a * a * a / 27.0 - a * b / 3.0 + c; + let disc = q * q / 4.0 + p * p * p / 27.0; + + var count = 0u; + let shift = a / 3.0; + + if disc > 0.0 { + let sd = sqrt(disc); + let u = sign(-q/2.0 + sd) * pow(abs(-q/2.0 + sd), 1.0/3.0); + let v = sign(-q/2.0 - sd) * pow(abs(-q/2.0 - sd), 1.0/3.0); + let x1 = u + v; + let t = x1 - shift; + if t >= 0.0 && t <= 1.0 { (*roots)[count] = t; count++; } + } else if disc == 0.0 { + let u = sign(-q/2.0) * pow(abs(-q/2.0), 1.0/3.0); + let x1 = 2.0 * u; + let x2 = -u; + let t1 = x1 - shift; + let t2 = x2 - shift; + if t1 >= 0.0 && t1 <= 1.0 { (*roots)[count] = t1; count++; } + if t2 >= 0.0 && t2 <= 1.0 { (*roots)[count] = t2; count++; } + } else { + let phi = acos( -q / (2.0 * sqrt(-p*p*p/27.0)) ); + let r = 2.0 * sqrt(-p/3.0); + for (var k = 0u; k < 3u; k++) { + let x = r * cos((phi + 2.0 * 3.1415926535 * f32(k)) / 3.0); + let t = x - shift; + if t >= 0.0 && t <= 1.0 { (*roots)[count] = t; count++; } + } + } + return count; +} + +// ---------- Winding number contribution from one cubic segment ---------- +fn ray_intersections_cubic(p: vec2f, seg: Segment) -> i32 { + // We cast a horizontal ray from p to +x; find t where y(t) = p.y + let y0 = seg.p0.y - p.y; + let y1 = seg.p1.y - p.y; + let y2 = seg.p2.y - p.y; + let y3 = seg.p3.y - p.y; + + // y(t) = (1-t)^3*y0 + 3(1-t)^2 t * y1 + 3(1-t) t^2 * y2 + t^3 * y3 = 0 + // Expand polynomial: c3 t^3 + c2 t^2 + c1 t + c0 + let c0 = y0; + let c1 = 3.0 * (y1 - y0); + let c2 = 3.0 * (y2 - 2.0 * y1 + y0); + let c3 = y3 - 3.0 * y2 + 3.0 * y1 - y0; + + var roots: array; + let num = solve_cubic_in_01(c3, c2, c1, c0, &roots); + var wind = 0i; + for (var i = 0u; i < num; i++) { + let t = roots[i]; + let x = eval_cubic(t, seg.p0, seg.p1, seg.p2, seg.p3).x; + if x > p.x { + let dy = eval_cubic_derivative(t, seg.p0, seg.p1, seg.p2, seg.p3).y; + if dy > 0.0 { wind += 1i; } + else if dy < 0.0 { wind -= 1i; } + } + } + return wind; +} + +// ---------- Main compute shader ---------- +@compute @workgroup_size(8, 8) +fn main(@builtin(global_invocation_id) id: vec3) { + let tile_dim = vec2f(tile_params.tile_size); + if id.x >= u32(tile_dim.x) || id.y >= u32(tile_dim.y) { + return; + } + + // Pixel center UV, then map to world space + let uv = (vec2f(id.xy) + 0.5) / tile_dim; + let world = mix(tile_params.world_min, tile_params.world_max, uv); + + var minDist: f32 = 1e20; + var winding: i32 = 0; + + for (var s = 0u; s < arrayLength(&shapes); s++) { + let shape = shapes[s]; + + // Quick bbox culling in world space + let bbox_min = shape.bbox_min; + let bbox_max = shape.bbox_max; + let dx = max(bbox_min.x - world.x, max(0.0, world.x - bbox_max.x)); + let dy = max(bbox_min.y - world.y, max(0.0, world.y - bbox_max.y)); + let dist_to_box = sqrt(dx * dx + dy * dy); + if dist_to_box >= minDist { + continue; // This shape cannot improve the distance + } + + // Process all segments of the shape + for (var i = shape.start_segment; i < shape.start_segment + shape.segment_count; i++) { + let seg = segments[i]; + let d = distance_to_cubic(world, seg); + minDist = min(minDist, d); + + winding += ray_intersections_cubic(world, seg); + } + } + + let sign = select(1.0, -1.0, winding != 0i); + let sdf = sign * minDist; + + sdf_buffer[id.y * u32(tile_params.tile_size) + id.x] = sdf; +} \ No newline at end of file diff --git a/src/shaders/display.wgsl b/src/shaders/display.wgsl new file mode 100644 index 0000000..f5b4d71 --- /dev/null +++ b/src/shaders/display.wgsl @@ -0,0 +1,27 @@ +struct Uniforms { + pan: vec2f, + zoom: f32, +} +struct TileParams { + lod: u32, + tile_size: f32, + world_min: vec2f, + world_max: vec2f, +} + +@group(0) @binding(0) var uniforms: Uniforms; +@group(0) @binding(1) var tile_params: TileParams; + +@vertex fn vs_main(@builtin(vertex_index) vertex_index : u32) -> @builtin(position) vec4f { + const pos = array( + vec2( 0.0, 0.5), + vec2(-0.5, -0.5), + vec2( 0.5, -0.5) + ); + + return vec4f(pos[vertex_index], 0, 1); +} + +@fragment fn fs_main() -> @location(0) vec4f { + return vec4(1, 0, 0, 1); +} diff --git a/src/shaders/overlay.wgsl b/src/shaders/overlay.wgsl deleted file mode 100644 index e76259a..0000000 --- a/src/shaders/overlay.wgsl +++ /dev/null @@ -1,44 +0,0 @@ -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 vs_uniforms: VsUniform; -@binding(1) @group(0) var 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; -} diff --git a/src/shaders/shape.wgsl b/src/shaders/shape.wgsl deleted file mode 100644 index 4d4a3d0..0000000 --- a/src/shaders/shape.wgsl +++ /dev/null @@ -1,50 +0,0 @@ -struct VsUniform { - mvp: mat4x4f, - instance_base: u32, -}; - -struct ShapeData { - transform: mat4x4f, - state: u32, -}; - -@binding(0) @group(0) var vs_uniforms: VsUniform; - -@binding(0) @group(1) var shape_data: array; -@binding(1) @group(1) var instance_map: array; - -struct VsIn { - @builtin(instance_index) instance_idx: u32, - @location(0) position: vec2f, -}; - -struct Vs2Fs { - @builtin(position) pos: vec4f, - @location(0) @interpolate(linear) color: vec4f, -}; - -struct FsOut { - @location(0) color: vec4f, -}; - -@vertex fn vs_main(input: VsIn) -> Vs2Fs { - var output: Vs2Fs; - let shape_idx = instance_map[vs_uniforms.instance_base + input.instance_idx]; - let shape = shape_data[shape_idx]; - let world_pos = shape.transform * vec4f(input.position.x, input.position.y, 0.0, 1.0); - output.pos = vs_uniforms.mvp * world_pos; - if (shape.state == 2u) { - output.color = vec4f(1.0, 0.84, 0.0, 1.0); - } else if (shape.state == 1u) { - output.color = vec4f(0.5, 0.6, 1.0, 1.0); - } else { - output.color = vec4f(0.8, 0.8, 0.8, 1.0); - } - return output; -} - -@fragment fn fs_main(input: Vs2Fs) -> FsOut { - var output: FsOut; - output.color = input.color; - return output; -} diff --git a/src/shaders/sprite.wgsl b/src/shaders/sprite.wgsl deleted file mode 100644 index f6036c9..0000000 --- a/src/shaders/sprite.wgsl +++ /dev/null @@ -1,40 +0,0 @@ -struct Sprite { - matrix: mat4x4f, -}; -struct VsUniform { - mvp: mat4x4f, -}; -struct VsI { //Vertex shader input - @builtin(instance_index) instance: u32, - @location(0) position: vec2f, - @location(1) uv: vec2f, -}; -struct Vs2Fs { //Vertex shader to Fragment shader - @builtin(position) pos: vec4f, - @location(0) @interpolate(linear) uv: vec2f, -}; -struct FsO { //Fragment shader output - @location(0) color: vec4f, -}; - -@binding(0) @group(0) var vs_uniforms: VsUniform; -@binding(0) @group(1) var tex: texture_2d; -@binding(1) @group(1) var samp: sampler; -@binding(2) @group(1) var sprites: array; - -@vertex fn vs_main(input: VsI) -> Vs2Fs { - var output: Vs2Fs; - - output.pos = vec4f(input.position.x, input.position.y, 0, 0) * sprites[input.instance].matrix * vs_uniforms.mvp; - output.uv = input.uv; - - return output; -} - -@fragment fn fs_main(input: Vs2Fs) -> FsO { - var output: FsO; - - output.color = textureSample(tex, samp, input.uv); - - return output; -} \ No newline at end of file diff --git a/src/shape.h b/src/shape.h index c3dd74b..70bc32b 100644 --- a/src/shape.h +++ b/src/shape.h @@ -1,1014 +1,206 @@ -#ifndef SHAPE_H -#define SHAPE_H - #include "api.h" -#define PEN_SUBDIVISIONS 20 -// Bezier tessellation: target chord length in local-space units. -// Smaller = denser curves. Circle (r=1) with 0.10 gives ~17 subd/segment. -#define BEZIER_TARGET_CHORD 0.05f -#define BEZIER_MIN_SUBD 4 -#define BEZIER_MAX_SUBD 64 +typedef struct uvec2 { uint32_t x, y; } uvec2; +typedef struct vec2 { float x, y; } vec2; -typedef struct shape_vertex_t { - float x, y; -} shape_vertex_t; +typedef struct segment_t { + vec2 p0, p1, p2, p3; // cubic Bézier: start, control1, control2, end +} segment_t; -// FNV-1a 64-bit hash over raw vertex bytes. Used to group shapes that share -// identical local-space geometry (e.g. all circles) while keeping freeform -// pen paths isolated in their own vertex buffers even when they happen to -// have the same vertex count. -static uint64_t hash_vertex_data(const shape_vertex_t *verts, uint32_t n) { - uint64_t h = 14695981039346656037ULL; - const uint8_t *data = (const uint8_t*)verts; - size_t len = (size_t)n * sizeof(shape_vertex_t); - for (size_t i = 0; i < len; i++) { - h ^= data[i]; - h *= 1099511628211ULL; - } - return h; -} +typedef struct shape_meta_t { + int start_segment; // index into global segments array + int segment_count; // number of segments forming this closed loop + float bbox_min_x, bbox_min_y, bbox_max_x, bbox_max_y; +} shape_meta_t; -typedef struct shape_uniform_t { - mat4 transform; - uint32_t state; - uint8_t _pad[12]; -} shape_uniform_t; +typedef struct scene_t { + uint32_t num_shapes; + uint32_t max_shapes; + shape_meta_t* shapes; -typedef struct { - mat4 transform; - uint32_t state; - uint8_t _pad[12]; -} shape_gpu_data_t; + uint32_t num_segments; + uint32_t max_segments; + segment_t* segments; -typedef struct shape_t { - shape_vertex_t *verts; - uint16_t *indices; - uint32_t num_elements; - uint32_t num_verts; - shape_uniform_t uniform; - bool hovered; - bool selected; - bool dirty; // true when GPU instance data needs re-upload + bool dirty; +} scene_t; - float cx, cy; - float sx, sy; - float rotation; - float cos_r, sin_r; - float aabb_hx, aabb_hy; +#define TILE_SIZE 64 // pixels per tile (same for all LODs) - int group_id; +// Tile descriptor +typedef struct tile_ID { + uint32_t lod; // LOD level (0 = coarsest) + uvec2 tile; // position in the grid at that LOD - // Bezier edit data (local space, NULL for shapes never edited) - shape_vertex_t *ctrl_points; - shape_vertex_t *ctrl_handle_in; - shape_vertex_t *ctrl_handle_out; - int ctrl_count; - bool closed; - char name[64]; + // world-space bounds (derived from lod, tile_x, tile_y) + vec2 world_min; + vec2 world_max; +} tile_ID; - // Grouping key for instanced rendering: shapes with the same - // (num_elements, vertex_hash) share a vertex buffer. Parametric shapes - // (circle, star, rect) naturally hash identically; freeform pen paths - // get unique hashes to prevent geometry cross-contamination. - uint64_t vertex_hash; - int group_index; // index into g_shape_groups[], -1 until pool rebuild -} shape_t; +// Tile cache entry (CPU side) +typedef struct tile_cache_entry { + tile_ID id; + // GPU texture holding the SDF values (R32Float) + WGPUTexture texture; //TODO + bool ready; +} tile_cache_entry; -// -- group entity (for nested groups) -- +// Initialise a scene_t with a given capacity for shapes and segments. +static int scene_init(scene_t* s, int init_shape_cap, int init_seg_cap) { + s->num_shapes = 0; + s->max_shapes = init_shape_cap; + s->shapes = (shape_meta_t*) malloc(init_shape_cap * sizeof(shape_meta_t)); -typedef struct { - int id; - int parent_id; // 0 = top-level group - int member_count; - int *member_indices; - bool collapsed; -} group_t; + s->num_segments = 0; + s->max_segments = init_seg_cap; + s->segments = (segment_t*) malloc(init_seg_cap * sizeof(segment_t)); -// Group lookup index — maps group_id → group_t* for O(1) find_group(). -// Must be defined before the lookup functions that dereference its fields. -struct group_index_ctx_t { - group_t **by_id; - int cap; - bool dirty; -}; -// Full typedef for client use (userdata_t, etc.) -typedef struct group_index_ctx_t group_index_ctx_t; - -// Group lookup functions now take group_index_ctx_t* so the index array is -// owned by the caller (embedded in userdata_t), not by a file-scope static. - -static void group_index_rebuild(group_index_ctx_t *gi, 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 >= gi->cap) { - if (gi->by_id) FREE(gi->by_id); - int new_cap = max_id + 64; - gi->by_id = (group_t**) ALLOC((size_t)new_cap * sizeof(group_t*)); - memset(gi->by_id, 0, (size_t)new_cap * sizeof(group_t*)); - gi->cap = new_cap; - } else { - for (int i = 0; i <= max_id; i++) gi->by_id[i] = NULL; - } - for (int i = 0; i < groups->count; i++) { - group_t *g = (group_t*) vec_get(groups, i); - gi->by_id[g->id] = g; - } - gi->dirty = false; -} - -static void group_index_ensure_cap(group_index_ctx_t *gi, int max_id) -{ - if (max_id >= gi->cap) { - int new_cap = max_id + 64; - group_t **old = gi->by_id; - gi->by_id = (group_t**) ALLOC((size_t)new_cap * sizeof(group_t*)); - if (old) { - memcpy(gi->by_id, old, (size_t)gi->cap * sizeof(group_t*)); - FREE(old); - } - memset(gi->by_id + gi->cap, 0, - (size_t)(new_cap - gi->cap) * sizeof(group_t*)); - gi->cap = new_cap; - } -} - -static group_t* find_group(group_index_ctx_t *gi, vector_t *groups, int id) { - if (gi->dirty) { - group_index_rebuild(gi, groups); - gi->dirty = false; - } - if (id <= 0 || id >= gi->cap) return NULL; - return gi->by_id[id]; -} - -static bool is_shape_in_group_hierarchy(group_index_ctx_t *gi, int shape_gid, int target_gid, vector_t *groups) { - (void)groups; - int cur = shape_gid; - while (cur != 0) { - if (cur == target_gid) return true; - group_t *g = find_group(gi, groups, cur); - if (!g) return false; - cur = g->parent_id; - } - return false; -} - -static void group_index_add(group_index_ctx_t *gi, vector_t *groups, int id, int parent_id) -{ - bool found = false; - for (int i = 0; i < groups->count; i++) { - if (((group_t*) vec_get(groups, i))->id == id) { found = true; break; } - } - if (!found) { - group_t g = { .id = id, .parent_id = parent_id, .collapsed = true }; - *((group_t*) vec_push(groups)) = g; - group_index_ensure_cap(gi, id); - if (id < gi->cap) gi->by_id[id] = (group_t*) vec_get(groups, groups->count - 1); - } -} - -static void group_index_remove(group_index_ctx_t *gi, vector_t *groups, int id) -{ - for (int i = 0; i < groups->count; i++) { - if (((group_t*) vec_get(groups, i))->id == id) { - group_t *grp = (group_t*) vec_get(groups, i); - if (grp->member_indices) FREE(grp->member_indices); - vec_remove_ordered(groups, i); - gi->dirty = true; - return; - } - } -} - -static void group_index_shutdown(group_index_ctx_t *gi) -{ - if (gi->by_id) FREE(gi->by_id); - gi->by_id = NULL; - gi->cap = 0; - gi->dirty = false; -} - -// Rebuild member_indices for every group by scanning all shapes once. -// Call after any structural change (group, ungroup, delete, paste, undo, redo). -static void group_rebuild_members(group_index_ctx_t *gi, vector_t *groups, vector_t *shapes) -{ - if (gi->dirty) group_index_rebuild(gi, groups); - - // Free old member arrays and count members - for (int g = 0; g < groups->count; g++) { - group_t *grp = (group_t*) vec_get(groups, g); - if (grp->member_indices) { FREE(grp->member_indices); grp->member_indices = NULL; } - grp->member_count = 0; - } - // Count shapes per group - for (int i = 0; i < shapes->count; i++) { - int gid = ((shape_t*) vec_get(shapes, i))->group_id; - if (gid == 0) continue; - if (gid < gi->cap) { - group_t *grp = gi->by_id[gid]; - if (grp) grp->member_count++; - } - } - // Allocate and fill - for (int g = 0; g < groups->count; g++) { - group_t *grp = (group_t*) vec_get(groups, g); - if (grp->member_count > 0) { - grp->member_indices = (int*) ALLOC((size_t)grp->member_count * sizeof(int)); - grp->member_count = 0; // reset for fill pass - } - } - for (int i = 0; i < shapes->count; i++) { - shape_t *s = (shape_t*) vec_get(shapes, i); - if (s->group_id == 0) continue; - if (s->group_id < gi->cap) { - group_t *grp = gi->by_id[s->group_id]; - if (grp && grp->member_indices) - grp->member_indices[grp->member_count++] = i; - } - } -} - -// Free all member_indices. Call before freeing groups vector or resetting count. -static void group_shutdown_members(vector_t *groups) -{ - for (int g = 0; g < groups->count; g++) { - group_t *grp = (group_t*) vec_get(groups, g); - if (grp->member_indices) { FREE(grp->member_indices); grp->member_indices = NULL; } - } -} - -// -- Context structs (owned by userdata_t) -- - -// Each unique (num_elements, vertex_hash) pair gets one vertex buffer shared -// by all shapes with identical local-space geometry (e.g. all circles). -typedef struct { - uint32_t num_elements; - uint64_t vertex_hash; - sg_buffer vbuf; -} shape_group_buf_t; - -// GPU pool state for instanced shape rendering. -typedef struct { - sg_buffer data_sbuf; - sg_buffer instance_map_sbuf; - sg_view data_view; - sg_view instance_map_view; - - shape_group_buf_t *groups; - int group_count; - - shape_gpu_data_t *upload_buf; // CPU-side mirror of data_sbuf contents - int upload_buf_cap; - - size_t data_buf_size; - int instance_map_capacity; - - bool pool_dirty; - bool data_dirty; - bool states_dirty; - int frame_id; -} shape_pool_ctx_t; - -// -- shared geometry buffers (one vbuf per group + one instance-data sbuf) -- -// All state is in shape_pool_ctx_t, owned by the caller (userdata_t). - -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 }, - }); -} - -// Upload shape instance data to GPU. Maintains a CPU-side mirror (upload_buf) -// so that only shapes marked dirty are rewritten; unchanged entries stay in -// the mirror from prior frames. Issues a single sg_update_buffer per frame -// to respect Sokol's per-buffer update limit. -static void shape_upload_data(shape_pool_ctx_t *sp, vector_t *shapes) -{ - int n = shapes->count; - if (n == 0 || !sp->data_sbuf.id) return; - - size_t need = (size_t)n * sizeof(shape_gpu_data_t); - if (need > sp->data_buf_size) { - sp->pool_dirty = true; - return; - } - - // Resize CPU mirror when shape count grows (add/delete/paste) - if (n > sp->upload_buf_cap) { - if (sp->upload_buf) FREE(sp->upload_buf); - sp->upload_buf = (shape_gpu_data_t*) ALLOC(need); - sp->upload_buf_cap = n; - // Full mirror rebuild — all shapes are dirty on capacity change - for (int i = 0; i < n; i++) { - shape_t *s = (shape_t*) vec_get(shapes, i); - memcpy(sp->upload_buf[i].transform, s->uniform.transform, sizeof(mat4)); - sp->upload_buf[i].state = s->uniform.state; - memset(sp->upload_buf[i]._pad, 0, sizeof(sp->upload_buf[i]._pad)); - s->dirty = false; - } - sg_update_buffer(sp->data_sbuf, &(sg_range){sp->upload_buf, need}); - sp->data_dirty = false; - return; - } - - // Incremental: only write entries for shapes whose transform changed - bool any_dirty = false; - for (int i = 0; i < n; i++) { - shape_t *s = (shape_t*) vec_get(shapes, i); - if (!s->dirty) continue; - memcpy(sp->upload_buf[i].transform, s->uniform.transform, sizeof(mat4)); - sp->upload_buf[i].state = s->uniform.state; - memset(sp->upload_buf[i]._pad, 0, sizeof(sp->upload_buf[i]._pad)); - s->dirty = false; - any_dirty = true; - } - - if (any_dirty) { - // Single sg_update_buffer — Sokol allows at most one per buffer per frame - sg_update_buffer(sp->data_sbuf, &(sg_range){sp->upload_buf, need}); - } - sp->data_dirty = false; -} - -static void shape_upload_instance_map(shape_pool_ctx_t *sp, const uint32_t *map, int count) -{ - if (count > sp->instance_map_capacity) { - if (sp->instance_map_sbuf.id) sg_destroy_buffer(sp->instance_map_sbuf); - sp->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", - }); - sp->instance_map_capacity = count; - shape_make_view_for_buffer(&sp->instance_map_view, sp->instance_map_sbuf); - } - sg_update_buffer(sp->instance_map_sbuf, &(sg_range){map, (size_t)count * sizeof(uint32_t)}); -} - -static void shape_pool_rebuild(shape_pool_ctx_t *sp, panel_log_ctx_t *pl, vector_t *shapes) -{ - int n = shapes->count; - - if (n == 0) { - for (int i = 0; i < sp->group_count; i++) { - if (sp->groups[i].vbuf.id) sg_destroy_buffer(sp->groups[i].vbuf); - } - FREE(sp->groups); - sp->groups = NULL; - sp->group_count = 0; - if (sp->data_sbuf.id) { sg_destroy_buffer(sp->data_sbuf); sp->data_sbuf.id = 0; } - if (sp->data_view.id) { sg_destroy_view(sp->data_view); sp->data_view.id = 0; } - sp->data_buf_size = 0; - sp->pool_dirty = false; - return; - } - - // Resize shape data buffer if needed (keep existing if size unchanged) - size_t need_data_size = (size_t)n * sizeof(shape_gpu_data_t); - if (need_data_size > sp->data_buf_size) { - if (sp->data_sbuf.id) { sg_destroy_buffer(sp->data_sbuf); sp->data_sbuf.id = 0; } - if (sp->data_view.id) { sg_destroy_view(sp->data_view); sp->data_view.id = 0; } - sp->data_buf_size = need_data_size; - sp->data_sbuf = sg_make_buffer(&(sg_buffer_desc){ - .size = sp->data_buf_size, - .usage = { .storage_buffer = true, .stream_update = true }, - .label = "Shape data", - }); - if (sp->data_sbuf.id == SG_INVALID_ID) - panel_log(pl, 1, "[shapes] FAILED to create shape data buffer (%zu bytes)", sp->data_buf_size); - shape_make_view_for_buffer(&sp->data_view, sp->data_sbuf); - } - - // Collect unique (num_elements, vertex_hash) pairs using a small - // open-addressing hash table to convert the O(n*n_keys) dedup scan - // into amortized O(n). n_keys is usually < 10 for procedural shapes. - typedef struct { uint32_t ne; uint64_t hash; } group_key_t; - group_key_t *keys = (group_key_t*) ALLOC((size_t)n * sizeof(group_key_t)); - int n_keys = 0; - enum { KEY_TABLE_SIZE = 64 }; - typedef struct { uint32_t ne; uint64_t hash; int idx; } key_entry_t; - key_entry_t key_table[KEY_TABLE_SIZE]; - memset(key_table, 0, sizeof(key_table)); - - for (int i = 0; i < n; i++) { - shape_t *s = (shape_t*) vec_get(shapes, i); - uint32_t ne = s->num_elements; - uint64_t hash = s->vertex_hash; - uint32_t slot = (uint32_t)((ne * 0x9E3779B9) ^ (hash >> 32) ^ (hash & 0xFFFFFFFF)) - & (KEY_TABLE_SIZE - 1); - while (key_table[slot].ne != 0 || key_table[slot].hash != 0) { - if (key_table[slot].ne == ne && key_table[slot].hash == hash) break; - slot = (slot + 1) & (KEY_TABLE_SIZE - 1); - } - if (key_table[slot].ne == 0 && key_table[slot].hash == 0) { - key_table[slot].ne = ne; - key_table[slot].hash = hash; - key_table[slot].idx = n_keys; - keys[n_keys].ne = ne; - keys[n_keys].hash = hash; - n_keys++; - } - } - - // Preserve existing group buffers whose (ne, hash) key still exists. - // Only new keys and orphaned keys cause allocation/deallocation. - shape_group_buf_t *new_groups = (shape_group_buf_t*) ALLOC((size_t)n_keys * sizeof(shape_group_buf_t)); - memset(new_groups, 0, (size_t)n_keys * sizeof(shape_group_buf_t)); - - bool *old_kept = (bool*) ALLOC((size_t)sp->group_count * sizeof(bool)); - memset(old_kept, 0, (size_t)sp->group_count * sizeof(bool)); - - // Build a quick lookup from (ne, hash) -> old group index using the same - // fixed-size open-addressing scheme used for key dedup above. - int old_lookup_size = sp->group_count > 0 ? sp->group_count * 2 + 16 : 16; - if (old_lookup_size > 512) old_lookup_size = 512; - int *old_lookup_keys = NULL; - int *old_lookup_vals = NULL; - if (sp->group_count > 0) { - old_lookup_keys = (int*) ALLOC((size_t)old_lookup_size * 2 * sizeof(int)); - old_lookup_vals = old_lookup_keys + old_lookup_size; - for (int i = 0; i < old_lookup_size; i++) { - old_lookup_keys[i] = -1; - old_lookup_vals[i] = -1; - } - for (int old_i = 0; old_i < sp->group_count; old_i++) { - uint32_t ne = sp->groups[old_i].num_elements; - uint64_t hash = sp->groups[old_i].vertex_hash; - uint32_t slot = (uint32_t)((ne * 0x9E3779B9) ^ (hash >> 32) ^ (hash & 0xFFFFFFFF)) - & (uint32_t)(old_lookup_size - 1); - while (old_lookup_keys[slot] != -1) - slot = (slot + 1) & (uint32_t)(old_lookup_size - 1); - int packed = (int)(ne ^ (uint32_t)(hash ^ (hash >> 32))); - old_lookup_keys[slot] = packed; - old_lookup_vals[slot] = old_i; - } - } - - for (int k = 0; k < n_keys; k++) { - bool reused = false; - if (sp->group_count > 0) { - uint32_t ne = keys[k].ne; - uint64_t hash = keys[k].hash; - int search_packed = (int)(ne ^ (uint32_t)(hash ^ (hash >> 32))); - uint32_t slot = (uint32_t)((ne * 0x9E3779B9) ^ (hash >> 32) ^ (hash & 0xFFFFFFFF)) - & (uint32_t)(old_lookup_size - 1); - while (old_lookup_keys[slot] != -1) { - if (old_lookup_keys[slot] == search_packed) { - int old_i = old_lookup_vals[slot]; - if (sp->groups[old_i].num_elements == ne && - sp->groups[old_i].vertex_hash == hash && - sp->groups[old_i].vbuf.id) { - new_groups[k] = sp->groups[old_i]; - old_kept[old_i] = true; - reused = true; - break; - } - } - slot = (slot + 1) & (uint32_t)(old_lookup_size - 1); - } - } - - if (!reused) { - shape_t *ref = NULL; - for (int i = 0; i < n; i++) { - shape_t *s = (shape_t*) vec_get(shapes, i); - if (s->num_elements == keys[k].ne && s->vertex_hash == keys[k].hash) { - ref = s; - break; - } - } - new_groups[k].num_elements = keys[k].ne; - new_groups[k].vertex_hash = keys[k].hash; - new_groups[k].vbuf = sg_make_buffer(&(sg_buffer_desc){ - .size = (size_t)keys[k].ne * sizeof(shape_vertex_t), - .usage = { .stream_update = true }, - .label = "Shape group verts", - }); - sg_update_buffer(new_groups[k].vbuf, &(sg_range){ - ref->verts, (size_t)keys[k].ne * sizeof(shape_vertex_t) - }); - } - } - - if (old_lookup_keys) FREE(old_lookup_keys); - - for (int old_i = 0; old_i < sp->group_count; old_i++) { - if (!old_kept[old_i] && sp->groups[old_i].vbuf.id) - sg_destroy_buffer(sp->groups[old_i].vbuf); - } - FREE(old_kept); - FREE(sp->groups); - sp->groups = new_groups; - sp->group_count = n_keys; - - // Assign group_index to shapes that were created since the last rebuild. - // Pre-existing shapes keep their cached group_index (validated above). - for (int i = 0; i < n; i++) { - shape_t *s = (shape_t*) vec_get(shapes, i); - if (s->group_index >= 0 && s->group_index < n_keys) continue; - s->group_index = -1; - for (int k = 0; k < n_keys; k++) { - if (sp->groups[k].num_elements == s->num_elements && - sp->groups[k].vertex_hash == s->vertex_hash) { - s->group_index = k; - break; - } - } - } - - FREE(keys); - - if (pl) { - panel_log_debug(pl, "[shapes] pool_rebuild: %d shapes, %d groups, data_buf=%d data_view=%d", - n, n_keys, sp->data_sbuf.id, sp->data_view.id); - for (int gi = 0; gi < n_keys; gi++) { - panel_log_debug(pl, "[shapes] group[%d]: ne=%u hash=%llx vbuf=%d", - gi, sp->groups[gi].num_elements, - (unsigned long long)sp->groups[gi].vertex_hash, - sp->groups[gi].vbuf.id); - } - } - - sp->pool_dirty = false; -} - -static void shape_pool_shutdown(shape_pool_ctx_t *sp) -{ - for (int i = 0; i < sp->group_count; i++) { - if (sp->groups[i].vbuf.id) sg_destroy_buffer(sp->groups[i].vbuf); - } - FREE(sp->groups); - sp->groups = NULL; - sp->group_count = 0; - - if (sp->data_view.id) { sg_destroy_view(sp->data_view); sp->data_view.id = 0; } - if (sp->instance_map_view.id) { sg_destroy_view(sp->instance_map_view); sp->instance_map_view.id = 0; } - if (sp->data_sbuf.id) { sg_destroy_buffer(sp->data_sbuf); sp->data_sbuf.id = 0; } - if (sp->instance_map_sbuf.id) { sg_destroy_buffer(sp->instance_map_sbuf); sp->instance_map_sbuf.id = 0; } - sp->instance_map_capacity = 0; - if (sp->upload_buf) { FREE(sp->upload_buf); sp->upload_buf = NULL; } - sp->upload_buf_cap = 0; -} - -#define SHAPE_HOVER_PX 6.0f - -static void shape_regenerate_from_ctrl(shape_pool_ctx_t *sp, shape_t *s); - -static int shape_calc_segments(float r) -{ - int n = (int)(fabsf(r) * 0.5f) + 16; - if (n < 8) n = 8; - if (n > 128) n = 128; - return n; -} - -static void shape_init_common(shape_t *s) -{ - s->hovered = false; - s->selected = false; - s->dirty = true; // first upload always needed - s->uniform.state = 0; - memset(s->uniform._pad, 0, sizeof(s->uniform._pad)); - s->group_id = 0; - s->aabb_hx = 0; - s->aabb_hy = 0; - s->ctrl_points = NULL; - s->ctrl_handle_in = NULL; - s->ctrl_handle_out = NULL; - s->ctrl_count = 0; - s->closed = false; - s->name[0] = '\0'; - s->vertex_hash = 0; - s->group_index = -1; -} - -// Recompute AABB from actual local-space vertex positions rather than -// assuming [-1,1] bounds. Needed because Bezier edits can push vertices -// outside the unit square. Also re-centers local-space vertices around -// origin so cx/cy tracks the true geometry center. -static void shape_update_aabb(shape_t *s) { - if (s->num_verts == 0) return; - float lmin_x = s->verts[0].x, lmax_x = s->verts[0].x; - float lmin_y = s->verts[0].y, lmax_y = s->verts[0].y; - for (uint32_t i = 1; i < s->num_verts; i++) { - float vx = s->verts[i].x, vy = s->verts[i].y; - if (vx < lmin_x) lmin_x = vx; - if (vx > lmax_x) lmax_x = vx; - if (vy < lmin_y) lmin_y = vy; - if (vy > lmax_y) lmax_y = vy; - } - float lcx = (lmin_x + lmax_x) * 0.5f; - float lcy = (lmin_y + lmax_y) * 0.5f; - float lhx = (lmax_x - lmin_x) * 0.5f; - float lhy = (lmax_y - lmin_y) * 0.5f; - float sc = s->cos_r, ss = s->sin_r; - s->cx += lcx * s->sx * sc - lcy * s->sy * ss; - s->cy += lcx * s->sx * ss + lcy * s->sy * sc; - for (uint32_t i = 0; i < s->num_verts; i++) { - s->verts[i].x -= lcx; - s->verts[i].y -= lcy; - } - for (int i = 0; i < s->ctrl_count; i++) { - s->ctrl_points[i].x -= lcx; - s->ctrl_points[i].y -= lcy; - s->ctrl_handle_in[i].x -= lcx; - s->ctrl_handle_in[i].y -= lcy; - s->ctrl_handle_out[i].x -= lcx; - s->ctrl_handle_out[i].y -= lcy; - } - s->vertex_hash = hash_vertex_data(s->verts, s->num_elements); - s->aabb_hx = fabsf(sc) * fabsf(s->sx) * lhx + fabsf(ss) * fabsf(s->sy) * lhy; - s->aabb_hy = fabsf(ss) * fabsf(s->sx) * lhx + fabsf(sc) * fabsf(s->sy) * lhy; -} - -static void shape_build_transform(shape_pool_ctx_t *sp, shape_t *s) -{ - mat4 T, R, S, RS; - glm_translate_make(T, (vec3){s->cx, s->cy, 0.0f}); - glm_rotate_make(R, s->rotation, (vec3){0.0f, 0.0f, 1.0f}); - glm_scale_make(S, (vec3){s->sx, s->sy, 1.0f}); - glm_mat4_mul(R, S, RS); - glm_mat4_mul(T, RS, s->uniform.transform); - s->cos_r = R[0][0]; - s->sin_r = R[0][1]; - s->aabb_hx = fabsf(s->cos_r) * fabsf(s->sx) + fabsf(s->sin_r) * fabsf(s->sy); - s->aabb_hy = fabsf(s->sin_r) * fabsf(s->sx) + fabsf(s->cos_r) * fabsf(s->sy); s->dirty = true; - sp->data_dirty = true; + + if (!s->shapes || !s->segments) return 0; // allocation failure + return 1; } -static void shape_retranslate(shape_pool_ctx_t *sp, shape_t *s) -{ - s->uniform.transform[3][0] = s->cx; - s->uniform.transform[3][1] = s->cy; +// Append a segment, returns its index. (Reallocs if needed, simplified.) +static int scene_add_segment(scene_t* s, segment_t seg) { + if (s->num_segments >= s->max_segments) { + int new_cap = s->max_segments * 2; + segment_t* tmp = (segment_t*) realloc(s->segments, new_cap * sizeof(segment_t)); + if (!tmp) return -1; + s->segments = tmp; + s->max_segments = new_cap; + } + int idx = s->num_segments++; + s->segments[idx] = seg; + s->dirty = true; - sp->data_dirty = true; + return idx; } -// Cache group_index at shape creation time. Only triggers a pool rebuild when -// the (num_elements, vertex_hash) key is genuinely new — the common case -// (e.g. creating another circle) reuses an existing vertex buffer. -static void shape_make_buffers(shape_pool_ctx_t *sp, shape_t *s) -{ - for (int i = 0; i < sp->group_count; i++) { - if (sp->groups[i].num_elements == s->num_elements && - sp->groups[i].vertex_hash == s->vertex_hash) { - s->group_index = i; - return; +// Append a shape (meta data) and return its index. +static int scene_add_shape(scene_t* s, shape_meta_t meta) { + if (s->num_shapes >= s->max_shapes) { + int new_cap = s->max_shapes * 2; + shape_meta_t* tmp = (shape_meta_t*) realloc(s->shapes, new_cap * sizeof(shape_meta_t)); + if (!tmp) return -1; + s->shapes = tmp; + s->max_shapes = new_cap; + } + int idx = s->num_shapes++; + s->shapes[idx] = meta; + + s->dirty = true; + return idx; +} + + +static int scene_shutdown(scene_t* s) { + free(s->segments); + free(s->shapes); + + free(s); + return 1; +} + +// Compute axis-aligned bounding box for a set of segments (used after adding a shape). +static void compute_bbox(segment_t* segs, int start, int count, + float* minx, float* miny, float* maxx, float* maxy) { + float mx = 1e30f, my = 1e30f, Mx = -1e30f, My = -1e30f; + for (int i = 0; i < count; i++) { + segment_t s = segs[start + i]; + float xs[4] = {s.p0.x, s.p1.x, s.p2.x, s.p3.x}; + float ys[4] = {s.p0.y, s.p1.y, s.p2.y, s.p3.y}; + for (int j = 0; j < 4; j++) { + if (xs[j] < mx) mx = xs[j]; + if (xs[j] > Mx) Mx = xs[j]; + if (ys[j] < my) my = ys[j]; + if (ys[j] > My) My = ys[j]; } } - s->group_index = -1; - sp->pool_dirty = true; + *minx = mx; *miny = my; *maxx = Mx; *maxy = My; } -static void shape_shutdown(shape_pool_ctx_t *sp, shape_t *s) -{ - sp->pool_dirty = true; - FREE(s->verts); - FREE(s->indices); - FREE(s->ctrl_points); - FREE(s->ctrl_handle_in); - FREE(s->ctrl_handle_out); - s->ctrl_count = 0; -} - -static void shape_regenerate(shape_pool_ctx_t *sp, shape_t *s) -{ - shape_build_transform(sp, s); - shape_update_aabb(s); -} - -static void shape_set_state(shape_pool_ctx_t *sp, shape_t *s, bool hovered, bool selected) -{ - uint32_t new_state = selected ? 2u : (hovered ? 1u : 0u); - if (s->uniform.state != new_state) { s->dirty = true; sp->data_dirty = true; } - s->hovered = hovered; - s->selected = selected; - s->uniform.state = new_state; -} - -static bool point_in_polygon(float px, float py, shape_vertex_t *verts, uint32_t n) -{ - bool inside = false; - for (uint32_t i = 0, j = n - 1; i < n; j = i++) { - float xi = verts[i].x, yi = verts[i].y; - float xj = verts[j].x, yj = verts[j].y; - if ((yi > py) != (yj > py) && px < (xj - xi) * (py - yi) / (yj - yi) + xi) - inside = !inside; - } - return inside; -} - -static bool shape_hit_test(shape_t *s, float wx, float wy, float world_tol) -{ - float sc = s->cos_r, ss = s->sin_r; - float dx = wx - s->cx, dy = wy - s->cy; - float lx = (dx * sc + dy * ss) / s->sx; - float ly = (-dx * ss + dy * sc) / s->sy; - - // Per-axis inverse tolerance: a world-space circle of radius world_tol - // maps to a local-space axis-aligned ellipse with semi-axes - // world_tol/|sx| and world_tol/|sy|. The rotation terms cancel out - // because the coordinate transform already handles them. - float ix = fabsf(s->sx) / world_tol; - float iy = fabsf(s->sy) / world_tol; - - if (point_in_polygon(lx, ly, s->verts, s->num_verts)) - return true; - - for (uint32_t i = 0, j = s->num_verts - 1; i < s->num_verts; j = i++) { - float ax = s->verts[i].x, ay = s->verts[i].y; - float bx = s->verts[j].x, by = s->verts[j].y; - float abx = bx - ax, aby = by - ay; - float len_sq = abx * abx + aby * aby; - if (len_sq < 0.0001f) continue; - float t = ((lx - ax) * abx + (ly - ay) * aby) / len_sq; - t = fmaxf(0.0f, fminf(1.0f, t)); - float cx = ax + t * abx, cy = ay + t * aby; - float ddx = (lx - cx) * ix, ddy = (ly - cy) * iy; - if (ddx * ddx + ddy * ddy <= 1.0f) return true; - } - return false; -} - -static shape_t shape_circle(shape_pool_ctx_t *sp, float x, float y, float r) -{ - // 4-point Bezier circle approximation: magic constant k = (4/3)*tan(pi/8) - const float k = 0.5522847498f; - - shape_t s; - memset(&s, 0, sizeof(s)); - s.cx = x; s.cy = y; - s.sx = r; s.sy = r; - s.rotation = 0.0f; - shape_init_common(&s); - - s.ctrl_count = 4; - s.closed = true; - s.ctrl_points = (shape_vertex_t*) ALLOC(4 * sizeof(shape_vertex_t)); - s.ctrl_handle_in = (shape_vertex_t*) ALLOC(4 * sizeof(shape_vertex_t)); - s.ctrl_handle_out = (shape_vertex_t*) ALLOC(4 * sizeof(shape_vertex_t)); - - // Anchors: top, right, bottom, left (clockwise from top) - s.ctrl_points[0] = (shape_vertex_t){ 0.0f, 1.0f}; - s.ctrl_points[1] = (shape_vertex_t){ 1.0f, 0.0f}; - s.ctrl_points[2] = (shape_vertex_t){ 0.0f, -1.0f}; - s.ctrl_points[3] = (shape_vertex_t){-1.0f, 0.0f}; - - // Handles at k distance along tangent (perpendicular to radius) - s.ctrl_handle_in[0] = (shape_vertex_t){ -k, 1.0f}; - s.ctrl_handle_out[0] = (shape_vertex_t){ k, 1.0f}; - s.ctrl_handle_in[1] = (shape_vertex_t){ 1.0f, k}; - s.ctrl_handle_out[1] = (shape_vertex_t){ 1.0f, -k}; - s.ctrl_handle_in[2] = (shape_vertex_t){ k, -1.0f}; - s.ctrl_handle_out[2] = (shape_vertex_t){ -k, -1.0f}; - s.ctrl_handle_in[3] = (shape_vertex_t){-1.0f, -k}; - s.ctrl_handle_out[3] = (shape_vertex_t){-1.0f, k}; - - strncpy(s.name, "Circle", sizeof(s.name) - 1); - shape_regenerate_from_ctrl(sp, &s); - return s; -} - -static shape_t shape_rectangle(shape_pool_ctx_t *sp, float x, float y, float w, float h) -{ - shape_t s; - memset(&s, 0, sizeof(s)); - s.cx = x; s.cy = y; - s.sx = w * 0.5f; s.sy = h * 0.5f; - s.rotation = 0.0f; - shape_init_common(&s); - - s.ctrl_count = 4; - s.closed = true; - s.ctrl_points = (shape_vertex_t*) ALLOC(4 * sizeof(shape_vertex_t)); - s.ctrl_handle_in = (shape_vertex_t*) ALLOC(4 * sizeof(shape_vertex_t)); - s.ctrl_handle_out = (shape_vertex_t*) ALLOC(4 * sizeof(shape_vertex_t)); - - s.ctrl_points[0] = (shape_vertex_t){-1.0f, -1.0f}; - s.ctrl_points[1] = (shape_vertex_t){ 1.0f, -1.0f}; - s.ctrl_points[2] = (shape_vertex_t){ 1.0f, 1.0f}; - s.ctrl_points[3] = (shape_vertex_t){-1.0f, 1.0f}; - - s.ctrl_handle_in[0] = (shape_vertex_t){-1.0f, -1.0f/3.0f}; - s.ctrl_handle_out[0] = (shape_vertex_t){-1.0f/3.0f, -1.0f}; - s.ctrl_handle_in[1] = (shape_vertex_t){ 1.0f/3.0f, -1.0f}; - s.ctrl_handle_out[1] = (shape_vertex_t){ 1.0f, -1.0f/3.0f}; - s.ctrl_handle_in[2] = (shape_vertex_t){ 1.0f, 1.0f/3.0f}; - s.ctrl_handle_out[2] = (shape_vertex_t){ 1.0f/3.0f, 1.0f}; - s.ctrl_handle_in[3] = (shape_vertex_t){-1.0f/3.0f, 1.0f}; - s.ctrl_handle_out[3] = (shape_vertex_t){-1.0f, 1.0f/3.0f}; - - strncpy(s.name, "Rectangle", sizeof(s.name) - 1); - shape_regenerate_from_ctrl(sp, &s); - return s; -} - -// -- Catmull-Rom spline for pen tool -- - -static shape_vertex_t catmull_rom_eval(float t, shape_vertex_t p0, shape_vertex_t p1, - shape_vertex_t p2, shape_vertex_t p3) -{ - float t2 = t * t; - float t3 = t2 * t; - return (shape_vertex_t){ - 0.5f * ((2.0f * p1.x) + - (-p0.x + p2.x) * t + - (2.0f * p0.x - 5.0f * p1.x + 4.0f * p2.x - p3.x) * t2 + - (-p0.x + 3.0f * p1.x - 3.0f * p2.x + p3.x) * t3), - 0.5f * ((2.0f * p1.y) + - (-p0.y + p2.y) * t + - (2.0f * p0.y - 5.0f * p1.y + 4.0f * p2.y - p3.y) * t2 + - (-p0.y + 3.0f * p1.y - 3.0f * p2.y + p3.y) * t3) +// Add an axis‑aligned rectangle centred at `center` with given width and height. +// Returns the shape index, or -1 on error. +int add_rectangle(scene_t* s, vec2 center, vec2 size) { + float hw = size.x * 0.5f, hh = size.y * 0.5f; + vec2 corners[4] = { + {center.x - hw, center.y - hh}, // bottom‑left + {center.x + hw, center.y - hh}, // bottom‑right + {center.x + hw, center.y + hh}, // top‑right + {center.x - hw, center.y + hh} // top‑left }; -} -// Build a smooth line strip from control points using Catmull-Rom interpolation. -// Returns the number of output vertices written to `out`. -static int pen_generate_curve(const shape_vertex_t *ctrl, int ctrl_count, - shape_vertex_t *out, int out_cap, int subdivisions) -{ - if (ctrl_count < 2 || out_cap < 2) return 0; - int max_out = (ctrl_count - 1) * subdivisions + 1; - if (max_out > out_cap) max_out = out_cap; - - // Phantom endpoints: duplicate first and last control points - for (int seg = 0; seg < ctrl_count - 1 && (seg * subdivisions) < max_out; seg++) { - shape_vertex_t p0 = ctrl[seg > 0 ? seg - 1 : 0]; - shape_vertex_t p1 = ctrl[seg]; - shape_vertex_t p2 = ctrl[seg + 1]; - shape_vertex_t p3 = ctrl[seg + 2 < ctrl_count ? seg + 2 : ctrl_count - 1]; - - int steps = subdivisions; - if (seg == ctrl_count - 2) steps = max_out - seg * subdivisions; - for (int s = 0; s < steps; s++) { - float t = (float)s / (float)steps; - out[seg * subdivisions + s] = catmull_rom_eval(t, p0, p1, p2, p3); - } - } - // Ensure last point is exactly the final control point - out[max_out - 1] = ctrl[ctrl_count - 1]; - return max_out; -} - -// Create a shape from world-space line-strip vertices. -// Vertices are converted to local space (centered, normalized to AABB). -static shape_t shape_from_world_verts(shape_pool_ctx_t *sp, const shape_vertex_t *wverts, int wcount) -{ - shape_t s; - memset(&s, 0, sizeof(s)); - s.rotation = 0.0f; - - // Compute world-space AABB - float min_x = wverts[0].x, min_y = wverts[0].y; - float max_x = min_x, max_y = min_y; - for (int i = 1; i < wcount; i++) { - if (wverts[i].x < min_x) min_x = wverts[i].x; - if (wverts[i].y < min_y) min_y = wverts[i].y; - if (wverts[i].x > max_x) max_x = wverts[i].x; - if (wverts[i].y > max_y) max_y = wverts[i].y; + // Four straight segments, control points = the corners themselves. + segment_t segs[4]; + for (int i = 0; i < 4; i++) + { + int next = (i + 1) % 4; + segs[i].p0 = corners[i]; + segs[i].p1 = corners[i]; // start = control1 → straight line + segs[i].p2 = corners[next]; // control2 = end + segs[i].p3 = corners[next]; } - float hx = (max_x - min_x) * 0.5f; - float hy = (max_y - min_y) * 0.5f; - s.cx = (min_x + max_x) * 0.5f; - s.cy = (min_y + max_y) * 0.5f; - s.sx = hx > 0.0001f ? hx : 1.0f; - s.sy = hy > 0.0001f ? hy : 1.0f; + int start = scene_add_segment(s, segs[0]); + if (start < 0) return -1; + for (int i = 1; i < 4; i++) + if (scene_add_segment(s, segs[i]) < 0) return -1; - s.num_verts = (uint32_t)wcount; - s.num_elements = (uint32_t)wcount; - s.verts = (shape_vertex_t*) ALLOC((size_t)wcount * sizeof(shape_vertex_t)); - s.indices = (uint16_t*) ALLOC((size_t)wcount * sizeof(uint16_t)); - - for (int i = 0; i < wcount; i++) { - s.verts[i].x = (wverts[i].x - s.cx) / s.sx; - s.verts[i].y = (wverts[i].y - s.cy) / s.sy; - s.indices[i] = (uint16_t)i; - } - - shape_init_common(&s); - s.vertex_hash = hash_vertex_data(s.verts, s.num_elements); - strncpy(s.name, "Path", sizeof(s.name) - 1); - shape_build_transform(sp, &s); - shape_update_aabb(&s); - shape_make_buffers(sp, &s); - return s; + shape_meta_t meta; + meta.start_segment = start; + meta.segment_count = 4; + compute_bbox(s->segments, start, 4, &meta.bbox_min_x, &meta.bbox_min_y, &meta.bbox_max_x, &meta.bbox_max_y); + return scene_add_shape(s, meta); } -// -- Coordinate helpers for edit mode -- +// Add a circle centred at `center` with given radius. +// approximated by 4 cubic Bézier segments (one per quadrant). +// Returns shape index or -1. +int add_circle(scene_t* s, vec2 center, float radius) { + const float k = 0.552284749831f; // magic constant for 90° arc (4/3 * (sqrt(2)-1)) + float rk = radius * k; -static shape_vertex_t local_to_world(const shape_t *s, float lx, float ly) -{ - return (shape_vertex_t){ - s->cx + lx * s->sx * s->cos_r - ly * s->sy * s->sin_r, - s->cy + lx * s->sx * s->sin_r + ly * s->sy * s->cos_r - }; -} + // Start at rightmost point (0°), moving counter‑clockwise. + vec2 p0 = {center.x + radius, center.y}; -static shape_vertex_t world_to_local(const shape_t *s, float wx, float wy) -{ - float dx = wx - s->cx, dy = wy - s->cy; - return (shape_vertex_t){ - (dx * s->cos_r + dy * s->sin_r) / s->sx, - (-dx * s->sin_r + dy * s->cos_r) / s->sy - }; -} + // Quadrant 1 (0° to 90°): from right to top + segment_t seg1; + seg1.p0 = p0; + seg1.p1 = (vec2) {p0.x, p0.y + rk}; + seg1.p2 = (vec2) {center.x + rk, center.y + radius}; + seg1.p3 = (vec2) {center.x, center.y + radius}; -// -- Cubic Bezier evaluation for edit mode -- + // Quadrant 2 (90° to 180°): top to left + segment_t seg2; + seg2.p0 = seg1.p3; + seg2.p1 = (vec2) {center.x - rk, center.y + radius}; + seg2.p2 = (vec2) {center.x - radius, center.y + rk}; + seg2.p3 = (vec2) {center.x - radius, center.y}; -static shape_vertex_t bezier_eval_segment(float t, - shape_vertex_t p0, shape_vertex_t p1, - shape_vertex_t p2, shape_vertex_t p3) -{ - float u = 1.0f - t; - float u2 = u * u, u3 = u2 * u; - float t2 = t * t, t3 = t2 * t; - return (shape_vertex_t){ - u3 * p0.x + 3.0f * u2 * t * p1.x + 3.0f * u * t2 * p2.x + t3 * p3.x, - u3 * p0.y + 3.0f * u2 * t * p1.y + 3.0f * u * t2 * p2.y + t3 * p3.y - }; -} + // Quadrant 3 (180° to 270°): left to bottom + segment_t seg3; + seg3.p0 = seg2.p3; + seg3.p1 = (vec2) {center.x - radius, center.y - rk}; + seg3.p2 = (vec2) {center.x - rk, center.y - radius}; + seg3.p3 = (vec2) {center.x, center.y - radius}; -// Approximate arc length of a bezier segment via its control polygon. -static float bezier_control_polygon_len(shape_vertex_t p0, shape_vertex_t p1, - shape_vertex_t p2, shape_vertex_t p3) { - float dx = p1.x - p0.x, dy = p1.y - p0.y; - float len = sqrtf(dx*dx + dy*dy); - dx = p2.x - p1.x; dy = p2.y - p1.y; - len += sqrtf(dx*dx + dy*dy); - dx = p3.x - p2.x; dy = p3.y - p2.y; - len += sqrtf(dx*dx + dy*dy); - return len; -} + // Quadrant 4 (270° to 360°): bottom to right + segment_t seg4; + seg4.p0 = seg3.p3; + seg4.p1 = (vec2) {center.x + rk, center.y - radius}; + seg4.p2 = (vec2) {center.x + radius, center.y - rk}; + seg4.p3 = p0; // closes back to rightmost point -// Regenerate verts/indices from ctrl_points + Bezier handles. -// Subdivisions per segment are derived from the control-polygon arc length, -// giving consistent smoothness regardless of shape type or size. -static void shape_regenerate_from_ctrl(shape_pool_ctx_t *sp, shape_t *s) -{ - int n = s->ctrl_count; - if (n < 2) return; + int start = scene_add_segment(s, seg1); + if (start < 0) return -1; + if (scene_add_segment(s, seg2) < 0) return -1; + if (scene_add_segment(s, seg3) < 0) return -1; + if (scene_add_segment(s, seg4) < 0) return -1; - FREE(s->verts); - FREE(s->indices); - - int segs = s->closed ? n : n - 1; - - // First pass: compute subdivisions per segment, sum total vertex count - int *seg_subd = (int*) ALLOC((size_t)segs * sizeof(int)); - int total_verts = 1; // +1 for the closing anchor duplicate - for (int seg = 0; seg < segs; seg++) { - int i0 = seg; - int i1 = s->closed ? ((seg + 1) % n) : seg + 1; - float len = bezier_control_polygon_len( - s->ctrl_points[i0], s->ctrl_handle_out[i0], - s->ctrl_handle_in[i1], s->ctrl_points[i1]); - int subd = (int)(len / BEZIER_TARGET_CHORD); - if (subd < BEZIER_MIN_SUBD) subd = BEZIER_MIN_SUBD; - if (subd > BEZIER_MAX_SUBD) subd = BEZIER_MAX_SUBD; - seg_subd[seg] = subd; - total_verts += subd; - } - - s->num_verts = s->closed ? (uint32_t)(total_verts - 1) : (uint32_t)total_verts; - s->num_elements = (uint32_t)total_verts; - s->verts = (shape_vertex_t*) ALLOC((size_t)total_verts * sizeof(shape_vertex_t)); - s->indices = (uint16_t*) ALLOC((size_t)total_verts * sizeof(uint16_t)); - - // Second pass: generate vertices - int vi = 0; - for (int seg = 0; seg < segs; seg++) { - int i0 = seg; - int i1 = s->closed ? ((seg + 1) % n) : seg + 1; - shape_vertex_t p0 = s->ctrl_points[i0]; - shape_vertex_t p1 = s->ctrl_handle_out[i0]; - shape_vertex_t p2 = s->ctrl_handle_in[i1]; - shape_vertex_t p3 = s->ctrl_points[i1]; - - int steps = seg_subd[seg]; - for (int k = 0; k < steps; k++) { - float t = (float)k / (float)steps; - s->verts[vi++] = bezier_eval_segment(t, p0, p1, p2, p3); - } - } - s->verts[vi++] = s->ctrl_points[0]; - - for (int i = 0; i < total_verts; i++) s->indices[i] = (uint16_t)i; - - FREE(seg_subd); - - s->vertex_hash = hash_vertex_data(s->verts, s->num_elements); - shape_build_transform(sp, s); - shape_update_aabb(s); - sp->pool_dirty = true; -} - -#endif + shape_meta_t meta; + meta.start_segment = start; + meta.segment_count = 4; + compute_bbox(s->segments, start, 4, &meta.bbox_min_x, &meta.bbox_min_y, &meta.bbox_max_x, &meta.bbox_max_y); + return scene_add_shape(s, meta); +} \ No newline at end of file diff --git a/src/spatial.h b/src/spatial.h deleted file mode 100644 index 2592982..0000000 --- a/src/spatial.h +++ /dev/null @@ -1,341 +0,0 @@ -#ifndef SPATIAL_H -#define SPATIAL_H - -#include "api.h" - -#define SPATIAL_CELL_SIZE 250.0f -#define SPATIAL_HASH_BITS 16 -#define SPATIAL_HASH_SIZE (1 << SPATIAL_HASH_BITS) -#define SPATIAL_MAX_CELLS_PER_SHAPE 64 - -typedef struct { - int shape_idx; - float min_x, min_y, max_x, max_y; -} spatial_entry_t; - -typedef struct { - bool occupied; - int cx, cy; - spatial_entry_t *entries; - int count; - int capacity; -} spatial_slot_t; - -typedef struct { - spatial_slot_t slots[SPATIAL_HASH_SIZE]; - int *visited; // per-shape query-dedup frame tags - int visited_cap; - int query_frame; // increments per query, never 0 - bool dirty; -} spatial_grid_t; - -static int spatial_hash(int cx, int cy) -{ - return (cx * 73856093) ^ (cy * 19349663); -} - -static void spatial_init(spatial_grid_t *grid) -{ - memset(grid, 0, sizeof(*grid)); - grid->dirty = true; - grid->query_frame = 1; -} - -static void spatial_mark_dirty(spatial_grid_t *grid) -{ - grid->dirty = true; -} - -static void spatial_destroy(spatial_grid_t *grid) -{ - for (int i = 0; i < SPATIAL_HASH_SIZE; i++) { - if (grid->slots[i].entries) FREE(grid->slots[i].entries); - } - if (grid->visited) FREE(grid->visited); - memset(grid, 0, sizeof(*grid)); -} - -// Find or create the slot for cell (cx, cy). Returns the slot index. -static int spatial_find_slot(spatial_grid_t *grid, int cx, int cy) -{ - int idx = spatial_hash(cx, cy) & (SPATIAL_HASH_SIZE - 1); - int probe = 0; - while (grid->slots[idx].occupied && probe < SPATIAL_HASH_SIZE) { - if (grid->slots[idx].cx == cx && grid->slots[idx].cy == cy) break; - idx = (idx + 1) & (SPATIAL_HASH_SIZE - 1); - probe++; - } - if (!grid->slots[idx].occupied) { - grid->slots[idx].occupied = true; - grid->slots[idx].cx = cx; - grid->slots[idx].cy = cy; - } - return idx; -} - -// Enumerate the cells a shape's AABB overlaps. Returns the number of cells. -// Capped at SPATIAL_MAX_CELLS_PER_SHAPE; falls back to the center cell for -// degenerate huge shapes to avoid hash-table explosion. -static int spatial_shape_cells(shape_t *s, int *cell_xs, int *cell_ys) -{ - int cmin_x = (int)floorf((s->cx - s->aabb_hx) / SPATIAL_CELL_SIZE); - int cmax_x = (int)floorf((s->cx + s->aabb_hx) / SPATIAL_CELL_SIZE); - int cmin_y = (int)floorf((s->cy - s->aabb_hy) / SPATIAL_CELL_SIZE); - int cmax_y = (int)floorf((s->cy + s->aabb_hy) / SPATIAL_CELL_SIZE); - - int ncx = cmax_x - cmin_x + 1; - int ncy = cmax_y - cmin_y + 1; - - if (ncx <= 0 || ncy <= 0 || ncx * ncy > SPATIAL_MAX_CELLS_PER_SHAPE) { - cell_xs[0] = (int)floorf(s->cx / SPATIAL_CELL_SIZE); - cell_ys[0] = (int)floorf(s->cy / SPATIAL_CELL_SIZE); - return 1; - } - - int count = 0; - for (int cy = cmin_y; cy <= cmax_y; cy++) { - for (int cx = cmin_x; cx <= cmax_x; cx++) { - cell_xs[count] = cx; - cell_ys[count] = cy; - count++; - } - } - return count; -} - -static void spatial_rebuild(spatial_grid_t *grid, vector_t *shapes) -{ - if (!grid->dirty) return; - grid->dirty = false; - - int n = shapes->count; - - // Phase 0: clear occupied flags - for (int i = 0; i < SPATIAL_HASH_SIZE; i++) { - grid->slots[i].occupied = false; - grid->slots[i].count = 0; - } - - if (n == 0) return; - - // Grow visited array if needed (used for query-result dedup) - if (n > grid->visited_cap) { - if (grid->visited) FREE(grid->visited); - grid->visited = (int*)ALLOC((size_t)n * sizeof(int)); - grid->visited_cap = n; - } - memset(grid->visited, 0, (size_t)n * sizeof(int)); - grid->query_frame = 1; - - int cell_xs[128], cell_ys[128]; - - // Phase 1: count shapes per cell - for (int i = 0; i < n; i++) { - shape_t *s = (shape_t*)vec_get(shapes, i); - int nc = spatial_shape_cells(s, cell_xs, cell_ys); - for (int c = 0; c < nc; c++) { - int idx = spatial_find_slot(grid, cell_xs[c], cell_ys[c]); - grid->slots[idx].count++; - } - } - - // Phase 2: resize entry arrays when needed - for (int i = 0; i < SPATIAL_HASH_SIZE; i++) { - if (!grid->slots[i].occupied) continue; - int need = grid->slots[i].count; - if (need > grid->slots[i].capacity) { - if (grid->slots[i].entries) FREE(grid->slots[i].entries); - grid->slots[i].entries = (spatial_entry_t*)ALLOC( - (size_t)need * sizeof(spatial_entry_t)); - grid->slots[i].capacity = need; - } - grid->slots[i].count = 0; - } - - // Phase 3: fill entries — each shape is added to every cell its AABB overlaps - for (int i = 0; i < n; i++) { - shape_t *s = (shape_t*)vec_get(shapes, i); - float min_x = s->cx - s->aabb_hx; - float min_y = s->cy - s->aabb_hy; - float max_x = s->cx + s->aabb_hx; - float max_y = s->cy + s->aabb_hy; - - int nc = spatial_shape_cells(s, cell_xs, cell_ys); - for (int c = 0; c < nc; c++) { - int idx = spatial_find_slot(grid, cell_xs[c], cell_ys[c]); - spatial_entry_t *e = &grid->slots[idx].entries[grid->slots[idx].count++]; - e->shape_idx = i; - e->min_x = min_x; e->min_y = min_y; - e->max_x = max_x; e->max_y = max_y; - } - } -} - -// Point query — O(1) average. Only checks the cell containing the query point -// because shapes are now inserted into every cell their AABB overlaps. -static int spatial_query_point(spatial_grid_t *grid, vector_t *shapes, - float wx, float wy, float world_tol) -{ - int cx = (int)floorf(wx / SPATIAL_CELL_SIZE); - int cy = (int)floorf(wy / SPATIAL_CELL_SIZE); - - int idx = spatial_hash(cx, cy) & (SPATIAL_HASH_SIZE - 1); - int probe_start = idx; - - do { - if (!grid->slots[idx].occupied) break; - - if (grid->slots[idx].cx == cx && grid->slots[idx].cy == cy) { - for (int e = 0; e < grid->slots[idx].count; e++) { - spatial_entry_t *entry = &grid->slots[idx].entries[e]; - - if (wx < entry->min_x - world_tol || - wx > entry->max_x + world_tol || - wy < entry->min_y - world_tol || - wy > entry->max_y + world_tol) - continue; - - shape_t *s = (shape_t*)vec_get(shapes, entry->shape_idx); - if (shape_hit_test(s, wx, wy, world_tol)) - return entry->shape_idx; - } - break; - } - - idx = (idx + 1) & (SPATIAL_HASH_SIZE - 1); - } while (idx != probe_start); - - return -1; -} - -// Rectangle selection for marquee. Uses a per-query frame counter for dedup -// since shapes now appear in every cell they overlap. -static int spatial_query_rect_select(spatial_grid_t *grid, vector_t *shapes, - float min_x, float min_y, - float max_x, float max_y) -{ - int n = shapes->count; - for (int i = 0; i < n; i++) { - ((shape_t*)vec_get(shapes, i))->selected = false; - } - int selected_count = 0; - - grid->query_frame++; - int frame = grid->query_frame; - - int cell_min_x = (int)floorf(min_x / SPATIAL_CELL_SIZE); - int cell_max_x = (int)floorf(max_x / SPATIAL_CELL_SIZE); - int cell_min_y = (int)floorf(min_y / SPATIAL_CELL_SIZE); - int cell_max_y = (int)floorf(max_y / SPATIAL_CELL_SIZE); - - int cell_count = (cell_max_x - cell_min_x + 1) * (cell_max_y - cell_min_y + 1); - if (cell_count > SPATIAL_HASH_SIZE) { - for (int s = 0; s < SPATIAL_HASH_SIZE; s++) { - if (!grid->slots[s].occupied) continue; - for (int e = 0; e < grid->slots[s].count; e++) { - spatial_entry_t *entry = &grid->slots[s].entries[e]; - if (grid->visited[entry->shape_idx] == frame) continue; - grid->visited[entry->shape_idx] = frame; - - if (entry->max_x < min_x || entry->min_x > max_x || - entry->max_y < min_y || entry->min_y > max_y) - continue; - shape_t *shape = (shape_t*)vec_get(shapes, entry->shape_idx); - bool hit = (shape->cx >= min_x && shape->cx <= max_x && - shape->cy >= min_y && shape->cy <= max_y); - float sx_cos = shape->sx * shape->cos_r; - float sy_sin = shape->sy * shape->sin_r; - float sx_sin = shape->sx * shape->sin_r; - float sy_cos = shape->sy * shape->cos_r; - for (uint32_t v = 0; !hit && v < shape->num_verts; v++) { - float wx = shape->cx + shape->verts[v].x * sx_cos - shape->verts[v].y * sy_sin; - float wy = shape->cy + shape->verts[v].x * sx_sin + shape->verts[v].y * sy_cos; - if (wx >= min_x && wx <= max_x && - wy >= min_y && wy <= max_y) - hit = true; - } - if (hit) { shape->selected = true; selected_count++; } - } - } - return selected_count; - } - - for (int cy = cell_min_y; cy <= cell_max_y; cy++) { - for (int cx = cell_min_x; cx <= cell_max_x; cx++) { - int idx = spatial_hash(cx, cy) & (SPATIAL_HASH_SIZE - 1); - int probe_start = idx; - do { - if (!grid->slots[idx].occupied) break; - if (grid->slots[idx].cx == cx && grid->slots[idx].cy == cy) { - for (int e = 0; e < grid->slots[idx].count; e++) { - spatial_entry_t *entry = &grid->slots[idx].entries[e]; - if (grid->visited[entry->shape_idx] == frame) continue; - grid->visited[entry->shape_idx] = frame; - - if (entry->max_x < min_x || entry->min_x > max_x || - entry->max_y < min_y || entry->min_y > max_y) - continue; - shape_t *shape = (shape_t*)vec_get(shapes, entry->shape_idx); - bool hit = (shape->cx >= min_x && shape->cx <= max_x && - shape->cy >= min_y && shape->cy <= max_y); - float sc = shape->cos_r, ss = shape->sin_r; - for (uint32_t v = 0; !hit && v < shape->num_verts; v++) { - float lx = shape->verts[v].x * shape->sx; - float ly = shape->verts[v].y * shape->sy; - float wx = shape->cx + lx * sc - ly * ss; - float wy = shape->cy + lx * ss + ly * sc; - if (wx >= min_x && wx <= max_x && - wy >= min_y && wy <= max_y) - hit = true; - } - if (hit) { shape->selected = true; selected_count++; } - } - break; - } - idx = (idx + 1) & (SPATIAL_HASH_SIZE - 1); - } while (idx != probe_start); - } - } - return selected_count; -} - -static int spatial_query_viewport(spatial_grid_t *grid, - float min_x, float min_y, float max_x, float max_y, - int *out_indices, int max_out) -{ - grid->query_frame++; - int frame = grid->query_frame; - - int cell_min_x = (int)floorf(min_x / SPATIAL_CELL_SIZE); - int cell_max_x = (int)floorf(max_x / SPATIAL_CELL_SIZE); - int cell_min_y = (int)floorf(min_y / SPATIAL_CELL_SIZE); - int cell_max_y = (int)floorf(max_y / SPATIAL_CELL_SIZE); - - int count = 0; - for (int cy = cell_min_y; cy <= cell_max_y && count < max_out; cy++) { - for (int cx = cell_min_x; cx <= cell_max_x && count < max_out; cx++) { - int idx = spatial_hash(cx, cy) & (SPATIAL_HASH_SIZE - 1); - int probe_start = idx; - do { - if (!grid->slots[idx].occupied) break; - if (grid->slots[idx].cx == cx && grid->slots[idx].cy == cy) { - for (int e = 0; e < grid->slots[idx].count && count < max_out; e++) { - spatial_entry_t *entry = &grid->slots[idx].entries[e]; - if (grid->visited[entry->shape_idx] == frame) continue; - grid->visited[entry->shape_idx] = frame; - - if (entry->max_x < min_x || entry->min_x > max_x || - entry->max_y < min_y || entry->min_y > max_y) - continue; - out_indices[count++] = entry->shape_idx; - } - break; - } - idx = (idx + 1) & (SPATIAL_HASH_SIZE - 1); - } while (idx != probe_start); - } - } - return count; -} - -#endif diff --git a/src/types.h b/src/types.h deleted file mode 100644 index 2674625..0000000 --- a/src/types.h +++ /dev/null @@ -1,202 +0,0 @@ -#ifndef TYPES_H -#define TYPES_H - -#include "api.h" - -#define LOG_RING_SIZE 256 -#define HANDLE_OFFSET_PX 0.0f -#define HANDLE_RADIUS_PX 12.0f -#define HANDLE_CIRCLE_SEGMENTS 64 -#define CORNER_SIZE_PX 8.0f -#define TOP_PANEL_H 32.0f -#define PEN_MAX_CONTROL_POINTS 256 -#define PEN_PREVIEW_MAX_VERTS 2048 -#define PEN_CLOSE_PX 10.0f -#define EDIT_ANCHOR_SIZE_PX 8.0f -#define EDIT_HANDLE_SIZE_PX 5.0f - -#define DOUBLE_CLICK_TIME 0.3 -#define DRAG_THRESHOLD_SQ 9.0f -#define FRUSTUM_CULL_MARGIN 300.0f -#define CAMERA_ZOOM_MIN 0.1f -#define CAMERA_ZOOM_MAX 6.0f - -typedef enum { - TOOL_SELECT, - TOOL_PEN, - TOOL_CIRCLE, - TOOL_RECTANGLE, - TOOL_COUNT -} tool_t; - -typedef struct log_entry_t { - char text[256]; - uint32_t level; - uint64_t hash; -} log_entry_t; - -typedef struct { - mat4 mvp; -} uniform_t; - -typedef struct renderer_t { - sg_pipeline pipeline; - sg_shader shader; - sg_pass_action clear_pass; - uniform_t uniform; -} renderer_t; - -typedef struct { - int idx; - float init_sx, init_sy, init_cx, init_cy; - float ext_x, ext_y; - float lpi_x, lpi_y; -} resize_init_t; - -typedef struct { - bool active; - float start_x, start_y; - float current_x, current_y; - bool dragging; - int clicked_shape; -} select_state_t; - -typedef struct { - bool dragging; - float start_wx, start_wy; - float total_dx, total_dy; -} move_state_t; - -typedef struct { - bool dragging; - float center_x, center_y; - float start_angle; - float total_delta; - float handle_radius; -} rotate_state_t; - -typedef struct { - bool dragging; - float pivot_x, pivot_y; - float start_wx, start_wy; - float total_scale_x, total_scale_y; - float mask_x, mask_y; - float angle; - resize_init_t *init; - int init_count; -} resize_state_t; - -typedef struct { - int selected_count; - int hovered_shape; - - select_state_t select; - move_state_t move; - rotate_state_t rotate; - resize_state_t resize; - - float cached_aabb[4]; - bool aabb_cached; - - double last_click_time; - int last_click_shape_idx; - - vector_t drag_indices; - - // Edit mode - int editing_shape_idx; - bool edit_dragging; - int edit_drag_idx; - bool edit_handle_dragging; - int edit_handle_idx; - bool edit_handle_is_in; - // Pre-drag control point snapshot (for undo) - shape_vertex_t *edit_saved_ctrl; - shape_vertex_t *edit_saved_hin; - shape_vertex_t *edit_saved_hout; - int edit_saved_count; -} interact_state_t; - -typedef struct { - float fps_immediate; - float fps_average; - float frame_times[60]; - int frame_time_head; - int frame_time_count; - float frame_time_sum; -} debug_stats_t; - -typedef struct { - float right_panel_w; - float left_panel_w; - log_entry_t log_ring[LOG_RING_SIZE]; - int log_head; - int log_count; - bool log_show; - char log_filter[32]; - tool_t active_tool; - int list_last_shape; - int list_prev_count; - int *display_cache; - int display_cache_len; - bool display_cache_dirty; -} ui_state_t; - -typedef struct { - shape_t *shapes; - int shape_count; -} clipboard_t; - -typedef struct { - bool drawing; - shape_vertex_t points[PEN_MAX_CONTROL_POINTS]; - int point_count; - shape_vertex_t preview_verts[PEN_PREVIEW_MAX_VERTS]; - int preview_count; -} pen_state_t; - -// Per-overlay-buffer upload flags — replaces the single overlay_upload_needed -// bool so that during drag we only upload the buffers that actually changed -// (e.g. moving shapes only needs rect+corners, not edit-mode buffers). -typedef struct { - bool rect; - bool handle_circle; - bool corners; - bool edit_anchors; - bool edit_handles; - bool edit_lines; - bool pen; -} overlay_upload_flags_t; - -typedef struct userdata_t { - camera_t camera; - renderer_t renderer; - shape_pool_ctx_t shape_pool; - group_index_ctx_t group_idx; - pipeline_ctx_t pipelines; - panel_log_ctx_t panel_log_ctx; - rand_ctx_t rand_ctx; - vector_t shapes; - spatial_grid_t spatial_grid; - interact_state_t interact; - history_t history; - debug_stats_t debug; - ui_state_t ui; - overlay_upload_flags_t overlay_upload; - sg_buffer rect_vbuf, rect_ibuf; - sg_buffer handle_vbuf, handle_ibuf; - sg_buffer corner_vbuf, corner_ibuf; - int next_group_id; - vector_t groups; - clipboard_t clipboard; - float map_w, map_h; - float mouse_x, mouse_y; - double time; - pen_state_t pen; - sg_buffer pen_vbuf, pen_ibuf; - // Edit mode buffers - sg_buffer ed_anchor_vbuf, ed_handle_vbuf, ed_handle_line_vbuf, ed_shared_ibuf; - int ed_anchor_count, ed_handle_count, ed_handle_line_count; -} userdata_t; - -#endif diff --git a/src/ui_panels.h b/src/ui_panels.h deleted file mode 100644 index 132c95e..0000000 --- a/src/ui_panels.h +++ /dev/null @@ -1,503 +0,0 @@ -#ifndef UI_PANELS_H -#define UI_PANELS_H - -#include "api.h" -#include "types.h" -#include "interact.h" - -static const char *shape_kind_label(const char *name) { - if (name[0]) return name; - return "Shape"; -} - -static void build_display_recursive(vector_t *shapes, vector_t *groups, int parent_gid, int *display, int *dlen) -{ - for (int g = 0; g < groups->count; g++) { - group_t *grp = (group_t*) vec_get(groups, g); - if (grp->parent_id != parent_gid) continue; - for (int m = 0; m < grp->member_count; m++) - display[(*dlen)++] = grp->member_indices[m]; - build_display_recursive(shapes, groups, grp->id, display, dlen); - } - if (parent_gid == 0) { - for (int i = 0; i < shapes->count; i++) { - if (((shape_t*) vec_get(shapes, i))->group_id == 0) - display[(*dlen)++] = i; - } - } -} - -// Count shapes and group headers in a subtree (for collapsed path). -static void count_subtree_items(vector_t *groups, int gid, int *shapes_out, int *headers_out) -{ - int s = 0, h = 0; - for (int g = 0; g < groups->count; g++) { - group_t *grp = (group_t*) vec_get(groups, g); - if (grp->id == gid) { - s = grp->member_count; - for (int k = 0; k < groups->count; k++) { - group_t *child = (group_t*) vec_get(groups, k); - if (child->parent_id == gid) { - h += 1; - int cs, ch; - count_subtree_items(groups, child->id, &cs, &ch); - s += cs; h += ch; - } - } - break; - } - } - *shapes_out = s; - *headers_out = h; -} - -static void list_shape_clicked(userdata_t *ud, shape_t *s, int *display, int display_len, int display_pos) -{ - int n = ud->shapes.count; - bool ctrl = igGetIO_Nil()->KeyCtrl; - bool shift = igGetIO_Nil()->KeyShift && ud->ui.list_last_shape >= 0; - - if (shift) { - int from = ud->ui.list_last_shape; - int to = display_pos; - if (from > to) { int tmp = from; from = to; to = tmp; } - for (int j = 0; j < n; j++) - ((shape_t*) vec_get(&ud->shapes, j))->selected = false; - ud->interact.selected_count = 0; - for (int d = from; d <= to; d++) { - shape_t *sv = (shape_t*) vec_get(&ud->shapes, display[d]); - sv->selected = true; - ud->interact.selected_count++; - } - } else if (ctrl) { - s->selected = !s->selected; - ud->interact.selected_count += s->selected ? 1 : -1; - } else { - for (int j = 0; j < n; j++) - ((shape_t*) vec_get(&ud->shapes, j))->selected = false; - ud->interact.selected_count = 0; - s->selected = true; - ud->interact.selected_count = 1; - } - ud->ui.list_last_shape = display_pos; - ud->interact.aabb_cached = false; - overlay_invalidate(ud); - update_shape_states(ud); -} - -// Count items that are currently visible (respecting collapse state). -// Used to compute the exact scrollbar range. -static int count_visible_items(vector_t *groups, int parent_gid) -{ - int c = 0; - for (int g = 0; g < groups->count; g++) { - group_t *grp = (group_t*) vec_get(groups, g); - if (grp->parent_id != parent_gid) continue; - c += 1; // group header (always visible) - if (!grp->collapsed) { - c += grp->member_count; - c += count_visible_items(groups, grp->id); - } - } - return c; -} - -// Render tree level with virtualized scrolling (ocornut's technique from imgui#3823). -// `item_idx` tracks only VISIBLE items (collapsed subtrees don't contribute) -// so that the scrollbar accurately reflects the viewable content. `pos` always -// tracks the display-array position for shift-click range selection. -static int render_tree_level(userdata_t *ud, int parent_gid, int *display, int display_len, - int display_pos, int first_visible, int last_visible, int *item_idx) -{ - int n = ud->shapes.count; - int pos = display_pos; - bool has_groups = (ud->groups.count > 0); - - if (has_groups) { - for (int g = 0; g < ud->groups.count; g++) { - group_t *grp = (group_t*) vec_get(&ud->groups, g); - if (grp->parent_id != parent_gid) continue; - - int gid = grp->id; - bool visible = (*item_idx >= first_visible && *item_idx < last_visible); - (*item_idx)++; - - if (visible) { - // --- VISIBLE GROUP HEADER --- - ImGuiID storage_id = (ImGuiID)gid; - igSetNextItemStorageID(storage_id); - - char hdr[64]; - snprintf(hdr, sizeof(hdr), "Group##g%d", gid); - ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow; - if (!grp->collapsed) flags |= ImGuiTreeNodeFlags_DefaultOpen; - bool open = igTreeNodeEx_Str(hdr, flags); - grp->collapsed = !open; - - if (igIsItemClicked(ImGuiMouseButton_Left)) { - bool ctrl = igGetIO_Nil()->KeyCtrl; - if (ctrl) - toggle_group_recursive(ud, gid); - else - deselect_and_select_group_recursive(ud, gid); - ud->ui.list_last_shape = display_pos; - ud->interact.aabb_cached = false; - overlay_invalidate(ud); - update_shape_states(ud); - } - - if (open) { - for (int m = 0; m < grp->member_count; m++) { - int si = grp->member_indices[m]; - bool child_visible = (*item_idx >= first_visible && *item_idx < last_visible); - (*item_idx)++; - - if (child_visible) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, si); - char label[128]; - snprintf(label, sizeof(label), " %s##s%d", shape_kind_label(s->name), si); - if (igSelectable_Bool(label, s->selected, ImGuiSelectableFlags_None, (ImVec2){0,0})) - list_shape_clicked(ud, s, display, display_len, pos); - } - pos++; - } - pos = render_tree_level(ud, gid, display, display_len, pos, - first_visible, last_visible, item_idx); - igTreePop(); - } - // Closed node: no TreePop — TreePushOverrideID was not called. - // Also no item_idx advance for the hidden subtree — only the - // header counts toward the visible-total. Still advance pos - // for display-index accuracy. - if (!open) { - int cs, ch; - count_subtree_items(&ud->groups, gid, &cs, &ch); - pos += cs; - // item_idx NOT advanced: collapsed items are not visible - } - } else { - // --- CLIPPED (OFF-SCREEN) GROUP --- - if (grp->collapsed) { - int cs, ch; - count_subtree_items(&ud->groups, gid, &cs, &ch); - pos += cs; - // item_idx already incremented for header; collapsed - // subtree adds nothing (not visible). - } else { - // Open but clipped: TreePush walks subtree via recursion. - *item_idx += grp->member_count; - pos += grp->member_count; - - char label[64]; - snprintf(label, sizeof(label), "Group##g%d", gid); - igTreePush_Str(label); - pos = render_tree_level(ud, gid, display, display_len, pos, - first_visible, last_visible, item_idx); - igTreePop(); - } - } - } - } - - // Ungrouped shapes — only at the top level - if (parent_gid == 0) { - for (int i = 0; i < n; i++) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, i); - if (has_groups && s->group_id != 0) continue; - - bool in_range = (*item_idx >= first_visible && *item_idx < last_visible); - (*item_idx)++; - - if (in_range) { - char label[128]; - snprintf(label, sizeof(label), "%s##s%d", shape_kind_label(s->name), i); - if (igSelectable_Bool(label, s->selected, ImGuiSelectableFlags_None, (ImVec2){0,0})) - list_shape_clicked(ud, s, display, display_len, pos); - } - pos++; - } - } - - return pos; -} - -static void draw_top_panel(userdata_t *ud) -{ - igSetNextWindowPos((ImVec2){0, 0}, ImGuiCond_Always, (ImVec2){0, 0}); - igSetNextWindowSize((ImVec2){ud->camera.width, TOP_PANEL_H}, ImGuiCond_Always); - igBegin("Toolbar", NULL, - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoCollapse); - - for (int t = 0; t < TOOL_COUNT; t++) { - const char *label = NULL; - switch (t) { - case TOOL_SELECT: label = "Sel"; break; - case TOOL_PEN: label = "Pen"; break; - case TOOL_CIRCLE: label = "Circle"; break; - case TOOL_RECTANGLE: label = "Rect"; break; - default: break; - } - if (t > 0) igSameLine(0.0f, 4.0f); - bool active = (ud->ui.active_tool == t); - if (active) { - igPushStyleColor_Vec4(ImGuiCol_Button, (ImVec4){0.3f, 0.5f, 0.8f, 1.0f}); - igPushStyleColor_Vec4(ImGuiCol_ButtonHovered, (ImVec4){0.4f, 0.6f, 0.9f, 1.0f}); - } - if (igButton(label, (ImVec2){0, 0})) { - tool_t new_tool = (tool_t)t; - if (new_tool != TOOL_SELECT && new_tool != ud->ui.active_tool) { - for (int i = 0; i < ud->shapes.count; i++) { - ((shape_t*)vec_get(&ud->shapes, i))->selected = false; - } - ud->interact.selected_count = 0; - overlay_invalidate(ud); - update_shape_states(ud); - } - ud->ui.active_tool = new_tool; - } - if (active) - igPopStyleColor(2); - } - - igSameLine(0.0f, 16.0f); - - if (igButton("Undo", (ImVec2){0, 0})) { - if (history_undo(&ud->history, &ud->shapes, &ud->shape_pool, - &ud->groups, &ud->group_idx)) { - group_rebuild_members(&ud->group_idx, &ud->groups, &ud->shapes); - interact_structural_change(ud); - } - } - - igSameLine(0.0f, 4.0f); - - if (igButton("Redo", (ImVec2){0, 0})) { - if (history_redo(&ud->history, &ud->shapes, &ud->shape_pool, - &ud->groups, &ud->group_idx)) { - group_rebuild_members(&ud->group_idx, &ud->groups, &ud->shapes); - interact_structural_change(ud); - } - } - - igEnd(); -} - -static void draw_shape_list_panel(userdata_t *ud) -{ - igSetNextWindowPos((ImVec2){0, TOP_PANEL_H}, ImGuiCond_Always, (ImVec2){0, 0}); - igSetNextWindowSize((ImVec2){ud->ui.left_panel_w, ud->camera.height - TOP_PANEL_H}, ImGuiCond_Always); - igBegin("Shapes", NULL, - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - - int n = ud->shapes.count; - - if (n == 0) { - igText("No shapes"); - igEnd(); - return; - } - - if (ud->ui.display_cache_dirty || ud->ui.display_cache_len != n) { - FREE(ud->ui.display_cache); - ud->ui.display_cache = (int*) ALLOC((size_t)n * sizeof(int)); - ud->ui.display_cache_len = 0; - build_display_recursive(&ud->shapes, &ud->groups, 0, ud->ui.display_cache, &ud->ui.display_cache_len); - ud->ui.display_cache_dirty = false; - } - int *display = ud->ui.display_cache; - int display_len = ud->ui.display_cache_len; - - if (n != ud->ui.list_prev_count) { ud->ui.list_last_shape = -1; ud->ui.list_prev_count = n; } - if (ud->ui.list_last_shape >= display_len) ud->ui.list_last_shape = -1; - - // Count only visible items (respects collapse state) for correct scrollbar. - int total_items = count_visible_items(&ud->groups, 0); - for (int i = 0; i < n; i++) { - if (((shape_t*) vec_get(&ud->shapes, i))->group_id == 0) - total_items++; - } - - igBeginChild_Str("ListScroll", (ImVec2){0, 0}, false, ImGuiWindowFlags_None); - - float line_h = igGetTextLineHeightWithSpacing(); - float scroll_y = igGetScrollY(); - int first_visible = (int)(scroll_y / line_h); - if (first_visible < 0) first_visible = 0; - int visible_slack = (int)(igGetWindowHeight() / line_h) + 4; - int last_visible = first_visible + visible_slack; - if (last_visible > total_items) last_visible = total_items; - if (first_visible > last_visible) first_visible = last_visible; - - // Position cursor at first visible item, render, then set total content height. - float cursor_base = igGetCursorPosY(); - igSetCursorPosY(cursor_base + first_visible * line_h); - - int item_idx = 0; - render_tree_level(ud, 0, display, display_len, 0, - first_visible, last_visible, &item_idx); - - // Stretch content height so the scrollbar reflects the total visible items. - igSetCursorPosY(cursor_base + total_items * line_h); - igDummy((ImVec2){0, 0}); - - igEndChild(); - igEnd(); -} - -static void draw_properties_panel(userdata_t *ud) -{ - igSetNextWindowPos((ImVec2){ud->camera.width - ud->ui.right_panel_w, TOP_PANEL_H}, ImGuiCond_Always, (ImVec2){0, 0}); - igSetNextWindowSize((ImVec2){ud->ui.right_panel_w, ud->camera.height - TOP_PANEL_H}, ImGuiCond_Always); - igBegin("Properties", NULL, - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - - if (ud->interact.selected_count == 0) { - igText("No shape selected"); - } else if (ud->interact.selected_count > 1) { - int common_gid = -1; - bool same_group = true; - for (int i = 0; i < ud->shapes.count; i++) { - shape_t *s = (shape_t*) vec_get(&ud->shapes, i); - if (!s->selected) continue; - if (common_gid == -1) common_gid = s->group_id; - else if (s->group_id != common_gid) { same_group = false; break; } - } - if (same_group && common_gid != 0) { - igText("Group %d — %d shapes", common_gid, ud->interact.selected_count); - } else { - igText("%d shapes selected", ud->interact.selected_count); - } - } else { - int idx = 0; - while (idx < ud->shapes.count) { - shape_t *tmp = (shape_t*) vec_get(&ud->shapes, idx); - if (tmp->selected) break; - idx++; - } - shape_t *s = (shape_t*) vec_get(&ud->shapes, idx); - if (s->group_id != 0) { - char path[256] = ""; - int gid = s->group_id; - while (gid != 0) { - char seg[32]; - snprintf(seg, sizeof(seg), "%d", gid); - if (path[0]) { - int plen = (int)strlen(path); - int slen = (int)strlen(seg); - memmove(path + slen + 3, path, (size_t)(plen + 1)); - memcpy(path, seg, (size_t)slen); - path[slen] = ' '; path[slen + 1] = '>'; path[slen + 2] = ' '; - } else { - strcpy(path, seg); - } - group_t *g = find_group(&ud->group_idx, &ud->groups, gid); - gid = g ? g->parent_id : 0; - } - int members = 0; - for (int i = 0; i < ud->shapes.count; i++) { - if (((shape_t*) vec_get(&ud->shapes, i))->group_id == s->group_id) members++; - } - igText("Group %s — %d member%s", path, members, members > 1 ? "s" : ""); - } - bool changed = false; - - changed |= igDragFloat2("Position", &s->cx, 1.0f, 0, 0, "%.1f", 0); - if (igIsItemActivated()) history_begin_edit(&ud->history, &ud->shapes, idx, HIST_POSITION); - changed |= igDragFloat2("Scale", &s->sx, 1.0f, -10.0f, 10.0f, "%.1f", 0); - if (igIsItemActivated()) history_begin_edit(&ud->history, &ud->shapes, idx, HIST_SCALE); - changed |= igDragFloat("Rotation", &s->rotation, 0.01f, 0, 0, "%.3f", 0); - if (igIsItemActivated()) history_begin_edit(&ud->history, &ud->shapes, idx, HIST_ROTATION); - - if (changed) { shape_regenerate(&ud->shape_pool, s); spatial_mark_dirty(&ud->spatial_grid); overlay_invalidate(ud); } - - igSeparator(); - { - mat4 *m = &s->uniform.transform; - char dbg[512]; - snprintf(dbg, sizeof(dbg), - "Transform Matrix:\n" - "[%+.3f %+.3f %+.3f %+.3f]\n" - "[%+.3f %+.3f %+.3f %+.3f]\n" - "[%+.3f %+.3f %+.3f %+.3f]\n" - "[%+.3f %+.3f %+.3f %+.3f]\n" - "\nLocal vert[0]: (%.1f, %.1f)\n" - "World vert[0]: (%.1f, %.1f)\n" - "cx=%.1f cy=%.1f sx=%.1f sy=%.1f rot=%.3f", - (*m)[0][0], (*m)[0][1], (*m)[0][2], (*m)[0][3], - (*m)[1][0], (*m)[1][1], (*m)[1][2], (*m)[1][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], - s->verts[0].x, s->verts[0].y, - s->cx + s->verts[0].x * s->sx * s->cos_r - s->verts[0].y * s->sy * s->sin_r, - s->cy + s->verts[0].x * s->sx * s->sin_r + s->verts[0].y * s->sy * s->cos_r, - s->cx, s->cy, s->sx, s->sy, s->rotation); - if (igButton("Copy Debug", (ImVec2){0, 0})) - sapp_set_clipboard_string(dbg); - } - } - - igEnd(); - - if (ud->history.capturing && !igIsAnyItemActive()) { - history_end_edit(&ud->history, &ud->shapes); - } -} - -static void draw_log_panel(userdata_t *ud) -{ - if (!ud->ui.log_show) return; - - igSetNextWindowPos((ImVec2){10.0f, ud->camera.height - 200.0f}, ImGuiCond_FirstUseEver, (ImVec2){0, 0}); - igSetNextWindowSize((ImVec2){400.0f, 180.0f}, ImGuiCond_FirstUseEver); - igBegin("Log", &ud->ui.log_show, 0); - if (igButton("Clear", (ImVec2){0, 0})) { - ud->ui.log_head = 0; - ud->ui.log_count = 0; - } - igSameLine(0.0f, 10.0f); - if (igButton("Copy", (ImVec2){0, 0})) { - int total = ud->ui.log_count < LOG_RING_SIZE ? ud->ui.log_count : LOG_RING_SIZE; - int start = ud->ui.log_count < LOG_RING_SIZE ? 0 : ud->ui.log_head; - int cap = total * 260; - char *buf = (char*) ALLOC((size_t)cap); - int off = 0; - for (int i = 0; i < total; i++) { - int idx = (start + i) % LOG_RING_SIZE; - off += snprintf(buf + off, (size_t)(cap - off), "%s\n", ud->ui.log_ring[idx].text); - } - sapp_set_clipboard_string(buf); - FREE(buf); - } - igSameLine(0.0f, 10.0f); - igText("%d entries", ud->ui.log_count); - igSameLine(0.0f, 10.0f); - igText("FPS: %.0f (avg: %.0f)", ud->debug.fps_immediate, ud->debug.fps_average); - igSameLine(0.0f, 10.0f); - igText("%.3fms", sapp_frame_duration() * 1000); - igSeparator(); - - igBeginChild_Str("LogScroll", (ImVec2){0, 0}, false, 0); - int total = ud->ui.log_count < LOG_RING_SIZE ? ud->ui.log_count : LOG_RING_SIZE; - int start = ud->ui.log_count < LOG_RING_SIZE ? 0 : ud->ui.log_head; - for (int i = 0; i < total; i++) { - int idx = (start + i) % LOG_RING_SIZE; - log_entry_t *e = &ud->ui.log_ring[idx]; - ImVec4 color; - switch (e->level) { - case 0: color = (ImVec4){1.0f, 0.3f, 0.3f, 1.0f}; break; - case 1: color = (ImVec4){1.0f, 0.5f, 0.3f, 1.0f}; break; - case 2: color = (ImVec4){1.0f, 0.9f, 0.3f, 1.0f}; break; - default:color = (ImVec4){0.7f, 0.7f, 0.7f, 1.0f}; break; - } - igPushStyleColor_Vec4(ImGuiCol_Text, color); - igTextUnformatted(e->text, NULL); - igPopStyleColor(1); - } - if (total > 0) igSetScrollHereY(1.0f); - igEndChild(); - igEnd(); -} - -#endif diff --git a/src/util.h b/src/util.h deleted file mode 100644 index 5f90cbe..0000000 --- a/src/util.h +++ /dev/null @@ -1,150 +0,0 @@ -#ifndef UTIL_H -#define UTIL_H - -#include -#include - -typedef struct vector_t { - uint8_t *data; - int count; - int capacity; - int stride; -} vector_t; - -/** - * Zero-initialize a vector with the given element stride. - * - * @param v vector to initialize - * @param stride byte size of each element - */ -static void vec_init(vector_t *v, int stride) { - memset(v, 0, sizeof(*v)); - v->stride = stride; -} - -/** - * Grow the vector's backing array to at least min_capacity elements. - * Doubles capacity (starting at 8) or uses min_capacity, whichever is larger. - * - * @param v vector to grow - * @param min_capacity minimum element count required - */ -static void vec_grow(vector_t *v, int min_capacity) { - int new_cap = v->capacity ? v->capacity * 2 : 8; - if (new_cap < min_capacity) new_cap = min_capacity; - uint8_t *new_data = (uint8_t*) ALLOC(new_cap * v->stride); - if (!new_data) { - EM_ASM({ console.error("vec_grow: ALLOC failed for %d elements of %d bytes", $0, $1); }, - new_cap, v->stride); - return; - } - if (v->data) { - memcpy(new_data, v->data, v->count * v->stride); - FREE(v->data); - } - v->data = new_data; - v->capacity = new_cap; -} - -/** - * Append an uninitialized element to the end of the vector. Grows if needed. - * - * @param v vector to push into - * @return pointer to the new (uninitialized) element - */ -static void *vec_push(vector_t *v) { - if (v->count >= v->capacity) vec_grow(v, v->count + 1); - return v->data + (v->count++) * v->stride; -} - -/** - * Remove the last element from the vector (decrements count, no free). - * - * @param v vector to pop from - */ -static void vec_pop(vector_t *v) { - if (v->count > 0) v->count--; -} - -static void vec_remove_ordered(vector_t *v, int index) { - if (index < 0 || index >= v->count) return; - if (index < v->count - 1) { - memmove(v->data + index * v->stride, - v->data + (index + 1) * v->stride, - (v->count - index - 1) * v->stride); - } - v->count--; -} - -// Remove `count` elements at given indices in a single compaction pass. -// Indices must be sorted in ascending order and must be valid. -static void vec_remove_ordered_bulk(vector_t *v, const int *indices, int count) { - if (count <= 0) return; - int write = indices[0]; - for (int k = 0; k < count; k++) { - int gap_start = indices[k]; - int gap_end = (k + 1 < count) ? indices[k + 1] : v->count; - int keep = gap_end - gap_start - 1; - if (keep > 0) { - memmove(v->data + write * v->stride, - v->data + (gap_start + 1) * v->stride, - (size_t)keep * (size_t)v->stride); - write += keep; - } - } - v->count -= count; -} - -static void *vec_insert(vector_t *v, int index) { - if (index < 0 || index > v->count) return NULL; - if (v->count >= v->capacity) vec_grow(v, v->count + 1); - if (index < v->count) { - memmove(v->data + (index + 1) * v->stride, - v->data + index * v->stride, - (v->count - index) * v->stride); - } - v->count++; - return v->data + index * v->stride; -} - -/** - * Remove the element at index by swapping in the last element (O(1)). - * Order is not preserved. - * - * @param v vector to remove from - * @param index index of the element to remove - */ -static void vec_remove(vector_t *v, int index) { - if (index < 0 || index >= v->count) return; - if (index < v->count - 1) { - memcpy(v->data + index * v->stride, - v->data + (v->count - 1) * v->stride, - v->stride); - } - v->count--; -} - -/** - * Return a pointer to the element at index (no bounds check). - * - * @param v vector to access - * @param index element index - * @return pointer to the element - */ -static void *vec_get(vector_t *v, int index) { - return v->data + index * v->stride; -} - -/** - * Free the backing array and reset the vector to empty. - * - * @param v vector to free - */ -static void vec_free(vector_t *v) { - if (v->data) FREE(v->data); - v->data = NULL; - v->count = 0; - v->capacity = 0; -} - -#endif