Files
prosperon/geometry.c
2025-12-29 20:46:42 -06:00

1312 lines
40 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "cell.h"
#include "prosperon.h"
#include <math.h>
#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] = worldspace 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 pixelsize 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)