983 lines
29 KiB
C
983 lines
29 KiB
C
#include "qjs_geometry.h"
|
||
#include "qjs_blob.h"
|
||
#include "jsffi.h"
|
||
#include "qjs_common.h"
|
||
|
||
#include <SDL3/SDL.h>
|
||
#include <math.h>
|
||
#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_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}
|
||
};
|
||
|
||
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_NULL;
|
||
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, 0, 2, 1, 0);
|
||
BufferCheckResult uv_chk = get_or_extend_buffer(js, old_mesh, "uv", uv_size, 0, 2, 1, 0);
|
||
BufferCheckResult color_chk = get_or_extend_buffer(js, old_mesh, "color", color_size, 0, 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, 0, 2, 1,0);
|
||
JSValue new_uv = make_gpu_buffer(js, uvdata, uv_size, 0, 2, 1,0);
|
||
JSValue new_color = make_gpu_buffer(js, colordata, color_size, 0, 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_IsNull(pos_chk.val)) JS_FreeValue(js, pos_chk.val);
|
||
if (!JS_IsNull(uv_chk.val)) JS_FreeValue(js, uv_chk.val);
|
||
if (!JS_IsNull(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, 0, 2, 0, 0);
|
||
JSValue jsuv = make_gpu_buffer(js, buffers.uv, sizeof(HMM_Vec2)*buffers.verts, 0, 2,0,0);
|
||
JSValue jscolor = make_gpu_buffer(js, buffers.color, sizeof(HMM_Vec4)*buffers.verts, 0, 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_NULL;
|
||
|
||
for (size_t i = 0; i < quads; i++) {
|
||
sprite *s = &sprites[i];
|
||
if (JS_IsNull(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_NULL;
|
||
|
||
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_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;
|
||
float world_y = logical_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_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 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;
|
||
}
|