#include "qjs_geometry.h" #include "qjs_blob.h" #include "jsffi.h" #include "qjs_common.h" #include #include #include "HandmadeMath.h" #include "font.h" #include "sprite.h" #include "transform.h" #include "stb_ds.h" // GEOMETRY FUNCTIONS JSC_CCALL(geometry_rect_intersection, rect a = js2rect(js,argv[0]); rect b = js2rect(js,argv[1]); rect c; SDL_GetRectIntersectionFloat(&a, &b, &c); return rect2js(js,c); ) JSC_CCALL(geometry_rect_intersects, rect a = js2rect(js,argv[0]); rect b = js2rect(js,argv[1]); return JS_NewBool(js, SDL_HasRectIntersectionFloat(&a,&b)); ) JSC_CCALL(geometry_rect_inside, rect inner = js2rect(js,argv[0]); rect outer = js2rect(js,argv[1]); return JS_NewBool(js, inner.x >= outer.x && inner.x + inner.w <= outer.x + outer.w && inner.y >= outer.y && inner.y + inner.h <= outer.y + outer.h ); ) JSC_CCALL(geometry_rect_random, rect a = js2rect(js,argv[0]); return vec22js(js,(HMM_Vec2){ a.x + rand_range(js,-0.5,0.5)*a.w, a.y + rand_range(js,-0.5,0.5)*a.h }); ) JSC_CCALL(geometry_rect_point_inside, rect a = js2rect(js,argv[0]); HMM_Vec2 p = js2vec2(js,argv[1]); return JS_NewBool(js,p.x >= a.x && p.x <= a.x+a.w && p.y <= a.y+a.h && p.y >= a.y); ) JSC_CCALL(geometry_cwh2rect, HMM_Vec2 c = js2vec2(js,argv[0]); HMM_Vec2 wh = js2vec2(js,argv[1]); rect r; r.x = c.x; r.y = c.y; r.w = wh.x; r.h = wh.y; return rect2js(js,r); ) JSC_CCALL(geometry_rect_pos, rect r = js2rect(js,argv[0]); return vec22js(js,(HMM_Vec2){ .x = r.x, .y = r.y }); ) JSC_CCALL(geometry_rect_move, rect r = js2rect(js,argv[0]); HMM_Vec2 move = js2vec2(js,argv[1]); r.x += move.x; r.y += move.y; return rect2js(js,r); ) JSC_CCALL(geometry_rect_expand, rect a = js2rect(js,argv[0]); rect b = js2rect(js,argv[1]); rect c = {0}; c.x = fmin(a.x,b.x); c.y = fmin(a.y,b.y); c.w = fmax(a.x+a.w,b.x+b.w); c.h = fmax(a.y+a.h,b.y+b.h); return rect2js(js,c); ) // Helper functions for geometry operations static inline void add_quad(text_vert **verts, rect *restrict src, rect *restrict dst) { text_vert v = (text_vert){ .pos = (HMM_Vec2){dst->x, dst->y}, .uv = (HMM_Vec2){src->x,src->y}, .color = (HMM_Vec4){1,1,1,1} }; arrput(*verts, v); v = (text_vert){ .pos = (HMM_Vec2){dst->x+dst->w, dst->y}, .uv = (HMM_Vec2){src->x+src->w,src->y}, .color = (HMM_Vec4){1,1,1,1} }; arrput(*verts, v); v = (text_vert){ .pos = (HMM_Vec2){dst->x, dst->y+dst->h}, .uv = (HMM_Vec2){src->x,src->y+src->h}, .color = (HMM_Vec4){1,1,1,1} }; arrput(*verts, v); v = (text_vert){ .pos = (HMM_Vec2){dst->x+dst->w, dst->y+dst->h}, .uv = (HMM_Vec2){src->x+src->w,src->y+src->h}, .color = (HMM_Vec4){1,1,1,1} }; arrput(*verts, v); } static inline void tile_region(text_vert **verts, rect src_uv, rect dst, float tex_w, float tex_h, bool tile_x, bool tile_y) { // Convert the incoming UV rect into pixel coords rect src_px = { .x = src_uv.x * tex_w, .y = src_uv.y * tex_h, .w = src_uv.w * tex_w, .h = src_uv.h * tex_h }; // If src_px or dst is degenerate, early out if (src_px.w <= 0.0f || src_px.h <= 0.0f || dst.w <= 0.0f || dst.h <= 0.0f) return; // How many full tiles horizontally/vertically? // If not tiling in a dimension, we treat it as exactly 1 tile (no leftover). float cols_f, rows_f; float remain_wf = 0.0f; float remain_hf = 0.0f; if (tile_x) { remain_wf = modff(dst.w / src_px.w, &cols_f); } else { // Only 1 "column" covering entire width, no leftover cols_f = 1.0f; remain_wf = 0.0f; } if (tile_y) { remain_hf = modff(dst.h / src_px.h, &rows_f); } else { // Only 1 "row" covering entire height, no leftover rows_f = 1.0f; remain_hf = 0.0f; } int cols = (int)cols_f; int rows = (int)rows_f; // The leftover portion in screen coords (pixels) float remain_dst_w = remain_wf * src_px.w; float remain_dst_h = remain_hf * src_px.h; // Build the UV rect for a "full" tile rect src_full = src_uv; // Partial leftover in UV float remain_src_w_uv = remain_dst_w / tex_w; float remain_src_h_uv = remain_dst_h / tex_h; // For partial leftover in X dimension rect src_partial_x = src_full; src_partial_x.w = remain_src_w_uv; // For partial leftover in Y dimension rect src_partial_y = src_full; src_partial_y.h = remain_src_h_uv; // For partial leftover in both X & Y rect src_partial_xy = src_full; src_partial_xy.w = remain_src_w_uv; src_partial_xy.h = remain_src_h_uv; // Each tile is drawn 1:1 in screen coords float tile_w = tile_x ? src_px.w : dst.w; // If not tiling horizontally, match the entire dst width float tile_h = tile_y ? src_px.h : dst.h; // If not tiling vertically, match the entire dst height rect curr_dst; curr_dst.w = tile_w; curr_dst.h = tile_h; curr_dst.y = dst.y; // Loop over rows for (int y = 0; y < rows; y++) { curr_dst.x = dst.x; // Loop over columns for (int x = 0; x < cols; x++) { add_quad(verts, &src_full, &curr_dst); curr_dst.x += tile_w; } // Right-side leftover tile (only if tile_x is true) if (tile_x && remain_dst_w > 0.0f) { rect partial_dst = { .x = curr_dst.x, .y = curr_dst.y, .w = remain_dst_w, .h = tile_h }; add_quad(verts, &src_partial_x, &partial_dst); } curr_dst.y += tile_h; } // Bottom leftover row (only if tile_y is true) if (tile_y && remain_dst_h > 0.0f) { rect partial_row_dst; partial_row_dst.w = tile_w; partial_row_dst.h = remain_dst_h; partial_row_dst.y = curr_dst.y; partial_row_dst.x = dst.x; // Full columns in leftover row for (int x = 0; x < cols; x++) { add_quad(verts, &src_partial_y, &partial_row_dst); partial_row_dst.x += tile_w; } // Partial leftover corner (both X & Y leftover) if (tile_x && remain_dst_w > 0.0f) { rect partial_corner_dst = { .x = partial_row_dst.x, .y = partial_row_dst.y, .w = remain_dst_w, .h = remain_dst_h }; add_quad(verts, &src_partial_xy, &partial_corner_dst); } } } JSC_CCALL(gpu_slice9, JSValue jstex = argv[0]; rect dst = js2rect(js, argv[1]); // Full texture in UV coords rect src = { .x = 0, .y = 0, .w = 1, .h = 1 }; // The "slice" LRTB in PIXELS, but we convert to UV below lrtb src_slice = js2lrtb(js, argv[2]); lrtb dst_slice = src_slice; HMM_Vec2 size; JS_GETPROP(js, size.x, jstex, width, number) JS_GETPROP(js, size.y, jstex, height, number) JSValue info = argv[3]; int tile_top, tile_bottom, tile_left, tile_right, center_x, center_y; JS_GETPROP(js,tile_top, info, tile_top, bool) JS_GETPROP(js,tile_bottom,info,tile_bottom,bool) JS_GETPROP(js,tile_left,info,tile_left,bool) JS_GETPROP(js,tile_right,info,tile_right,bool) JS_GETPROP(js, center_x, info, tile_center_x, bool) JS_GETPROP(js, center_y, info, tile_center_y, bool) // Convert the slice edges from pixel to UV src_slice.l /= size.x; src_slice.r /= size.x; src_slice.t /= size.y; src_slice.b /= size.y; text_vert *verts = NULL; rect curr_src; rect curr_dst; // bottom-left corner (single quad) curr_src = src; curr_src.w = src_slice.l; curr_src.h = src_slice.b; curr_dst = dst; curr_dst.w = dst_slice.l; curr_dst.h = dst_slice.b; add_quad(&verts, &curr_src, &curr_dst); // top-left corner (single quad) curr_src = src; curr_src.x = src.x; curr_src.y = src.y + src.h - src_slice.t; curr_src.w = src_slice.l; curr_src.h = src_slice.t; curr_dst = dst; curr_dst.x = dst.x; curr_dst.y = dst.y + dst.h - dst_slice.t; curr_dst.w = dst_slice.l; curr_dst.h = dst_slice.t; add_quad(&verts, &curr_src, &curr_dst); // bottom-right corner (single quad) curr_src = src; curr_src.x = src.x + src.w - src_slice.r; curr_src.y = src.y; curr_src.w = src_slice.r; curr_src.h = src_slice.b; curr_dst = dst; curr_dst.x = dst.x + dst.w - dst_slice.r; curr_dst.y = dst.y; curr_dst.w = dst_slice.r; curr_dst.h = dst_slice.b; add_quad(&verts, &curr_src, &curr_dst); // top-right corner (single quad) curr_src = src; curr_src.x = src.x + src.w - src_slice.r; curr_src.y = src.y + src.h - src_slice.t; curr_src.w = src_slice.r; curr_src.h = src_slice.t; curr_dst = dst; curr_dst.x = dst.x + dst.w - dst_slice.r; curr_dst.y = dst.y + dst.h - dst_slice.t; curr_dst.w = dst_slice.r; curr_dst.h = dst_slice.t; add_quad(&verts, &curr_src, &curr_dst); // left bar (tiled) curr_src = src; curr_src.x = src.x; curr_src.y = src.y + src_slice.b; curr_src.w = src_slice.l; curr_src.h = src.h - src_slice.t - src_slice.b; curr_dst = dst; curr_dst.x = dst.x; curr_dst.y = dst.y + dst_slice.b; curr_dst.w = dst_slice.l; curr_dst.h = dst.h - dst_slice.t - dst_slice.b; tile_region(&verts, curr_src, curr_dst, size.x, size.y,tile_left,tile_left); // right bar (tiled) curr_src = src; curr_src.x = src.x + src.w - src_slice.r; curr_src.y = src.y + src_slice.b; curr_src.w = src_slice.r; curr_src.h = src.h - src_slice.t - src_slice.b; curr_dst = dst; curr_dst.x = dst.x + dst.w - dst_slice.r; curr_dst.y = dst.y + dst_slice.b; curr_dst.w = dst_slice.r; curr_dst.h = dst.h - dst_slice.t - dst_slice.b; tile_region(&verts, curr_src, curr_dst, size.x, size.y,tile_right,tile_right); // bottom bar (tiled) curr_src = src; curr_src.x = src.x + src_slice.l; curr_src.y = src.y; curr_src.w = src.w - src_slice.l - src_slice.r; curr_src.h = src_slice.b; curr_dst = dst; curr_dst.x = dst.x + dst_slice.l; curr_dst.y = dst.y; curr_dst.w = dst.w - dst_slice.l - dst_slice.r; curr_dst.h = dst_slice.b; tile_region(&verts, curr_src, curr_dst, size.x, size.y,tile_bottom,tile_bottom); // top bar (tiled) curr_src = src; curr_src.x = src.x + src_slice.l; curr_src.y = src.y + src.h - src_slice.t; curr_src.w = src.w - src_slice.l - src_slice.r; curr_src.h = src_slice.t; curr_dst = dst; curr_dst.x = dst.x + dst_slice.l; curr_dst.y = dst.y + dst.h - dst_slice.t; curr_dst.w = dst.w - dst_slice.l - dst_slice.r; curr_dst.h = dst_slice.t; tile_region(&verts, curr_src, curr_dst, size.x, size.y,tile_top,tile_top); // center (tiled) curr_src = src; curr_src.x = src.x + src_slice.l; curr_src.y = src.y + src_slice.b; curr_src.w = src.w - src_slice.l - src_slice.r; curr_src.h = src.h - src_slice.t - src_slice.b; curr_dst = dst; curr_dst.x = dst.x + dst_slice.l; curr_dst.y = dst.y + dst_slice.b; curr_dst.w = dst.w - dst_slice.l - dst_slice.r; curr_dst.h = dst.h - dst_slice.t - dst_slice.b; tile_region(&verts, curr_src, curr_dst, size.x, size.y, center_x,center_y); JSValue mesh = quads_to_mesh(js, verts); arrfree(verts); ret = mesh; ) JSC_CCALL(gpu_tile, HMM_Vec2 size; JSValue jstex = argv[0]; JS_GETATOM(js,size.x,jstex,width,number) JS_GETATOM(js, size.y, jstex, height, number) rect src_pixels = js2rect(js, argv[1]); // 'src' as pixel dimensions rect dst = js2rect(js, argv[2]); // 'dst' as screen coords int tilex, tiley; JSValue jstile = argv[3]; JS_GETPROP(js,tilex,jstile,repeat_x,bool) JS_GETPROP(js,tiley,jstile,repeat_y,bool) text_vert *verts = NULL; tile_region(&verts, src_pixels, dst, size.x, size.y,tilex,tiley); ret = quads_to_mesh(js,verts); arrfree(verts); ) // Sprite mesh generation functions typedef struct { JSValue val; void *ptr; size_t size; int need_new; } BufferCheckResult; static BufferCheckResult get_or_extend_buffer( JSContext *js, JSValue old_mesh, const char *prop, size_t needed_size, int type, int elements_per_item, int copy, int index ) { BufferCheckResult res = { JS_UNDEFINED, NULL, 0, 0 }; if (!JS_IsUndefined(old_mesh)) { JSValue old_buf = JS_GetPropertyStr(js, old_mesh, prop); if (!JS_IsUndefined(old_buf)) { size_t old_size; void *data = get_gpu_buffer(js, old_buf, NULL, &old_size); if (data && old_size >= needed_size) { // Old buffer is large enough res.val = old_buf; // keep it res.ptr = data; res.size = old_size; return res; } JS_FreeValue(js, old_buf); } } // If we reach here, we need a new buffer res.need_new = 1; return res; } static HMM_Vec3 base_quad[4] = { {0.0,0.0,1.0}, {1.0,0.0,1.0}, {0.0,1.0,1.0}, {1.0,1.0,1.0} }; JSC_CCALL(gpu_make_sprite_mesh, size_t quads = JS_ArrayLength(js, argv[0]); size_t verts = quads*4; size_t count = quads*6; // Prepare arrays on CPU HMM_Vec2 *posdata = malloc(sizeof(*posdata)*verts); HMM_Vec2 *uvdata = malloc(sizeof(*uvdata)*verts); HMM_Vec4 *colordata = malloc(sizeof(*colordata)*verts); for (int i = 0; i < quads; i++) { JSValue sub = JS_GetPropertyUint32(js,argv[0],i); transform *tr; rect src; HMM_Vec4 color; JS_GETATOM(js,src,sub,src,rect) JS_GETATOM(js,color,sub,color,color) JS_GETATOM(js,tr,sub,transform,transform) JS_FreeValue(js,sub); size_t base = i*4; if (tr) { HMM_Mat3 trmat = transform2mat3(tr); for (int j = 0; j < 4; j++) posdata[base+j] = HMM_MulM3V3(trmat, base_quad[j]).xy; } else { rect dst; JS_GETATOM(js,dst,sub,rect,rect); posdata[base+0] = (HMM_Vec2){dst.x,dst.y}; posdata[base + 1] = (HMM_Vec2){ dst.x+dst.w, dst.y }; posdata[base + 2] = (HMM_Vec2){ dst.x, dst.y+dst.h }; posdata[base + 3] = (HMM_Vec2){ dst.x+dst.w, dst.y+dst.h }; } uvdata[base+0] = (HMM_Vec2){src.x, src.y+src.h}; uvdata[base+1] = (HMM_Vec2){src.x+src.w, src.y+src.h}; uvdata[base+2] = (HMM_Vec2){src.x, src.y}; uvdata[base+3] = (HMM_Vec2){src.x+src.w, src.y}; colordata[base+0] = color; colordata[base+1] = color; colordata[base+2] = color; colordata[base+3] = color; } // Check old mesh JSValue old_mesh = JS_UNDEFINED; if (argc > 1) old_mesh = argv[1]; // Needed sizes size_t pos_size = sizeof(*posdata)*verts; size_t uv_size = sizeof(*uvdata)*verts; size_t color_size = sizeof(*colordata)*verts; BufferCheckResult pos_chk = get_or_extend_buffer(js, old_mesh, "pos", pos_size, JS_TYPED_ARRAY_FLOAT32, 2, 1, 0); BufferCheckResult uv_chk = get_or_extend_buffer(js, old_mesh, "uv", uv_size, JS_TYPED_ARRAY_FLOAT32, 2, 1, 0); BufferCheckResult color_chk = get_or_extend_buffer(js, old_mesh, "color", color_size, JS_TYPED_ARRAY_FLOAT32, 4, 1, 0); int need_new_all = pos_chk.need_new || uv_chk.need_new || color_chk.need_new; ret = JS_NewObject(js); if (need_new_all) { // Create all new buffers JSValue new_pos = make_gpu_buffer(js, posdata, pos_size, JS_TYPED_ARRAY_FLOAT32, 2, 1,0); JSValue new_uv = make_gpu_buffer(js, uvdata, uv_size, JS_TYPED_ARRAY_FLOAT32, 2, 1,0); JSValue new_color = make_gpu_buffer(js, colordata, color_size, JS_TYPED_ARRAY_FLOAT32, 0, 1,0); JS_SetPropertyStr(js, ret, "pos", new_pos); JS_SetPropertyStr(js, ret, "uv", new_uv); JS_SetPropertyStr(js, ret, "color", new_color); // Indices JSValue indices = make_quad_indices_buffer(js, quads); JS_SetPropertyStr(js, ret, "indices", indices); } else { // Reuse the old buffers // Just copy data into existing buffers via their pointers memcpy(pos_chk.ptr, posdata, pos_size); memcpy(uv_chk.ptr, uvdata, uv_size); memcpy(color_chk.ptr, colordata, color_size); // Duplicate old references since we're returning a new object JS_SetPropertyStr(js, ret, "pos", JS_DupValue(js, pos_chk.val)); JS_SetPropertyStr(js, ret, "uv", JS_DupValue(js, uv_chk.val)); JS_SetPropertyStr(js, ret, "color", JS_DupValue(js, color_chk.val)); // Indices can remain the same if they were also large enough. If using a shared global index buffer: JSValue indices = make_quad_indices_buffer(js, quads); JS_SetPropertyStr(js, ret, "indices", indices); } JS_SetPropertyStr(js, ret, "vertices", number2js(js, verts)); JS_SetPropertyStr(js, ret, "count", number2js(js, count)); JS_SetPropertyStr(js,ret,"num_indices", number2js(js,count)); // Free temporary CPU arrays free(posdata); free(uvdata); free(colordata); // Free old buffer values if they were fetched if (!JS_IsUndefined(pos_chk.val)) JS_FreeValue(js, pos_chk.val); if (!JS_IsUndefined(uv_chk.val)) JS_FreeValue(js, uv_chk.val); if (!JS_IsUndefined(color_chk.val)) JS_FreeValue(js, color_chk.val); return ret; ) struct quad_buffers { HMM_Vec2 *pos; HMM_Vec2 *uv; HMM_Vec4 *color; int verts; }; struct quad_buffers quad_buffers_new(int verts) { struct quad_buffers b; b.verts = verts; b.pos = malloc(sizeof(HMM_Vec2)*verts); b.uv = malloc(sizeof(HMM_Vec2)*verts); b.color = malloc(sizeof(HMM_Vec4)*verts); return b; } JSValue quadbuffers_to_mesh(JSContext *js, struct quad_buffers buffers) { JSValue jspos = make_gpu_buffer(js, buffers.pos, sizeof(HMM_Vec2)*buffers.verts, JS_TYPED_ARRAY_FLOAT32, 2, 0, 0); JSValue jsuv = make_gpu_buffer(js, buffers.uv, sizeof(HMM_Vec2)*buffers.verts, JS_TYPED_ARRAY_FLOAT32, 2,0,0); JSValue jscolor = make_gpu_buffer(js, buffers.color, sizeof(HMM_Vec4)*buffers.verts, JS_TYPED_ARRAY_FLOAT32, 4,0,0); size_t quads = buffers.verts/4; size_t count = buffers.verts/2*3; JSValue jsidx = make_quad_indices_buffer(js, quads); JSValue ret = JS_NewObject(js); JS_SetPropertyStr(js, ret, "pos", jspos); JS_SetPropertyStr(js, ret, "uv", jsuv); JS_SetPropertyStr(js, ret, "color", jscolor); JS_SetPropertyStr(js, ret, "indices", jsidx); JS_SetPropertyStr(js, ret, "vertices", number2js(js, buffers.verts)); JS_SetPropertyStr(js,ret,"num_indices", number2js(js,count)); return ret; } int sort_sprite(const sprite *a, const sprite *b) { if (a->layer != b->layer) return a->layer - b->layer; if (a->pos.Y != b->pos.Y) return (b->pos.Y - a->pos.Y); if (JS_VALUE_GET_PTR(a->image) != JS_VALUE_GET_PTR(b->image)) return JS_VALUE_GET_PTR(a->image) < JS_VALUE_GET_PTR(b->image) ? -1 : 1; return 0; } JSC_CCALL(gpu_make_sprite_queue, sprite *sprites = NULL; size_t quads = 0; int needfree = 1; if (js_is_blob(js, argv[0])) { // test for fastest size_t size; sprite *sprites = js_get_blob_data(js, &size, argv[0]); quads = size/sizeof(*sprites); needfree = 0; for (int i = 0; i < quads; i++) JS_DupValue(js,sprites[i].image); } else { quads = JS_ArrayLength(js, argv[0]); if (quads == 0) return JS_ThrowReferenceError(js, "Expected an array of sprites with length > 0."); arrsetcap(sprites, quads); for (int i = 0; i < quads; i++) { JSValue sub = JS_GetPropertyUint32(js, argv[0], i); sprite *jsp = js2sprite(js, sub); if (jsp) { arrput(sprites, *jsp); JS_DupValue(js,jsp->image); } else { sprite sp = {0}; JS_GETATOM(js, sp.pos, sub, pos, vec2) JS_GETATOM(js, sp.center, sub, center, vec2) JS_GETATOM(js, sp.skew, sub, skew, vec2) JS_GETATOM(js, sp.scale, sub, scale, vec2) JS_GETATOM(js, sp.color,sub,color,color) JS_GETATOM(js, sp.layer,sub,layer,number) sp.image = JS_GetPropertyStr(js,sub,"image"); sprite_apply(&sp); arrput(sprites,sp); } JS_FreeValue(js, sub); } } qsort(sprites, quads, sizeof(sprite), sort_sprite); struct quad_buffers buffers = quad_buffers_new(quads*4); const HMM_Vec2 local[4] = { {0,0}, {1,0}, {0,1}, {1,1} }; rect uv; rect uv_px; JSValue cur_img = JS_UNDEFINED; for (size_t i = 0; i < quads; i++) { sprite *s = &sprites[i]; if (JS_IsUndefined(cur_img) || !JS_StrictEq(js, s->image, cur_img)) { cur_img = s->image; JS_GETATOM(js, uv, cur_img, rect, rect) JS_GETATOM(js, uv_px, cur_img, rect_px, rect) } HMM_Vec2 px_size = { uv_px.w * s->scale.X, uv_px.h * s->scale.Y }; HMM_Vec2 anchor = { px_size.X * s->center.X, px_size.Y * s->center.Y }; size_t base = i * 4; for (int v = 0; v < 4; v++) { HMM_Vec2 lp = { local[v].X * px_size.X - anchor.X, local[v].Y * px_size.Y - anchor.Y }; HMM_Vec2 world = HMM_AddV2(s->pos, HMM_MulM2V2(s->affine, lp)); buffers.pos[base + v] = world; buffers.color[base + v] = s->color; } /* UVs are still top-left-origin pixel coords, so keep previous packing */ buffers.uv[base + 0] = (HMM_Vec2){ uv.x, uv.y + uv.h }; buffers.uv[base + 1] = (HMM_Vec2){ uv.x + uv.w, uv.y + uv.h }; buffers.uv[base + 2] = (HMM_Vec2){ uv.x, uv.y }; buffers.uv[base + 3] = (HMM_Vec2){ uv.x + uv.w, uv.y }; } JSValue mesh = quadbuffers_to_mesh(js, buffers); ret = JS_NewArray(js); int first_index = 0; int count = 0; int n = 0; JSValue img = JS_UNDEFINED; for (int i = 0; i < quads; i++) { if (!JS_SameValue(js, sprites[i].image, img)) { if (count > 0) { JSValue q = JS_NewObject(js); JS_SetPropertyStr(js, q, "type", JS_NewString(js, "geometry")); JS_SetPropertyStr(js, q, "mesh", JS_DupValue(js, mesh)); JS_SetPropertyStr(js, q, "pipeline", JS_DupValue(js, argv[2])); JS_SetPropertyStr(js, q, "image", img); JS_SetPropertyStr(js, q, "first_index", number2js(js, first_index)); JS_SetPropertyStr(js, q, "num_indices", number2js(js, count * 6)); JS_SetPropertyUint32(js, ret, n++, q); } first_index = i*6; count = 1; img = JS_DupValue(js, sprites[i].image); } else count++; JS_FreeValue(js,sprites[i].image); } if (count > 0) { JSValue q = JS_NewObject(js); JS_SetPropertyStr(js, q, "type", JS_NewString(js, "geometry")); JS_SetPropertyStr(js, q, "mesh", JS_DupValue(js, mesh)); JS_SetPropertyStr(js, q, "pipeline", JS_DupValue(js, argv[2])); JS_SetPropertyStr(js, q, "image", img); JS_SetPropertyStr(js, q, "first_index", number2js(js, first_index)); JS_SetPropertyStr(js, q, "num_indices", number2js(js, count * 6)); JS_SetPropertyUint32(js, ret, n++, q); } if (needfree) arrfree(sprites); JS_FreeValue(js, mesh); ) JSC_CCALL(geometry_rect_transform, // argv[0] = world‐space rect rect r = js2rect(js, argv[0]); // argv[1] = world_to_projection (16 floats) size_t byte_len; float *data12 = js_get_blob_data(js, &byte_len, argv[1]); HMM_Mat4 wp; memcpy(wp.Elements, data12, sizeof(wp.Elements)); // make our two corners at z=0 HMM_Vec4 p0 = { r.x, r.y, 0.0f, 1.0f }; HMM_Vec4 p1 = { r.x + r.w, r.y + r.h, 0.0f, 1.0f }; // transform into clip space HMM_Vec4 t0 = HMM_MulM4V4(wp, p0); HMM_Vec4 t1 = HMM_MulM4V4(wp, p1); // perspective divide → NDC float ndc_x0 = t0.X / t0.W, ndc_y0 = t0.Y / t0.W; float ndc_x1 = t1.X / t1.W, ndc_y1 = t1.Y / t1.W; // NDC → UV float u0 = ndc_x0 * 0.5f + 0.5f; float v0 = ndc_y0 * 0.5f + 0.5f; float u1 = ndc_x1 * 0.5f + 0.5f; float v1 = ndc_y1 * 0.5f + 0.5f; // (Optionally multiply by pixel‐size here, or do that in JS) rect newrect = { .x = u0, .y = v0, .w = u1 - u0, .h = v1 - v0 }; return rect2js(js, newrect); ) JSC_CCALL(geometry_tilemap_to_data, JSValue tilemap_obj = argv[0]; // Get tilemap properties double offset_x, offset_y, size_x, size_y; JS_GETPROP(js, offset_x, tilemap_obj, offset_x, number) JS_GETPROP(js, offset_y, tilemap_obj, offset_y, number) JS_GETPROP(js, size_x, tilemap_obj, size_x, number) JS_GETPROP(js, size_y, tilemap_obj, size_y, number) JSValue tiles_array = JS_GetPropertyStr(js, tilemap_obj, "tiles"); if (!JS_IsArray(js, tiles_array)) { JS_FreeValue(js, tiles_array); return JS_ThrowTypeError(js, "tilemap.tiles must be an array"); } // Count tiles int tile_count = 0; int tiles_len = JS_ArrayLength(js, tiles_array); for (int x = 0; x < tiles_len; x++) { JSValue col = JS_GetPropertyUint32(js, tiles_array, x); if (JS_IsArray(js, col)) { int col_len = JS_ArrayLength(js, col); for (int y = 0; y < col_len; y++) { JSValue tile = JS_GetPropertyUint32(js, col, y); if (!JS_IsUndefined(tile) && !JS_IsNull(tile)) { tile_count++; } JS_FreeValue(js, tile); } } JS_FreeValue(js, col); } if (tile_count == 0) { JS_FreeValue(js, tiles_array); return JS_NewObject(js); } // Allocate buffers - 4 vertices per tile int vertex_count = tile_count * 4; int index_count = tile_count * 6; float *xy_data = malloc(vertex_count * 2 * sizeof(float)); float *uv_data = malloc(vertex_count * 2 * sizeof(float)); SDL_FColor *color_data = malloc(vertex_count * sizeof(SDL_FColor)); uint16_t *index_data = malloc(index_count * sizeof(uint16_t)); // Generate vertices int vertex_idx = 0; int index_idx = 0; for (int x = 0; x < tiles_len; x++) { JSValue col = JS_GetPropertyUint32(js, tiles_array, x); if (JS_IsArray(js, col)) { int col_len = JS_ArrayLength(js, col); for (int y = 0; y < col_len; y++) { JSValue tile = JS_GetPropertyUint32(js, col, y); if (!JS_IsUndefined(tile) && !JS_IsNull(tile)) { // Calculate world position float world_x = (x + offset_x) * size_x; float world_y = (y + offset_y) * size_y; // Set vertex positions (4 corners of the tile) int base = vertex_idx * 2; xy_data[base + 0] = world_x; xy_data[base + 1] = world_y; xy_data[base + 2] = world_x + size_x; xy_data[base + 3] = world_y; xy_data[base + 4] = world_x; xy_data[base + 5] = world_y + size_y; xy_data[base + 6] = world_x + size_x; xy_data[base + 7] = world_y + size_y; // Set UVs (normalized 0-1 for now) uv_data[base + 0] = 0.0f; uv_data[base + 1] = 0.0f; uv_data[base + 2] = 1.0f; uv_data[base + 3] = 0.0f; uv_data[base + 4] = 0.0f; uv_data[base + 5] = 1.0f; uv_data[base + 6] = 1.0f; uv_data[base + 7] = 1.0f; // Set colors (check if tile has color property) SDL_FColor default_color = {1.0f, 1.0f, 1.0f, 1.0f}; if (JS_IsObject(tile)) { JSValue color_val = JS_GetPropertyStr(js, tile, "color"); if (!JS_IsUndefined(color_val)) { HMM_Vec4 color = js2color(js, color_val); default_color.r = color.r; default_color.g = color.g; default_color.b = color.b; default_color.a = color.a; JS_FreeValue(js, color_val); } } for (int i = 0; i < 4; i++) { color_data[vertex_idx + i] = default_color; } // Set indices (two triangles per tile) uint16_t base_idx = vertex_idx; index_data[index_idx++] = base_idx + 0; index_data[index_idx++] = base_idx + 1; index_data[index_idx++] = base_idx + 2; index_data[index_idx++] = base_idx + 1; index_data[index_idx++] = base_idx + 3; index_data[index_idx++] = base_idx + 2; vertex_idx += 4; } JS_FreeValue(js, tile); } } JS_FreeValue(js, col); } JS_FreeValue(js, tiles_array); // Create result object with blob data ret = JS_NewObject(js); // Create blobs for each data type JSValue xy_blob = js_new_blob_stoned_copy(js, xy_data, vertex_count * 2 * sizeof(float)); JSValue uv_blob = js_new_blob_stoned_copy(js, uv_data, vertex_count * 2 * sizeof(float)); JSValue color_blob = js_new_blob_stoned_copy(js, color_data, vertex_count * sizeof(SDL_FColor)); JSValue index_blob = js_new_blob_stoned_copy(js, index_data, index_count * sizeof(uint16_t)); JS_SetPropertyStr(js, ret, "xy", xy_blob); JS_SetPropertyStr(js, ret, "xy_stride", JS_NewInt32(js, 2 * sizeof(float))); JS_SetPropertyStr(js, ret, "uv", uv_blob); JS_SetPropertyStr(js, ret, "uv_stride", JS_NewInt32(js, 2 * sizeof(float))); JS_SetPropertyStr(js, ret, "color", color_blob); JS_SetPropertyStr(js, ret, "color_stride", JS_NewInt32(js, sizeof(SDL_FColor))); JS_SetPropertyStr(js, ret, "indices", index_blob); JS_SetPropertyStr(js, ret, "num_vertices", JS_NewInt32(js, vertex_count)); JS_SetPropertyStr(js, ret, "num_indices", JS_NewInt32(js, index_count)); JS_SetPropertyStr(js, ret, "size_indices", JS_NewInt32(js, 2)); // using uint16_t free(xy_data); free(uv_data); free(color_data); free(index_data); ) static const JSCFunctionListEntry js_geometry_funcs[] = { MIST_FUNC_DEF(geometry, rect_intersection, 2), MIST_FUNC_DEF(geometry, rect_intersects, 2), MIST_FUNC_DEF(geometry, rect_expand, 2), MIST_FUNC_DEF(geometry, rect_inside, 2), MIST_FUNC_DEF(geometry, rect_random, 1), MIST_FUNC_DEF(geometry, cwh2rect, 2), MIST_FUNC_DEF(geometry, rect_point_inside, 2), MIST_FUNC_DEF(geometry, rect_pos, 1), MIST_FUNC_DEF(geometry, rect_move, 2), MIST_FUNC_DEF(geometry, rect_transform, 2), MIST_FUNC_DEF(geometry, tilemap_to_data, 1), MIST_FUNC_DEF(gpu, tile, 4), MIST_FUNC_DEF(gpu, slice9, 3), MIST_FUNC_DEF(gpu, make_sprite_mesh, 2), MIST_FUNC_DEF(gpu, make_sprite_queue, 4), }; JSValue js_geometry_use(JSContext *js) { JSValue mod = JS_NewObject(js); JS_SetPropertyFunctionList(js,mod,js_geometry_funcs,countof(js_geometry_funcs)); return mod; }