Files
cell/source/jsffi.c
John Alanbrook 2038ce15a7
Some checks failed
Build and Deploy / build-macos (push) Failing after 2s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
factor out sdl input
2025-06-04 16:25:06 -05:00

1632 lines
49 KiB
C

#include "jsffi.h"
#include "font.h"
#include "datastream.h"
#include "qjs_sdl.h"
#include "qjs_sdl_input.h"
#include "qjs_io.h"
#include "qjs_fd.h"
#include "transform.h"
#include "stb_ds.h"
#include "stb_image.h"
#include "stb_rect_pack.h"
#define STB_DXT_IMPLEMENTATION
#include "stb_dxt.h"
#include "stb_image_write.h"
#include "string.h"
#include <assert.h>
#include <time.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <limits.h>
#include "render.h"
#include "model.h"
#include "HandmadeMath.h"
#include "par/par_streamlines.h"
#include "par/par_shapes.h"
#include <stdint.h>
#include "cute_aseprite.h"
#include "cgltf.h"
#include "cell.h"
#include "qjs_blob.h"
#include "qjs_dmon.h"
#include "qjs_nota.h"
#include "qjs_wota.h"
#include "qjs_enet.h"
#include "qjs_soloud.h"
#include "qjs_qr.h"
#include "qjs_sdl.h"
#include "qjs_sdl_video.h"
#include "qjs_math.h"
#include "qjs_geometry.h"
#include "qjs_transform.h"
#include "qjs_sprite.h"
#include "qjs_io.h"
#include "qjs_sdl_gpu.h"
#include "qjs_os.h"
#include "qjs_actor.h"
#include "qjs_rtree.h"
#include "qjs_spline.h"
#include "qjs_js.h"
#include "qjs_debug.h"
#include "qjs_sdl_surface.h"
#include "qjs_sdl.h"
#ifndef NSTEAM
#include "qjs_steam.h"
#endif
#include <signal.h>
void gui_input(SDL_Event *e);
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#include <sys/utsname.h>
#ifdef __linux__
#include <sys/sysinfo.h>
#endif
#endif
#include "wildmatch.h"
#include "freelist.h"
#include "sprite.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_gpu.h>
#include <SDL3/SDL_error.h>
#include <SDL3/SDL_properties.h>
#include <SDL3/SDL_loadso.h>
#include <SDL3/SDL_cpuinfo.h>
int randombytes(void *buf, size_t n);
int trace = 0;
// External transform function declarations
extern JSClassID js_transform_id;
JSValue transform2js(JSContext *js, transform *t);
transform *js2transform(JSContext *js, JSValue v);
#ifdef __APPLE__
#include <Accelerate/Accelerate.h>
//#else
//#include <cblas.h>
#endif
// Random number generation constants
#define UPPER_MASK 0x80000000
#define LOWER_MASK 0x7fffffff
#define TEMPERING_MASK_B 0x9d2c5680
#define TEMPERING_MASK_C 0xefc60000
// Random number generation functions
void m_seedRand(MTRand* rand, uint32_t seed) {
/* set initial seeds to mt[STATE_VECTOR_LENGTH] using the generator
* from Line 25 of Table 1 in: Donald Knuth, "The Art of Computer
* Programming," Vol. 2 (2nd Ed.) pp.102.
*/
rand->mt[0] = seed & 0xffffffff;
for(rand->index=1; rand->index<STATE_VECTOR_LENGTH; rand->index++) {
rand->mt[rand->index] = (6069 * rand->mt[rand->index-1]) & 0xffffffff;
}
}
uint32_t genRandLong(MTRand* rand) {
uint32_t y;
static uint32_t mag[2] = {0x0, 0x9908b0df}; /* mag[x] = x * 0x9908b0df for x = 0,1 */
if(rand->index >= STATE_VECTOR_LENGTH || rand->index < 0) {
/* generate STATE_VECTOR_LENGTH words at a time */
int32_t kk;
if(rand->index >= STATE_VECTOR_LENGTH+1 || rand->index < 0) {
m_seedRand(rand, 4357);
}
for(kk=0; kk<STATE_VECTOR_LENGTH-STATE_VECTOR_M; kk++) {
y = (rand->mt[kk] & UPPER_MASK) | (rand->mt[kk+1] & LOWER_MASK);
rand->mt[kk] = rand->mt[kk+STATE_VECTOR_M] ^ (y >> 1) ^ mag[y & 0x1];
}
for(; kk<STATE_VECTOR_LENGTH-1; kk++) {
y = (rand->mt[kk] & UPPER_MASK) | (rand->mt[kk+1] & LOWER_MASK);
rand->mt[kk] = rand->mt[kk+(STATE_VECTOR_M-STATE_VECTOR_LENGTH)] ^ (y >> 1) ^ mag[y & 0x1];
}
y = (rand->mt[STATE_VECTOR_LENGTH-1] & UPPER_MASK) | (rand->mt[0] & LOWER_MASK);
rand->mt[STATE_VECTOR_LENGTH-1] = rand->mt[STATE_VECTOR_M-1] ^ (y >> 1) ^ mag[y & 0x1];
rand->index = 0;
}
y = rand->mt[rand->index++];
y ^= (y >> 11);
y ^= (y << 7) & TEMPERING_MASK_B;
y ^= (y << 15) & TEMPERING_MASK_C;
y ^= (y >> 18);
return y;
}
double genRand(MTRand* rand) {
return((double)genRandLong(rand) / (uint32_t)0xffffffff);
}
double rand_range(JSContext *js, double min, double max)
{
MTRand *mrand = &((cell_rt*)JS_GetContextOpaque(js))->mrand;
return genRand(mrand) * (max-min)+min;
}
typedef struct texture_vertex {
float x, y, z;
float u, v;
uint8_t r, g, b,a;
} texture_vertex;
static inline size_t typed_array_bytes(JSTypedArrayEnum type) {
switch(type) {
case JS_TYPED_ARRAY_UINT8C:
case JS_TYPED_ARRAY_INT8:
case JS_TYPED_ARRAY_UINT8:
return 1;
case JS_TYPED_ARRAY_INT16:
case JS_TYPED_ARRAY_UINT16:
return 2;
case JS_TYPED_ARRAY_INT32:
case JS_TYPED_ARRAY_UINT32:
case JS_TYPED_ARRAY_FLOAT32:
return 4;
case JS_TYPED_ARRAY_BIG_INT64:
case JS_TYPED_ARRAY_BIG_UINT64:
case JS_TYPED_ARRAY_FLOAT64:
return 8;
default:
return 0; // Return 0 for unknown types
}
}
#define JS_GETNUM(JS,VAL,I,TO,TYPE) { \
JSValue val = JS_GetPropertyUint32(JS,VAL,I); \
TO = js2##TYPE(JS, val); \
JS_FreeValue(JS, val); } \
int js2bool(JSContext *js, JSValue v) { return JS_ToBool(js,v); }
JSValue number2js(JSContext *js, double g) { return JS_NewFloat64(js,g); }
double js2number(JSContext *js, JSValue v) {
double g;
JS_ToFloat64(js, &g, v);
if (isnan(g)) g = 0;
return g;
}
JSValue js_getpropertyuint32(JSContext *js, JSValue v, unsigned int i)
{
JSValue ret = JS_GetPropertyUint32(js,v,i);
JS_FreeValue(js,ret);
return ret;
}
double js_getnum_uint32(JSContext *js, JSValue v, unsigned int i)
{
JSValue val = JS_GetPropertyUint32(js,v,i);
double ret = js2number(js, val);
JS_FreeValue(js,val);
return ret;
}
double js_getnum_str(JSContext *js, JSValue v, const char *str)
{
JSValue val = JS_GetPropertyStr(js,v,str);
double ret = js2number(js,val);
JS_FreeValue(js,val);
return ret;
}
#define JS_GETPROP(JS, TARGET, VALUE, PROP, TYPE) {\
JSValue __##PROP##__v = JS_GetPropertyStr(JS,VALUE,#PROP); \
TARGET = js2##TYPE(JS, __##PROP##__v); \
JS_FreeValue(JS,__##PROP##__v); }\
#define JS_GETATOM(JS, TARGET, VALUE, ATOM, TYPE) {\
JSValue __##PROP##__v = JS_GetPropertyStr(JS,VALUE,#ATOM); \
TARGET = js2##TYPE(JS, __##PROP##__v); \
JS_FreeValue(JS,__##PROP##__v); }\
#define JS_SETATOM(JS, TARGET, ATOM, VALUE, TYPE) JS_SetProperty(JS, TARGET, #ATOM, TYPE##2js(JS, VALUE));
int JS_GETBOOL(JSContext *js, JSValue v, const char *prop)
{
JSValue __v = JS_GetPropertyStr(js,v,prop);
int r = JS_ToBool(js,__v);
JS_FreeValue(js,__v);
return r;
}
JSValue js_getproperty(JSContext *js, JSValue v, const char *prop)
{
JSValue ret = JS_GetPropertyStr(js, v, prop);
JS_FreeValue(js,ret);
return ret;
}
void free_gpu_buffer(JSRuntime *rt, void *opaque, void *ptr)
{
free(ptr);
}
JSValue make_gpu_buffer(JSContext *js, void *data, size_t size, int type, int elements, int copy, int index)
{
JSValue tstack[3];
tstack[1] = JS_UNDEFINED;
tstack[2] = JS_UNDEFINED;
// TODO: always copying; implement "takeover"
tstack[0] = js_new_blob_stoned_copy(js,data,size);//, make_gpu_buffer, NULL, 1);
JSValue ret = JS_NewTypedArray(js, 3, tstack, type);
JS_SetPropertyStr(js,ret,"stride", number2js(js,typed_array_bytes(type)*elements));
JS_SetPropertyStr(js,ret,"elen", number2js(js,typed_array_bytes(type)));
JS_SetPropertyStr(js,ret,"index", JS_NewBool(js,index));
JS_FreeValue(js,tstack[0]);
return ret;
}
void *get_gpu_buffer(JSContext *js, JSValue argv, size_t *stride, size_t *size)
{
size_t o, len, bytes, msize;
JSValue buf = JS_GetTypedArrayBuffer(js, argv, &o, &len, &bytes);
void *data = js_get_blob_data(js, &msize, buf);
JS_FreeValue(js,buf);
if (stride) *stride = js_getnum_str(js, argv, "stride");
if (size) *size = msize;
return data;
}
JSValue make_quad_indices_buffer(JSContext *js, int quads)
{
cell_rt *rt = JS_GetContextOpaque(js);
int count = quads*6;
if (!JS_IsUndefined(rt->idx_buffer) && rt->idx_count >= count)
return JS_DupValue(js,rt->idx_buffer);
int verts = quads*4;
uint16_t *indices = malloc(sizeof(*indices)*count);
for (int i = 0, v = 0; v < verts; 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;
}
if (!JS_IsUndefined(rt->idx_buffer))
JS_FreeValue(js,rt->idx_buffer);
rt->idx_buffer = make_gpu_buffer(js,indices, sizeof(*indices)*count, JS_TYPED_ARRAY_UINT16, 1,0,1);
rt->idx_count = count;
return JS_DupValue(js,rt->idx_buffer);
}
JSValue quads_to_mesh(JSContext *js, text_vert *buffer)
{
size_t verts = arrlen(buffer);
HMM_Vec2 *pos = malloc(arrlen(buffer)*sizeof(HMM_Vec2));
HMM_Vec2 *uv = malloc(arrlen(buffer)*sizeof(HMM_Vec2));
HMM_Vec4 *color = malloc(arrlen(buffer)*sizeof(HMM_Vec4));
for (int i = 0; i < arrlen(buffer); i++) {
pos[i] = buffer[i].pos;
uv[i] = buffer[i].uv;
color[i] = buffer[i].color;
}
JSValue jspos = make_gpu_buffer(js, pos, sizeof(HMM_Vec2)*arrlen(buffer), JS_TYPED_ARRAY_FLOAT32, 2,0,0);
JSValue jsuv = make_gpu_buffer(js, uv, sizeof(HMM_Vec2)*arrlen(buffer), JS_TYPED_ARRAY_FLOAT32, 2,0,0);
JSValue jscolor = make_gpu_buffer(js, color, sizeof(HMM_Vec4)*arrlen(buffer), JS_TYPED_ARRAY_FLOAT32, 4,0,0);
size_t quads = verts/4;
size_t count = 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, verts));
JS_SetPropertyStr(js,ret,"num_indices", number2js(js,count));
return ret;
}
#ifndef _WIN32
#include <sys/resource.h>
#endif
typedef struct lrtb lrtb;
lrtb js2lrtb(JSContext *js, JSValue v)
{
lrtb ret = {0};
JS_GETATOM(js,ret.l,v,l,number)
JS_GETATOM(js,ret.r,v,r,number)
JS_GETATOM(js,ret.b,v,b,number)
JS_GETATOM(js,ret.t,v,t,number)
return ret;
}
JSValue vec22js(JSContext *js,HMM_Vec2 v)
{
JSValue array = JS_NewArray(js);
JS_SetPropertyUint32(js, array,0,number2js(js,v.x));
JS_SetPropertyUint32(js, array,1,number2js(js,v.y));
return array;
}
char *js2strdup(JSContext *js, JSValue v) {
const char *str = JS_ToCString(js, v);
char *ret = strdup(str);
JS_FreeCString(js, str);
return ret;
}
#include "qjs_macros.h"
void SDL_GPUCommandBuffer_free(JSRuntime *rt, SDL_GPUCommandBuffer *c)
{
}
QJSCLASS(font,)
QJSCLASS(datastream,)
JSValue angle2js(JSContext *js,double g) {
return number2js(js,g*HMM_RadToTurn);
}
double js2angle(JSContext *js,JSValue v) {
double n = js2number(js,v);
return n * HMM_TurnToRad;
}
typedef HMM_Vec4 colorf;
colorf js2color(JSContext *js,JSValue v) {
if (JS_IsUndefined(v)) return (colorf){1,1,1,1};
JSValue c[4];
for (int i = 0; i < 4; i++) c[i] = JS_GetPropertyUint32(js,v,i);
float a = JS_IsUndefined(c[3]) ? 1.0 : js2number(js,c[3]);
colorf color = {
.r = js2number(js,c[0]),
.g = js2number(js,c[1]),
.b = js2number(js,c[2]),
.a = a,
};
for (int i = 0; i < 4; i++) JS_FreeValue(js,c[i]);
return color;
}
JSValue color2js(JSContext *js, colorf color)
{
JSValue arr = JS_NewArray(js);
JS_SetPropertyUint32(js, arr,0,number2js(js,(double)color.r));
JS_SetPropertyUint32(js, arr,1,number2js(js,(double)color.g));
JS_SetPropertyUint32(js, arr,2,number2js(js,(double)color.b));
JS_SetPropertyUint32(js, arr,3,number2js(js,(double)color.a));
return arr;
}
HMM_Vec2 js2vec2(JSContext *js,JSValue v)
{
HMM_Vec2 v2;
v2.X = js_getnum_uint32(js,v,0);
v2.Y = js_getnum_uint32(js,v,1);
return v2;
}
HMM_Vec3 js2vec3(JSContext *js,JSValue v)
{
HMM_Vec3 v3;
v3.x = js_getnum_uint32(js, v,0);
v3.y = js_getnum_uint32(js, v,1);
v3.z = js_getnum_uint32(js, v,2);
return v3;
}
float *js2floats(JSContext *js, JSValue v, size_t *len)
{
*len = JS_ArrayLength(js,v);
float *arr = malloc(sizeof(float)* *len);
for (int i = 0; i < *len; i++)
arr[i] = js_getnum_uint32(js,v,i);
return arr;
}
double *js2doubles(JSContext *js, JSValue v, size_t *len)
{
*len = JS_ArrayLength(js,v);
double *arr = malloc(sizeof(double)* *len);
for (int i = 0; i < *len; i++)
arr[i] = js_getnum_uint32(js,v,i);
return arr;
}
HMM_Vec3 js2vec3f(JSContext *js, JSValue v)
{
HMM_Vec3 vec;
if (JS_IsArray(js, v))
return js2vec3(js,v);
else
vec.x = vec.y = vec.z = js2number(js,v);
return vec;
}
JSValue vec32js(JSContext *js, HMM_Vec3 v)
{
JSValue array = JS_NewArray(js);
JS_SetPropertyUint32(js, array,0,number2js(js,v.x));
JS_SetPropertyUint32(js, array,1,number2js(js,v.y));
JS_SetPropertyUint32(js, array,2,number2js(js,v.z));
return array;
}
JSValue vec3f2js(JSContext *js, HMM_Vec3 v)
{
return vec32js(js,v);
}
JSValue quat2js(JSContext *js, HMM_Quat q)
{
JSValue arr = JS_NewArray(js);
JS_SetPropertyUint32(js, arr, 0, number2js(js,q.x));
JS_SetPropertyUint32(js, arr,1,number2js(js,q.y));
JS_SetPropertyUint32(js, arr,2,number2js(js,q.z));
JS_SetPropertyUint32(js, arr,3,number2js(js,q.w));
return arr;
}
HMM_Vec4 js2vec4(JSContext *js, JSValue v)
{
HMM_Vec4 v4;
for (int i = 0; i < 4; i++)
v4.e[i] = js_getnum_uint32(js, v,i);
return v4;
}
double arr_vec_length(JSContext *js,JSValue v)
{
int len = JS_ArrayLength(js,v);
switch(len) {
case 2: return HMM_LenV2(js2vec2(js,v));
case 3: return HMM_LenV3(js2vec3(js,v));
case 4: return HMM_LenV4(js2vec4(js,v));
}
double sum = 0;
for (int i = 0; i < len; i++)
sum += pow(js_getnum_uint32(js, v, i), 2);
return sqrt(sum);
}
HMM_Quat js2quat(JSContext *js,JSValue v)
{
return js2vec4(js,v).quat;
}
JSValue vec42js(JSContext *js, HMM_Vec4 v)
{
JSValue array = JS_NewArray(js);
for (int i = 0; i < 4; i++)
JS_SetPropertyUint32(js, array,i,number2js(js,v.e[i]));
return array;
}
HMM_Vec2 *js2cpvec2arr(JSContext *js,JSValue v) {
HMM_Vec2 *arr = NULL;
int n = JS_ArrayLength(js,v);
arrsetlen(arr,n);
for (int i = 0; i < n; i++)
arr[i] = js2vec2(js,js_getpropertyuint32(js, v, i));
return arr;
}
JSValue vecarr2js(JSContext *js,HMM_Vec2 *points, int n) {
JSValue array = JS_NewArray(js);
for (int i = 0; i < n; i++)
JS_SetPropertyUint32(js, array,i,vec22js(js,points[i]));
return array;
}
rect js2rect(JSContext *js,JSValue v) {
if (JS_IsUndefined(v)) return (rect){0,0,1,1};
rect rect;
JS_GETATOM(js,rect.x,v,x,number)
JS_GETATOM(js,rect.y,v,y,number)
JS_GETATOM(js,rect.w,v,width,number)
JS_GETATOM(js,rect.h,v,height,number)
float anchor_x, anchor_y;
JS_GETATOM(js, anchor_x, v, anchor_x, number)
JS_GETATOM(js, anchor_y, v, anchor_y, number)
rect.y -= anchor_y*rect.h;
rect.x -= anchor_x*rect.w;
return rect;
}
irect js2irect(JSContext *js, JSValue v)
{
if (JS_IsUndefined(v)) return (irect){0,0,1,1};
irect rect;
JS_GETATOM(js,rect.x,v,x,number)
JS_GETATOM(js,rect.y,v,y,number)
JS_GETATOM(js,rect.w,v,width,number)
JS_GETATOM(js,rect.h,v,height,number)
float anchor_x, anchor_y;
JS_GETATOM(js, anchor_x, v, anchor_x, number)
JS_GETATOM(js, anchor_y, v, anchor_y, number)
rect.y -= anchor_y*rect.h;
rect.x -= anchor_x*rect.w;
return rect;
}
rect transform_rect(SDL_Renderer *ren, rect in, HMM_Mat3 *t)
{
HMM_Vec3 bottom_left = (HMM_Vec3){in.x,in.y,1.0};
HMM_Vec3 transformed_bl = HMM_MulM3V3(*t, bottom_left);
in.x = transformed_bl.x;
in.y = transformed_bl.y;
in.y = in.y - in.h; // should be done for any platform that draws rectangles from top left
return in;
}
HMM_Vec2 transform_point(SDL_Renderer *ren, HMM_Vec2 in, HMM_Mat3 *t)
{
rect logical;
SDL_GetRenderLogicalPresentationRect(ren, &logical);
in.y *= -1;
in.y += logical.h;
in.x -= t->Columns[2].x;
in.y -= t->Columns[2].y;
return in;
}
JSValue rect2js(JSContext *js,rect rect) {
JSValue obj = JS_NewObject(js);
JS_SETATOM(js, obj, x, rect.x, number);
JS_SETATOM(js, obj, y, rect.y, number);
JS_SETATOM(js, obj, width, rect.w, number);
JS_SETATOM(js, obj, height, rect.h, number);
return obj;
}
JSValue ints2js(JSContext *js,int *ints) {
JSValue arr = JS_NewArray(js);
for (int i = 0; i < arrlen(ints); i++)
JS_SetPropertyUint32(js, arr,i, number2js(js,ints[i]));
return arr;
}
int vec_between(HMM_Vec2 p, HMM_Vec2 a, HMM_Vec2 b) {
HMM_Vec2 n;
n.x = b.x - a.x;
n.y = b.y - a.y;
n = HMM_NormV2(n);
return HMM_DotV2(n, HMM_SubV2(p,a)) > 0 && HMM_DotV2(HMM_MulV2F(n, -1), HMM_SubV2(p,b)) > 0;
}
/* Determines between which two points in 'segs' point 'p' falls.
0 indicates 'p' comes before the first point.
arrlen(segs) indicates it comes after the last point.
*/
int point2segindex(HMM_Vec2 p, HMM_Vec2 *segs, double slop) {
float shortest = slop < 0 ? INFINITY : slop;
int best = -1;
for (int i = 0; i < arrlen(segs) - 1; i++) {
float a = (segs[i + 1].y - segs[i].y) / (segs[i + 1].x - segs[i].x);
float c = segs[i].y - (a * segs[i].x);
float b = -1;
float dist = fabsf(a * p.x + b * p.y + c) / sqrt(pow(a, 2) + 1);
if (dist > shortest) continue;
int between = vec_between(p, segs[i], segs[i + 1]);
if (between) {
shortest = dist;
best = i + 1;
} else {
if (i == 0 && HMM_DistV2(p,segs[0]) < slop) {
shortest = dist;
best = i;
} else if (i == arrlen(segs) - 2 && HMM_DistV2(p,arrlast(segs)) < slop) {
shortest = dist;
best = arrlen(segs);
}
}
}
if (best == 1) {
HMM_Vec2 n;
n.x = segs[1].x - segs[0].x;
n.y = segs[1].y - segs[0].y;
n = HMM_NormV2(n);
if (HMM_DotV2(n, HMM_SubV2(p,segs[0])) < 0 ){
if (HMM_DistV2(p, segs[0]) >= slop)
best = -1;
else
best = 0;
}
}
if (best == arrlen(segs) - 1) {
HMM_Vec2 n;
n.x = segs[best - 1].x - segs[best].x;
n.y = segs[best - 1].y - segs[best - 1].y;
n = HMM_NormV2(n);
if (HMM_DotV2(n, HMM_SubV2(p, segs[best])) < 0) {
if (HMM_DistV2(p, segs[best]) >= slop)
best = -1;
else
best = arrlen(segs);
}
}
return best;
}
JSC_CCALL(os_make_text_buffer,
const char *s = JS_ToCString(js, argv[0]);
rect rectpos = js2rect(js,argv[1]);
float size = js2number(js,argv[2]);
font *f = js2font(js,argv[5]);
if (!size) size = f->height;
colorf c = js2color(js,argv[3]);
int wrap = js2number(js,argv[4]);
HMM_Vec2 startpos = {.x = rectpos.x, .y = rectpos.y };
text_vert *buffer = renderText(s, startpos, f, size, c, wrap);
ret = quads_to_mesh(js,buffer);
JS_FreeCString(js, s);
arrfree(buffer);
)
JSValue js_util_camera_globals(JSContext *js, JSValue self, int argc, JSValue *argv)
{
JSValue camera = argv[0];
if(JS_IsUndefined(camera)) return JS_UNDEFINED;
HMM_Vec2 size; HMM_Vec3 pos; HMM_Quat rotation;
double fov = 0; int ortho; double near_z = 0; double far_z = 0;
HMM_Vec2 anchor;
JS_GETPROP(js, size, camera, size, vec2)
JS_GETPROP(js, fov, camera, fov, number)
JS_GETPROP(js, ortho, camera, ortho, bool)
JS_GETPROP(js, near_z, camera, near_z, number)
JS_GETPROP(js, far_z, camera, far_z, number)
JS_GETPROP(js, anchor, camera, anchor, vec2)
JS_GETPROP(js, pos, camera, pos, vec3)
JS_GETPROP(js, rotation, camera, rotation, quat)
rotation.w = 1;
HMM_Mat4 proj, view;
if(ortho) {
float left = -anchor.x * size.x;
float bottom = -anchor.y * size.y;
float right = left + size.x;
float top = bottom + size.y;
proj = HMM_Orthographic_RH_NO(left, right, bottom, top, -1.0f, 1.0f);
} else {
proj = HMM_Perspective_RH_NO(fov, size.x/size.y, near_z, far_z);
proj.Columns[1] = HMM_MulV4F(proj.Columns[1], -1.0f);
}
view = HMM_MulM4(
HMM_InvTranslate(HMM_Translate(pos)),
HMM_InvRotate (HMM_QToM4(rotation))
);
JSValue data = JS_NewObject(js);
HMM_Mat4 world_to_projection = HMM_MulM4(proj, view);
HMM_Mat4 projection_to_world = HMM_InvGeneralM4(world_to_projection);
HMM_Vec3 camera_dir_world = HMM_NormV3(
HMM_QVRot((HMM_Vec3){0,0,-1}, rotation)
);
JS_SetPropertyStr(js, data, "world_to_projection",
js_new_blob_stoned_copy(js, world_to_projection.em,
sizeof(float)*16));
JS_SetPropertyStr(js, data, "projection_to_world",
js_new_blob_stoned_copy(js, projection_to_world.em,
sizeof(float)*16));
JS_SetPropertyStr(js, data, "world_to_view",
js_new_blob_stoned_copy(js, view.em, sizeof(float)*16));
JS_SetPropertyStr(js, data, "view_to_projection",
js_new_blob_stoned_copy(js, proj.em, sizeof(float)*16));
JS_SetPropertyStr(js, data, "camera_pos_world", vec32js(js, pos));
JS_SetPropertyStr(js, data, "camera_dir_world", vec32js(js, camera_dir_world));
JS_SetPropertyStr(js, data, "render_size", vec22js(js, size));
JS_SetPropertyStr(js, data, "viewport_size", vec22js(js, (HMM_Vec2){0.5,0.5}));
JS_SetPropertyStr(js, data, "viewport_offset", vec22js(js, (HMM_Vec2){0,0}));
JS_SetPropertyStr(js, data, "viewport_min_z", number2js(js, near_z));
JS_SetPropertyStr(js, data, "viewport_max_z", number2js(js, far_z));
return data;
}
static JSValue floats2array(JSContext *js, float *vals, size_t len) {
JSValue arr = JS_NewArray(js);
for (size_t i = 0; i < len; i++) {
JS_SetPropertyUint32(js, arr, i, number2js(js, vals[i]));
}
return arr;
}
#define JS_HMM_FN(OP, HMM, SIGN) \
JSC_CCALL(array_##OP, \
int len = JS_ArrayLength(js,self); \
if (!JS_IsArray(js, argv[0])) { \
double n = js2number(js,argv[0]); \
JSValue arr = JS_NewArray(js); \
for (int i = 0; i < len; i++) \
JS_SetPropertyUint32(js, arr, i, number2js(js,js_getnum_uint32(js, self,i) SIGN n)); \
return arr; \
} \
switch(len) { \
case 2: \
return vec22js(js,HMM_##HMM##V2(js2vec2(js,self), js2vec2(js,argv[0]))); \
case 3: \
return vec32js(js, HMM_##HMM##V3(js2vec3(js,self), js2vec3(js,argv[0]))); \
case 4: \
return vec42js(js,HMM_##HMM##V4(js2vec4(js,self), js2vec4(js,argv[0]))); \
} \
\
JSValue arr = JS_NewArray(js); \
for (int i = 0; i < len; i++) { \
double a = js_getnum_uint32(js, self,i); \
double b = js_getnum_uint32(js, argv[0],i); \
JS_SetPropertyUint32(js, arr, i, number2js(js,a SIGN b)); \
} \
return arr; \
) \
JS_HMM_FN(add, Add, +)
JS_HMM_FN(sub, Sub, -)
JS_HMM_FN(div, Div, /)
JS_HMM_FN(scale, Mul, *)
JSC_CCALL(array_lerp,
double t = js2number(js,argv[1]);
int len = JS_ArrayLength(js,self);
JSValue arr = JS_NewArray(js);
for (int i = 0; i < len; i++) {
double from = js_getnum_uint32(js, self, i);
double to = js_getnum_uint32(js, argv[0], i);
JS_SetPropertyUint32(js, arr, i, number2js(js,(to - from) * t + from));
}
return arr;
)
JSValue js_array_get_x(JSContext *js, JSValue self) { return JS_GetPropertyUint32(js,self,0); }
JSValue js_array_set_x(JSContext *js, JSValue self, JSValue val) { JS_SetPropertyUint32(js,self,0,val); return JS_UNDEFINED; }
JSValue js_array_get_y(JSContext *js, JSValue self) { return JS_GetPropertyUint32(js,self,1); }
JSValue js_array_set_y(JSContext *js, JSValue self, JSValue val) { JS_SetPropertyUint32(js,self,1,val); return JS_UNDEFINED; }
JSValue js_array_get_xy(JSContext *js, JSValue self)
{
JSValue arr = JS_NewArray(js);
JS_SetPropertyUint32(js,arr,0,JS_GetPropertyUint32(js,self,0));
JS_SetPropertyUint32(js,arr,1,JS_GetPropertyUint32(js,self,1));
return arr;
}
JSValue js_array_set_xy(JSContext *js, JSValue self, JSValue v)
{
JS_SetPropertyUint32(js,self,0,JS_GetPropertyUint32(js,v,0));
JS_SetPropertyUint32(js,self,1,JS_GetPropertyUint32(js,v,1));
return JS_UNDEFINED;
}
// Single-value accessors
JSValue js_array_get_r(JSContext *js, JSValue self)
{ return JS_GetPropertyUint32(js, self, 0); }
JSValue js_array_set_r(JSContext *js, JSValue self, JSValue val)
{ JS_SetPropertyUint32(js, self, 0, val); return JS_UNDEFINED; }
JSValue js_array_get_g(JSContext *js, JSValue self)
{ return JS_GetPropertyUint32(js, self, 1); }
JSValue js_array_set_g(JSContext *js, JSValue self, JSValue val)
{ JS_SetPropertyUint32(js, self, 1, val); return JS_UNDEFINED; }
JSValue js_array_get_b(JSContext *js, JSValue self)
{ return JS_GetPropertyUint32(js, self, 2); }
JSValue js_array_set_b(JSContext *js, JSValue self, JSValue val)
{ JS_SetPropertyUint32(js, self, 2, val); return JS_UNDEFINED; }
JSValue js_array_get_a(JSContext *js, JSValue self)
{ return JS_GetPropertyUint32(js, self, 3); }
JSValue js_array_set_a(JSContext *js, JSValue self, JSValue val)
{ JS_SetPropertyUint32(js, self, 3, val); return JS_UNDEFINED; }
// Multi-value accessors
JSValue js_array_get_rgb(JSContext *js, JSValue self)
{
JSValue arr = JS_NewArray(js);
JS_SetPropertyUint32(js, arr, 0, JS_GetPropertyUint32(js, self, 0));
JS_SetPropertyUint32(js, arr, 1, JS_GetPropertyUint32(js, self, 1));
JS_SetPropertyUint32(js, arr, 2, JS_GetPropertyUint32(js, self, 2));
return arr;
}
JSValue js_array_set_rgb(JSContext *js, JSValue self, JSValue val)
{
JS_SetPropertyUint32(js, self, 0, JS_GetPropertyUint32(js, val, 0));
JS_SetPropertyUint32(js, self, 1, JS_GetPropertyUint32(js, val, 1));
JS_SetPropertyUint32(js, self, 2, JS_GetPropertyUint32(js, val, 2));
return JS_UNDEFINED;
}
JSValue js_array_get_rgba(JSContext *js, JSValue self)
{
JSValue arr = JS_NewArray(js);
JS_SetPropertyUint32(js, arr, 0, JS_GetPropertyUint32(js, self, 0));
JS_SetPropertyUint32(js, arr, 1, JS_GetPropertyUint32(js, self, 1));
JS_SetPropertyUint32(js, arr, 2, JS_GetPropertyUint32(js, self, 2));
JS_SetPropertyUint32(js, arr, 3, JS_GetPropertyUint32(js, self, 3));
return arr;
}
JSValue js_array_set_rgba(JSContext *js, JSValue self, JSValue val)
{
JS_SetPropertyUint32(js, self, 0, JS_GetPropertyUint32(js, val, 0));
JS_SetPropertyUint32(js, self, 1, JS_GetPropertyUint32(js, val, 1));
JS_SetPropertyUint32(js, self, 2, JS_GetPropertyUint32(js, val, 2));
JS_SetPropertyUint32(js, self, 3, JS_GetPropertyUint32(js, val, 3));
return JS_UNDEFINED;
}
static const JSCFunctionListEntry js_array_funcs[] = {
PROTO_FUNC_DEF(array, add, 1),
PROTO_FUNC_DEF(array, sub, 1),
PROTO_FUNC_DEF(array, div,1),
PROTO_FUNC_DEF(array, scale, 1),
PROTO_FUNC_DEF(array, lerp, 2),
JS_CGETSET_DEF("x", js_array_get_x,js_array_set_x),
JS_CGETSET_DEF("y", js_array_get_y, js_array_set_y),
JS_CGETSET_DEF("xy", js_array_get_xy, js_array_set_xy),
JS_CGETSET_DEF("r", js_array_get_r, js_array_set_r),
JS_CGETSET_DEF("g", js_array_get_g, js_array_set_g),
JS_CGETSET_DEF("b", js_array_get_b, js_array_set_b),
JS_CGETSET_DEF("a", js_array_get_a, js_array_set_a),
JS_CGETSET_DEF("rgb", js_array_get_rgb, js_array_set_rgb),
JS_CGETSET_DEF("rgba", js_array_get_rgba, js_array_set_rgba),
};
JSC_CCALL(number_lerp,
double a = js2number(js,self);
double b = js2number(js,argv[0]);
double t = js2number(js,argv[1]);
return number2js(js, (b-a)*t+a);
)
static const JSCFunctionListEntry js_number_funcs[] = {
PROTO_FUNC_DEF(number, lerp, 2),
};
JSC_CCALL(os_guid,
SDL_GUID guid;
randombytes(guid.data, 16);
char guid_str[33];
SDL_GUIDToString(guid, guid_str, 33);
return JS_NewString(js,guid_str);
)
JSC_SCALL(console_print, printf("%s", str); )
static const JSCFunctionListEntry js_console_funcs[] = {
MIST_FUNC_DEF(console,print,1),
};
JSC_CCALL(datastream_time, return number2js(js,plm_get_time(js2datastream(js,self)->plm)); )
JSC_CCALL(datastream_seek, ds_seek(js2datastream(js,self), js2number(js,argv[0])))
JSC_CCALL(datastream_advance, ds_advance(js2datastream(js,self), js2number(js,argv[0])))
JSC_CCALL(datastream_duration, return number2js(js,ds_length(js2datastream(js,self))))
JSC_CCALL(datastream_framerate, return number2js(js,plm_get_framerate(js2datastream(js,self)->plm)))
JSC_GETSET_CALLBACK(datastream, callback)
static const JSCFunctionListEntry js_datastream_funcs[] = {
MIST_FUNC_DEF(datastream, time, 0),
MIST_FUNC_DEF(datastream, seek, 1),
MIST_FUNC_DEF(datastream, advance, 1),
MIST_FUNC_DEF(datastream, duration, 0),
MIST_FUNC_DEF(datastream, framerate, 0),
CGETSET_ADD(datastream, callback),
};
JSC_GETSET(font, linegap, number)
JSC_GET(font, height, number)
JSC_GET(font, ascent, number)
JSC_GET(font, descent, number)
JSC_SCALL(font_text_size,
font *f = js2font(js,self);
float size = js2number(js,argv[0]);
if (!size) size = f->height;
float letterSpacing = js2number(js,argv[1]);
float wrap = js2number(js,argv[2]);
ret = vec22js(js,measure_text(str, f, size, letterSpacing, wrap));
)
static const JSCFunctionListEntry js_font_funcs[] = {
CGETSET_ADD(font, linegap),
MIST_GET(font, height),
MIST_GET(font, ascent),
MIST_GET(font, descent),
MIST_FUNC_DEF(font, text_size, 3),
};
// input: (encoded image data of jpg, png, bmp, tiff)
JSC_CCALL(os_make_texture,
size_t len;
void *raw = js_get_blob_data(js, &len, argv[0]);
if (!raw) return JS_ThrowReferenceError(js, "could not load texture with array buffer");
int n, width, height;
void *data = stbi_load_from_memory(raw, len, &width, &height, &n, 4);
if (!data)
return JS_ThrowReferenceError(js, "no known image type from pixel data: %s", stbi_failure_reason());
if (width <= 0 || height <= 0) {
free(data);
return JS_ThrowReferenceError(js, "decoded image has invalid size: %dx%d", width, height);
}
int pitch = width*4;
size_t pixels_size = pitch * height;
// Create JS object with surface data
JSValue obj = JS_NewObject(js);
JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, width));
JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, height));
JS_SetPropertyStr(js, obj, "format", JS_NewString(js, "rgba32"));
JS_SetPropertyStr(js, obj, "pitch", JS_NewInt32(js, pitch));
JS_SetPropertyStr(js, obj, "pixels", js_new_blob_stoned_copy(js, data, pixels_size));
free(data);
ret = obj;
)
// input: (gif image data)
JSC_CCALL(os_make_gif,
size_t rawlen;
void *raw = js_get_blob_data(js, &rawlen, argv[0]);
if (!raw) return JS_ThrowReferenceError(js, "could not load gif from supplied array buffer");
int n;
int frames;
int *delays;
int width;
int height;
void *pixels = stbi_load_gif_from_memory(raw, rawlen, &delays, &width, &height, &frames, &n, 4);
JSValue gif = JS_NewObject(js);
ret = gif;
if (frames == 1) {
// still image, so return surface data object
JSValue surfData = JS_NewObject(js);
JS_SetPropertyStr(js, surfData, "width", JS_NewInt32(js, width));
JS_SetPropertyStr(js, surfData, "height", JS_NewInt32(js, height));
JS_SetPropertyStr(js, surfData, "format", JS_NewString(js, "rgba32"));
JS_SetPropertyStr(js, surfData, "pitch", JS_NewInt32(js, width*4));
JS_SetPropertyStr(js, surfData, "pixels", js_new_blob_stoned_copy(js, pixels, width*height*4));
JS_SetPropertyStr(js, gif, "surface", surfData);
return gif;
}
JSValue delay_arr = JS_NewArray(js);
for (int i = 0; i < frames; i++) {
JSValue frame = JS_NewObject(js);
JS_SetPropertyStr(js, frame, "time", number2js(js,(float)delays[i]/1000.0));
// Create surface data object instead of SDL_Surface
JSValue surfData = JS_NewObject(js);
JS_SetPropertyStr(js, surfData, "width", JS_NewInt32(js, width));
JS_SetPropertyStr(js, surfData, "height", JS_NewInt32(js, height));
JS_SetPropertyStr(js, surfData, "format", JS_NewString(js, "rgba32"));
JS_SetPropertyStr(js, surfData, "pitch", JS_NewInt32(js, width*4));
void *frame_pixels = (unsigned char*)pixels+(width*height*4*i);
JS_SetPropertyStr(js, surfData, "pixels", js_new_blob_stoned_copy(js, frame_pixels, width*height*4));
JS_SetPropertyStr(js, frame, "surface", surfData);
JS_SetPropertyUint32(js, delay_arr, i, frame);
}
JS_SetPropertyStr(js, gif, "frames", delay_arr);
CLEANUP:
free(delays);
free(pixels);
)
JSValue aseframe2js(JSContext *js, ase_frame_t aframe)
{
JSValue frame = JS_NewObject(js);
// Create surface data object instead of SDL_Surface
JSValue surfData = JS_NewObject(js);
JS_SetPropertyStr(js, surfData, "width", JS_NewInt32(js, aframe.ase->w));
JS_SetPropertyStr(js, surfData, "height", JS_NewInt32(js, aframe.ase->h));
JS_SetPropertyStr(js, surfData, "format", JS_NewString(js, "rgba32"));
JS_SetPropertyStr(js, surfData, "pitch", JS_NewInt32(js, aframe.ase->w*4));
JS_SetPropertyStr(js, surfData, "pixels", js_new_blob_stoned_copy(js, aframe.pixels, aframe.ase->w*aframe.ase->h*4));
JS_SetPropertyStr(js, frame, "surface", surfData);
JS_SetPropertyStr(js, frame, "time", number2js(js,(float)aframe.duration_milliseconds/1000.0));
return frame;
}
// input: (aseprite data)
JSC_CCALL(os_make_aseprite,
size_t rawlen;
void *raw = js_get_blob_data(js,&rawlen,argv[0]);
ase_t *ase = cute_aseprite_load_from_memory(raw, rawlen, NULL);
if (!ase)
return JS_ThrowReferenceError(js, "could not load aseprite from supplied array buffer: %s", aseprite_GetError());
if (ase->tag_count == 0) {
// we're dealing with a single frame image, or single animation
if (ase->frame_count == 1) {
JSValue obj = aseframe2js(js,ase->frames[0]);
cute_aseprite_free(ase);
return obj;
}
}
JSValue obj = JS_NewObject(js);
for (int t = 0; t < ase->tag_count; t++) {
ase_tag_t tag = ase->tags[t];
JSValue anim = JS_NewObject(js);
JS_SetPropertyStr(js, anim, "repeat", number2js(js,tag.repeat));
switch(tag.loop_animation_direction) {
case ASE_ANIMATION_DIRECTION_FORWARDS:
JS_SetPropertyStr(js, anim, "loop", JS_NewString(js,"forward"));
break;
case ASE_ANIMATION_DIRECTION_BACKWORDS:
JS_SetPropertyStr(js, anim, "loop", JS_NewString(js,"backward"));
break;
case ASE_ANIMATION_DIRECTION_PINGPONG:
JS_SetPropertyStr(js, anim, "loop", JS_NewString(js,"pingpong"));
break;
}
int _frame = 0;
JSValue frames = JS_NewArray(js);
for (int f = tag.from_frame; f <= tag.to_frame; f++) {
JSValue frame = aseframe2js(js,ase->frames[f]);
JS_SetPropertyUint32(js, frames, _frame, frame);
_frame++;
}
JS_SetPropertyStr(js, anim, "frames", frames);
JS_SetPropertyStr(js, obj, tag.name, anim);
}
ret = obj;
cute_aseprite_free(ase);
)
JSC_CCALL(os_make_font,
size_t len;
void *data = js_get_blob_data(js,&len,argv[0]);
if (!data) return JS_ThrowReferenceError(js, "could not get array buffer data");
font *f = MakeFont(data, len, js2number(js,argv[1]));
if (!f) return JS_ThrowReferenceError(js, "could not create font");
ret = font2js(js,f);
// Create surface data object for the font's atlas
if (f->surface) {
JSValue surfData = JS_NewObject(js);
JS_SetPropertyStr(js, surfData, "width", JS_NewInt32(js, f->surface->w));
JS_SetPropertyStr(js, surfData, "height", JS_NewInt32(js, f->surface->h));
JS_SetPropertyStr(js, surfData, "format", pixelformat2js(js, f->surface->format));
JS_SetPropertyStr(js, surfData, "pitch", JS_NewInt32(js, f->surface->pitch));
// Lock surface if needed
int locked = 0;
if (SDL_MUSTLOCK(f->surface)) {
if (SDL_LockSurface(f->surface) < 0)
return JS_ThrowInternalError(js, "Lock surface failed: %s", SDL_GetError());
locked = 1;
}
size_t byte_size = f->surface->pitch * f->surface->h;
JS_SetPropertyStr(js, surfData, "pixels", js_new_blob_stoned_copy(js, f->surface->pixels, byte_size));
if (locked)
SDL_UnlockSurface(f->surface);
JS_SetPropertyStr(js, ret, "surface", surfData);
}
)
JSC_SCALL(os_system, ret = number2js(js,system(str)); )
JSValue make_color_buffer(JSContext *js, colorf c, int verts)
{
HMM_Vec4 *colordata = malloc(sizeof(*colordata)*verts);
for (int i = 0; i < verts; i++)
colordata[i] = c;
return make_gpu_buffer(js, colordata, sizeof(*colordata)*verts, JS_TYPED_ARRAY_FLOAT32, 4, 0, 0);
}
JSC_CCALL(os_make_line_prim,
JSValue prim = JS_NewObject(js);
HMM_Vec2 *v = js2cpvec2arr(js,argv[0]);
parsl_context *par_ctx = parsl_create_context((parsl_config){
.thickness = js2number(js,argv[1]),
.flags= PARSL_FLAG_ANNOTATIONS,
.u_mode = js2number(js,argv[2])
});
uint16_t spine_lens[] = {arrlen(v)};
parsl_mesh *m = parsl_mesh_from_lines(par_ctx, (parsl_spine_list){
.num_vertices = arrlen(v),
.num_spines = 1,
.vertices = v,
.spine_lengths = spine_lens,
.closed = JS_ToBool(js,argv[3])
});
JS_SetPropertyStr(js, prim, "pos", make_gpu_buffer(js,m->positions,sizeof(*m->positions)*m->num_vertices, JS_TYPED_ARRAY_FLOAT32, 2,1,0));
JS_SetPropertyStr(js, prim, "indices", make_gpu_buffer(js,m->triangle_indices,sizeof(*m->triangle_indices)*m->num_triangles*3, JS_TYPED_ARRAY_UINT32, 1,1,1));
float uv[m->num_vertices*2];
for (int i = 0; i < m->num_vertices; i++) {
uv[i*2] = m->annotations[i].u_along_curve;
uv[i*2+1] = m->annotations[i].v_across_curve;
}
JS_SetPropertyStr(js, prim, "uv", make_gpu_buffer(js, uv, sizeof(uv), JS_TYPED_ARRAY_FLOAT32,2,1,0));
JS_SetPropertyStr(js,prim,"vertices", number2js(js,m->num_vertices));
JS_SetPropertyStr(js,prim,"color",make_color_buffer(js,js2color(js,argv[4]), m->num_vertices));
JS_SetPropertyStr(js,prim,"num_indices", number2js(js,m->num_triangles*3));
JS_SetPropertyStr(js,prim,"first_index", number2js(js,0));
parsl_destroy_context(par_ctx);
return prim;
)
static void render_frame(plm_t *mpeg, plm_frame_t *frame, datastream *ds) {
if (JS_IsUndefined(ds->callback)) return;
uint8_t *rgb = malloc(frame->height*frame->width*4);
memset(rgb,255,frame->height*frame->width*4);
plm_frame_to_rgba(frame, rgb, frame->width*4);
// Create surface data object instead of SDL_Surface
JSValue surfData = JS_NewObject(ds->js);
JS_SetPropertyStr(ds->js, surfData, "width", JS_NewInt32(ds->js, frame->width));
JS_SetPropertyStr(ds->js, surfData, "height", JS_NewInt32(ds->js, frame->height));
JS_SetPropertyStr(ds->js, surfData, "format", JS_NewString(ds->js, "rgba32"));
JS_SetPropertyStr(ds->js, surfData, "pitch", JS_NewInt32(ds->js, frame->width*4));
JS_SetPropertyStr(ds->js, surfData, "pixels", js_new_blob_stoned_copy(ds->js, rgb, frame->height*frame->width*4));
JSValue s[1];
s[0] = surfData;
JSValue cb = JS_DupValue(ds->js,ds->callback);
JSValue ret = JS_Call(ds->js, cb, JS_UNDEFINED, 1, s);
JS_FreeValue(ds->js,cb);
free(rgb);
uncaught_exception(ds->js,ret);
}
JSC_CCALL(os_make_video,
size_t len;
void *data = js_get_blob_data(js,&len,argv[0]);
datastream *ds = ds_openvideo(data, len);
if (!ds) return JS_ThrowReferenceError(js, "Video file was not valid.");
ds->js = js;
ds->callback = JS_UNDEFINED;
plm_set_video_decode_callback(ds->plm, render_frame, ds);
return datastream2js(js,ds);
)
JSC_CCALL(os_rectpack,
int width = js2number(js,argv[0]);
int height = js2number(js,argv[1]);
int num = JS_ArrayLength(js,argv[2]);
stbrp_context ctx[1];
stbrp_rect rects[num];
for (int i = 0; i < num; i++) {
HMM_Vec2 wh = js2vec2(js,js_getpropertyuint32(js, argv[2], i));
rects[i].w = wh.x;
rects[i].h = wh.y;
rects[i].id = i;
}
stbrp_node nodes[width];
stbrp_init_target(ctx, width, height, nodes, width);
int packed = stbrp_pack_rects(ctx, rects, num);
if (!packed) {
return JS_UNDEFINED;
}
ret = JS_NewArray(js);
for (int i = 0; i < num; i++) {
HMM_Vec2 pos;
pos.x = rects[i].x;
pos.y = rects[i].y;
JS_SetPropertyUint32(js, ret, i, vec22js(js,pos));
}
)
JSC_CCALL(os_insertion_sort,
JSValue arr = argv[0];
JSValue cmp = argv[1];
int len = JS_ArrayLength(js, arr);
for (int i = 1; i < len; i++) {
JSValue key = JS_GetPropertyUint32(js, arr, i);
int j = i - 1;
while (j >= 0) {
JSValue arr_j = JS_GetPropertyUint32(js, arr, j);
JSValue ret = JS_Call(js, cmp, JS_UNDEFINED, 2, (JSValue[]){ arr_j, key });
if (JS_IsException(ret)) {
JS_FreeValue(js,arr_j);
JS_FreeValue(js,key);
return ret;
}
double c = js2number(js, ret);
JS_FreeValue(js, ret);
if (c > 0) {
JS_SetPropertyUint32(js, arr, j + 1, arr_j);
j--;
} else {
JS_FreeValue(js, arr_j);
break;
}
}
JS_SetPropertyUint32(js, arr, j + 1, key);
}
ret = JS_DupValue(js,arr);
)
JSC_CCALL(os_power_state,
SDL_PowerState state = SDL_GetPowerInfo(NULL, NULL);
switch(state) {
case SDL_POWERSTATE_ERROR: return JS_ThrowTypeError(js, "Error determining power status");
case SDL_POWERSTATE_UNKNOWN: return JS_UNDEFINED;
case SDL_POWERSTATE_ON_BATTERY: return JS_NewString(js, "on battery");
case SDL_POWERSTATE_NO_BATTERY: return JS_NewString(js, "no battery");
case SDL_POWERSTATE_CHARGING: return JS_NewString(js, "charging");
case SDL_POWERSTATE_CHARGED: return JS_NewString(js, "charged");
}
return JS_UNDEFINED;
)
JSC_CCALL(os_cull_sprites,
ret = JS_NewArray(js);
int n = 0;
JSValue sprites = argv[0];
shader_globals info = {0}; // TODO: get this as a JS object
rect camera_rect = {0};
camera_rect.x = info.camera_pos_world.x - info.render_size.x/2.0;
camera_rect.y = info.camera_pos_world.y - info.render_size.y/2.0;
camera_rect.w = info.render_size.x;
camera_rect.h = info.render_size.y;
int len = JS_ArrayLength(js,sprites);
for (int i = 0; i < len; i++) {
JSValue sub = JS_GetPropertyUint32(js,sprites,i);
transform *t;
JS_GETATOM(js,t,sub,transform,transform)
rect sprite = transform2rect(t);
if (SDL_HasRectIntersectionFloat(&sprite, &camera_rect)) {
JS_SetPropertyUint32(js,ret,n,JS_DupValue(js,sub));
n++;
}
JS_FreeValue(js,sub);
}
)
static const JSCFunctionListEntry js_util_funcs[] = {
MIST_FUNC_DEF(os, guid, 0),
MIST_FUNC_DEF(os, insertion_sort, 2),
MIST_FUNC_DEF(util, camera_globals, 1),
};
JSC_CCALL(graphics_hsl_to_rgb,
float h, s, l;
JS_ToFloat64(js, &h, argv[0]);
JS_ToFloat64(js, &s, argv[1]);
JS_ToFloat64(js, &l, argv[2]);
float c = (1 - abs(2 * l - 1)) * s;
float x = c * (1 - abs(fmod((h/60),2) - 1));
float m = l - c / 2;
float r = 0, g = 0, b = 0;
if (h < 60) { r = c; g = x; }
else if (h < 120) { r = x; g = c; }
else if (h < 180) { g = c; b = x; }
else if (h < 240) { g = x; b = c; }
else if (h < 300) { r = x; b = c; }
else { r = c; b = x; }
return color2js(js, (colorf){r+m, g+m, b+m, 1});
)
JSC_CCALL(graphics_save_png,
const char *file = JS_ToCString(js, argv[0]);
int w, h, comp, pitch;
JS_ToInt32(js, &w, argv[1]);
JS_ToInt32(js, &h, argv[2]);
JS_ToInt32(js, &pitch, argv[4]);
size_t size;
void *data = js_get_blob_data(js, &size, argv[3]);
if (!stbi_write_png(file, w, h, 4, data, pitch))
return JS_ThrowInternalError(js, "Could not write png");
)
JSC_CCALL(graphics_save_jpg,
const char *file = JS_ToCString(js, argv[0]);
int w, h, comp, pitch, quality;
JS_ToInt32(js, &w, argv[1]);
JS_ToInt32(js, &h, argv[2]);
JS_ToInt32(js, &pitch, argv[4]);
JS_ToInt32(js, &quality, argv[5]);
if (!quality) quality = 80;
size_t size;
void *data = js_get_blob_data(js, &size, argv[3]);
if (!stbi_write_jpg(file, w, h, 4, data, quality))
return JS_ThrowInternalError(js, "Could not write png");
)
static const JSCFunctionListEntry js_graphics_funcs[] = {
MIST_FUNC_DEF(os, make_text_buffer, 6),
MIST_FUNC_DEF(os, rectpack, 3),
MIST_FUNC_DEF(os, make_texture, 1),
MIST_FUNC_DEF(os, make_gif, 1),
MIST_FUNC_DEF(os, make_aseprite, 1),
MIST_FUNC_DEF(os, cull_sprites, 2),
MIST_FUNC_DEF(os, make_font, 2),
MIST_FUNC_DEF(os, make_line_prim, 5),
MIST_FUNC_DEF(graphics, hsl_to_rgb, 3),
MIST_FUNC_DEF(graphics, save_png, 4),
MIST_FUNC_DEF(graphics, save_jpg, 4),
};
static const JSCFunctionListEntry js_video_funcs[] = {
MIST_FUNC_DEF(os, make_video, 1),
};
JSC_SCALL(os_use_embed,
cell_rt *rt = JS_GetContextOpaque(js);
ModuleEntry *module_registry = rt->module_registry;
for (int i = 0; i < arrlen(module_registry); i++) {
if (strcmp(str,module_registry[i].name) == 0) {
ret = module_registry[i].fn(js);
break;
}
}
)
JSC_SCALL(os_use_dyn,
SDL_SharedObject *ptr = SDL_LoadObject(str);
if (!ptr)
return JS_ThrowReferenceError(js, "Shared library %s could not be loaded", SDL_GetError());
JSValue (*js_use)(JSContext*);
js_use = (JSValue (*)(JSContext*))SDL_LoadFunction(ptr, "use");
if (!js_use)
ret = JS_ThrowReferenceError(js, "Shared library %s has no use function", str);
else
ret = js_use(js);
SDL_UnloadObject(ptr);
)
#define JSSTATIC(NAME, PARENT) \
js_##NAME = JS_NewObject(js); \
JS_SetPropertyFunctionList(js, js_##NAME, js_##NAME##_funcs, countof(js_##NAME##_funcs)); \
JS_SetPrototype(js, js_##NAME, PARENT); \
JSValue js_layout_use(JSContext *js);
JSValue js_miniz_use(JSContext *js);
JSValue js_graphics_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_graphics_funcs,countof(js_graphics_funcs));
return mod;
}
JSValue js_util_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_util_funcs,countof(js_util_funcs));
return mod;
}
JSValue js_video_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_video_funcs,countof(js_video_funcs));
return mod;
}
JSValue js_console_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_console_funcs,countof(js_console_funcs));
return mod;
}
JSC_CCALL(os_value_id,
JS_SetPropertyStr(js, argv[0], "id", number2js(js, (uintptr_t)JS_VALUE_GET_PTR(argv[0])));
return argv[0];
)
#include "qjs_crypto.h"
#include "qjs_time.h"
#include "qjs_http.h"
#include "qjs_wota.h"
//JSValue js_imgui_use(JSContext *js);
#define MISTLINE(NAME) (ModuleEntry){#NAME, js_##NAME##_use}
void ffi_load(JSContext *js)
{
cell_rt *rt = JS_GetContextOpaque(js);
JS_FreeValue(js, js_blob_use(js)); // juice blob
m_seedRand(&rt->mrand, time(NULL));
// cell modules
arrput(rt->module_registry, MISTLINE(time));
arrput(rt->module_registry, ((ModuleEntry){"math", js_math_use}));
arrput(rt->module_registry, MISTLINE(blob));
// extra
arrput(rt->module_registry, ((ModuleEntry){"io", js_io_use}));
arrput(rt->module_registry, ((ModuleEntry){"fd", js_fd_use}));
arrput(rt->module_registry, ((ModuleEntry){"os", js_os_use}));
arrput(rt->module_registry, MISTLINE(qr));
arrput(rt->module_registry, MISTLINE(http));
arrput(rt->module_registry, MISTLINE(crypto));
arrput(rt->module_registry, MISTLINE(miniz));
// power user
arrput(rt->module_registry, MISTLINE(js));
arrput(rt->module_registry, MISTLINE(debug));
arrput(rt->module_registry, MISTLINE(dmon));
arrput(rt->module_registry, MISTLINE(util));
// prosperon
arrput(rt->module_registry, ((ModuleEntry){"sdl_audio", js_sdl_audio_use}));
arrput(rt->module_registry, ((ModuleEntry){"sdl_video", js_sdl_video_use}));
arrput(rt->module_registry, ((ModuleEntry){"input", js_input_use}));
arrput(rt->module_registry, ((ModuleEntry){"surface", js_sdl_surface_use}));
arrput(rt->module_registry, MISTLINE(spline));
arrput(rt->module_registry, ((ModuleEntry){"geometry", js_geometry_use}));
arrput(rt->module_registry, MISTLINE(graphics));
arrput(rt->module_registry, MISTLINE(video));
arrput(rt->module_registry, MISTLINE(soloud));
arrput(rt->module_registry, MISTLINE(layout));
// arrput(rt->module_registry, MISTLINE(imgui));
arrput(rt->module_registry, ((ModuleEntry){"camera", js_camera_use}));
arrput(rt->module_registry, MISTLINE(rtree));
arrput(rt->module_registry, MISTLINE(sprite));
arrput(rt->module_registry, MISTLINE(transform));
#ifndef NSTEAM
arrput(rt->module_registry, MISTLINE(steam));
#endif
JSValue globalThis = JS_GetGlobalObject(js);
JSValue prosp = JS_NewObject(js);
JS_SetPropertyStr(js,globalThis,"prosperon", prosp);
JSValue jsarray = JS_GetPropertyStr(js,globalThis, "Array");
JSValue array_proto = JS_GetPropertyStr(js,jsarray, "prototype");
JS_SetPropertyFunctionList(js, array_proto, js_array_funcs, countof(js_array_funcs));
JS_FreeValue(js,jsarray);
JS_FreeValue(js,array_proto);
JSValue jsnumber = JS_GetPropertyStr(js,globalThis, "Number");
JSValue number_proto = JS_GetPropertyStr(js,jsnumber, "prototype");
JS_SetPropertyFunctionList(js, number_proto, js_number_funcs, countof(js_number_funcs));
JS_FreeValue(js,jsnumber);
JS_FreeValue(js,number_proto);
JSValue hidden_fn = JS_NewObject(js);
// add engine.js-only functions to hidden_fn. It should grab them and then remove so nothing else can use them.
// Add modules that should only be accessible to engine.js
JS_SetPropertyStr(js, hidden_fn, "actor", js_actor_use(js));
JS_SetPropertyStr(js, hidden_fn, "wota", js_wota_use(js));
JS_SetPropertyStr(js, hidden_fn, "console", js_console_use(js));
JS_SetPropertyStr(js, hidden_fn, "nota", js_nota_use(js));
JS_SetPropertyStr(js, hidden_fn, "enet", js_enet_use(js));
// Add functions that should only be accessible to engine.js
JS_SetPropertyStr(js, hidden_fn, "use_dyn", JS_NewCFunction(js, js_os_use_dyn, "use_dyn", 1));
JS_SetPropertyStr(js, hidden_fn, "use_embed", JS_NewCFunction(js, js_os_use_embed, "use_embed", 1));
if (rt->init_wota) {
JS_SetPropertyStr(js, hidden_fn, "init", wota2value(js, rt->init_wota));
// init wota can now be freed
free(rt->init_wota);
rt->init_wota = NULL;
}
// cell_rt *actor = JS_GetContextOpaque(js);
// JSValue actorsym = js_newsymbol(js, "actor symbol", 0);
// actor->actor_sym = JS_ValueToAtom(js, actorsym);
// JS_SetPropertyStr(js, hidden_fn, "ACTORDATA", JS_DupValue(js,actorsym));
// JS_FreeValue(js, actorsym);
JS_SetPropertyStr(js, prosp, "hidden", hidden_fn);
JS_FreeValue(js,globalThis);
}