#include "cell.h" #include "prosperon.h" #include #include "HandmadeMath.h" #include "stb_ds.h" extern void *get_gpu_buffer(JSContext *js, JSValue argv, size_t *stride, size_t *size); // 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 JS_NULL; // 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); ) 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); } } } JSValue quads_to_mesh(JSContext *js, text_vert *buffer) { size_t verts = arrlen(buffer); JSValue ret = JS_NewObject(js); // Allocate flat arrays for xy, uv, and color data size_t xy_size = verts * 2 * sizeof(float); size_t uv_size = verts * 2 * sizeof(float); size_t color_size = verts * sizeof(SDL_FColor); float *xy_data = malloc(xy_size); float *uv_data = malloc(uv_size); SDL_FColor *color_data = malloc(color_size); // Convert vertex data to flat arrays for (int i = 0; i < verts; i++) { xy_data[i*2] = buffer[i].pos.x; xy_data[i*2+1] = buffer[i].pos.y; uv_data[i*2] = buffer[i].uv.x; uv_data[i*2+1] = buffer[i].uv.y; color_data[i].r = buffer[i].color.x; color_data[i].g = buffer[i].color.y; color_data[i].b = buffer[i].color.z; color_data[i].a = buffer[i].color.w; } size_t quads = verts/4; size_t count = quads*6; // Create indices uint16_t *indices = malloc(sizeof(uint16_t)*count); for (int i = 0, v = 0; i < count; i += 6, v += 4) { indices[i] = v; indices[i+1] = v+2; indices[i+2] = v+1; indices[i+3] = v+2; indices[i+4] = v+3; indices[i+5] = v+1; } // Create blobs for geometry data JS_SetPropertyStr(js, ret, "xy", js_new_blob_stoned_copy(js, xy_data, xy_size)); JS_SetPropertyStr(js, ret, "xy_stride", JS_NewInt32(js, 2 * sizeof(float))); JS_SetPropertyStr(js, ret, "uv", js_new_blob_stoned_copy(js, uv_data, uv_size)); JS_SetPropertyStr(js, ret, "uv_stride", JS_NewInt32(js, 2 * sizeof(float))); JS_SetPropertyStr(js, ret, "color", js_new_blob_stoned_copy(js, color_data, color_size)); JS_SetPropertyStr(js, ret, "color_stride", JS_NewInt32(js, sizeof(SDL_FColor))); JS_SetPropertyStr(js, ret, "indices", js_new_blob_stoned_copy(js, indices, sizeof(uint16_t)*count)); JS_SetPropertyStr(js, ret, "num_vertices", JS_NewInt32(js, verts)); JS_SetPropertyStr(js, ret, "num_indices", JS_NewInt32(js, count)); JS_SetPropertyStr(js, ret, "size_indices", JS_NewInt32(js, 2)); // uint16_t size free(xy_data); free(uv_data); free(color_data); free(indices); return ret; } 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_NULL, NULL, 0, 0 }; if (!JS_IsNull(old_mesh)) { JSValue old_buf = JS_GetPropertyStr(js, old_mesh, prop); if (!JS_IsNull(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} }; 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; } 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]); if (data12 == -1) return JS_EXCEPTION; if (byte_len == 0) return JS_ThrowTypeError(js, "Matrix blob contains no data"); 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; double pos_x = 0, pos_y = 0; 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) // Get position properties (optional, default to 0) JSValue pos_x_val = JS_GetPropertyStr(js, tilemap_obj, "pos_x"); if (!JS_IsNull(pos_x_val)) { JS_ToFloat64(js, &pos_x, pos_x_val); } JS_FreeValue(js, pos_x_val); JSValue pos_y_val = JS_GetPropertyStr(js, tilemap_obj, "pos_y"); if (!JS_IsNull(pos_y_val)) { JS_ToFloat64(js, &pos_y, pos_y_val); } JS_FreeValue(js, pos_y_val); 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_IsNull(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_IsNull(tile) && !JS_IsNull(tile)) { // Calculate world position // x and y are array indices, need to convert to logical coordinates float logical_x = x + offset_x; float logical_y = y + offset_y; float world_x = logical_x * size_x + pos_x; float world_y = logical_y * size_y + pos_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; uv_data[base + 1] = 1.0f; // now samples the TOP of the image uv_data[base + 2] = 1.0f; uv_data[base + 3] = 1.0f; uv_data[base + 4] = 0; uv_data[base + 5] = 0; uv_data[base + 6] = 1.0f; uv_data[base + 7] = 0; // 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_IsNull(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 void print_buffers(float *xy_data, float *uv_data, SDL_FColor *color_data, uint16_t *index_data, int vertex_count, int index_count) { printf("=== GEOMETRY BUFFERS ===\n"); printf("XY Data (%d vertices):\n", vertex_count); for (int i = 0; i < vertex_count; i++) { printf(" Vertex %d: x=%.2f, y=%.2f\n", i, xy_data[i*2], xy_data[i*2+1]); } printf("\nUV Data:\n"); for (int i = 0; i < vertex_count; i++) { printf(" Vertex %d: u=%.4f, v=%.4f\n", i, uv_data[i*2], uv_data[i*2+1]); } printf("\nColor Data:\n"); for (int i = 0; i < vertex_count; i++) { printf(" Vertex %d: r=%.2f, g=%.2f, b=%.2f, a=%.2f\n", i, color_data[i].r, color_data[i].g, color_data[i].b, color_data[i].a); } printf("\nIndex Data (%d indices):\n", index_count); for (int i = 0; i < index_count; i += 3) { printf(" Triangle %d: %d, %d, %d\n", i/3, index_data[i], index_data[i+1], index_data[i+2]); } printf("========================\n"); } JSC_CCALL(geometry_sprites_to_data, JSValue sprites_array = argv[0]; if (!JS_IsArray(js, sprites_array)) { return JS_ThrowTypeError(js, "sprites must be an array"); } int sprite_count = JS_ArrayLength(js, sprites_array); if (sprite_count == 0) { return JS_NewObject(js); } // Allocate buffers - 4 vertices per sprite int vertex_count = sprite_count * 4; int index_count = sprite_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 i = 0; i < sprite_count; i++) { JSValue sprite = JS_GetPropertyUint32(js, sprites_array, i); // Get sprite properties JSValue pos_val = JS_GetPropertyStr(js, sprite, "pos"); JSValue texture_val = JS_GetPropertyStr(js, sprite, "texture"); JSValue color_val = JS_GetPropertyStr(js, sprite, "color"); double width = 32, height = 32, anchor_x = 0.5, anchor_y = 0.5; // Try to get width/height from sprite first, otherwise use texture dimensions JSValue width_val = JS_GetPropertyStr(js, sprite, "width"); if (!JS_IsNull(width_val)) { JS_ToFloat64(js, &width, width_val); } else if (!JS_IsNull(texture_val)) { // Get width from texture JSValue texture_width = JS_GetPropertyStr(js, texture_val, "width"); if (!JS_IsNull(texture_width)) { JS_ToFloat64(js, &width, texture_width); } JS_FreeValue(js, texture_width); } JS_FreeValue(js, width_val); JSValue height_val = JS_GetPropertyStr(js, sprite, "height"); if (!JS_IsNull(height_val)) { JS_ToFloat64(js, &height, height_val); } else if (!JS_IsNull(texture_val)) { // Get height from texture JSValue texture_height = JS_GetPropertyStr(js, texture_val, "height"); if (!JS_IsNull(texture_height)) { JS_ToFloat64(js, &height, texture_height); } JS_FreeValue(js, texture_height); } JS_FreeValue(js, height_val); JSValue anchor_x_val = JS_GetPropertyStr(js, sprite, "anchor_x"); if (!JS_IsNull(anchor_x_val)) { JS_ToFloat64(js, &anchor_x, anchor_x_val); } JS_FreeValue(js, anchor_x_val); JSValue anchor_y_val = JS_GetPropertyStr(js, sprite, "anchor_y"); if (!JS_IsNull(anchor_y_val)) { JS_ToFloat64(js, &anchor_y, anchor_y_val); } JS_FreeValue(js, anchor_y_val); HMM_Vec2 pos = js2vec2(js, pos_val); // Calculate sprite corners with anchor float half_w = width * 0.5f; float half_h = height * 0.5f; float anchor_offset_x = width * anchor_x - half_w; float anchor_offset_y = height * anchor_y - half_h; float left = pos.x - half_w - anchor_offset_x; float right = pos.x + half_w - anchor_offset_x; float bottom = pos.y - half_h - anchor_offset_y; float top = pos.y + half_h - anchor_offset_y; // Set vertex positions (4 corners of the sprite) int base = vertex_idx * 2; xy_data[base + 0] = left; // bottom-left xy_data[base + 1] = bottom; xy_data[base + 2] = right; // bottom-right xy_data[base + 3] = bottom; xy_data[base + 4] = left; // top-left xy_data[base + 5] = top; xy_data[base + 6] = right; // top-right xy_data[base + 7] = top; // Get UV coordinates from texture (if available) if (!JS_IsNull(texture_val)) { JSValue rect_val = JS_GetPropertyStr(js, texture_val, "rect"); if (!JS_IsNull(rect_val)) { rect uv_rect = js2rect(js, rect_val); // Get texture dimensions to normalize pixel coordinates double tex_width = 1.0, tex_height = 1.0; JSValue texture_width = JS_GetPropertyStr(js, texture_val, "width"); JSValue texture_height = JS_GetPropertyStr(js, texture_val, "height"); if (!JS_IsNull(texture_width)) { JS_ToFloat64(js, &tex_width, texture_width); } if (!JS_IsNull(texture_height)) { JS_ToFloat64(js, &tex_height, texture_height); } JS_FreeValue(js, texture_width); JS_FreeValue(js, texture_height); // The rect contains pixel coordinates, normalize them float u0 = uv_rect.x / tex_width; float v0 = uv_rect.y / tex_height; float u1 = (uv_rect.x + uv_rect.w) / tex_width; float v1 = (uv_rect.y + uv_rect.h) / tex_height; // Set UVs based on normalized texture rect uv_data[base + 0] = u0; uv_data[base + 1] = v1; // bottom-left uv_data[base + 2] = u1; uv_data[base + 3] = v1; // bottom-right uv_data[base + 4] = u0; uv_data[base + 5] = v0; // top-left uv_data[base + 6] = u1; uv_data[base + 7] = v0; // top-right JS_FreeValue(js, rect_val); } else { // Default UVs (0-1) uv_data[base + 0] = 0.0f; uv_data[base + 1] = 1.0f; uv_data[base + 2] = 1.0f; uv_data[base + 3] = 1.0f; uv_data[base + 4] = 0.0f; uv_data[base + 5] = 0.0f; uv_data[base + 6] = 1.0f; uv_data[base + 7] = 0.0f; } } else { // Default UVs (0-1) uv_data[base + 0] = 0.0f; uv_data[base + 1] = 1.0f; uv_data[base + 2] = 1.0f; uv_data[base + 3] = 1.0f; uv_data[base + 4] = 0.0f; uv_data[base + 5] = 0.0f; uv_data[base + 6] = 1.0f; uv_data[base + 7] = 0.0f; } // Set colors SDL_FColor default_color = {1.0f, 1.0f, 1.0f, 1.0f}; if (!JS_IsNull(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; } for (int j = 0; j < 4; j++) { color_data[vertex_idx + j] = default_color; } // Set indices (two triangles per sprite) uint16_t base_idx = vertex_idx; index_data[index_idx++] = base_idx + 0; // triangle 1: 0,1,2 index_data[index_idx++] = base_idx + 1; index_data[index_idx++] = base_idx + 2; index_data[index_idx++] = base_idx + 1; // triangle 2: 1,3,2 index_data[index_idx++] = base_idx + 3; index_data[index_idx++] = base_idx + 2; vertex_idx += 4; JS_FreeValue(js, pos_val); JS_FreeValue(js, texture_val); JS_FreeValue(js, color_val); JS_FreeValue(js, sprite); } // 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); ) JSC_CCALL(geometry_transform_xy_blob, // argv[0] = xy blob (contains vertex positions as float pairs) // argv[1] = camera transform parameters [a, c, e, f] JSValue xy_blob = argv[0]; if (!js_is_blob(js, xy_blob)) { return JS_ThrowTypeError(js, "First argument must be an XY blob"); } JSValue camera_params = argv[1]; if (!JS_IsArray(js, camera_params) || JS_ArrayLength(js, camera_params) != 4) { return JS_ThrowTypeError(js, "Second argument must be an array of 4 camera transform parameters [a, c, e, f]"); } // Get camera transform parameters double a, c, e, f; JSValue a_val = JS_GetPropertyUint32(js, camera_params, 0); JSValue c_val = JS_GetPropertyUint32(js, camera_params, 1); JSValue e_val = JS_GetPropertyUint32(js, camera_params, 2); JSValue f_val = JS_GetPropertyUint32(js, camera_params, 3); JS_ToFloat64(js, &a, a_val); JS_ToFloat64(js, &c, c_val); JS_ToFloat64(js, &e, e_val); JS_ToFloat64(js, &f, f_val); JS_FreeValue(js, a_val); JS_FreeValue(js, c_val); JS_FreeValue(js, e_val); JS_FreeValue(js, f_val); // Get blob data size_t xy_size; float *xy_data = (float*)js_get_blob_data(js, &xy_size, xy_blob); if ((intptr_t)xy_data == -1) { return JS_EXCEPTION; } if (!xy_data || xy_size == 0) { return JS_ThrowTypeError(js, "XY blob contains no data"); } // Calculate number of vertices (each vertex has 2 floats: x, y) int vertex_count = xy_size / (2 * sizeof(float)); if (vertex_count * 2 * sizeof(float) != xy_size) { return JS_ThrowTypeError(js, "XY blob size is not a multiple of vertex size"); } // Allocate new buffer for transformed coordinates float *transformed_xy = malloc(xy_size); if (!transformed_xy) { return JS_ThrowTypeError(js, "Failed to allocate memory for transformed coordinates"); } // Apply camera transformation to each vertex for (int i = 0; i < vertex_count; i++) { float world_x = xy_data[i * 2 + 0]; float world_y = xy_data[i * 2 + 1]; // Apply 2D affine transformation: screen = world * camera_matrix float screen_x = a * world_x + c; float screen_y = e * world_y + f; transformed_xy[i * 2 + 0] = screen_x; transformed_xy[i * 2 + 1] = screen_y; } // Create new blob with transformed data JSValue transformed_blob = js_new_blob_stoned_copy(js, transformed_xy, xy_size); free(transformed_xy); ret = transformed_blob; ) JSC_CCALL(geometry_array_blob, JSValue arr = argv[0]; size_t len = JS_ArrayLength(js,arr); float data[len]; for (int i = 0; i < len; i++) { JSValue val = JS_GetPropertyUint32(js,arr,i); data[i] = js2number(js, val); JS_FreeValue(js,val); } return js_new_blob_stoned_copy(js, data, sizeof(float)*len); ) JSC_CCALL(geometry_make_quad_indices, int quads; if (JS_ToInt32(js, &quads, argv[0]) < 0) { return JS_ThrowTypeError(js, "quads must be a number"); } if (quads < 0) { return JS_ThrowTypeError(js, "quads must be >= 0"); } int index_count = quads * 6; uint16_t *index_data = malloc(index_count * sizeof(uint16_t)); int index_idx = 0; for (int i = 0; i < quads; i++) { uint16_t base_idx = i * 4; 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; } JSValue result = js_new_blob_stoned_copy(js, index_data, index_count * sizeof(uint16_t)); free(index_data); return result; ) JSC_CCALL(geometry_make_rect_quad, rect r = js2rect(js, argv[0]); // Optional UV rect (default to 0,0,1,1) rect uv = {0, 0, 1, 1}; if (argc > 1 && !JS_IsNull(argv[1])) { uv = js2rect(js, argv[1]); } // Optional color (default to white) SDL_FColor color = {1.0f, 1.0f, 1.0f, 1.0f}; if (argc > 2 && !JS_IsNull(argv[2])) { HMM_Vec4 c = js2color(js, argv[2]); color.r = c.r; color.g = c.g; color.b = c.b; color.a = c.a; } // Allocate buffers for 1 quad (4 vertices) int vertex_count = 4; int index_count = 6; float xy_data[vertex_count*2]; float uv_data[vertex_count*2]; SDL_FColor color_data[vertex_count]; // Set vertex positions (4 corners) xy_data[0] = r.x; xy_data[1] = r.y; // bottom-left xy_data[2] = r.x + r.w; xy_data[3] = r.y; // bottom-right xy_data[4] = r.x; xy_data[5] = r.y + r.h; // top-left xy_data[6] = r.x + r.w; xy_data[7] = r.y + r.h; // top-right // Set UV coordinates uv_data[0] = uv.x; uv_data[1] = uv.y + uv.h; // bottom-left uv_data[2] = uv.x + uv.w; uv_data[3] = uv.y + uv.h; // bottom-right uv_data[4] = uv.x; uv_data[5] = uv.y; // top-left uv_data[6] = uv.x + uv.w; uv_data[7] = uv.y; // top-right // Set colors for (int i = 0; i < 4; i++) color_data[i] = color; // Create result object ret = JS_NewObject(js); 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)); 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, "num_vertices", JS_NewInt32(js, vertex_count)); ) JSC_CCALL(geometry_weave, uint32_t stream_count = JS_ArrayLength(js, argv[0]); size_t total_stride_bytes = 0; typedef struct { void *data; size_t size; int stride; } Stream; Stream streams[stream_count]; int num_elements = -1; for (uint32_t i = 0; i < stream_count; i++) { JSValue stream_obj = JS_GetPropertyUint32(js, argv[0], i); JSValue data_blob = JS_GetPropertyStr(js, stream_obj, "data"); JSValue stride_val = JS_GetPropertyStr(js, stream_obj, "stride"); size_t sz; streams[i].data = js_get_blob_data(js, &sz, data_blob); streams[i].size = sz; JS_ToInt32(js, &streams[i].stride, stride_val); int elements = sz / streams[i].stride; if (num_elements == -1) num_elements = elements; else if (num_elements != elements) return JS_ThrowTypeError(js, "All streams must have the same number of elements"); total_stride_bytes += streams[i].stride; } uint8_t data[total_stride_bytes * num_elements]; for (int i = 0; i < num_elements; i++) { size_t current_offset = 0; for (uint32_t j = 0; j < stream_count; j++) { memcpy(data + (i * total_stride_bytes) + current_offset, streams[j].data + (i * streams[j].stride), streams[j].stride); current_offset += streams[j].stride; } } JSValue result = js_new_blob_stoned_copy(js, data, total_stride_bytes * num_elements); return result; ) JSC_CCALL(geometry_sprite_vertices, JSValue sprite = argv[0]; // Get sprite properties double x, y, w, h, u0, v0, u1, v1; JSValue x_val = JS_GetPropertyStr(js, sprite, "x"); JSValue y_val = JS_GetPropertyStr(js, sprite, "y"); JSValue w_val = JS_GetPropertyStr(js, sprite, "w"); JSValue h_val = JS_GetPropertyStr(js, sprite, "h"); JSValue u0_val = JS_GetPropertyStr(js, sprite, "u0"); JSValue v0_val = JS_GetPropertyStr(js, sprite, "v0"); JSValue u1_val = JS_GetPropertyStr(js, sprite, "u1"); JSValue v1_val = JS_GetPropertyStr(js, sprite, "v1"); JSValue c_val = JS_GetPropertyStr(js, sprite, "c"); JS_ToFloat64(js, &x, x_val); JS_ToFloat64(js, &y, y_val); JS_ToFloat64(js, &w, w_val); JS_ToFloat64(js, &h, h_val); JS_ToFloat64(js, &u0, u0_val); JS_ToFloat64(js, &v0, v0_val); JS_ToFloat64(js, &u1, u1_val); JS_ToFloat64(js, &v1, v1_val); HMM_Vec4 c = {1.0f, 1.0f, 1.0f, 1.0f}; if (!JS_IsNull(c_val)) { c = js2color(js, c_val); } JS_FreeValue(js, x_val); JS_FreeValue(js, y_val); JS_FreeValue(js, w_val); JS_FreeValue(js, h_val); JS_FreeValue(js, u0_val); JS_FreeValue(js, v0_val); JS_FreeValue(js, u1_val); JS_FreeValue(js, v1_val); JS_FreeValue(js, c_val); // 4 vertices * 8 floats per vertex (x, y, u, v, r, g, b, a) float vertex_data[4 * 8]; // v0: bottom-left vertex_data[0] = x; vertex_data[1] = y; vertex_data[2] = u0; vertex_data[3] = v1; // Flip V vertex_data[4] = c.r; vertex_data[5] = c.g; vertex_data[6] = c.b; vertex_data[7] = c.a; // v1: bottom-right vertex_data[8] = x + w; vertex_data[9] = y; vertex_data[10] = u1; vertex_data[11] = v1; // Flip V vertex_data[12] = c.r; vertex_data[13] = c.g; vertex_data[14] = c.b; vertex_data[15] = c.a; // v2: top-right vertex_data[16] = x + w; vertex_data[17] = y + h; vertex_data[18] = u1; vertex_data[19] = v0; // Flip V vertex_data[20] = c.r; vertex_data[21] = c.g; vertex_data[22] = c.b; vertex_data[23] = c.a; // v3: top-left vertex_data[24] = x; vertex_data[25] = y + h; vertex_data[26] = u0; vertex_data[27] = v0; // Flip V vertex_data[28] = c.r; vertex_data[29] = c.g; vertex_data[30] = c.b; vertex_data[31] = c.a; return js_new_blob_stoned_copy(js, vertex_data, sizeof(vertex_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(geometry, sprites_to_data, 1), MIST_FUNC_DEF(geometry, sprite_vertices, 1), MIST_FUNC_DEF(geometry, transform_xy_blob, 2), MIST_FUNC_DEF(gpu, tile, 4), MIST_FUNC_DEF(gpu, slice9, 4), MIST_FUNC_DEF(geometry, array_blob, 2), MIST_FUNC_DEF(geometry, make_quad_indices, 1), MIST_FUNC_DEF(geometry, make_rect_quad, 3), MIST_FUNC_DEF(geometry, weave, 1), }; CELL_USE_FUNCS(js_geometry_funcs)