From 42cfccfc33c1f83bcebc702dd787dd0f50c62b66 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Mon, 9 Dec 2024 16:00:10 -0600 Subject: [PATCH] sdl --- scripts/base.js | 7 ++ scripts/components.js | 2 +- scripts/engine.js | 20 +++- scripts/input.js | 4 +- scripts/prosperon.js | 93 +++++++----------- scripts/render.js | 136 ++++++++++++-------------- scripts/std.js | 40 ++------ source/HandmadeMath.c | 18 ++++ source/HandmadeMath.h | 1 + source/jsffi.c | 217 ++++++++++++++++++++++++++++++++++-------- source/script.c | 5 + source/transform.c | 17 ++-- source/transform.h | 1 + 13 files changed, 337 insertions(+), 224 deletions(-) diff --git a/scripts/base.js b/scripts/base.js index e63e8286..9393d5f8 100644 --- a/scripts/base.js +++ b/scripts/base.js @@ -664,6 +664,13 @@ Object.defineProperty(String.prototype, "ext", { }, }); +Object.defineProperty(String.prototype, 'has_ext', { + value: function() { + var lastdot = this.lastIndexOf('.'); + return lastdot > 0 && lastdot < this.length-1; + } +}); + Object.defineProperty(String.prototype, "set_ext", { value: function (val) { return this.strip_ext() + val; diff --git a/scripts/components.js b/scripts/components.js index bdffafbd..dd2f0769 100644 --- a/scripts/components.js +++ b/scripts/components.js @@ -773,4 +773,4 @@ Object.mix(os.make_circle2d(), { }, }); -return { component, SpriteAnim }; +return { component }; diff --git a/scripts/engine.js b/scripts/engine.js index be29b4b5..b0f2a0fd 100644 --- a/scripts/engine.js +++ b/scripts/engine.js @@ -153,18 +153,21 @@ Resources.is_image = function (path) { var res_cache = {}; -function find_ext(file, ext, root = "") { +// ext is a list of extensions to search +function find_ext(file, ext) { if (!file) return; var file_ext = file.ext(); + var has_ext = file_ext.length > 0; - if (ext.some(x => x === file_ext)) return file; for (var e of ext) { var nf = `${file}.${e}`; if (io.exists(nf)) return nf; } - var all_files = io.glob(`**/${file}.*`); + var glob_pat = has_ext ? `**/${file}` : `**/${file}.*`; + + var all_files = io.glob(glob_pat); var find = undefined; for (var e of ext) { var finds = all_files.filter(x => x.ext() === e); @@ -338,13 +341,20 @@ io.slurpbytes = function(path) } var ignore = io.slurp('.prosperonignore').split('\n'); -var allpaths = io.globfs(ignore); +var allpaths; var tmpglob = io.glob; io.glob = function glob(pat) { -// var allpaths = io.globfs(ignore); + if (!allpaths) + allpaths = io.globfs(ignore); + return allpaths.filter(str => game.glob(pat,str)).sort(); } +io.invalidate = function() +{ + allpaths = undefined; +} + function splitPath(path) { return path.split('/').filter(part => part.length > 0); } diff --git a/scripts/input.js b/scripts/input.js index 865f1cd7..e4bb13aa 100644 --- a/scripts/input.js +++ b/scripts/input.js @@ -103,13 +103,13 @@ var mousepos = [0, 0]; prosperon.textinput = function (c) { player[0].raw_input("char", "pressed", c); }; -prosperon.mousemove = function (pos, dx) { +prosperon.mouse_move = function (pos, dx) { pos.y *= -1; dx.y *= -1; mousepos = pos; player[0].mouse_input("move", pos, dx); }; -prosperon.mousescroll = function mousescroll(dx) { +prosperon.mouse_scroll = function mousescroll(dx) { player[0].mouse_input(modstr() + "scroll", dx); }; prosperon.mousedown = function mousedown(b) { diff --git a/scripts/prosperon.js b/scripts/prosperon.js index 50a359d2..8f57f60c 100644 --- a/scripts/prosperon.js +++ b/scripts/prosperon.js @@ -250,54 +250,12 @@ function pack_into_sheet(images) return spritesheet; } -var SpriteAnim = {}; -globalThis.SpriteAnim = SpriteAnim; -SpriteAnim.aseprite = function (path) { - function aseframeset2anim(frameset, meta) { - var anim = {}; - anim.frames = []; - var dim = meta.size; - - var ase_make_frame = function (ase_frame) { - var f = ase_frame.frame; - var frame = {}; - frame.rect = { - x: f.x / dim.w, - y: f.y / dim.h, - width: f.w / dim.w, - height: f.h / dim.h, - }; - frame.time = ase_frame.duration / 1000; - anim.frames.push(frame); - }; - - frameset.forEach(ase_make_frame); - anim.dim = frameset[0].sourceSize; - anim.loop = true; - return anim; - } - - var data = json.decode(io.slurp(path)); - if (!data?.meta?.app.includes("aseprite")) return; - var anims = {}; - var frames = Array.isArray(data.frames) ? data.frames : Object.values(data.frames); - - if (!data.meta.frameTags || data.meta.frameTags.length === 0) { - anims[0] = aseframeset2anim(frames, data.meta); - return anims; - } - for (var tag of data.meta.frameTags) - anims[tag.name] = aseframeset2anim(frames.slice(tag.from, tag.to + 1), data.meta); - - return anims; -}; - game.is_image = function(obj) { if (obj.texture && obj.rect) return true; } -// Any request to it returns an image, which is a texture and rect. But they can +// Any request to it returns an image, which is a texture and rect. game.texture = function texture(path) { if (typeof path !== 'string') { return path; @@ -307,10 +265,22 @@ game.texture = function texture(path) { path = Resources.find_image(parts[0]); if (game.texture.cache[path]) return game.texture.cache[path]; - var newimg = {}; var data = io.slurpbytes(path); - newimg.surface = os.make_texture(data); - newimg.texture = render._main.load_texture(newimg.surface); + var newimg; + switch(path.ext()) { + case 'gif': + newimg = os.make_gif(data); + break; + case 'ase': + newimg = os.make_aseprite(data); + break; + default: + newimg = { + surface: os.make_texture(data) + }; + newimg.texture = render._main.load_texture(newimg.surface); + break; + } game.texture.cache[path] = newimg; return newimg; @@ -399,9 +369,6 @@ game.texture = function texture(path) { var image; var anim; - if (io.exists(path.set_ext(".json"))) - anim = SpriteAnim.aseprite(path.set_ext(".json")); - if (!anim) { image = { texture: tex, @@ -490,16 +457,6 @@ Range is given by a semantic versioning number, prefixed with nothing, a ~, or a ~ means that MAJOR and MINOR must match exactly, but any PATCH greater or equal is valid. ^ means that MAJOR must match exactly, but any MINOR and PATCH greater or equal is valid.`; -prosperon.iconified = function (icon) {}; -prosperon.focus = function (focus) {}; -prosperon.suspended = function (sus) {}; -prosperon.mouseenter = function () {}; -prosperon.mouseleave = function () {}; -prosperon.touchpress = function (touches) {}; -prosperon.touchrelease = function (touches) {}; -prosperon.touchmove = function (touches) {}; -prosperon.clipboardpaste = function (str) {}; - global.mixin("input"); global.mixin("std"); global.mixin("diff"); @@ -669,6 +626,24 @@ function world_start() { game.cam = world; } +function make_spritesheet(paths, width, height) +{ + var textures = paths.map(path => game.texture(path)); + var sizes = textures.map(tex => [tex.width, tex.height]); + var pos = os.rectpack(width, height, sizes); + if (!pos) return; + + var sheet = os.make_tex_data(width,height); + + var st = profile.now(); + for (var i = 0; i < pos.length; i++) + sheet.copy(textures[i], pos[i].x, pos[i].y); + + sheet.save("spritesheet.qoi"); + gamestate.spritess = sheet; + sheet.load_gpu(); +} + return { Register, sim, diff --git a/scripts/render.js b/scripts/render.js index e1fed1c7..045c896a 100644 --- a/scripts/render.js +++ b/scripts/render.js @@ -1038,47 +1038,23 @@ render.tile = function tile(image, rect = [0,0], color = Color.white) return; } -render.image = function image(image, rect = [0,0], rotation = 0, color) { +render.image = function image(image, rect = [0,0], rotation = 0, color = Color.white) { if (!image) throw Error ('Need an image to render.') if (typeof image === "string") image = game.texture(image); + rect.width ??= image.texture.width; + rect.height ??= image.texture.height; + render._main.texture(image.texture, rect, image.rect, color); - return; - - var tex = image.texture; - if (!tex) return; - - var image_size = calc_image_size(image); //image.size; - - var size = [rect.width ? rect.width : image_size.x, rect.height ? rect.height : image_size.y]; - - if (!lasttex) { - check_flush(flush_img); - lasttex = tex; - } - - if (lasttex !== tex) { - flush_img(); - lasttex = tex; - } - - render._main.texture(image.texture, rect, image.rect); - return; - - var e = img_e(); - var pos = [rect.x,rect.y].sub(size.scale([rect.anchor_x, rect.anchor_y])); - e.transform.trs(pos, undefined, size); - e.image = image; - e.shade = color; - - return; }; render.images = function images(image, rects) { if (!image) throw Error ('Need an image to render.'); if (typeof image === "string") image = game.texture(image); + for (var rect of rects) render.image(image,rect); + return; var tex = image.texture; if (!tex) return; @@ -1109,34 +1085,16 @@ render.images = function images(image, rects) return; } -var slice9_t = os.make_transform(); -// pos is the lower left corner, scale is the width and height // slice is given in pixels render.slice9 = function slice9(image, rect = [0,0], slice = 0, color = Color.white) { if (typeof image === 'string') image = game.texture(image); - var tex = image.texture; - var image_size = calc_image_size(image); - var size = [rect.width ? rect.width : image_size.x, rect.height ? rect.height : image_size.y]; + rect.width ??= image.texture.width; + rect.height ??= image.texture.height; + slice = clay.normalizeSpacing(slice); - check_flush(); - - slice9_t.trs([rect.x,rect.y].sub(size.scale([rect.anchor_x, rect.anchor_y])), undefined, size); - slice = clay.normalizeSpacing(slice); - var border = [slice.l/image_size.x, slice.b/image_size.y, slice.r/image_size.x, slice.t/image_size.y]; - render.use_shader(slice9shader); - set_model(slice9_t); - render.use_mat({ - shade: color, - diffuse: tex, - win_tex_scale: size.div(image_size), - rect: [image.rect.x, image.rect.y,image.rect.width,image.rect.height], - frag_rect: [image.rect.x, image.rect.y,image.rect.width,image.rect.height], - border: border, - }); - - render.draw(shape.quad); + render._main.slice9(image.texture, rect, slice); }; var textssbos = []; @@ -1202,6 +1160,7 @@ render.scissor = function(rect) // Camera viewport is a rectangle with the bottom left corner defined as x,y. Units are pixels on the window. function camviewport() { + var aspect = (((this.viewport[2] - this.viewport[0]) / (this.viewport[3] - this.viewport[1])) * prosperon.size.x) / prosperon.size.y; var raspect = this.size.x / this.size.y; @@ -1288,16 +1247,6 @@ function screen2cam(pos) { return viewpos; } -function camextents() { - var half = this.size; //.scale(0.5); - return { - l: this.pos.x - half.x, - r: this.pos.x + half.x, - t: this.pos.y + half.y, - b: this.pos.y - half.y, - }; -} - screen2cam.doc = "Convert a screen space position in pixels to a normalized viewport position in a camera."; prosperon.gizmos = function gizmos() { @@ -1314,20 +1263,33 @@ function screen2hud(pos) return campos; } +/* cameras + * Cameras have a position and rotation. They are not affected by scale. +*/ + prosperon.make_camera = function make_camera() { var cam = world.spawn(); cam.near = 1; cam.far = -1000; - cam.ortho = true; + cam.ortho = true; // True if this is a 2d camera cam.viewport = [0, 0, 1, 1]; // normalized screen coordinates of where to draw - cam.size = game.size.slice(); // The render size of this camera in pixels + cam.size = prosperon.size.slice() // The render size of this camera in pixels // In ortho mode, this determines how many pixels it will see cam.mode = "stretch"; cam.screen2world = camscreen2world; cam.screen2cam = screen2cam; cam.screen2hud = screen2hud; - cam.extents = camextents; cam.view = camviewport; + cam.zoom = 1; // the "scale factor" this camera demonstrates + // camera renders draw calls, and then hud + cam.render = function() { + render._main.camera(this.transform); + render._main.scale([this.zoom, this.zoom]); + prosperon.draw(); + render._main.scale([1,1]); + render._main.camera(unit_transform); + prosperon.hud(); + } return cam; }; @@ -1434,14 +1396,14 @@ prosperon.render = function prosperon_render() { try{ render._main.draw_color(clearcolor); render._main.clear(); - // set to world cam - prosperon.draw(); - if (render.draw_hud) prosperon.hud(); - + // render each camera + prosperon.camera.render(); // prosperon.app(); // if (debug.show) imgui_fn(); } catch(e) { - throw e; + console.log(e); + console.log(e.stack) +// throw e; } finally { render._main.present(); tracy.end_frame(); @@ -1452,6 +1414,8 @@ if (dmon) dmon.watch('.'); function dmon_cb(e) { + io.invalidated(); + if (e.file.startsWith('.')) return; if (e.file.endsWith('.js')) actor.hotreload(e.file); @@ -1470,13 +1434,33 @@ prosperon.process = function process() { frame_t = profile.now(); prosperon.appupdate(dt); + input.procdown(); - if (sim.mode === "play" || sim.mode === "step") { - prosperon.update(dt * game.timescale); - update_emitters(dt * game.timescale); - os.update_timers(dt * game.timescale); - if (sim.mode === "step") sim.pause(); - } + + game.engine_input(e => { + switch(e.type) { + case "quit": + os.exit(0); + break; + case "mouse": + prosperon.mousemove(e.mouse, e.mouse_d); + break; + case "text": + prosperon.textinput(e.text); + break; + case "key": + if (e.down) + prosperon.keydown(e.key); + else + prosperon.keyup(e.key); + break; + } + }); + + update_emitters(dt * game.timescale); + os.update_timers(dt * game.timescale); + prosperon.update(dt*game.timescale); + if (sim.mode === "step") sim.pause(); if (sim.mode === "play" || sim.mode === "step") { /* diff --git a/scripts/std.js b/scripts/std.js index 2b934099..30dd7061 100644 --- a/scripts/std.js +++ b/scripts/std.js @@ -83,6 +83,10 @@ Resources.gif.frames = function (path) { return render.gif_frames(path); }; +prosperon.SIGINT = function() { + os.exit(); +} + /* io path rules. Starts with, meaning: "@": user path @@ -221,15 +225,14 @@ Cmdline.register_order( } var project = json.decode(io.slurp(projectfile)); - game.title = project.title; - game.size = [1280, 720]; - + prosperon.title = project.title; prosperon.width = 1280; prosperon.height = 720; - prosperon.size = [1280,720]; + prosperon.size = [1280, 720]; prosperon.cleanup = function(){} prosperon.event = function(e){ + prosperon[e.type]?.(e); switch(e.type) { case "mouse_move": prosperon.mousemove(e.mouse, e.mouse_d); @@ -252,34 +255,9 @@ Cmdline.register_order( case "char": prosperon.textinput(e.char_code); break; - case "resized": - prosperon.size = e.window_size; - prosperon.resize?.(e.window_size); - break; - case "iconified": - prosperon.iconified(false); - break; - case "restored": - prosperon.iconified(true); - break; - case "focused": - prosperon.focus(true); - break; - case "unfocused": - prosperon.focus(false); - break; - case "suspended": - prosperon.suspended(true); - break; case "quit_requested": os.exit(0); break; - case "mouse_enter": - prosperon.mouseenter(); - break; - case "mouse_leave": - prosperon.mouseleave(); - break; case "files_dropped": console.log(json.encode(e)); break; @@ -300,8 +278,8 @@ Cmdline.register_order( if (io.exists("config.js")) global.mixin("config.js"); else console.warn("No config.js file found. Starting with default parameters."); - var window = game.engine_start(prosperon); - var renderer = window.make_renderer("gpu"); + prosperon.window = game.engine_start(prosperon); + var renderer = prosperon.window.make_renderer("gpu"); render._main = renderer; prosperon.init(); diff --git a/source/HandmadeMath.c b/source/HandmadeMath.c index fb4df869..89eb3213 100644 --- a/source/HandmadeMath.c +++ b/source/HandmadeMath.c @@ -1801,6 +1801,24 @@ HMM_Mat4 HMM_M4TRS(HMM_Vec3 t, HMM_Quat q, HMM_Vec3 s) return l; } +HMM_Mat3 HMM_M3TRS(HMM_Vec2 pos, float angle, HMM_Vec2 s) +{ + HMM_Mat3 m; + float *lm = (float*)&m; + lm[0] = s.x*cos(angle); + lm[1] = s.x * sin(angle); + lm[2] = 0; + + lm[3] = -s.y*sin(angle); + lm[4] = s.y*cos(angle); + lm[5] = 0; + + lm[6] = pos.x; + lm[7] = pos.y; + lm[8] = 1; + return m; +} + // This method taken from Mike Day at Insomniac Games. // https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2015/01/matrix-to-quat.pdf // diff --git a/source/HandmadeMath.h b/source/HandmadeMath.h index e1029828..80a90062 100644 --- a/source/HandmadeMath.h +++ b/source/HandmadeMath.h @@ -660,6 +660,7 @@ HMM_Quat HMM_NLerp(HMM_Quat Left, float Time, HMM_Quat Right); HMM_Quat HMM_SLerp(HMM_Quat Left, float Time, HMM_Quat Right); HMM_Mat4 HMM_QToM4(HMM_Quat Left); HMM_Mat4 HMM_M4TRS(HMM_Vec3 t, HMM_Quat q, HMM_Vec3 s); +HMM_Mat3 HMM_M3TRS(HMM_Vec2 t, float angle, HMM_Vec2 s); HMM_Quat HMM_M4ToQ_RH(HMM_Mat4 M); HMM_Quat HMM_M4ToQ_LH(HMM_Mat4 M); diff --git a/source/jsffi.c b/source/jsffi.c index 5f303a54..aa9c27d7 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -109,6 +109,7 @@ double js_getnum_uint32(JSContext *js, JSValue v, unsigned int i) return ret; } +static HMM_Mat3 cam_mat; static HMM_Vec2 campos = (HMM_Vec2){0,0}; static HMM_Vec2 logical = {0}; @@ -260,6 +261,11 @@ void SDL_Camera_free(JSRuntime *rt, SDL_Camera *cam) SDL_CloseCamera(cam); } +void SDL_Cursor_free(JSRuntime *rt, SDL_Cursor *c) +{ + SDL_DestroyCursor(c); +} + QJSCLASS(transform) QJSCLASS(font) //QJSCLASS(warp_gravity) @@ -267,9 +273,9 @@ QJSCLASS(font) QJSCLASS(datastream) QJSCLASS(timer) QJSCLASS(skin) + QJSCLASS(SDL_Window) QJSCLASS(SDL_Renderer) - QJSCLASS(SDL_Camera) QJSCLASS(SDL_Texture, TracyCAllocN(n, n->w*n->h*4, "vram"); @@ -282,6 +288,8 @@ QJSCLASS(SDL_Surface, JS_SetProperty(js,j,height_atom,number2js(js,n->h)); ) +QJSCLASS(SDL_Cursor) + int js_arrlen(JSContext *js,JSValue v) { if (JS_IsUndefined(v)) return 0; int len; @@ -511,13 +519,23 @@ rect js2rect(JSContext *js,JSValue v) { JS_FreeValue(js,yv); float anchor_x = js_getnum(js,v, anchor_x_atom); float anchor_y = js_getnum(js,v, anchor_y_atom); - + rect.y -= anchor_y*rect.h; rect.x -= anchor_x*rect.w; return rect; } +rect transform_rect(rect in, HMM_Mat3 *t) +{ + in.y *= -1; + in.y += logical.y; + in.y -= in.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_SetProperty(js, obj, x_atom, number2js(js,rect.x)); @@ -733,8 +751,6 @@ JSC_CCALL(os_make_text_buffer, for (int i = 0; i < arrlen(buffer); i++) { pos[i] = buffer[i].pos; - pos[i].y *= -1; - pos[i].y += logical.y; uv[i] = buffer[i].uv; color[i] = buffer[i].color; } @@ -1426,9 +1442,9 @@ JSC_CCALL(game_open_camera, #include "wildmatch.h" JSC_SSCALL(game_glob, if (wildmatch(str, str2, WM_PATHNAME | WM_PERIOD | WM_WILDSTAR) == WM_MATCH) - return JS_NewBool(js,1); + ret = JS_NewBool(js,1); else - return JS_NewBool(js, 0); + ret = JS_NewBool(js, 0); ) JSC_CCALL(game_camera_name, @@ -1481,10 +1497,84 @@ JSValue js_SDL_Window_keyboard_shown(JSContext *js, JSValue 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); +} + +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()); +} + +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])); + +} + 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, 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), }; JSC_CCALL(SDL_Renderer_clear, @@ -1514,10 +1604,7 @@ JSC_CCALL(SDL_Renderer_rect, rect rects[len]; for (int i = 0; i < len; i++) { JSValue val = JS_GetPropertyUint32(js,argv[0],i); - rects[i] = js2rect(js,val); - rects[i].y *= -1; - rects[i].y += logical.y; - rects[i].y -= rects[i].h; + rects[i] = transform_rect(js2rect(js,val), &cam_mat); JS_FreeValue(js,val); } SDL_RenderRects(r,rects,len); @@ -1525,9 +1612,7 @@ JSC_CCALL(SDL_Renderer_rect, } rect rect = js2rect(js,argv[0]); - rect.y *= -1; - rect.y += logical.y; - rect.y -= rect.h; + rect = transform_rect(rect, &cam_mat); SDL_RenderRect(r, &rect); ) @@ -1558,24 +1643,19 @@ JSC_CCALL(SDL_Renderer_fillrect, rects[i] = js2rect(js,val); JS_FreeValue(js,val); } - SDL_RenderFillRects(r,rects,len); - return JS_UNDEFINED; + if (!SDL_RenderFillRects(r,rects,len)) + return JS_ThrowReferenceError("Could not render rectangle: %s", SDL_GetError()); } - rect rect = js2rect(js,argv[0]); - rect.y *= -1; - rect.y += logical.y; - rect.y -= rect.h; + rect rect = transform_rect(js2rect(js,argv[0]),&cam_mat); - SDL_RenderFillRect(r, &rect); + if (!SDL_RenderFillRect(r, &rect)) + return JS_ThrowReferenceError("Could not render rectangle: %s", SDL_GetError()); ) JSC_CCALL(renderer_texture, SDL_Renderer *renderer = js2SDL_Renderer(js,self); SDL_Texture *tex = js2SDL_Texture(js,argv[0]); - rect dst = js2rect(js,argv[1]); - dst.y *= -1; - dst.y += logical.y; - dst.y -= dst.h; + rect dst = transform_rect(js2rect(js,argv[1]), &cam_mat); if (!JS_IsUndefined(argv[3])) { colorf color = js2color(js,argv[3]); @@ -1588,7 +1668,7 @@ JSC_CCALL(renderer_texture, rect src = js2rect(js,argv[2]); - SDL_RenderTexture(renderer,tex,&src,&dst); + SDL_RenderTextureRotated(renderer, tex, &src, &dst, 0, NULL, SDL_FLIP_NONE); } ) @@ -1610,12 +1690,18 @@ JSC_CCALL(renderer_tile, } ) -JSC_CCALL(renderer_9slice, +JSC_CCALL(renderer_slice9, SDL_Renderer *renderer = js2SDL_Renderer(js,self); SDL_Texture *tex = js2SDL_Texture(js,argv[0]); - rect *dst, *src = NULL; lrtb bounds = js2lrtb(js,argv[2]); - SDL_RenderTexture9Grid(renderer, tex, src, bounds.l, bounds.r, bounds.t, bounds.b, 0.0, dst); + rect src, dst; + src = transform_rect(js2rect(js,argv[3]),&cam_mat); + dst = transform_rect(js2rect(js,argv[1]), &cam_mat); + + SDL_RenderTexture9Grid(renderer, tex, + JS_IsUndefined(argv[3]) ? NULL : &src, + bounds.l, bounds.r, bounds.t, bounds.b, 0.0, + JS_IsUndefined(argv[1]) ? NULL : &dst); ) JSC_CCALL(renderer_get_image, @@ -1734,10 +1820,8 @@ JSC_CCALL(renderer_geometry, HMM_Vec2 *trans_pos = malloc(vertices*sizeof(HMM_Vec2)); memcpy(trans_pos,posdata, sizeof(HMM_Vec2)*vertices); - for (int i = 0; i < vertices; i++) { - trans_pos[i].x -= campos.x; - trans_pos[i].y -= campos.y; - } + for (int i = 0; i < vertices; i++) + trans_pos[i] = HMM_MulM3V3(cam_mat, (HMM_Vec3){trans_pos[i].x, trans_pos[i].y, 1}).xy; 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()); @@ -1800,6 +1884,21 @@ JSC_CCALL(renderer_coords, return vec22js(js,coord); ) +JSC_CCALL(renderer_camera, + HMM_Mat3 t; + t.Columns[0] = (HMM_Vec3){1,0,0}; + t.Columns[1] = (HMM_Vec3){0,-1,0}; + t.Columns[2] = (HMM_Vec3){0,logical.y,1}; + + transform *tra = js2transform(js,argv[0]); + tra->pos.x -= logical.x/2; + tra->pos.y -= logical.y/2; + HMM_Mat3 T = transform2mat3(tra); + cam_mat = HMM_MulM3(t,T); + tra->pos.x += logical.x/2; + tra->pos.y += logical.y/2; +) + static const JSCFunctionListEntry js_SDL_Renderer_funcs[] = { MIST_FUNC_DEF(SDL_Renderer, draw_color, 1), MIST_FUNC_DEF(SDL_Renderer, present, 0), @@ -1810,7 +1909,7 @@ static const JSCFunctionListEntry js_SDL_Renderer_funcs[] = { MIST_FUNC_DEF(renderer, point, 2), MIST_FUNC_DEF(renderer, load_texture, 1), MIST_FUNC_DEF(renderer, texture, 4), - MIST_FUNC_DEF(renderer, 9slice, 4), + MIST_FUNC_DEF(renderer, slice9, 4), MIST_FUNC_DEF(renderer, tile, 4), MIST_FUNC_DEF(renderer, get_image, 1), MIST_FUNC_DEF(renderer, fasttext, 2), @@ -1822,6 +1921,7 @@ static const JSCFunctionListEntry js_SDL_Renderer_funcs[] = { MIST_FUNC_DEF(renderer,clip,1), MIST_FUNC_DEF(renderer,vsync,1), MIST_FUNC_DEF(renderer, coords, 1), + MIST_FUNC_DEF(renderer, camera, 1), }; JSC_CCALL(surface_blit, @@ -1931,6 +2031,8 @@ static const JSCFunctionListEntry js_SDL_Camera_funcs[] = MIST_FUNC_DEF(camera, release_frame, 1), }; +static const JSCFunctionListEntry js_SDL_Cursor_funcs[] = {}; + JSC_CCALL(texture_mode, SDL_Texture *tex = js2SDL_Texture(js,self); SDL_SetTextureScaleMode(tex,js2number(js,argv[0])); @@ -1948,9 +2050,17 @@ JSC_CCALL(input_mouse_show, SDL_HideCursor(); ) +JSC_CCALL(input_cursor_set, + SDL_Cursor *c = js2SDL_Cursor(js,argv[0]); + printf("setting cursor to %p\n", c); + if (!SDL_SetCursor(c)) + return JS_ThrowReferenceError(js, "could not set cursor: %s", SDL_GetError()); +) + static const JSCFunctionListEntry js_input_funcs[] = { MIST_FUNC_DEF(input, mouse_show, 1), MIST_FUNC_DEF(input, mouse_lock, 1), + MIST_FUNC_DEF(input, cursor_set, 1), }; JSC_CCALL(prosperon_guid, @@ -2161,7 +2271,7 @@ int globfs_cb(struct globdata *data, char *dir, char *file) char **glob = data->globs; while (*glob != NULL) { - if (wildmatch(*glob, path, WM_PATHNAME | WM_PERIOD | WM_WILDSTAR) == WM_MATCH) + if (wildmatch(*glob, path, WM_WILDSTAR) == WM_MATCH) goto END; *glob++; } @@ -2669,20 +2779,40 @@ JSC_CCALL(os_make_gif, 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)); - SDL_Surface *framesurf = SDL_CreateSurfaceFrom(width,height,SDL_PIXELFORMAT_RGBA32,pixels+(width*height*4*i), width*4); + 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); - - ret = gif; + free(pixels); ) JSValue aseframe2js(JSContext *js, ase_frame_t aframe) @@ -2756,6 +2886,15 @@ JSC_CCALL(os_make_surface, ret = SDL_Surface2js(js, surface); ) +JSC_CCALL(os_make_cursor, + 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("couldn't make cursor: %s", SDL_GetError()); + printf("made cursor %p\n", c); + return SDL_Cursor2js(js,c); +) + JSC_CCALL(os_make_font, size_t len; void *data = JS_GetArrayBuffer(js,&len,argv[0]); @@ -3117,6 +3256,7 @@ static const JSCFunctionListEntry js_os_funcs[] = { MIST_FUNC_DEF(os, make_gif, 1), MIST_FUNC_DEF(os, make_aseprite, 1), 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_transform, 0), MIST_FUNC_DEF(os, make_line_prim, 4), @@ -3216,6 +3356,7 @@ void ffi_load(JSContext *js) { QJSCLASSPREP_FUNCS(SDL_Texture) QJSCLASSPREP_FUNCS(SDL_Renderer) QJSCLASSPREP_FUNCS(SDL_Camera) + QJSCLASSPREP_FUNCS(SDL_Cursor) QJSGLOBALCLASS(os); @@ -3226,8 +3367,6 @@ void ffi_load(JSContext *js) { QJSCLASSPREP_FUNCS(datastream); QJSCLASSPREP_FUNCS(timer); - - QJSGLOBALCLASS(input); QJSGLOBALCLASS(io); QJSGLOBALCLASS(prosperon); diff --git a/source/script.c b/source/script.c index 5ab19689..ccd8298f 100644 --- a/source/script.c +++ b/source/script.c @@ -52,6 +52,11 @@ char* read_file(const char* filename) { return content; } +int js_interrupt(JSRuntime *rt, void *data) +{ +// printf("INTERRUPT\n"); +} + void script_startup() { rt = JS_NewRuntime(); js = JS_NewContextRaw(rt); diff --git a/source/transform.c b/source/transform.c index d336545c..48468aee 100644 --- a/source/transform.c +++ b/source/transform.c @@ -53,19 +53,14 @@ HMM_Vec3 mat3_t_dir(HMM_Mat4 m, HMM_Vec3 dir) return mat3_t_pos(m, dir); } -HMM_Mat4 transform2mat(transform *t) { +HMM_Mat4 transform2mat(transform *t) +{ return HMM_M4TRS(t->pos, t->rotation, t->scale); - HMM_Mat4 scale = HMM_Scale(t->scale); - HMM_Mat4 rot = HMM_QToM4(t->rotation); - HMM_Mat4 pos = HMM_Translate(t->pos); - return HMM_MulM4(pos, HMM_MulM4(rot, scale)); +} - if (t->dirty) { - t->cache = HMM_M4TRS(t->pos, t->rotation, t->scale); - t->dirty = 0; - } - - return t->cache; +HMM_Mat3 transform2mat3(transform *t) +{ + return HMM_M3TRS(t->pos.xy, t->rotation.x, t->scale.xy); } HMM_Quat angle2rotation(float angle) diff --git a/source/transform.h b/source/transform.h index f826f387..5fab352e 100644 --- a/source/transform.h +++ b/source/transform.h @@ -39,6 +39,7 @@ HMM_Vec3 mat3_t_pos(HMM_Mat4 m, HMM_Vec3 pos); HMM_Vec3 mat3_t_dir(HMM_Mat4 m, HMM_Vec3 dir); HMM_Mat4 transform2mat(transform *t); +HMM_Mat3 transform2mat3(transform *t); transform mat2transform(HMM_Mat4 m); HMM_Quat angle2rotation(float angle);