Rebuild from scratch with a new strategy using compute shaders and SDF.

This commit is contained in:
2026-05-06 16:25:52 +02:00
parent 7e3da1c424
commit 9789e449c3
28 changed files with 1288 additions and 5827 deletions

View File

@@ -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 <emscripten.h>
#include <stdio.h>
@@ -74,4 +19,8 @@ static void panel_log(panel_log_ctx_t *pl, int level, const char *fmt, ...) {
#include <math.h>
#include <stdarg.h>
#include "shape.h"
#include "generated/compute.h"
#include "generated/display.h"
#endif

View File

@@ -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

View File

@@ -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

627
src/generated/compute.h Normal file
View File

@@ -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;

53
src/generated/display.h Normal file
View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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,
};
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

217
src/shaders/compute.wgsl Normal file
View File

@@ -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<storage> segments: array<Segment>;
@group(1) @binding(1) var<storage> shapes: array<ShapeMeta>;
@group(1) @binding(2) var<storage, read_write> sdf_buffer: array<f32>;
struct Uniforms {
pan: vec2f,
zoom: f32,
};
struct TileParams {
lod: u32,
tile_size: f32,
world_min: vec2f,
world_max: vec2f,
};
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
@group(0) @binding(1) var<uniform> 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<function, array<f32, 3>>) -> 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<f32, 3>;
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<u32>) {
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;
}

27
src/shaders/display.wgsl Normal file
View File

@@ -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<uniform> uniforms: Uniforms;
@group(0) @binding(1) var<uniform> 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);
}

View File

@@ -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<uniform> vs_uniforms: VsUniform;
@binding(1) @group(0) var<uniform> shape_uniform: ShapeUniform;
@vertex fn vs_main(input: VsIn) -> Vs2Fs {
var output: Vs2Fs;
let world_pos = shape_uniform.transform * vec4f(input.position.x, input.position.y, 0.0, 1.0);
output.pos = vs_uniforms.mvp * world_pos;
if (shape_uniform.state == 2u) {
output.color = vec4f(1.0, 0.84, 0.0, 1.0);
} else if (shape_uniform.state == 1u) {
output.color = vec4f(0.5, 0.6, 1.0, 1.0);
} else {
output.color = vec4f(0.8, 0.8, 0.8, 1.0);
}
return output;
}
@fragment fn fs_main(input: Vs2Fs) -> FsOut {
var output: FsOut;
output.color = input.color;
return output;
}

View File

@@ -1,50 +0,0 @@
struct VsUniform {
mvp: mat4x4f,
instance_base: u32,
};
struct ShapeData {
transform: mat4x4f,
state: u32,
};
@binding(0) @group(0) var<uniform> vs_uniforms: VsUniform;
@binding(0) @group(1) var<storage> shape_data: array<ShapeData>;
@binding(1) @group(1) var<storage> instance_map: array<u32>;
struct VsIn {
@builtin(instance_index) instance_idx: u32,
@location(0) position: vec2f,
};
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;
}

View File

@@ -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<uniform> vs_uniforms: VsUniform;
@binding(0) @group(1) var tex: texture_2d<f32>;
@binding(1) @group(1) var samp: sampler;
@binding(2) @group(1) var<storage> sprites: array<Sprite>;
@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;
}

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,150 +0,0 @@
#ifndef UTIL_H
#define UTIL_H
#include <stdint.h>
#include <string.h>
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