2939 lines
88 KiB
C
2939 lines
88 KiB
C
#include "jsffi.h"
|
||
#include "font.h"
|
||
#include "datastream.h"
|
||
#include "qjs_sdl.h"
|
||
#include "qjs_io.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 "physfs.h"
|
||
#include "prosperon.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_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_rtree.h"
|
||
#include "qjs_spline.h"
|
||
#include "qjs_js.h"
|
||
#include "qjs_debug.h"
|
||
|
||
SDL_Window *global_window;
|
||
|
||
#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 = &((prosperon_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;
|
||
|
||
#pragma pack(push, 1)
|
||
typedef struct shader_globals {
|
||
HMM_Mat4 world_to_projection;
|
||
HMM_Mat4 projection_to_world;
|
||
HMM_Mat4 world_to_view;
|
||
HMM_Mat4 view_to_projection;
|
||
HMM_Vec3 camera_pos_world;
|
||
HMM_Vec3 camera_dir_world;
|
||
float viewport_min_z;
|
||
float viewport_max_z;
|
||
HMM_Vec2 viewport_size;
|
||
HMM_Vec2 viewport_offset;
|
||
HMM_Vec2 render_size;
|
||
float time;
|
||
} shader_globals;
|
||
#pragma pack(pop)
|
||
|
||
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); }
|
||
static inline const char *js2cstring(JSContext *js, JSValue v) { return JS_ToCString(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;
|
||
}
|
||
|
||
static HMM_Mat3 cam_mat;
|
||
|
||
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;
|
||
if (copy)
|
||
tstack[0] = JS_NewArrayBufferCopy(js,data,size);//, make_gpu_buffer, NULL, 1);
|
||
else
|
||
tstack[0] = JS_NewArrayBuffer(js,data,size,free_gpu_buffer, NULL, 0);
|
||
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_GetArrayBuffer(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)
|
||
{
|
||
prosperon_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_Window_free(JSRuntime *rt, SDL_Window *w)
|
||
{
|
||
SDL_DestroyWindow(w);
|
||
}
|
||
|
||
typedef struct renderer_ctx {
|
||
SDL_Renderer *sdl;
|
||
shader_globals cam;
|
||
} renderer_ctx;
|
||
|
||
void renderer_ctx_free(JSRuntime *rt, renderer_ctx *ctx)
|
||
{
|
||
SDL_DestroyRenderer(ctx->sdl);
|
||
free(ctx);
|
||
}
|
||
|
||
void SDL_Surface_free(JSRuntime *rt, SDL_Surface *s) {
|
||
if (s->flags & SDL_SURFACE_PREALLOCATED)
|
||
free(s->pixels);
|
||
SDL_DestroySurface(s);
|
||
}
|
||
|
||
void SDL_GPUCommandBuffer_free(JSRuntime *rt, SDL_GPUCommandBuffer *c)
|
||
{
|
||
|
||
}
|
||
|
||
QJSCLASS(renderer_ctx,)
|
||
|
||
QJSCLASS(font,)
|
||
QJSCLASS(datastream,)
|
||
QJSCLASS(SDL_Window,)
|
||
|
||
void SDL_Texture_free(JSRuntime *rt, SDL_Texture *t){
|
||
SDL_DestroyTexture(t);
|
||
}
|
||
|
||
QJSCLASS(SDL_Texture,
|
||
JS_SetPropertyStr(js, j, "width", number2js(js,n->w));
|
||
JS_SetPropertyStr(js,j,"height",number2js(js,n->h));
|
||
)
|
||
|
||
QJSCLASS(SDL_Surface,
|
||
JS_SetProperty(js, j, width_atom, number2js(js,n->w));
|
||
JS_SetProperty(js,j,height_atom,number2js(js,n->h));
|
||
)
|
||
|
||
|
||
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;
|
||
}
|
||
|
||
struct quad_buffers {
|
||
HMM_Vec2 *pos;
|
||
HMM_Vec2 *uv;
|
||
HMM_Vec4 *color;
|
||
int verts;
|
||
};
|
||
|
||
struct quad_buffers quad_buffers_new(int verts)
|
||
{
|
||
struct quad_buffers b;
|
||
b.verts = verts;
|
||
b.pos = malloc(sizeof(HMM_Vec2)*verts);
|
||
b.uv = malloc(sizeof(HMM_Vec2)*verts);
|
||
b.color = malloc(sizeof(HMM_Vec4)*verts);
|
||
return b;
|
||
}
|
||
|
||
JSValue quadbuffers_to_mesh(JSContext *js, struct quad_buffers buffers)
|
||
{
|
||
JSValue jspos = make_gpu_buffer(js, buffers.pos, sizeof(HMM_Vec2)*buffers.verts, JS_TYPED_ARRAY_FLOAT32, 2, 0, 0);
|
||
JSValue jsuv = make_gpu_buffer(js, buffers.uv, sizeof(HMM_Vec2)*buffers.verts, JS_TYPED_ARRAY_FLOAT32, 2,0,0);
|
||
JSValue jscolor = make_gpu_buffer(js, buffers.color, sizeof(HMM_Vec4)*buffers.verts, JS_TYPED_ARRAY_FLOAT32, 4,0,0);
|
||
|
||
size_t quads = buffers.verts/4;
|
||
size_t count = buffers.verts/2*3;
|
||
JSValue jsidx = make_quad_indices_buffer(js, quads);
|
||
|
||
JSValue ret = JS_NewObject(js);
|
||
JS_SetProperty(js, ret, pos, jspos);
|
||
JS_SetProperty(js, ret, uv, jsuv);
|
||
JS_SetProperty(js, ret, color, jscolor);
|
||
JS_SetProperty(js, ret, indices, jsidx);
|
||
JS_SetProperty(js, ret, vertices, number2js(js, buffers.verts));
|
||
JS_SetProperty(js,ret,num_indices, number2js(js,count));
|
||
|
||
return ret;
|
||
}
|
||
|
||
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);
|
||
)
|
||
|
||
shader_globals camera_globals(JSContext *js, JSValue camera)
|
||
{
|
||
shader_globals data = {0};
|
||
if (JS_IsUndefined(camera))
|
||
return data;
|
||
|
||
HMM_Vec2 size;
|
||
transform *transform;
|
||
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, transform, camera, transform, transform)
|
||
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)
|
||
|
||
HMM_Mat4 proj;
|
||
HMM_Mat4 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(transform->pos)),
|
||
HMM_InvRotate(HMM_QToM4(transform->rotation))
|
||
);
|
||
|
||
// Update your shader globals
|
||
data.world_to_projection = HMM_MulM4(proj, view);
|
||
data.projection_to_world = HMM_InvGeneralM4(data.world_to_projection);
|
||
data.camera_pos_world = transform->pos;
|
||
data.viewport_min_z = near_z;
|
||
data.viewport_max_z = far_z;
|
||
data.render_size = size;
|
||
data.world_to_view = view;
|
||
data.view_to_projection = proj;
|
||
data.camera_dir_world = HMM_NormV3(HMM_QVRot((HMM_Vec3){0,0,-1},transform->rotation));
|
||
data.viewport_size = (HMM_Vec2){0.5,0.5};
|
||
data.viewport_offset = (HMM_Vec2){0,0};
|
||
data.time = SDL_GetTicksNS() / 1000000000.0f;
|
||
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),
|
||
};
|
||
|
||
#define JS_SDL_PROP(JS, VAL, SDLPROP, PROP) \
|
||
{ \
|
||
JSValue v = JS_GetPropertyStr(JS,VAL,#PROP); \
|
||
const char *str = JS_ToCString(JS, v); \
|
||
SDL_SetAppMetadataProperty(SDLPROP, str); \
|
||
JS_FreeCString(JS,str); \
|
||
JS_FreeValue(js,v); \
|
||
} \
|
||
|
||
JSC_CCALL(os_engine_start,
|
||
if (SDL_GetCurrentThreadID() != main_thread)
|
||
return JS_ThrowInternalError(js, "This can only be called from the root actor.");
|
||
|
||
if (global_window)
|
||
return JS_ThrowReferenceError(js, "The engine was already started.");
|
||
|
||
JSValue p = argv[0];
|
||
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_NAME_STRING, name)
|
||
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_VERSION_STRING, version)
|
||
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_IDENTIFIER_STRING, identifier)
|
||
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_CREATOR_STRING, creator)
|
||
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_COPYRIGHT_STRING, copyright)
|
||
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_URL_STRING, url)
|
||
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_TYPE_STRING, type)
|
||
|
||
const char *title;
|
||
JS_GETPROP(js,title,argv[0],title,cstring)
|
||
|
||
SDL_Window *new = SDL_CreateWindow(title, js2number(js, js_getproperty(js,argv[0], "width")), js2number(js,js_getproperty(js,argv[0], "height")), SDL_WINDOW_RESIZABLE);
|
||
|
||
JS_FreeCString(js,title);
|
||
|
||
if (!new) return JS_ThrowReferenceError(js, "Couldn't open window: %s\n", SDL_GetError());
|
||
|
||
SDL_StartTextInput(new);
|
||
global_window = new;
|
||
return SDL_Window2js(js,new);
|
||
)
|
||
|
||
JSC_SCALL(SDL_Window_make_renderer,
|
||
SDL_Window *win = js2SDL_Window(js,self);
|
||
renderer_ctx *ctx = malloc(sizeof(*ctx));
|
||
ctx->sdl = SDL_CreateRenderer(win, NULL);
|
||
ctx->cam = (shader_globals){0};
|
||
if (!ctx->sdl) {
|
||
free(ctx);
|
||
return JS_ThrowReferenceError(js, "Error creating renderer: %s",SDL_GetError());
|
||
}
|
||
SDL_SetRenderDrawBlendMode(ctx->sdl, SDL_BLENDMODE_BLEND);
|
||
return renderer_ctx2js(js,ctx);
|
||
)
|
||
|
||
/*
|
||
JSC_CCALL(SDL_Window_make_gpu,
|
||
if (global_gpu)
|
||
return JS_ThrowReferenceError(js, "A gpu has already been created somewhere.");
|
||
const char *name = JS_ToCString(js,argv[1]);
|
||
SDL_PropertiesID props = SDL_CreateProperties();
|
||
SDL_SetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING, name);
|
||
JS_FreeCString(js,name);
|
||
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, JS_ToBool(js,argv[0]));
|
||
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, 1);
|
||
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN, 1);
|
||
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN, 1);
|
||
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN, 1);
|
||
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN, 1);
|
||
|
||
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN, 1);
|
||
|
||
SDL_GPUDevice *gpu = SDL_CreateGPUDeviceWithProperties(props);
|
||
SDL_DestroyProperties(props);
|
||
|
||
if (!gpu) return JS_ThrowReferenceError(js, "Could not create GPU device! %s", SDL_GetError());
|
||
|
||
global_gpu = gpu;
|
||
return SDL_GPUDevice2js(js,gpu);
|
||
)
|
||
*/
|
||
|
||
JSC_CCALL(SDL_Window_fullscreen,
|
||
SDL_SetWindowFullscreen(js2SDL_Window(js,self), SDL_WINDOW_FULLSCREEN)
|
||
)
|
||
|
||
JSValue js_SDL_Window_keyboard_shown(JSContext *js, JSValue self) {
|
||
SDL_Window *window = js2SDL_Window(js,self);
|
||
return JS_NewBool(js,SDL_ScreenKeyboardShown(window));
|
||
}
|
||
|
||
JSValue js_window_theme(JSContext *js, JSValue self)
|
||
{
|
||
return JS_UNDEFINED;
|
||
}
|
||
|
||
JSValue js_window_safe_area(JSContext *js, JSValue self)
|
||
{
|
||
SDL_Window *w = js2SDL_Window(js,self);
|
||
rect r;
|
||
SDL_GetWindowSafeArea(w, &r);
|
||
return rect2js(js,r);
|
||
}
|
||
|
||
JSValue js_window_bordered(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||
{
|
||
SDL_Window *w = js2SDL_Window(js,self);
|
||
SDL_SetWindowBordered(w, JS_ToBool(js,argv[0]));
|
||
return JS_UNDEFINED;
|
||
}
|
||
|
||
JSValue js_window_get_title(JSContext *js, JSValue self)
|
||
{
|
||
SDL_Window *w = js2SDL_Window(js,self);
|
||
const char *title = SDL_GetWindowTitle(w);
|
||
return JS_NewString(js,title);
|
||
}
|
||
|
||
JSValue js_window_set_title(JSContext *js, JSValue self, JSValue val)
|
||
{
|
||
SDL_Window *w = js2SDL_Window(js,self);
|
||
const char *title = JS_ToCString(js,val);
|
||
SDL_SetWindowTitle(w,title);
|
||
JS_FreeCString(js,title);
|
||
return JS_UNDEFINED;
|
||
}
|
||
|
||
JSValue js_window_get_size(JSContext *js, JSValue self)
|
||
{
|
||
SDL_Window *win = js2SDL_Window(js,self);
|
||
int w, h;
|
||
SDL_GetWindowSize(win, &w, &h);
|
||
return vec22js(js, (HMM_Vec2){w,h});
|
||
}
|
||
|
||
JSValue js_window_set_size(JSContext *js, JSValue self, JSValue val)
|
||
{
|
||
SDL_Window *w = js2SDL_Window(js,self);
|
||
HMM_Vec2 size = js2vec2(js,val);
|
||
SDL_SetWindowSize(w,size.x,size.y);
|
||
return JS_UNDEFINED;
|
||
}
|
||
|
||
JSValue js_window_set_icon(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||
{
|
||
SDL_Window *w = js2SDL_Window(js,self);
|
||
SDL_Surface *s = js2SDL_Surface(js,argv[0]);
|
||
if (!SDL_SetWindowIcon(w,s))
|
||
return JS_ThrowReferenceError(js, "could not set window icon: %s", SDL_GetError());
|
||
return JS_UNDEFINED;
|
||
}
|
||
|
||
JSValue js_window_mouse_grab(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||
{
|
||
SDL_Window *w = js2SDL_Window(js,self);
|
||
SDL_SetWindowMouseGrab(w, JS_ToBool(js,argv[0]));
|
||
return JS_UNDEFINED;
|
||
}
|
||
|
||
static const JSCFunctionListEntry js_SDL_Window_funcs[] = {
|
||
MIST_FUNC_DEF(SDL_Window, fullscreen, 0),
|
||
MIST_FUNC_DEF(SDL_Window, make_renderer, 1),
|
||
// MIST_FUNC_DEF(SDL_Window, make_gpu, 2),
|
||
MIST_FUNC_DEF(SDL_Window, keyboard_shown, 0),
|
||
MIST_FUNC_DEF(window, theme, 0),
|
||
MIST_FUNC_DEF(window, safe_area, 0),
|
||
MIST_FUNC_DEF(window, bordered, 1),
|
||
MIST_FUNC_DEF(window, set_icon, 1),
|
||
CGETSET_ADD(window, title),
|
||
CGETSET_ADD(window, size),
|
||
MIST_FUNC_DEF(window, mouse_grab, 1),
|
||
};
|
||
|
||
static inline SDL_FPoint renderer_world_to_screen(renderer_ctx *ctx, HMM_Vec2 p)
|
||
{
|
||
HMM_Vec4 clip = HMM_MulM4V4(
|
||
ctx->cam.world_to_projection,
|
||
(HMM_Vec4){ p.x, p.y, 0.0f, 1.0f });
|
||
|
||
float inv_w = 1.0f / clip.w;
|
||
float ndc_x = clip.x * inv_w; /* −1 … +1 */
|
||
float ndc_y = clip.y * inv_w;
|
||
|
||
float scr_x = (ndc_x * 0.5f + 0.5f) * ctx->cam.render_size.x;
|
||
float scr_y = (1.0f - (ndc_y * 0.5f + 0.5f)) * ctx->cam.render_size.y;
|
||
|
||
return (SDL_FPoint){ scr_x, scr_y };
|
||
}
|
||
|
||
static inline rect renderer_worldrect_to_screen(renderer_ctx *ctx, rect r_world)
|
||
{
|
||
SDL_FPoint bl = renderer_world_to_screen(
|
||
ctx, (HMM_Vec2){ r_world.x,
|
||
r_world.y });
|
||
|
||
SDL_FPoint tr = renderer_world_to_screen(
|
||
ctx, (HMM_Vec2){ r_world.x + r_world.w,
|
||
r_world.y + r_world.h });
|
||
|
||
/* SDL wants the *top-left* corner, and y grows down. */
|
||
rect out;
|
||
out.x = SDL_min(bl.x, tr.x); /* left edge */
|
||
out.y = SDL_min(bl.y, tr.y); /* top edge */
|
||
|
||
/* always positive width / height */
|
||
out.w = fabsf(tr.x - bl.x);
|
||
out.h = fabsf(tr.y - bl.y);
|
||
return out;
|
||
}
|
||
|
||
|
||
JSC_CCALL(SDL_Renderer_clear,
|
||
SDL_Renderer *renderer = js2renderer_ctx(js,self)->sdl;
|
||
SDL_RenderClear(renderer);
|
||
)
|
||
|
||
JSC_CCALL(SDL_Renderer_present,
|
||
SDL_Renderer *ren = js2renderer_ctx(js,self)->sdl;
|
||
SDL_RenderPresent(ren);
|
||
)
|
||
|
||
JSC_CCALL(SDL_Renderer_draw_color,
|
||
SDL_Renderer *renderer = js2renderer_ctx(js,self)->sdl;
|
||
colorf color = js2color(js,argv[0]);
|
||
SDL_SetRenderDrawColorFloat(renderer, color.r,color.g,color.b,color.a);
|
||
)
|
||
|
||
JSC_CCALL(renderer_load_texture,
|
||
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
|
||
SDL_Surface *surf = js2SDL_Surface(js,argv[0]);
|
||
if (!surf) return JS_ThrowReferenceError(js, "Surface was not a surface.");
|
||
SDL_Texture *tex = SDL_CreateTextureFromSurface(r,surf);
|
||
if (!tex) return JS_ThrowReferenceError(js, "Could not create texture from surface: %s", SDL_GetError());
|
||
ret = SDL_Texture2js(js,tex);
|
||
JS_SetProperty(js,ret,width, number2js(js,tex->w));
|
||
JS_SetProperty(js,ret,height, number2js(js,tex->h));
|
||
)
|
||
|
||
JSC_CCALL(renderer_get_image,
|
||
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
|
||
SDL_Surface *surf = NULL;
|
||
if (!JS_IsUndefined(argv[0])) {
|
||
rect rect = js2rect(js,argv[0]);
|
||
surf = SDL_RenderReadPixels(r,&rect);
|
||
} else
|
||
surf = SDL_RenderReadPixels(r,NULL);
|
||
if (!surf) return JS_ThrowReferenceError(js, "could not make surface from renderer");
|
||
return SDL_Surface2js(js,surf);
|
||
)
|
||
|
||
JSC_CCALL(renderer_line,
|
||
if (!JS_IsArray(js, argv[0])) return JS_ThrowReferenceError(js, "First arugment must be an array of points.");
|
||
|
||
renderer_ctx *ctx = js2renderer_ctx(js,self);
|
||
SDL_Renderer *r = ctx->sdl;
|
||
|
||
int len = JS_ArrayLength(js,argv[0]);
|
||
if (len < 2) return JS_ThrowReferenceError(js, "Must provide at least 2 points to render.");
|
||
|
||
SDL_FPoint points[len];
|
||
assert(sizeof(HMM_Vec2) == sizeof(SDL_FPoint));
|
||
for (int i = 0; i < len; i++) {
|
||
JSValue val = JS_GetPropertyUint32(js,argv[0],i);
|
||
HMM_Vec2 wpt = js2vec2(js,val);
|
||
JS_FreeValue(js,val);
|
||
|
||
points[i] = renderer_world_to_screen(ctx, wpt);
|
||
}
|
||
|
||
SDL_RenderLines(r,points,len);
|
||
)
|
||
|
||
JSC_CCALL(renderer_point,
|
||
renderer_ctx *ctx = js2renderer_ctx(js, self);
|
||
SDL_Renderer *r = ctx->sdl;
|
||
|
||
if (JS_IsArray(js, argv[0])) {
|
||
int len = JS_ArrayLength(js, argv[0]);
|
||
SDL_FPoint pts[len];
|
||
|
||
for (int i = 0; i < len; ++i) {
|
||
JSValue val = JS_GetPropertyUint32(js, argv[0], i);
|
||
HMM_Vec2 w = js2vec2(js, val);
|
||
JS_FreeValue(js, val);
|
||
pts[i] = renderer_world_to_screen(ctx, w);
|
||
}
|
||
SDL_RenderPoints(r, pts, len);
|
||
return JS_UNDEFINED;
|
||
}
|
||
|
||
HMM_Vec2 w = js2vec2(js, argv[0]);
|
||
SDL_FPoint p = renderer_world_to_screen(ctx, w);
|
||
SDL_RenderPoint(r, p.x, p.y);
|
||
)
|
||
|
||
JSC_CCALL(renderer_rects,
|
||
renderer_ctx *ctx = js2renderer_ctx(js, self);
|
||
SDL_Renderer *r = ctx->sdl;
|
||
|
||
/* array-of-rectangles case */
|
||
if (JS_IsArray(js, argv[0])) {
|
||
int len = JS_ArrayLength(js, argv[0]);
|
||
if (len <= 0) return JS_UNDEFINED;
|
||
|
||
SDL_FRect rects[len];
|
||
|
||
for (int i = 0; i < len; ++i) {
|
||
JSValue val = JS_GetPropertyUint32(js, argv[0], i);
|
||
rect w = js2rect(js, val);
|
||
JS_FreeValue(js, val);
|
||
w = renderer_worldrect_to_screen(ctx, w);
|
||
rects[i] = w;
|
||
}
|
||
|
||
if (!SDL_RenderFillRects(r, rects, len))
|
||
return JS_ThrowReferenceError(js, "SDL_RenderFillRects: %s", SDL_GetError());
|
||
return JS_UNDEFINED;
|
||
}
|
||
|
||
/* single-rect path */
|
||
rect w = js2rect(js, argv[0]);
|
||
w = renderer_worldrect_to_screen(ctx, w);
|
||
if (!SDL_RenderFillRects(r, &w, 1))
|
||
return JS_ThrowReferenceError(js, "SDL_RenderFillRects: %s", SDL_GetError());
|
||
)
|
||
|
||
// Should take a single struct with pos, color, uv, and indices arrays
|
||
JSC_CCALL(renderer_geometry,
|
||
renderer_ctx *ctx = js2renderer_ctx(js,self);
|
||
SDL_Renderer *r = ctx->sdl;
|
||
JSValue pos = JS_GetPropertyStr(js,argv[1], "pos");
|
||
JSValue color = JS_GetPropertyStr(js,argv[1], "color");
|
||
JSValue uv = JS_GetPropertyStr(js,argv[1], "uv");
|
||
JSValue indices = JS_GetPropertyStr(js,argv[1], "indices");
|
||
int vertices = js_getnum_str(js, argv[1], "vertices");
|
||
int count = js_getnum_str(js, argv[1], "num_indices");
|
||
|
||
size_t pos_stride, indices_stride, uv_stride, color_stride;
|
||
void *posdata = get_gpu_buffer(js,pos, &pos_stride, NULL);
|
||
void *idxdata = get_gpu_buffer(js,indices, &indices_stride, NULL);
|
||
void *uvdata = get_gpu_buffer(js,uv, &uv_stride, NULL);
|
||
void *colordata = get_gpu_buffer(js,color,&color_stride, NULL);
|
||
|
||
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
|
||
|
||
HMM_Vec2 *trans_pos = malloc(vertices*sizeof(HMM_Vec2));
|
||
memcpy(trans_pos,posdata, sizeof(HMM_Vec2)*vertices);
|
||
|
||
for (int i = 0; i < vertices; i++) {
|
||
SDL_FPoint p = renderer_world_to_screen(ctx, trans_pos[i]);
|
||
trans_pos[i].x = p.x;
|
||
trans_pos[i].y = p.y;
|
||
}
|
||
|
||
if (!SDL_RenderGeometryRaw(r, tex, trans_pos, pos_stride,colordata,color_stride,uvdata, uv_stride, vertices, idxdata, count, indices_stride))
|
||
ret = JS_ThrowReferenceError(js, "Error rendering geometry: %s",SDL_GetError());
|
||
|
||
free(trans_pos);
|
||
|
||
JS_FreeValue(js,pos);
|
||
JS_FreeValue(js,color);
|
||
JS_FreeValue(js,uv);
|
||
JS_FreeValue(js,indices);
|
||
)
|
||
|
||
JSC_CCALL(renderer_geometry2,
|
||
renderer_ctx *ctx = js2renderer_ctx(js, self);
|
||
SDL_Renderer *r = ctx->sdl;
|
||
|
||
SDL_Texture *tex = js2SDL_Texture(js, argv[0]);
|
||
|
||
JSValue geo = argv[1];
|
||
int vertices = js_getnum_str(js, geo, "vertices");
|
||
int count = js_getnum_str(js, geo, "num_indices");
|
||
|
||
JSValue pos_v = JS_GetPropertyStr(js, geo, "pos");
|
||
JSValue color_v = JS_GetPropertyStr(js, geo, "color");
|
||
JSValue uv_v = JS_GetPropertyStr(js, geo, "uv");
|
||
JSValue idx_v = JS_GetPropertyStr(js, geo, "indices");
|
||
|
||
size_t pos_stride, color_stride, uv_stride, idx_stride;
|
||
void *pos_data = get_gpu_buffer(js, pos_v, &pos_stride, NULL);
|
||
void *color_data = get_gpu_buffer(js, color_v, &color_stride, NULL);
|
||
void *uv_data = get_gpu_buffer(js, uv_v, &uv_stride, NULL);
|
||
void *idx_data = get_gpu_buffer(js, idx_v, &idx_stride, NULL);
|
||
|
||
SDL_Vertex *verts = malloc(vertices * sizeof *verts);
|
||
for(int i = 0; i < vertices; ++i) {
|
||
const float *p = (const float *)((uint8_t *)pos_data + i * pos_stride);
|
||
const float *u = (const float *)((uint8_t *)uv_data + i * uv_stride);
|
||
const float *c = (const float *)((uint8_t *)color_data + i * color_stride);
|
||
|
||
SDL_FPoint screen = renderer_world_to_screen(ctx,
|
||
(HMM_Vec2){ .x = p[0], .y = p[1] });
|
||
|
||
verts[i].position = screen;
|
||
verts[i].tex_coord = (SDL_FPoint){ u[0], u[1] };
|
||
verts[i].color = (SDL_FColor){ c[0], c[1], c[2], c[3] };
|
||
}
|
||
|
||
const int *indices32 = NULL;
|
||
int *tmp_idx = NULL;
|
||
|
||
if(idx_data && count) {
|
||
if(idx_stride == 4) indices32 = (const int *)idx_data;
|
||
else {
|
||
tmp_idx = malloc(count * sizeof *tmp_idx);
|
||
if(idx_stride == 2) {
|
||
const uint16_t *src = idx_data;
|
||
for(int i = 0; i < count; ++i) tmp_idx[i] = src[i];
|
||
} else { /* 8-bit */
|
||
const uint8_t *src = idx_data;
|
||
for(int i = 0; i < count; ++i) tmp_idx[i] = src[i];
|
||
}
|
||
indices32 = tmp_idx;
|
||
}
|
||
}
|
||
|
||
printf("num verts, num indices: %d, %d\n", vertices, count);
|
||
|
||
if(!SDL_RenderGeometry(r, tex, verts, vertices, indices32, count))
|
||
printf("Error rendering geometry: %s\n", SDL_GetError());
|
||
|
||
free(tmp_idx);
|
||
free(verts);
|
||
|
||
JS_FreeValue(js, pos_v);
|
||
JS_FreeValue(js, color_v);
|
||
JS_FreeValue(js, uv_v);
|
||
JS_FreeValue(js, idx_v);
|
||
)
|
||
|
||
JSC_CCALL(renderer_logical_size,
|
||
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
|
||
HMM_Vec2 v = js2vec2(js,argv[0]);
|
||
SDL_SetRenderLogicalPresentation(r,v.x,v.y,SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);
|
||
)
|
||
|
||
JSC_CCALL(renderer_viewport,
|
||
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
|
||
if (JS_IsUndefined(argv[0]))
|
||
SDL_SetRenderViewport(r,NULL);
|
||
else {
|
||
rect view = js2rect(js,argv[0]);
|
||
SDL_SetRenderViewport(r,&view);
|
||
}
|
||
)
|
||
|
||
JSC_CCALL(renderer_clip,
|
||
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
|
||
if (JS_IsUndefined(argv[0]))
|
||
SDL_SetRenderClipRect(r,NULL);
|
||
else {
|
||
rect view = js2rect(js,argv[0]);
|
||
SDL_SetRenderClipRect(r,&view);
|
||
}
|
||
)
|
||
|
||
JSC_CCALL(renderer_scale,
|
||
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
|
||
HMM_Vec2 v = js2vec2(js,argv[0]);
|
||
SDL_SetRenderScale(r, v.x, v.y);
|
||
)
|
||
|
||
JSC_CCALL(renderer_vsync,
|
||
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
|
||
return JS_NewBool(js, SDL_SetRenderVSync(r,js2number(js,argv[0])));
|
||
)
|
||
|
||
// This returns the coordinates inside the
|
||
JSC_CCALL(renderer_coords,
|
||
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
|
||
HMM_Vec2 pos, coord;
|
||
pos = js2vec2(js,argv[0]);
|
||
SDL_RenderCoordinatesFromWindow(r,pos.x,pos.y, &coord.x, &coord.y);
|
||
return vec22js(js,coord);
|
||
)
|
||
|
||
JSC_CCALL(renderer_camera,
|
||
renderer_ctx *ctx = js2renderer_ctx(js,self);
|
||
ctx->cam = camera_globals(js, argv[0]);
|
||
)
|
||
|
||
JSC_CCALL(renderer_screen2world,
|
||
HMM_Mat3 inv = HMM_InvGeneralM3(cam_mat);
|
||
HMM_Vec3 pos = js2vec3(js,argv[0]);
|
||
return vec22js(js, HMM_MulM3V3(inv, pos).xy);
|
||
)
|
||
|
||
JSC_CCALL(renderer_target,
|
||
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
|
||
if (JS_IsUndefined(argv[0]))
|
||
SDL_SetRenderTarget(r, NULL);
|
||
else {
|
||
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
|
||
SDL_SetRenderTarget(r,tex);
|
||
}
|
||
)
|
||
|
||
// Given an array of sprites, make the necessary geometry
|
||
// A sprite is expected to have:
|
||
// image: a standard prosperon image of a surface, rect, and texture
|
||
// color: the color this sprite should be hued by
|
||
JSC_CCALL(renderer_make_sprite_mesh,
|
||
JSValue sprites = argv[0];
|
||
size_t quads = JS_ArrayLength(js,argv[0]);
|
||
size_t verts = quads*4;
|
||
size_t count = quads*6;
|
||
|
||
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,sprites,i);
|
||
|
||
JSValue jssrc = JS_GetProperty(js,sub,src);
|
||
JSValue jscolor = JS_GetProperty(js,sub,color);
|
||
HMM_Vec4 color;
|
||
|
||
rect src;
|
||
if (JS_IsUndefined(jssrc))
|
||
src = (rect){.x = 0, .y = 0, .w = 1, .h = 1};
|
||
else
|
||
src = js2rect(js,jssrc);
|
||
|
||
if (JS_IsUndefined(jscolor))
|
||
color = (HMM_Vec4){1,1,1,1};
|
||
else
|
||
color = js2vec4(js,jscolor);
|
||
|
||
// Calculate the base index for the current quad
|
||
size_t base = i * 4;
|
||
|
||
// Define the UV coordinates based on the source rectangle
|
||
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] = color;
|
||
colordata[base+1] = color;
|
||
colordata[base+2] = color;
|
||
colordata[base+3] = color;
|
||
|
||
JS_FreeValue(js,sub);
|
||
JS_FreeValue(js,jscolor);
|
||
JS_FreeValue(js,jssrc);
|
||
}
|
||
|
||
ret = JS_NewObject(js);
|
||
JS_SetProperty(js, ret, pos, make_gpu_buffer(js, posdata, sizeof(*posdata) * verts, JS_TYPED_ARRAY_FLOAT32, 2, 0,0));
|
||
JS_SetProperty(js, ret, uv, make_gpu_buffer(js, uvdata, sizeof(*uvdata) * verts, JS_TYPED_ARRAY_FLOAT32, 2, 0,0));
|
||
JS_SetProperty(js, ret, color, make_gpu_buffer(js, colordata, sizeof(*colordata) * verts, JS_TYPED_ARRAY_FLOAT32, 4, 0,0));
|
||
JS_SetProperty(js, ret, indices, make_quad_indices_buffer(js, quads));
|
||
JS_SetProperty(js, ret, vertices, number2js(js, verts));
|
||
JS_SetProperty(js, ret, count, number2js(js, count));
|
||
)
|
||
|
||
static sprite js_getsprite(JSContext *js, JSValue sp)
|
||
{
|
||
sprite *s = js2sprite(js, sp);
|
||
if (s)
|
||
return *s;
|
||
|
||
sprite pp = {0};
|
||
return pp;
|
||
}
|
||
|
||
JSC_CCALL(renderer_texture,
|
||
renderer_ctx *ctx = js2renderer_ctx(js, self);
|
||
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
|
||
rect src = js2rect(js,argv[1]);
|
||
rect dst = js2rect(js,argv[2]);
|
||
dst = renderer_worldrect_to_screen(ctx, dst);
|
||
double angle;
|
||
JS_ToFloat64(js, &angle, argv[3]);
|
||
angle *= -360;
|
||
HMM_Vec2 anchor = js2vec2(js, argv[4]);
|
||
anchor.y = dst.h - anchor.y*dst.h;
|
||
anchor.x *= dst.w;
|
||
// anchor.x *= dst.w;
|
||
// anchor.y *= dst.h;
|
||
// anchor.x += dst.x;
|
||
// anchor.y += dst.y;
|
||
SDL_RenderTextureRotated(ctx->sdl, tex, &src, &dst, angle, &anchor, SDL_FLIP_NONE);
|
||
)
|
||
|
||
JSC_CCALL(renderer_sprite,
|
||
renderer_ctx *ctx = js2renderer_ctx(js, self);
|
||
sprite sp = js_getsprite(js, argv[0]);
|
||
SDL_Texture *tex;
|
||
JS_GETATOM(js, tex, sp.image, texture, SDL_Texture);
|
||
rect uv;
|
||
JS_GETATOM(js, uv, sp.image, rect, rect);
|
||
|
||
float w = uv.w, h = uv.h;
|
||
|
||
HMM_Vec2 tl_local = { -sp.center.X, -sp.center.Y };
|
||
HMM_Vec2 tr_local = { -sp.center.X + w, -sp.center.Y };
|
||
HMM_Vec2 bl_local = { -sp.center.X, -sp.center.Y + h };
|
||
|
||
HMM_Vec2 world_tl = HMM_AddV2(sp.pos, HMM_MulM2V2(sp.affine, tl_local));
|
||
HMM_Vec2 world_tr = HMM_AddV2(sp.pos, HMM_MulM2V2(sp.affine, tr_local));
|
||
HMM_Vec2 world_bl = HMM_AddV2(sp.pos, HMM_MulM2V2(sp.affine, bl_local));
|
||
|
||
SDL_FPoint origin = renderer_world_to_screen(ctx, world_tl);
|
||
SDL_FPoint right = renderer_world_to_screen(ctx, world_tr);
|
||
SDL_FPoint down = renderer_world_to_screen(ctx, world_bl);
|
||
|
||
if (!SDL_RenderTextureAffine(ctx->sdl, tex, &uv, &origin, &right, &down))
|
||
return JS_ThrowInternalError(js, "Render sprite error: %s", SDL_GetError());
|
||
)
|
||
|
||
static const JSCFunctionListEntry js_renderer_ctx_funcs[] = {
|
||
MIST_FUNC_DEF(SDL_Renderer, draw_color, 1),
|
||
MIST_FUNC_DEF(SDL_Renderer, present, 0),
|
||
MIST_FUNC_DEF(SDL_Renderer, clear, 0),
|
||
MIST_FUNC_DEF(renderer, line, 1),
|
||
MIST_FUNC_DEF(renderer, point, 1),
|
||
MIST_FUNC_DEF(renderer, texture, 5),
|
||
MIST_FUNC_DEF(renderer, rects, 1),
|
||
MIST_FUNC_DEF(renderer, geometry, 2),
|
||
MIST_FUNC_DEF(renderer, geometry2, 2),
|
||
MIST_FUNC_DEF(renderer, sprite, 1),
|
||
MIST_FUNC_DEF(renderer, load_texture, 1),
|
||
MIST_FUNC_DEF(renderer, get_image, 1),
|
||
|
||
MIST_FUNC_DEF(renderer, scale, 1),
|
||
MIST_FUNC_DEF(renderer, logical_size,1),
|
||
MIST_FUNC_DEF(renderer, viewport,1),
|
||
MIST_FUNC_DEF(renderer, clip,1),
|
||
MIST_FUNC_DEF(renderer, vsync,1),
|
||
|
||
MIST_FUNC_DEF(renderer, coords, 1),
|
||
MIST_FUNC_DEF(renderer, camera, 2),
|
||
MIST_FUNC_DEF(renderer, screen2world, 1),
|
||
MIST_FUNC_DEF(renderer, target, 1),
|
||
MIST_FUNC_DEF(renderer, make_sprite_mesh, 2),
|
||
};
|
||
|
||
typedef struct { const char *name; SDL_PixelFormat fmt; } fmt_entry;
|
||
|
||
static const fmt_entry k_fmt_table[] = {
|
||
{ "rgba32", SDL_PIXELFORMAT_RGBA32 },
|
||
{ "argb32", SDL_PIXELFORMAT_ARGB32 },
|
||
{ "bgra32", SDL_PIXELFORMAT_BGRA32 },
|
||
{ "abgr32", SDL_PIXELFORMAT_ABGR32 },
|
||
{ "rgb565", SDL_PIXELFORMAT_RGB565 },
|
||
{ "bgr565", SDL_PIXELFORMAT_BGR565 },
|
||
{ "rgb24", SDL_PIXELFORMAT_RGB24 },
|
||
{ "bgr24", SDL_PIXELFORMAT_BGR24 },
|
||
{ "rgb332", SDL_PIXELFORMAT_RGB332 },
|
||
{ "rgba64", SDL_PIXELFORMAT_RGBA64 },
|
||
{ "rgb48", SDL_PIXELFORMAT_RGB48 },
|
||
{ NULL, SDL_PIXELFORMAT_UNKNOWN }
|
||
};
|
||
|
||
static JSValue pixelformat2js(JSContext *js, SDL_PixelFormat fmt)
|
||
{
|
||
fmt_entry *it;
|
||
for (it = k_fmt_table; it->name; it++)
|
||
if (it->fmt == fmt)
|
||
break;
|
||
|
||
if (it->name)
|
||
return JS_NewString(js, it->name);
|
||
|
||
return JS_UNDEFINED;
|
||
}
|
||
|
||
static SDL_PixelFormat js2pixelformat(JSContext *js, JSValue v)
|
||
{
|
||
if (JS_IsUndefined(v)) return SDL_PIXELFORMAT_UNKNOWN;
|
||
const char *s = JS_ToCString(js, v);
|
||
if (!s) return SDL_PIXELFORMAT_UNKNOWN;
|
||
|
||
fmt_entry *it;
|
||
for (it = k_fmt_table; it->name; it++)
|
||
if (!strcmp(it->name, s))
|
||
break;
|
||
|
||
JS_FreeCString(js,s);
|
||
return it->fmt;
|
||
}
|
||
|
||
typedef struct { const char *name; SDL_ScaleMode mode; } scale_entry;
|
||
|
||
static const scale_entry k_scale_table[] = {
|
||
{ "nearest", SDL_SCALEMODE_NEAREST },
|
||
{ "linear", SDL_SCALEMODE_LINEAR },
|
||
{ NULL, SDL_SCALEMODE_LINEAR } /* fallback */
|
||
};
|
||
|
||
static JSValue scalemode2js(JSContext *js, SDL_ScaleMode mode){
|
||
const scale_entry *it;
|
||
for(it = k_scale_table; it->name; ++it)
|
||
if(it->mode == mode) break;
|
||
return JS_NewString(js, it->name ? it->name : "nearest");
|
||
}
|
||
|
||
static SDL_ScaleMode js2SDL_ScaleMode(JSContext *js, JSValue v){
|
||
if(JS_IsUndefined(v)) return SDL_SCALEMODE_NEAREST;
|
||
const char *s = JS_ToCString(js, v);
|
||
if(!s) return SDL_SCALEMODE_NEAREST;
|
||
const scale_entry *it;
|
||
for(it = k_scale_table; it->name; ++it)
|
||
if(!strcmp(it->name, s)) break;
|
||
JS_FreeCString(js, s);
|
||
return it->mode;
|
||
}
|
||
|
||
/* -------------------------------------------------------------------------
|
||
* surface.blit(dstrect, src, srcrect, scalemode)
|
||
* -------------------------------------------------------------------------*/
|
||
JSC_CCALL(surface_blit,
|
||
SDL_Surface *dst = js2SDL_Surface(js, self);
|
||
|
||
irect dr = {0}, *pdr = NULL;
|
||
if(!JS_IsUndefined(argv[0])){ dr = js2irect(js, argv[0]); pdr = &dr; }
|
||
|
||
SDL_Surface *src = js2SDL_Surface(js, argv[1]);
|
||
if (!src)
|
||
return JS_ThrowReferenceError(js, "Argument must be a surface.");
|
||
|
||
irect sr = {0}, *psr = NULL;
|
||
if(!JS_IsUndefined(argv[2])){ sr = js2irect(js, argv[2]); psr = &sr; }
|
||
|
||
SDL_ScaleMode mode = js2SDL_ScaleMode(js, argv[3]);
|
||
|
||
SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE);
|
||
|
||
printf("src %p, dst %p, src rect %g,%g,%g,%g [%p], dst rect %g,%g,%g,%g [%p]\n", src, dst, sr.x, sr.y, sr.w, sr.h, psr, dr.x, dr.y, dr.w, dr.h, pdr);
|
||
|
||
SDL_BlitSurfaceScaled(src, psr, dst, pdr, mode);
|
||
)
|
||
|
||
JSC_CCALL(surface_scale,
|
||
SDL_Surface *src = js2SDL_Surface(js,self);
|
||
HMM_Vec2 wh = js2vec2(js,argv[0]);
|
||
SDL_ScaleMode mode = js2SDL_ScaleMode(js, argv[1]);
|
||
SDL_Surface *new = SDL_ScaleSurface(src, wh.x, wh.y, mode);
|
||
ret = SDL_Surface2js(js,new);
|
||
)
|
||
|
||
static SDL_PixelFormatDetails pdetails = {
|
||
.format = SDL_PIXELFORMAT_RGBA8888, // Standard RGBA8888 format
|
||
.bits_per_pixel = 32, // 8 bits per channel, 4 channels = 32 bits
|
||
.bytes_per_pixel = 4, // 4 bytes per pixel
|
||
.padding = {0, 0}, // Unused padding
|
||
.Rmask = 0xFF000000, // Red mask
|
||
.Gmask = 0x00FF0000, // Green mask
|
||
.Bmask = 0x0000FF00, // Blue mask
|
||
.Amask = 0x000000FF, // Alpha mask
|
||
.Rbits = 8, // 8 bits for Red
|
||
.Gbits = 8, // 8 bits for Green
|
||
.Bbits = 8, // 8 bits for Blue
|
||
.Abits = 8, // 8 bits for Alpha
|
||
.Rshift = 24, // Red shift
|
||
.Gshift = 16, // Green shift
|
||
.Bshift = 8, // Blue shift
|
||
.Ashift = 0 // Alpha shift
|
||
};
|
||
|
||
JSC_CCALL(surface_fill,
|
||
SDL_Surface *src = js2SDL_Surface(js,self);
|
||
colorf color = js2color(js,argv[0]);
|
||
rect r = {
|
||
.x = 0,
|
||
.y = 0,
|
||
.w = src->w,
|
||
.h = src->h
|
||
};
|
||
SDL_FillSurfaceRect(src, &r, SDL_MapRGBA(&pdetails, NULL, color.r*255,color.g*255,color.b*255,color.a*255));
|
||
)
|
||
|
||
JSC_CCALL(surface_rect,
|
||
SDL_Surface *dst = js2SDL_Surface(js,self);
|
||
rect r = js2rect(js,argv[0]);
|
||
colorf color = js2color(js,argv[1]);
|
||
SDL_FillSurfaceRect(dst,&r,SDL_MapRGBA(&pdetails,NULL, color.r*255,color.g*255,color.b*255,color.a*255));
|
||
)
|
||
|
||
JSC_CCALL(surface_convert,
|
||
SDL_Surface *surf = js2SDL_Surface(js,self);
|
||
SDL_PixelFormat fmt = js2pixelformat(js, argv[0]);
|
||
SDL_Surface *dst = SDL_ConvertSurface(surf, fmt);
|
||
if (!dst) return JS_ThrowInternalError(js, "Convert failed: %s", SDL_GetError());
|
||
|
||
return SDL_Surface2js(js, dst);
|
||
)
|
||
|
||
JSC_CCALL(surface_dup,
|
||
SDL_Surface *surf = js2SDL_Surface(js,self);
|
||
SDL_Surface *conv = SDL_DuplicateSurface(surf);
|
||
if (!conv)
|
||
return JS_ThrowReferenceError(js, "could not blit to dup'd surface: %s", SDL_GetError());
|
||
|
||
return SDL_Surface2js(js,conv);
|
||
)
|
||
|
||
JSC_CCALL(surface_pixels,
|
||
SDL_Surface *surf = js2SDL_Surface(js,self);
|
||
|
||
/* if (SDL_ISPIXELFORMAT_FOURCC(surf->format->format))
|
||
return JS_ThrowTypeError(js, "planar or FOURCC formats are not supported - convert first");
|
||
*/
|
||
|
||
int locked = 0;
|
||
if (SDL_MUSTLOCK(surf)) {
|
||
if (SDL_LockSurface(surf) < 0)
|
||
return JS_ThrowInternalError(js, "Lock surface failed: %s", SDL_GetError());
|
||
|
||
locked = 1;
|
||
}
|
||
|
||
size_t byte_size = surf->pitch*surf->h;
|
||
|
||
ret = JS_NewArrayBufferCopy(js, surf->pixels, byte_size);
|
||
|
||
if (locked)
|
||
SDL_UnlockSurface(surf);
|
||
)
|
||
|
||
JSC_CCALL(surface_get_width,
|
||
SDL_Surface *s = js2SDL_Surface(js,self);
|
||
return JS_NewFloat64(js, s->w);
|
||
)
|
||
|
||
JSC_CCALL(surface_get_height,
|
||
SDL_Surface *s = js2SDL_Surface(js,self);
|
||
return JS_NewFloat64(js, s->h);
|
||
)
|
||
|
||
JSC_CCALL(surface_get_format,
|
||
SDL_Surface *s = js2SDL_Surface(js,self);
|
||
return pixelformat2js(js, s->format);
|
||
)
|
||
|
||
JSC_CCALL(surface_get_pitch,
|
||
SDL_Surface *s = js2SDL_Surface(js,self);
|
||
return JS_NewFloat64(js, s->pitch);
|
||
)
|
||
|
||
static const JSCFunctionListEntry js_SDL_Surface_funcs[] = {
|
||
MIST_FUNC_DEF(surface, blit, 4),
|
||
MIST_FUNC_DEF(surface, scale, 1),
|
||
MIST_FUNC_DEF(surface, fill,1),
|
||
MIST_FUNC_DEF(surface, rect,2),
|
||
MIST_FUNC_DEF(surface, dup, 0),
|
||
MIST_FUNC_DEF(surface, pixels, 0),
|
||
MIST_FUNC_DEF(surface, convert, 1),
|
||
JS_CGETSET_DEF("width", js_surface_get_width, NULL),
|
||
JS_CGETSET_DEF("height", js_surface_get_height, NULL),
|
||
JS_CGETSET_DEF("format", js_surface_get_format, NULL),
|
||
JS_CGETSET_DEF("pitch", js_surface_get_pitch, NULL),
|
||
};
|
||
|
||
JSC_CCALL(texture_mode,
|
||
SDL_Texture *tex = js2SDL_Texture(js,self);
|
||
SDL_SetTextureScaleMode(tex,js2number(js,argv[0]));
|
||
)
|
||
|
||
static const JSCFunctionListEntry js_SDL_Texture_funcs[] = {
|
||
MIST_FUNC_DEF(texture, mode, 1),
|
||
};
|
||
|
||
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(os_openurl,
|
||
if (!SDL_OpenURL(str))
|
||
ret = JS_ThrowReferenceError(js, "unable to open url %s: %s\n", str, SDL_GetError());
|
||
)
|
||
|
||
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),
|
||
};
|
||
|
||
JSValue js_os_sys(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||
{
|
||
return JS_NewString(js, SDL_GetPlatform());
|
||
}
|
||
|
||
|
||
// input: (encoded image data of jpg, png, bmp, tiff)
|
||
JSC_CCALL(os_make_texture,
|
||
size_t len;
|
||
void *raw = JS_GetArrayBuffer(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;
|
||
int fmt = SDL_PIXELFORMAT_RGBA32;
|
||
|
||
SDL_Surface *surf = SDL_CreateSurfaceFrom(width,height,fmt, data, pitch);
|
||
if (!surf) {
|
||
free(data);
|
||
return JS_ThrowReferenceError(js, "Error creating surface from data: %s",SDL_GetError());
|
||
}
|
||
|
||
ret = SDL_Surface2js(js,surf);
|
||
)
|
||
|
||
JSC_CCALL(graphics_surface_from_pixels,
|
||
int w, h, pitch;
|
||
SDL_PixelFormat fmt;
|
||
JS_GETATOM(js, w, argv[0], width, number)
|
||
JS_GETATOM(js, h, argv[0], height, number)
|
||
JS_GETATOM(js, fmt, argv[0], format, pixelformat)
|
||
JS_GETATOM(js, pitch, argv[0], pitch, number)
|
||
JSValue js_px = JS_GetPropertyStr(js, argv[0], "buffer");
|
||
|
||
size_t len;
|
||
void *raw = JS_GetArrayBuffer(js, &len, js_px);
|
||
|
||
JS_FreeValue(js, js_px);
|
||
|
||
if (!raw || !w || !h || !fmt)
|
||
return JS_ThrowReferenceError(js, "Invalid source.");
|
||
|
||
void *newraw = malloc(len);
|
||
memcpy(newraw, raw, len);
|
||
|
||
SDL_Surface *s = SDL_CreateSurfaceFrom(w, h, fmt, newraw, pitch);
|
||
|
||
if (!s)
|
||
return JS_ThrowInternalError(js, "Unable to create surface: %s", SDL_GetError());
|
||
|
||
return SDL_Surface2js(js, s);
|
||
)
|
||
|
||
// input: (gif image data)
|
||
JSC_CCALL(os_make_gif,
|
||
size_t rawlen;
|
||
void *raw = JS_GetArrayBuffer(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 just that
|
||
JS_SetPropertyStr(js, gif, "surface", SDL_Surface2js(js,SDL_CreateSurfaceFrom(width,height,SDL_PIXELFORMAT_RGBA32, pixels, width*4)));
|
||
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));
|
||
void *frame_pixels = malloc(width*height*4);
|
||
if (!frame_pixels) {
|
||
JS_FreeValue(js,gif);
|
||
ret = JS_ThrowOutOfMemory(js);
|
||
goto CLEANUP;
|
||
}
|
||
memcpy(frame_pixels, (unsigned char*)pixels+(width*height*4*i), width*height*4);
|
||
SDL_Surface *framesurf = SDL_CreateSurfaceFrom(width,height,SDL_PIXELFORMAT_RGBA32,frame_pixels, width*4);
|
||
if (!framesurf) {
|
||
ret = JS_ThrowReferenceError(js, "failed to create SDL_Surface: %s", SDL_GetError());
|
||
goto CLEANUP;
|
||
}
|
||
JS_SetPropertyStr(js, frame, "surface", SDL_Surface2js(js,framesurf));
|
||
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);
|
||
void *frame_pixels = malloc(aframe.ase->w*aframe.ase->h*4);
|
||
memcpy(frame_pixels, aframe.pixels, aframe.ase->w*aframe.ase->h*4);
|
||
SDL_Surface *surf = SDL_CreateSurfaceFrom(aframe.ase->w, aframe.ase->h, SDL_PIXELFORMAT_RGBA32, frame_pixels, aframe.ase->w*4);
|
||
JS_SetPropertyStr(js, frame, "surface", SDL_Surface2js(js,surf));
|
||
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_GetArrayBuffer(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_surface,
|
||
HMM_Vec2 wh = js2vec2(js,argv[0]);
|
||
SDL_Surface *surface = SDL_CreateSurface(wh.x, wh.y, SDL_PIXELFORMAT_RGBA32);
|
||
ret = SDL_Surface2js(js, surface);
|
||
)
|
||
|
||
// TODO: Implement this correctly
|
||
JSC_CCALL(os_make_cursor,
|
||
/*if (SDL_GetCurrentThreadID() != main_thread)
|
||
return JS_ThrowInternalError(js, "This can only be called from the root actor.");
|
||
|
||
SDL_Surface *s = js2SDL_Surface(js,argv[0]);
|
||
HMM_Vec2 hot = js2vec2(js,argv[1]);
|
||
SDL_Cursor *c = SDL_CreateColorCursor(s, hot.x, hot.y);
|
||
if (!c) return JS_ThrowReferenceError(js,"couldn't make cursor: %s", SDL_GetError());
|
||
return SDL_Cursor2js(js,c);*/
|
||
)
|
||
|
||
JSC_CCALL(os_make_font,
|
||
size_t len;
|
||
void *data = JS_GetArrayBuffer(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);
|
||
JS_SetPropertyStr(js, ret, "surface", SDL_Surface2js(js,f->surface));
|
||
)
|
||
|
||
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);
|
||
SDL_Surface *surf = SDL_CreateSurfaceFrom(frame->width,frame->height, SDL_PIXELFORMAT_RGBA32, rgb, frame->width*4);
|
||
JSValue s[1];
|
||
s[0] = SDL_Surface2js(ds->js,surf);
|
||
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_GetArrayBuffer(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_SCALL(os_kill,
|
||
int sig = 0;
|
||
if (!strcmp(str, "SIGABRT")) sig = SIGABRT;
|
||
else if (!strcmp(str, "SIGFPE")) sig = SIGFPE;
|
||
else if (!strcmp(str, "SIGILL")) sig = SIGILL;
|
||
else if (!strcmp(str, "SIGINT")) sig = SIGINT;
|
||
else if (!strcmp(str, "SIGSEGV")) sig = SIGSEGV;
|
||
else if (!strcmp(str, "SIGTERM")) sig = SIGTERM;
|
||
|
||
if (!sig) return JS_ThrowReferenceError(js, "string %s is not a valid signal", str);
|
||
raise(sig);
|
||
)
|
||
|
||
JSC_CCALL(os_sleep,
|
||
double time = js2number(js,argv[0]);
|
||
time *= 1000000000.;
|
||
SDL_DelayNS(time);
|
||
)
|
||
|
||
JSC_CCALL(os_battery_pct,
|
||
int pct;
|
||
SDL_GetPowerInfo(NULL, &pct);
|
||
return number2js(js,pct);
|
||
)
|
||
|
||
JSC_CCALL(os_battery_voltage,
|
||
|
||
)
|
||
|
||
JSC_CCALL(os_battery_seconds,
|
||
int seconds;
|
||
SDL_GetPowerInfo(&seconds, NULL);
|
||
return number2js(js,seconds);
|
||
)
|
||
|
||
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 = camera_globals(js,argv[1]);
|
||
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),
|
||
};
|
||
|
||
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_GetArrayBuffer(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_GetArrayBuffer(js, &size, argv[3]);
|
||
|
||
if (!stbi_write_jpg(file, w, h, 4, data, quality))
|
||
return JS_ThrowInternalError(js, "Could not write png");
|
||
)
|
||
|
||
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_IsArrayBuffer(js, argv[0])) {
|
||
// test for fastest
|
||
size_t size;
|
||
sprite *sprites = JS_GetArrayBuffer(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_GetProperty(js,sub,image);
|
||
sprite_apply(&sp);
|
||
arrput(sprites,sp);
|
||
}
|
||
JS_FreeValue(js, sub);
|
||
}
|
||
}
|
||
|
||
qsort(sprites, quads, sizeof(sprite), sort_sprite);
|
||
|
||
struct quad_buffers buffers = quad_buffers_new(quads*4);
|
||
|
||
const HMM_Vec2 local[4] = { {0,0}, {1,0}, {0,1}, {1,1} };
|
||
|
||
rect uv;
|
||
rect uv_px;
|
||
JSValue cur_img = JS_UNDEFINED;
|
||
|
||
for (size_t i = 0; i < quads; i++) {
|
||
sprite *s = &sprites[i];
|
||
if (JS_IsUndefined(cur_img) || !JS_StrictEq(js, s->image, cur_img)) {
|
||
cur_img = s->image;
|
||
JS_GETATOM(js, uv, cur_img, rect, rect)
|
||
JS_GETATOM(js, uv_px, cur_img, rect_px, rect)
|
||
}
|
||
|
||
HMM_Vec2 px_size = {
|
||
uv_px.w * s->scale.X,
|
||
uv_px.h * s->scale.Y
|
||
};
|
||
|
||
HMM_Vec2 anchor = {
|
||
px_size.X * s->center.X,
|
||
px_size.Y * s->center.Y
|
||
};
|
||
|
||
size_t base = i * 4;
|
||
|
||
for (int v = 0; v < 4; v++) {
|
||
HMM_Vec2 lp = {
|
||
local[v].X * px_size.X - anchor.X,
|
||
local[v].Y * px_size.Y - anchor.Y
|
||
};
|
||
|
||
HMM_Vec2 world = HMM_AddV2(s->pos, HMM_MulM2V2(s->affine, lp));
|
||
|
||
buffers.pos[base + v] = world;
|
||
buffers.color[base + v] = s->color;
|
||
}
|
||
|
||
/* UVs are still top-left-origin pixel coords, so keep previous packing */
|
||
buffers.uv[base + 0] = (HMM_Vec2){ uv.x, uv.y + uv.h };
|
||
buffers.uv[base + 1] = (HMM_Vec2){ uv.x + uv.w, uv.y + uv.h };
|
||
buffers.uv[base + 2] = (HMM_Vec2){ uv.x, uv.y };
|
||
buffers.uv[base + 3] = (HMM_Vec2){ uv.x + uv.w, uv.y };
|
||
}
|
||
|
||
JSValue mesh = quadbuffers_to_mesh(js, buffers);
|
||
|
||
ret = JS_NewArray(js);
|
||
int first_index = 0;
|
||
int count = 0;
|
||
int n = 0;
|
||
JSValue img = JS_UNDEFINED;
|
||
|
||
for (int i = 0; i < quads; i++) {
|
||
if (!JS_SameValue(js, sprites[i].image, img)) {
|
||
if (count > 0) {
|
||
JSValue q = JS_NewObject(js);
|
||
JS_SetPropertyStr(js, q, "type", JS_NewString(js, "geometry"));
|
||
JS_SetPropertyStr(js, q, "mesh", JS_DupValue(js, mesh));
|
||
JS_SetPropertyStr(js, q, "pipeline", JS_DupValue(js, argv[2]));
|
||
JS_SetPropertyStr(js, q, "image", img);
|
||
JS_SetPropertyStr(js, q, "first_index", number2js(js, first_index));
|
||
JS_SetPropertyStr(js, q, "num_indices", number2js(js, count * 6));
|
||
JS_SetPropertyUint32(js, ret, n++, q);
|
||
}
|
||
first_index = i*6;
|
||
count = 1;
|
||
img = JS_DupValue(js, sprites[i].image);
|
||
} else count++;
|
||
JS_FreeValue(js,sprites[i].image);
|
||
}
|
||
|
||
if (count > 0) {
|
||
JSValue q = JS_NewObject(js);
|
||
JS_SetPropertyStr(js, q, "type", JS_NewString(js, "geometry"));
|
||
JS_SetPropertyStr(js, q, "mesh", JS_DupValue(js, mesh));
|
||
JS_SetPropertyStr(js, q, "pipeline", JS_DupValue(js, argv[2]));
|
||
JS_SetPropertyStr(js, q, "image", img);
|
||
JS_SetPropertyStr(js, q, "first_index", number2js(js, first_index));
|
||
JS_SetPropertyStr(js, q, "num_indices", number2js(js, count * 6));
|
||
JS_SetPropertyUint32(js, ret, n++, q);
|
||
}
|
||
|
||
if (needfree)
|
||
arrfree(sprites);
|
||
|
||
JS_FreeValue(js, mesh);
|
||
)
|
||
|
||
typedef struct {
|
||
JSValue val;
|
||
void *ptr;
|
||
size_t size;
|
||
int need_new;
|
||
} BufferCheckResult;
|
||
|
||
static BufferCheckResult get_or_extend_buffer(
|
||
JSContext *js,
|
||
JSValue old_mesh,
|
||
const char *prop,
|
||
size_t needed_size,
|
||
int type,
|
||
int elements_per_item,
|
||
int copy,
|
||
int index
|
||
) {
|
||
BufferCheckResult res = { JS_UNDEFINED, NULL, 0, 0 };
|
||
if (!JS_IsUndefined(old_mesh)) {
|
||
JSValue old_buf = JS_GetProperty(js, old_mesh, prop);
|
||
if (!JS_IsUndefined(old_buf)) {
|
||
size_t old_size;
|
||
void *data = get_gpu_buffer(js, old_buf, NULL, &old_size);
|
||
if (data && old_size >= needed_size) {
|
||
// Old buffer is large enough
|
||
res.val = old_buf; // keep it
|
||
res.ptr = data;
|
||
res.size = old_size;
|
||
return res;
|
||
}
|
||
JS_FreeValue(js, old_buf);
|
||
}
|
||
}
|
||
// If we reach here, we need a new buffer
|
||
res.need_new = 1;
|
||
return res;
|
||
}
|
||
|
||
static HMM_Vec3 base_quad[4] = {
|
||
{0.0,0.0,1.0},
|
||
{1.0,0.0,1.0},
|
||
{0.0,1.0,1.0},
|
||
{1.0,1.0,1.0}
|
||
};
|
||
|
||
JSC_CCALL(gpu_make_sprite_mesh,
|
||
size_t quads = JS_ArrayLength(js, argv[0]);
|
||
size_t verts = quads*4;
|
||
size_t count = quads*6;
|
||
|
||
// Prepare arrays on CPU
|
||
HMM_Vec2 *posdata = malloc(sizeof(*posdata)*verts);
|
||
HMM_Vec2 *uvdata = malloc(sizeof(*uvdata)*verts);
|
||
HMM_Vec4 *colordata = malloc(sizeof(*colordata)*verts);
|
||
|
||
for (int i = 0; i < quads; i++) {
|
||
JSValue sub = JS_GetPropertyUint32(js,argv[0],i);
|
||
|
||
transform *tr;
|
||
rect src;
|
||
HMM_Vec4 color;
|
||
JS_GETATOM(js,src,sub,src,rect)
|
||
JS_GETATOM(js,color,sub,color,color)
|
||
JS_GETATOM(js,tr,sub,transform,transform)
|
||
JS_FreeValue(js,sub);
|
||
|
||
size_t base = i*4;
|
||
if (tr) {
|
||
HMM_Mat3 trmat = transform2mat3(tr);
|
||
for (int j = 0; j < 4; j++)
|
||
posdata[base+j] = HMM_MulM3V3(trmat, base_quad[j]).xy;
|
||
} else {
|
||
rect dst;
|
||
JS_GETATOM(js,dst,sub,rect,rect);
|
||
posdata[base+0] = (HMM_Vec2){dst.x,dst.y};
|
||
posdata[base + 1] = (HMM_Vec2){ dst.x+dst.w, dst.y };
|
||
posdata[base + 2] = (HMM_Vec2){ dst.x, dst.y+dst.h };
|
||
posdata[base + 3] = (HMM_Vec2){ dst.x+dst.w, dst.y+dst.h };
|
||
}
|
||
|
||
uvdata[base+0] = (HMM_Vec2){src.x, src.y+src.h};
|
||
uvdata[base+1] = (HMM_Vec2){src.x+src.w, src.y+src.h};
|
||
uvdata[base+2] = (HMM_Vec2){src.x, src.y};
|
||
uvdata[base+3] = (HMM_Vec2){src.x+src.w, src.y};
|
||
|
||
colordata[base+0] = color;
|
||
colordata[base+1] = color;
|
||
colordata[base+2] = color;
|
||
colordata[base+3] = color;
|
||
}
|
||
|
||
// Check old mesh
|
||
JSValue old_mesh = JS_UNDEFINED;
|
||
if (argc > 1)
|
||
old_mesh = argv[1];
|
||
|
||
// Needed sizes
|
||
size_t pos_size = sizeof(*posdata)*verts;
|
||
size_t uv_size = sizeof(*uvdata)*verts;
|
||
size_t color_size = sizeof(*colordata)*verts;
|
||
|
||
BufferCheckResult pos_chk = get_or_extend_buffer(js, old_mesh, "pos", pos_size, JS_TYPED_ARRAY_FLOAT32, 2, 1, 0);
|
||
BufferCheckResult uv_chk = get_or_extend_buffer(js, old_mesh, "uv", uv_size, JS_TYPED_ARRAY_FLOAT32, 2, 1, 0);
|
||
BufferCheckResult color_chk = get_or_extend_buffer(js, old_mesh, "color", color_size, JS_TYPED_ARRAY_FLOAT32, 4, 1, 0);
|
||
|
||
int need_new_all = pos_chk.need_new || uv_chk.need_new || color_chk.need_new;
|
||
|
||
ret = JS_NewObject(js);
|
||
|
||
if (need_new_all) {
|
||
// Create all new buffers
|
||
JSValue new_pos = make_gpu_buffer(js, posdata, pos_size, JS_TYPED_ARRAY_FLOAT32, 2, 1,0);
|
||
JSValue new_uv = make_gpu_buffer(js, uvdata, uv_size, JS_TYPED_ARRAY_FLOAT32, 2, 1,0);
|
||
JSValue new_color = make_gpu_buffer(js, colordata, color_size, JS_TYPED_ARRAY_FLOAT32, 0, 1,0);
|
||
|
||
JS_SetPropertyStr(js, ret, "pos", new_pos);
|
||
JS_SetPropertyStr(js, ret, "uv", new_uv);
|
||
JS_SetPropertyStr(js, ret, "color", new_color);
|
||
|
||
// Indices
|
||
JSValue indices = make_quad_indices_buffer(js, quads);
|
||
JS_SetPropertyStr(js, ret, "indices", indices);
|
||
} else {
|
||
// Reuse the old buffers
|
||
// Just copy data into existing buffers via their pointers
|
||
memcpy(pos_chk.ptr, posdata, pos_size);
|
||
memcpy(uv_chk.ptr, uvdata, uv_size);
|
||
memcpy(color_chk.ptr, colordata, color_size);
|
||
|
||
// Duplicate old references since we're returning a new object
|
||
JS_SetPropertyStr(js, ret, "pos", JS_DupValue(js, pos_chk.val));
|
||
JS_SetPropertyStr(js, ret, "uv", JS_DupValue(js, uv_chk.val));
|
||
JS_SetPropertyStr(js, ret, "color", JS_DupValue(js, color_chk.val));
|
||
|
||
// Indices can remain the same if they were also large enough. If using a shared global index buffer:
|
||
JSValue indices = make_quad_indices_buffer(js, quads);
|
||
JS_SetPropertyStr(js, ret, "indices", indices);
|
||
}
|
||
|
||
JS_SetPropertyStr(js, ret, "vertices", number2js(js, verts));
|
||
JS_SetPropertyStr(js, ret, "count", number2js(js, count));
|
||
JS_SetPropertyStr(js,ret,"num_indices", number2js(js,count));
|
||
|
||
// Free temporary CPU arrays
|
||
free(posdata);
|
||
free(uvdata);
|
||
free(colordata);
|
||
|
||
// Free old buffer values if they were fetched
|
||
if (!JS_IsUndefined(pos_chk.val)) JS_FreeValue(js, pos_chk.val);
|
||
if (!JS_IsUndefined(uv_chk.val)) JS_FreeValue(js, uv_chk.val);
|
||
if (!JS_IsUndefined(color_chk.val)) JS_FreeValue(js, color_chk.val);
|
||
|
||
return ret;
|
||
)
|
||
|
||
static const JSCFunctionListEntry js_graphics_funcs[] = {
|
||
MIST_FUNC_DEF(gpu, make_sprite_mesh, 2),
|
||
MIST_FUNC_DEF(gpu, make_sprite_queue, 4),
|
||
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_surface, 1),
|
||
MIST_FUNC_DEF(os, make_cursor, 1),
|
||
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),
|
||
MIST_FUNC_DEF(graphics, surface_from_pixels, 1),
|
||
};
|
||
|
||
static const JSCFunctionListEntry js_video_funcs[] = {
|
||
MIST_FUNC_DEF(os, make_video, 1),
|
||
};
|
||
|
||
JSC_SCALL(os_use_embed,
|
||
prosperon_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;
|
||
}
|
||
}
|
||
|
||
if (JS_IsUndefined(ret))
|
||
ret = JS_ThrowReferenceError(js,"Library %s could not be found embedded", str);
|
||
)
|
||
|
||
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);
|
||
|
||
#ifdef TRACY_ENABLE
|
||
JSValue js_tracy_use(JSContext *js);
|
||
#endif
|
||
|
||
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_blob.h"
|
||
#include "qjs_http.h"
|
||
|
||
//JSValue js_imgui_use(JSContext *js);
|
||
#define MISTLINE(NAME) (ModuleEntry){#NAME, js_##NAME##_use}
|
||
|
||
void ffi_load(JSContext *js)
|
||
{
|
||
prosperon_rt *rt = JS_GetContextOpaque(js);
|
||
|
||
m_seedRand(&rt->mrand, time(NULL));
|
||
|
||
arrput(rt->module_registry, ((ModuleEntry){"io", js_io_use}));
|
||
arrput(rt->module_registry, ((ModuleEntry){"os", js_os_use}));
|
||
arrput(rt->module_registry, ((ModuleEntry){"input", js_input_use}));
|
||
arrput(rt->module_registry, MISTLINE(time));
|
||
arrput(rt->module_registry, ((ModuleEntry){"math", js_math_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(js));
|
||
arrput(rt->module_registry, MISTLINE(util));
|
||
arrput(rt->module_registry, MISTLINE(video));
|
||
|
||
arrput(rt->module_registry, MISTLINE(soloud));
|
||
arrput(rt->module_registry, MISTLINE(layout));
|
||
arrput(rt->module_registry, MISTLINE(miniz));
|
||
// arrput(rt->module_registry, MISTLINE(imgui));
|
||
arrput(rt->module_registry, ((ModuleEntry){"camera", js_camera_use}));
|
||
arrput(rt->module_registry, MISTLINE(debug));
|
||
arrput(rt->module_registry, MISTLINE(dmon));
|
||
arrput(rt->module_registry, MISTLINE(nota));
|
||
arrput(rt->module_registry, MISTLINE(enet));
|
||
arrput(rt->module_registry, MISTLINE(qr));
|
||
arrput(rt->module_registry, MISTLINE(wota));
|
||
arrput(rt->module_registry, MISTLINE(crypto));
|
||
arrput(rt->module_registry, MISTLINE(blob));
|
||
arrput(rt->module_registry, MISTLINE(http));
|
||
arrput(rt->module_registry, ((ModuleEntry){"sdl_audio", js_sdl_audio_use}));
|
||
arrput(rt->module_registry, MISTLINE(console));
|
||
arrput(rt->module_registry, MISTLINE(rtree));
|
||
arrput(rt->module_registry, MISTLINE(sprite));
|
||
arrput(rt->module_registry, MISTLINE(transform));
|
||
|
||
#ifdef TRACY_ENABLE
|
||
arrput(rt->module_registry, MISTLINE(tracy));
|
||
#endif
|
||
|
||
JSValue globalThis = JS_GetGlobalObject(js);
|
||
|
||
JSValue prosp = JS_NewObject(js);
|
||
JS_SetPropertyStr(js,globalThis,"prosperon", prosp);
|
||
JSValue c_types = JS_NewObject(js);
|
||
JS_SetPropertyStr(js,prosp, "c_types", c_types);
|
||
|
||
QJSCLASSPREP_FUNCS(SDL_Window)
|
||
QJSCLASSPREP_FUNCS(SDL_Surface)
|
||
QJSCLASSPREP_FUNCS(SDL_Texture)
|
||
|
||
// Initialize extracted modules which handle their own class registrations
|
||
js_input_use(js); // Handles input
|
||
js_camera_use(js); // Handles camera
|
||
js_sdl_audio_use(js); // Handles SDL_AudioStream and other SDL classes
|
||
// js_sdl_gpu_use(js); // Handles all GPU classes
|
||
js_io_use(js); // Handles PHYSFS_File
|
||
|
||
QJSCLASSPREP_FUNCS(renderer_ctx)
|
||
QJSCLASSPREP_FUNCS(font);
|
||
QJSCLASSPREP_FUNCS(datastream);
|
||
|
||
JS_SetPropertyStr(js, globalThis, "use_dyn", JS_NewCFunction(js,js_os_use_dyn,"use_dyn", 1));
|
||
JS_SetPropertyStr(js, globalThis, "use_embed", JS_NewCFunction(js,js_os_use_embed,"use_embed", 1));
|
||
|
||
JSValue jsobject = JS_GetPropertyStr(js,globalThis, "Object");
|
||
JS_SetPropertyStr(js, jsobject, "id", JS_NewCFunction(js, js_os_value_id, "id", 1));
|
||
JS_FreeValue(js,jsobject);
|
||
|
||
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 args = JS_NewArray(js);
|
||
for (int i = 0; i < rt->cmd.argc; i++)
|
||
JS_SetPropertyUint32(js,args, i, JS_NewString(js,rt->cmd.argv[i]));
|
||
|
||
JS_SetPropertyStr(js,prosp,"argv", args);
|
||
//JS_SetPropertyStr(js,prosp, "version", JS_NewString(js,PROSPERON_VERSION));
|
||
//JS_SetPropertyStr(js,prosp,"revision",JS_NewString(js,PROSPERON_COMMIT));
|
||
JS_SetPropertyStr(js,prosp,"engine_start", JS_NewCFunction(js,js_os_engine_start, "engine_start", 1));
|
||
|
||
JS_FreeValue(js,globalThis);
|
||
}
|