From 3d7ea7d358085cd3673eaa3184f4679790e0a763 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 21 Jan 2025 16:46:18 -0600 Subject: [PATCH] refactor --- meson.build | 13 +- scripts/actor.js | 198 ----------- scripts/base.js | 229 ++++++------- scripts/components.js | 780 ------------------------------------------ scripts/debug.js | 24 +- scripts/diff.js | 99 ------ scripts/engine.js | 469 +++++++++++++++++++++---- scripts/entity.js | 755 ---------------------------------------- scripts/geometry.js | 7 +- scripts/gizmos.js | 26 ++ scripts/graphics.js | 197 +++++++++++ scripts/input.js | 12 +- scripts/layout.js | 50 ++- scripts/nogame.js | 4 +- scripts/particle.js | 14 +- scripts/physics.js | 10 +- scripts/profile.js | 10 +- scripts/prosperon.js | 556 ------------------------------ scripts/render.js | 141 +++++--- scripts/search.js | 37 ++ scripts/sim.js | 29 ++ scripts/sound.js | 8 +- scripts/sprite.js | 61 ++-- scripts/std.js | 85 +---- scripts/transform.js | 32 ++ scripts/tween.js | 2 + source/jsffi.c | 312 +++++++++-------- source/qjs_macros.h | 1 - source/transform.c | 1 - source/transform.h | 5 +- source/warp.c | 62 ---- source/warp.h | 47 --- 32 files changed, 1209 insertions(+), 3067 deletions(-) delete mode 100644 scripts/actor.js delete mode 100644 scripts/components.js delete mode 100644 scripts/diff.js delete mode 100644 scripts/entity.js create mode 100644 scripts/gizmos.js create mode 100644 scripts/graphics.js delete mode 100644 scripts/prosperon.js create mode 100644 scripts/search.js create mode 100644 scripts/sim.js create mode 100644 scripts/transform.js delete mode 100644 source/warp.c delete mode 100644 source/warp.h diff --git a/meson.build b/meson.build index be15f90c..47a94eb3 100644 --- a/meson.build +++ b/meson.build @@ -71,7 +71,7 @@ endif deps += dependency('qjs-layout',static:true) deps += dependency('qjs-nota',static:true) -deps += dependency('qjs-miniz',static:true) +deps += dependency('qjs-miniz') deps += dependency('qjs-soloud',static:true) deps += dependency('physfs') @@ -81,15 +81,15 @@ deps += dependency('physfs') deps += dependency('threads') if get_option('chipmunk') - deps += dependency('qjs-chipmunk',static:true) + deps += dependency('qjs-chipmunk', static:false) endif if get_option('enet') - deps += dependency('qjs-enet',static:true) + deps += dependency('qjs-enet', static:false) endif sources = [] -src += ['anim.c', 'config.c', 'datastream.c','font.c','gameobject.c','HandmadeMath.c','jsffi.c','model.c','render.c','script.c','simplex.c','spline.c', 'timer.c', 'transform.c','warp.c','yugine.c', 'wildmatch.c', 'sprite.c', 'quadtree.c', 'aabb.c', 'rtree.c'] +src += ['anim.c', 'config.c', 'datastream.c','font.c','gameobject.c','HandmadeMath.c','jsffi.c','model.c','render.c','script.c','simplex.c','spline.c', 'timer.c', 'transform.c','yugine.c', 'wildmatch.c', 'sprite.c', 'quadtree.c', 'aabb.c', 'rtree.c'] imsrc = ['GraphEditor.cpp','ImCurveEdit.cpp','ImGradient.cpp','imgui_draw.cpp','imgui_tables.cpp','imgui_widgets.cpp','imgui.cpp','ImGuizmo.cpp','imnodes.cpp','implot_items.cpp','implot.cpp', 'imgui_impl_sdlrenderer3.cpp', 'imgui_impl_sdl3.cpp', 'imgui_impl_sdlgpu3.cpp'] @@ -108,7 +108,7 @@ if get_option('editor') foreach imgui : imsrc sources += tp / 'imgui' / imgui endforeach - deps += dependency('qjs-dmon',static:true) + deps += dependency('qjs-dmon') endif includers = [] @@ -130,7 +130,8 @@ core = custom_target('core.zip', prosperon = executable('prosperon', sources, dependencies: deps, include_directories: includers, - link_args: link + link_args: link, + build_rpath: '$ORIGIN' ) prosperon_dep = declare_dependency( diff --git a/scripts/actor.js b/scripts/actor.js deleted file mode 100644 index 24d7d269..00000000 --- a/scripts/actor.js +++ /dev/null @@ -1,198 +0,0 @@ -var actor = {}; - -var actor_urs = {}; - -var actor_spawns = {}; - -var script_fns = {}; - -function use(file) -{ - var par = script_fn(file) - if (!par.module_ret) - throw new Error(`File ${file} has no valid module definition`) - - return par.module_ret; -} - -var script_fn = function script_fn(file) { - file = Resources.find_script(file) - if (!file) return - var content = Resources.replstrs(file); - var parsed = parse_file(content) - var module_name = file.name() - - if (parsed.module) { - var mod_script = `(function setup_${module_name}_module(){ var self = this; ${parsed.module}})`; - var module_fn = os.eval(file, mod_script) - var module_ret = module_fn.call(); - if (module_ret === undefined || module_ret === null) - throw new Error(`Module ${module_name} must return a value`); - - parsed.module_fn = module_fn; - parsed.module_ret = module_ret; - } else - parsed.module_ret = {} - - if (parsed.program) { - var prog_script = `(function use_${module_name}() { var self = this; var $ = this.__proto__; ${parsed.program}})`; - parsed.prog_fn = os.eval(file, prog_script); - } - - return parsed; -}.hashify() - -function parse_file(content) { - var parts = content.split(/\n\s*---\s*\n/) - if (parts.length === 1) - return { - program: parts[0] - } - - var module = parts[0].trim(); - if (!module.match(/return\s+[^;]+;?\s*$/)) - throw new Error("Module section must end with a return statement"); - - return { - module, - program: parts[1] - } -} - -globalThis.actor_use = function actor_use(script) -{ - var file = Resources.find_script(script); - if (!file) - return; - - return use(file) -} - -globalThis.class_use = function class_use(script, config, base = actor, callback, overling) { - var prog = script_fn(script); - - if (!prog) { - var ret = Object.create(base); - if (callback) callback(ret); - return ret; - } - - var padawan; - prog.module_ret.__proto__ = base; - padawan = Object.create(prog.module_ret); - - padawan.overling = overling; - if (callback) callback(padawan) - prog.prog_fn.call(padawan) - if (typeof config === 'object') Object.assign(padawan,config); - - if (!padawan.__reggies) - padawan.__proto__.__reggies = pull_registers(padawan) - - return padawan; -}; - -globalThis.rmactor = function (e) { - if (!actor_spawns[e._file]) return; - actor_spawns[e._file].remove(e); -}; - -actor.__stats = function () { - var total = 0; - var stats = {}; - game.all_objects(obj => { - if (!actor_spawns[obj._file]) return; - stats[obj._file] ??= 0; - stats[obj._file]++; - total++; - }); -/* for (var i in actor_spawns) { - stats[i] = actor_spawns[i].length; - total += stats[i]; - }*/ -// stats.total = total; - return stats; -}; - -actor.hotreload = function hotreload(file) { - var script = Resources.replstrs(file); - script = `(function() { var self = this; var $ = this.__proto__;${script};})`; - var fn = os.eval(file, script); - - for (var obj of actor_spawns[file]) { - var a = obj; - a.timers.forEachRight(t=>t()); - a.timers = []; - var save = json.decode(json.encode(a)); - fn.call(a); - Object.merge(a, save); - check_registers(a); - } -}; - -actor.spawn = function (script, config) { - if (typeof script !== "string") return undefined; - - var padawan = class_use(script, config, actor, undefined, this); - - padawan.padawans = []; - padawan.timers = []; - padawan.master = this; - Object.hide(padawan, "master", "padawans"); - padawan.toString = function () { - return script; - }; - check_registers(padawan); - this.padawans.push(padawan); - if (padawan.awake) padawan.awake(); - return padawan; -}; - -actor.spawn.doc = `Create a new actor, using this actor as the master, initializing it with 'script' and with data (as a JSON or Nota file) from 'config'.`; - -actor.rm_pawn = function (pawn) { - this.padawans.remove(pawn); -}; - -actor.timers = []; -actor.kill = function () { - if (this.__dead__) return; - this.timers.forEach(t => t()); - input.do_uncontrol(this); - Event.rm_obj(this); - if (this.master) this.master.rm_pawn(this); - this.padawans.forEach(p => p.kill()); - this.padawans = []; - this.__dead__ = true; - actor_spawns[this._file].remove(this); - if (typeof this.die === "function") this.die(); - if (typeof this.stop === "function") this.stop(); - if (typeof this.garbage === "function") this.garbage(); - if (typeof this.then === "function") this.then(); -}; - -actor.kill.doc = `Remove this actor and all its padawans from existence.`; - -actor.delay = function (fn, seconds) { prosperon.add_timer(this, fn, seconds); } -actor.delay.doc = `Call 'fn' after 'seconds' with 'this' set to the actor.`; - -actor.interval = function (fn, seconds) { - var self = this; - var stop; - var usefn = function () { - fn(); - stop = self.delay(usefn, seconds); - }; - stop = self.delay(usefn, seconds); - - return stop; -}; - -actor.padawans = []; - -global.app = Object.create(actor); -app.die = function () { - os.exit(0); -}; - -return { actor, app }; diff --git a/scripts/base.js b/scripts/base.js index 9393d5f8..4ed55fe7 100644 --- a/scripts/base.js +++ b/scripts/base.js @@ -50,6 +50,8 @@ convert.buf2hex = function (buffer) { return [...new Uint8Array(buffer)].map(x => x.toString(16).padStart(2, "0")).join(" "); }; +var time = os.use('time') + /* Time values are always expressed in terms of real earth-seconds */ Object.assign(time, { hour2minute() { @@ -490,7 +492,7 @@ Object.defineProperty(Object.prototype, "obscure", { }); Object.defineProperty(Object.prototype, "mixin", { - value: function (obj) { + value: function mixin(obj) { if (typeof obj === "string") obj = use(obj); if (obj) Object.mixin(this, obj); @@ -1058,6 +1060,18 @@ Object.defineProperty(Array.prototype, "remove", { }, }); +Object.defineProperty(Array.prototype, "delete", { + value: function remove(b) { + var idx = this.indexOf(b); + + if (idx === -1) return false; + + this.splice(idx, 1); + + return true; + }, +}); + Object.defineProperty(Array.prototype, "set", { value: function set(b) { if (this.length !== b.length) return; @@ -1195,6 +1209,8 @@ Object.defineProperty(Array.prototype, "forEachRight", { } }); +var vector = os.use('vector') + Math.lerp = vector.lerp; Math.gcd = vector.gcd; Math.lcm = vector.lcm; @@ -1314,122 +1330,6 @@ Math.randomint = function (max) { }; Math.variate = vector.variate; -/* BOUNDINGBOXES */ -var bbox = {}; - -bbox.overlap = function (box1, box2) { - return box1.l > box2.l && box1.r < box2.r && box1.t < box2.t && box1.b > box2.b; - return box1.l > box2.r || box1.r < box2.l || box1.t > box2.b || box1.b < box2.t; -}; - -bbox.fromcwh = function (c, wh) { - return { - t: c.y + wh.y / 2, - b: c.y - wh.y / 2, - l: c.x - wh.x / 2, - r: c.x + wh.x / 2, - }; -}; - -bbox.frompoints = function (points) { - var b = { t: 0, b: 0, l: 0, r: 0 }; - - points.forEach(function (x) { - if (x.y > b.t) b.t = x.y; - if (x.y < b.b) b.b = x.y; - if (x.x > b.r) b.r = x.x; - if (x.x < b.l) b.l = x.x; - }); - - return b; -}; - -bbox.topoints = function (bb) { - return [ - [bb.l, bb.t], - [bb.r, bb.t], - [bb.r, bb.b], - [bb.l, bb.b], - ]; -}; - -bbox.tocwh = function (bb) { - if (!bb) return undefined; - var cwh = {}; - - var w = bb.r - bb.l; - var h = bb.t - bb.b; - cwh.wh = [w, h]; - cwh.c = [bb.l + w / 2, bb.b + h / 2]; - - return cwh; -}; - -bbox.towh = function (bb) { - return [bb.r - bb.l, bb.t - bb.b]; -}; - -bbox.pointin = function (bb, p) { - if (bb.t < p.y || bb.b > p.y || bb.l > p.x || bb.r < p.x) return false; - - return true; -}; - -bbox.zero = function (bb) { - var newbb = Object.assign({}, bb); - newbb.r -= newbb.l; - newbb.t -= newbb.b; - newbb.b = 0; - newbb.l = 0; - return newbb; -}; - -bbox.move = function (bb, pos) { - var newbb = Object.assign({}, bb); - newbb.t += pos.y; - newbb.b += pos.y; - newbb.l += pos.x; - newbb.r += pos.x; - return newbb; -}; - -bbox.moveto = function (bb, pos) { - bb = bbox.zero(bb); - return bbox.move(bb, pos); -}; - -bbox.expand = function (oldbb, x) { - if (!oldbb || !x) return; - var bb = {}; - Object.assign(bb, oldbb); - - if (bb.t < x.t) bb.t = x.t; - if (bb.r < x.r) bb.r = x.r; - if (bb.b > x.b) bb.b = x.b; - if (bb.l > x.l) bb.l = x.l; - - return bb; -}; - -bbox.blwh = function (bl, wh) { - return { - b: bl.y, - l: bl.x, - r: bl.x + wh.x, - t: bl.y + wh.y, - }; -}; - -bbox.blwh.doc = "Bounding box from (bottom left, width height)"; - -bbox.fromobjs = function (objs) { - var bb = objs[0].boundingbox; - objs.forEach(function (obj) { - bb = bbox.expand(bb, obj.boundingbox); - }); - return bb; -}; - /* VECTORS */ var Vector = {}; Vector.length = vector.length; @@ -1563,11 +1463,104 @@ lodash.get = function (obj, path, defValue) { return result === undefined ? defValue : result; }; +function deep_copy(from) { + return json.decode(json.encode(from)); +} + +function valdiff(from, to) { + if (typeof from !== typeof to) return from; + if (typeof from === "function") return undefined; + if (typeof from === "undefined") return undefined; + + if (typeof from === "number") { + return to; + + return undefined; + } + + if (typeof from === "object") return ediff(from, to); + + if (from !== to) return to; + + return undefined; +} + +function ediff(from, to) { + var ret = {}; + + if (!to) + // return ediff(from, {}); + return deep_copy(from); + + Object.entries(from).forEach(function ([key, v]) { + if (typeof v === "function") return; + if (typeof v === "undefined") return; + + if (Array.isArray(v)) { + if (!Array.isArray(to[key]) || v.length !== to[key].length) { + var r = ediff(v, []); + if (r) ret[key] = Object.values(r); + return; + } + + var diff = ediff(from[key], to[key]); + if (diff && !Object.empty(diff)) ret[key] = Object.values(ediff(v, [])); + + return; + } + + if (typeof v === "object" && v !== null) { + var diff = ediff(v, to[key]); + if (diff && !Object.empty(diff)) ret[key] = diff; + return; + } + + if (typeof v === "number" || v === null) { + if (!isFinite(v)) v = null; // Squash infinity to null + if (v !== to[key]) ret[key] = v; + return; + } + + if (!to || v !== to[key]) ret[key] = v; + }); + if (Object.empty(ret)) return undefined; + + return ret; +} + +ediff.doc = "Given a from and to object, returns an object that, if applied to from, will make it the same as to. Does not include deletion; it is only additive. If one element in an array is different, the entire array is copied. Squashes infinite numbers to null for use in JSON."; + +function samediff(from, to) { + var same = []; + if (!to) return same; + if (typeof to !== "object") { + console.warn("'To' must be an object. Got " + to); + return same; + } + Object.keys(from).forEach(function (k) { + if (Object.isObject(from[k])) { + samediff(from[k], to[k]); + return; + } + + // if (Array.isArray(from[k])) { + // var d = valdiff(from[k], to[k]); + // if (!d) + // } + + var d = valdiff(from[k], to[k]); + if (!d) delete from[k]; + }); + + return same; +} + +samediff.doc = "Given a from and to object, returns an array of keys that are the same on from as on to."; + return { convert, time, Vector, - bbox, yaml, lodash, }; diff --git a/scripts/components.js b/scripts/components.js deleted file mode 100644 index eab57e78..00000000 --- a/scripts/components.js +++ /dev/null @@ -1,780 +0,0 @@ -var component = {}; - -function make_point_obj(o, p) { - return { - pos: p, - move(d) { - d = o.gameobject.dir_world2this(d); - p.x += d.x; - p.y += d.y; - }, - sync: o.sync.bind(o), - }; -}; - -/* an anim is simply an array of images */ -/* an anim set is like this -frog = { - walk: [], - hop: [], - ...etc -} -*/ - -if (!globalThis.sprite_qt) globalThis.sprite_qt = os.make_rtree(); -//globalThis.sprite_qt = os.make_quadtree({x:-2000,y:2000,w:4000,h:4000}); - -var sprite = { - image: undefined, - get diffuse() { return this.image; }, - set diffuse(x) {}, - set color(x) { - this._sprite.color = x; - }, - get color() { return this._sprite.color; }, - anim_speed: 1, - play(str, loop = true, reverse = false, fn) { - if (!this.animset) { - fn?.(); - return; - } - - if (typeof str === 'string') { - if (!this.animset[str]) { - fn?.(); - return; - } - this.anim = this.animset[str]; - } - - var playing = this.anim; - - var self = this; - var stop; - - this.del_anim?.(); - self.del_anim = function () { - self.del_anim = undefined; - self = undefined; - advance = undefined; - stop?.(); - }; - - var f = 0; - if (reverse) f = playing.frames.length - 1; - - function advance(time) { - if (!self) return; - if (!self.gameobject) return; - - var done = false; - if (reverse) { - f = (((f - 1) % playing.frames.length) + playing.frames.length) % playing.frames.length; - if (f === playing.frames.length - 1) done = true; - } else { - f = (f + 1) % playing.frames.length; - if (f === 0) done = true; - } - - self.image = playing.frames[f]; - - if (done) { - // notify requestor - fn?.(); - if (!loop) { - self?.stop(); - return; - } - } - - return playing.frames[f].time/self.anim_speed; - } - stop = self.gameobject.delay(advance, playing.frames[f].time/self.anim_speed); - advance(); - }, - stop() { - this.del_anim?.(); - }, - set path(p) { - var image = game.texture(p); - if (!image) { - console.warn(`Could not find image ${p}.`); - return; - } - - this._p = p; - - this.stop(); - - if (image.texture) - this.image = image; - else if (image.frames) { - // It's an animation - this.anim = image; - this.image = image.frames[0]; - this.animset = [this.anim] - this.play(); - } else { - // Maybe an animset; try to grab the first one - for (var anim in image) { - if (image[anim].frames) { - this.anim = image[anim]; - this.image = image[anim].frames[0]; - this.animset = image; - this.play(); - break; - } - } - } - - this.transform.scale = [this.image.texture.width, this.image.texture.height]; - this._sprite.set_image(this.image); - }, - get path() { - return this._p; - }, - kill: function kill() { - this.del_anim?.(); - this.anim = undefined; - this.gameobject = undefined; - sprite_qt.remove(this._sprite) - }, - anchor: [0, 0], - set layer(v) { this._sprite.layer = v; }, - get layer() { return this._sprite.layer; }, - pick() { - return this; - }, - boundingbox() { - var dim = this.dimensions(); - dim = dim.scale(this.gameobject.scale); - var realpos = dim.scale(0.5).add(this.pos); - return bbox.fromcwh(realpos, dim); - }, -}; - -sprite.doc = { - path: "Path to the texture.", - color: "Color to mix with the sprite.", - pos: "The offset position of the sprite, relative to its entity.", -}; - -sprite.setanchor = function (anch) { - var off = [0, 0]; - switch (anch) { - case "ll": - break; - case "lm": - off = [-0.5, 0]; - break; - case "lr": - off = [-1, 0]; - break; - case "ml": - off = [0, -0.5]; - break; - case "mm": - off = [-0.5, -0.5]; - break; - case "mr": - off = [-1, -0.5]; - break; - case "ul": - off = [0, -1]; - break; - case "um": - off = [-0.5, -1]; - break; - case "ur": - off = [-1, -1]; - break; - } - this.anchor = off; - this.pos = this.dimensions().scale(off); -}; - -sprite.inputs = {}; -sprite.inputs.kp9 = function () { - this.setanchor("ll"); -}; -sprite.inputs.kp8 = function () { - this.setanchor("lm"); -}; -sprite.inputs.kp7 = function () { - this.setanchor("lr"); -}; -sprite.inputs.kp6 = function () { - this.setanchor("ml"); -}; -sprite.inputs.kp5 = function () { - this.setanchor("mm"); -}; -sprite.inputs.kp4 = function () { - this.setanchor("mr"); -}; -sprite.inputs.kp3 = function () { - this.setanchor("ur"); -}; -sprite.inputs.kp2 = function () { - this.setanchor("um"); -}; -sprite.inputs.kp1 = function () { - this.setanchor("ul"); -}; - -var changed_ts = []; - -function bulkupdate() -{ - for (var t of changed_ts) { - var sprite = t.sprite; - sprite_qt.remove(sprite); - sprite.rect = t.torect(); - sprite.set_affine(t) - sprite_qt.insert(sprite) - } - -// changed_ts.clear(); - changed_ts = []; -} - -Register.registries.prerender.register(bulkupdate) -function sprite_t_hook() -{ - changed_ts.push(this); -} - -component.sprite = function (obj) { - var sp = Object.create(sprite); - - sp.gameobject = obj; - sp.transform = os.make_transform(); - sp.transform.parent = obj.transform; - sp.transform.change_hook = sprite_t_hook; - - var msp = os.make_sprite(); - sp._sprite = msp; - msp.color = Color.white; - sp.transform.sprite = msp; // or msp - - return sp; -}; - -return {component}; - -Object.mixin(os.make_seg2d(), { - sync() { - this.set_endpoints(this.points[0], this.points[1]); - }, -}); - -var collider2d = {}; -collider2d.inputs = {}; -collider2d.inputs["M-s"] = function () { - this.sensor = !this.sensor; -}; -collider2d.inputs["M-s"].doc = "Toggle if this collider is a sensor."; - -collider2d.inputs["M-t"] = function () { - this.enabled = !this.enabled; -}; -collider2d.inputs["M-t"].doc = "Toggle if this collider is enabled."; - -Object.mix(os.make_poly2d(), { - boundingbox() { - return bbox.frompoints(this.spoints()); - }, - - /* EDITOR */ - spoints() { - var spoints = this.points.slice(); - - if (this.flipx) { - spoints.forEach(function (x) { - var newpoint = x.slice(); - newpoint.x = -newpoint.x; - spoints.push(newpoint); - }); - } - - if (this.flipy) { - spoints.forEach(function (x) { - var newpoint = x.slice(); - newpoint.y = -newpoint.y; - spoints.push(newpoint); - }); - } - return spoints; - }, - - gizmo() { - this.spoints().forEach(x => render.point(this.gameobject.this2screen(x), 3, Color.green)); - this.points.forEach((x, i) => render.coordinate(this.gameobject.this2screen(x))); - }, - - pick(pos) { - if (!Object.hasOwn(this, "points")) this.points = deep_copy(this.__proto__.points); - - var i = Gizmos.pick_gameobject_points(pos, this.gameobject, this.points); - var p = this.points[i]; - if (p) return make_point_obj(this, p); - - return undefined; - }, -}); - -function pointscaler(x) { - if (typeof x === "number") return; - this.points = this.points.map(p => p.mult(x)); -} - -Object.mixin(os.make_poly2d(), { - sync() { - this.setverts(this.points); - }, - grow: pointscaler, -}); - -var polyinputs = Object.create(collider2d.inputs); -os.make_poly2d().inputs = polyinputs; - -polyinputs = {}; -polyinputs.f10 = function () { - this.points = Math.sortpointsccw(this.points); -}; -polyinputs.f10.doc = "Sort all points to be CCW order."; - -polyinputs["C-lm"] = function () { - this.points.push(this.gameobject.world2this(input.mouse.worldpos())); -}; -polyinputs["C-lm"].doc = "Add a point to location of mouse."; -polyinputs.lm = function () {}; -polyinputs.lm.released = function () {}; - -polyinputs["C-M-lm"] = function () { - var idx = Math.grab_from_points( - input.mouse.worldpos(), - this.points.map(p => this.gameobject.this2world(p)), - 25, - ); - if (idx === -1) return; - this.points.splice(idx, 1); -}; -polyinputs["C-M-lm"].doc = "Remove point under mouse."; - -polyinputs["C-b"] = function () { - this.points = this.spoints; - this.flipx = false; - this.flipy = false; -}; -polyinputs["C-b"].doc = "Freeze mirroring in place."; - -var edge2d = { - dimensions: 2, - thickness: 1, - /* if type === -1, point to point */ - type: Spline.type.catmull, - C: 1 /* when in bezier, continuity required. 0, 1 or 2. */, - looped: false, - angle: 0.5 /* smaller for smoother bezier */, - elasticity: 0, - friction: 0, - sync() { - var ppp = this.sample(); - this.segs ??= []; - var count = ppp.length - 1; - this.segs.length = count; - for (var i = 0; i < count; i++) { - this.segs[i] ??= os.make_seg2d(this.body); - this.segs[i].set_endpoints(ppp[i], ppp[i + 1]); - this.segs[i].set_neighbors(ppp[i], ppp[i + 1]); - this.segs[i].radius = this.thickness; - this.segs[i].elasticity = this.elasticity; - this.segs[i].friction = this.friction; - this.segs[i].collide = this.collide; - } - }, - - flipx: false, - flipy: false, - - hollow: false, - hollowt: 0, - - spoints() { - if (!this.points) return []; - var spoints = this.points.slice(); - - if (this.flipx) { - if (Spline.is_bezier(this.type)) spoints.push(Vector.reflect_point(spoints.at(-2), spoints.at(-1))); - - for (var i = spoints.length - 1; i >= 0; i--) { - var newpoint = spoints[i].slice(); - newpoint.x = -newpoint.x; - spoints.push(newpoint); - } - } - - if (this.flipy) { - if (Spline.is_bezier(this.type)) spoints.push(Vector.reflect(point(spoints.at(-2), spoints.at(-1)))); - - for (var i = spoints.length - 1; i >= 0; i--) { - var newpoint = spoints[i].slice(); - newpoint.y = -newpoint.y; - spoints.push(newpoint); - } - } - - if (this.hollow) { - var hpoints = vector.inflate(spoints, this.hollowt); - if (hpoints.length === spoints.length) return spoints; - var arr1 = hpoints.filter(function (x, i) { - return i % 2 === 0; - }); - var arr2 = hpoints.filter(function (x, i) { - return i % 2 !== 0; - }); - return arr1.concat(arr2.reverse()); - } - - if (this.looped) spoints = spoints.wrapped(1); - - return spoints; - }, - - post() { - this.points = []; - }, - - sample() { - var spoints = this.spoints(); - if (spoints.length === 0) return []; - - if (this.type === -1) { - if (this.looped) spoints.push(spoints[0]); - return spoints; - } - - if (this.type === Spline.type.catmull) { - if (this.looped) spoints = Spline.catmull_loop(spoints); - else spoints = Spline.catmull_caps(spoints); - - return Spline.sample_angle(this.type, spoints, this.angle); - } - - if (this.looped && Spline.is_bezier(this.type)) spoints = Spline.bezier_loop(spoints); - - return Spline.sample_angle(this.type, spoints, this.angle); - }, - - boundingbox() { - return bbox.frompoints(this.points.map(x => x.scale(this.gameobject.scale))); - }, - - /* EDITOR */ - gizmo() { - if (this.type === Spline.type.catmull || this.type === -1) { - this.spoints().forEach(x => render.point(this.gameobject.this2screen(x), 3, Color.teal)); - this.points.forEach((x, i) => render.coordinate(this.gameobject.this2screen(x))); - } else { - for (var i = 0; i < this.points.length; i += 3) render.coordinate(this.gameobject.this2screen(this.points[i]), 1, Color.teal); - - for (var i = 1; i < this.points.length; i += 3) { - render.coordinate(this.gameobject.this2screen(this.points[i]), 1, Color.green); - render.coordinate(this.gameobject.this2screen(this.points[i + 1]), 1, Color.green); - render.line([this.gameobject.this2screen(this.points[i - 1]), this.gameobject.this2screen(this.points[i])], Color.yellow); - render.line([this.gameobject.this2screen(this.points[i + 1]), this.gameobject.this2screen(this.points[i + 2])], Color.yellow); - } - } - }, - - finish_center(change) { - this.points = this.points.map(function (x) { - return x.sub(change); - }); - }, - - pick(pos) { - var i = Gizmos.pick_gameobject_points(pos, this.gameobject, this.points); - var p = this.points[i]; - if (!p) return undefined; - - if (Spline.is_catmull(this.type) || this.type === -1) return make_point_obj(this, p); - - var that = this.gameobject; - var me = this; - if (p) { - var o = { - pos: p, - sync: me.sync.bind(me), - }; - if (Spline.bezier_is_handle(this.points, i)) - o.move = function (d) { - d = that.dir_world2this(d); - p.x += d.x; - p.y += d.y; - Spline.bezier_cp_mirror(me.points, i); - }; - else - o.move = function (d) { - d = that.dir_world2this(d); - p.x += d.x; - p.y += d.y; - var pp = Spline.bezier_point_handles(me.points, i); - pp.forEach(ph => (me.points[ph] = me.points[ph].add(d))); - }; - return o; - } - }, - - rm_node(idx) { - if (idx < 0 || idx >= this.points.length) return; - if (Spline.is_catmull(this.type)) this.points.splice(idx, 1); - - if (Spline.is_bezier(this.type)) { - assert(Spline.bezier_is_node(this.points, idx), "Attempted to delete a bezier handle."); - if (idx === 0) this.points.splice(idx, 2); - else if (idx === this.points.length - 1) this.points.splice(this.points.length - 2, 2); - else this.points.splice(idx - 1, 3); - } - }, - - add_node(pos) { - pos = this.gameobject.world2this(pos); - var idx = 0; - if (Spline.is_catmull(this.type) || this.type === -1) { - if (this.points.length >= 2) idx = physics.closest_point(pos, this.points, 400); - - if (idx === this.points.length) this.points.push(pos); - else this.points.splice(idx, 0, pos); - } - - if (Spline.is_bezier(this.type)) { - idx = physics.closest_point(pos, Spline.bezier_nodes(this.points), 400); - - if (idx < 0) return; - - if (idx === 0) { - this.points.unshift(pos.slice(), pos.add([-100, 0]), Vector.reflect_point(this.points[1], this.points[0])); - return; - } - if (idx === Spline.bezier_node_count(this.points)) { - this.points.push(Vector.reflect_point(this.points.at(-2), this.points.at(-1)), pos.add([-100, 0]), pos.slice()); - return; - } - idx = 2 + (idx - 1) * 3; - var adds = [pos.add([100, 0]), pos.slice(), pos.add([-100, 0])]; - this.points.splice(idx, 0, ...adds); - } - }, - - pick_all() { - var picks = []; - this.points.forEach(x => picks.push(make_point_obj(this, x))); - return picks; - }, -}; - -component.edge2d = function (obj) { -// if (!obj.body) obj.rigidify(); - var edge = Object.create(edge2d); - edge.body = obj.body; - return edge; -}; - -edge2d.spoints.doc = "Returns the controls points after modifiers are applied, such as it being hollow or mirrored on its axises."; -edge2d.inputs = {}; -edge2d.inputs.h = function () { - this.hollow = !this.hollow; -}; -edge2d.inputs.h.doc = "Toggle hollow."; - -edge2d.inputs["C-g"] = function () { - if (this.hollowt > 0) this.hollowt--; -}; -edge2d.inputs["C-g"].doc = "Thin the hollow thickness."; -edge2d.inputs["C-g"].rep = true; - -edge2d.inputs["C-f"] = function () { - this.hollowt++; -}; -edge2d.inputs["C-f"].doc = "Increase the hollow thickness."; -edge2d.inputs["C-f"].rep = true; - -edge2d.inputs["M-v"] = function () { - if (this.thickness > 0) this.thickness--; -}; -edge2d.inputs["M-v"].doc = "Decrease spline thickness."; -edge2d.inputs["M-v"].rep = true; - -edge2d.inputs["C-y"] = function () { - this.points = this.spoints(); - this.flipx = false; - this.flipy = false; - this.hollow = false; -}; -edge2d.inputs["C-y"].doc = "Freeze mirroring,"; -edge2d.inputs["M-b"] = function () { - this.thickness++; -}; -edge2d.inputs["M-b"].doc = "Increase spline thickness."; -edge2d.inputs["M-b"].rep = true; - -edge2d.inputs.plus = function () { - if (this.angle <= 1) { - this.angle = 1; - return; - } - this.angle *= 0.9; -}; -edge2d.inputs.plus.doc = "Increase the number of samples of this spline."; -edge2d.inputs.plus.rep = true; - -edge2d.inputs.minus = function () { - this.angle *= 1.1; -}; -edge2d.inputs.minus.doc = "Decrease the number of samples on this spline."; -edge2d.inputs.minus.rep = true; - -edge2d.inputs["C-r"] = function () { - this.points = this.points.reverse(); -}; -edge2d.inputs["C-r"].doc = "Reverse the order of the spline's points."; - -edge2d.inputs["C-l"] = function () { - this.looped = !this.looped; -}; -edge2d.inputs["C-l"].doc = "Toggle spline being looped."; - -edge2d.inputs["C-c"] = function () { - switch (this.type) { - case Spline.type.bezier: - this.points = Spline.bezier2catmull(this.points); - break; - } - this.type = Spline.type.catmull; -}; - -edge2d.inputs["C-c"].doc = "Set type of spline to catmull-rom."; - -edge2d.inputs["C-b"] = function () { - switch (this.type) { - case Spline.type.catmull: - this.points = Spline.catmull2bezier(Spline.catmull_caps(this.points)); - break; - } - this.type = Spline.type.bezier; -}; - -edge2d.inputs["C-o"] = function () { - this.type = -1; -}; -edge2d.inputs["C-o"].doc = "Set spline to linear."; - -edge2d.inputs["C-M-lm"] = function () { - if (Spline.is_catmull(this.type)) { - var idx = Math.grab_from_points( - input.mouse.worldpos(), - this.points.map(p => this.gameobject.this2world(p)), - 25, - ); - if (idx === -1) return; - } else { - } - - this.points = this.points.newfirst(idx); -}; -edge2d.inputs["C-M-lm"].doc = "Select the given point as the '0' of this spline."; - -edge2d.inputs["C-lm"] = function () { - this.add_node(input.mouse.worldpos()); -}; -edge2d.inputs["C-lm"].doc = "Add a point to the spline at the mouse position."; - -edge2d.inputs["C-M-lm"] = function () { - var idx = -1; - if (Spline.is_catmull(this.type)) - idx = Math.grab_from_points( - input.mouse.worldpos(), - this.points.map(p => this.gameobject.this2world(p)), - 25, - ); - else { - var nodes = Spline.bezier_nodes(this.points); - idx = Math.grab_from_points( - input.mouse.worldpos(), - nodes.map(p => this.gameobject.this2world(p)), - 25, - ); - idx *= 3; - } - - this.rm_node(idx); -}; -edge2d.inputs["C-M-lm"].doc = "Remove point from the spline."; - -edge2d.inputs.lm = function () {}; -edge2d.inputs.lm.released = function () {}; - -edge2d.inputs.lb = function () { - var np = []; - - this.points.forEach(function (c) { - np.push(Vector.rotate(c, Math.deg2rad(-1))); - }); - - this.points = np; -}; -edge2d.inputs.lb.doc = "Rotate the points CCW."; -edge2d.inputs.lb.rep = true; - -edge2d.inputs.rb = function () { - var np = []; - - this.points.forEach(function (c) { - np.push(Vector.rotate(c, Math.deg2rad(1))); - }); - - this.points = np; -}; -edge2d.inputs.rb.doc = "Rotate the points CW."; -edge2d.inputs.rb.rep = true; - -/* CIRCLE */ - -function shape_maker(maker) { - return function (obj) { -// if (!obj.body) obj.rigidify(); - return maker(obj.body); - }; -} -component.circle2d = shape_maker(os.make_circle2d); -component.poly2d = shape_maker(os.make_poly2d); -component.seg2d = shape_maker(os.make_seg2d); - -Object.mix(os.make_circle2d(), { - boundingbox() { - return bbox.fromcwh(this.offset, [this.radius, this.radius]); - }, - - set scale(x) { - this.radius = x; - }, - get scale() { - return this.radius; - }, - - get pos() { - return this.offset; - }, - set pos(x) { - this.offset = x; - }, - - grow(x) { - if (typeof x === "number") this.scale *= x; - else if (typeof x === "object") this.scale *= x[0]; - }, -}); - -return { component }; diff --git a/scripts/debug.js b/scripts/debug.js index 5b36ff29..ebe1b6da 100644 --- a/scripts/debug.js +++ b/scripts/debug.js @@ -1,3 +1,6 @@ +var render = use('render') +var debug = os.use('debug') + debug.build = function (fn) { if (!debug.show) return; fn(); @@ -20,6 +23,8 @@ debug.fn_break = function (fn, obj = globalThis) { obj[fn.name] = newfn; }; +var sim = use('sim') + debug.draw_phys = false; debug.draw_bb = false; debug.draw_gizmos = false; @@ -59,18 +64,6 @@ debug.draw = function () { render.text(sim.playing() ? "PLAYING" : sim.stepping() ? "STEP" : sim.paused() ? "PAUSED; EDITING" : "EDIT", [0, 0], 1); }; -var assert = function (op, str = `assertion failed [value '${op}']`) { - if (!op) console.panic(str); -}; - -var Gizmos = { - pick_gameobject_points(worldpos, gameobject, points) { - var idx = Math.grab_from_points(worldpos, points.map(gameobject.this2world, gameobject), 25); - if (idx === -1) return undefined; - return idx; - }, -}; - /* These controls are available during editing, and during play of debug builds */ debug.inputs = {}; debug.inputs.f1 = function () { @@ -267,8 +260,5 @@ debug.try = function(fn) } } -return { - debug, - Gizmos, - assert, -}; +return {debug} + diff --git a/scripts/diff.js b/scripts/diff.js deleted file mode 100644 index 2ad751e8..00000000 --- a/scripts/diff.js +++ /dev/null @@ -1,99 +0,0 @@ -function deep_copy(from) { - return json.decode(json.encode(from)); -} - -function valdiff(from, to) { - if (typeof from !== typeof to) return from; - if (typeof from === "function") return undefined; - if (typeof from === "undefined") return undefined; - - if (typeof from === "number") { - return to; - - return undefined; - } - - if (typeof from === "object") return ediff(from, to); - - if (from !== to) return to; - - return undefined; -} - -function ediff(from, to) { - var ret = {}; - - if (!to) - // return ediff(from, {}); - return deep_copy(from); - - Object.entries(from).forEach(function ([key, v]) { - if (typeof v === "function") return; - if (typeof v === "undefined") return; - - if (Array.isArray(v)) { - if (!Array.isArray(to[key]) || v.length !== to[key].length) { - var r = ediff(v, []); - if (r) ret[key] = Object.values(r); - return; - } - - var diff = ediff(from[key], to[key]); - if (diff && !Object.empty(diff)) ret[key] = Object.values(ediff(v, [])); - - return; - } - - if (typeof v === "object" && v !== null) { - var diff = ediff(v, to[key]); - if (diff && !Object.empty(diff)) ret[key] = diff; - return; - } - - if (typeof v === "number" || v === null) { - if (!isFinite(v)) v = null; // Squash infinity to null - if (v !== to[key]) ret[key] = v; - return; - } - - if (!to || v !== to[key]) ret[key] = v; - }); - if (Object.empty(ret)) return undefined; - - return ret; -} - -ediff.doc = "Given a from and to object, returns an object that, if applied to from, will make it the same as to. Does not include deletion; it is only additive. If one element in an array is different, the entire array is copied. Squashes infinite numbers to null for use in JSON."; - -function samediff(from, to) { - var same = []; - if (!to) return same; - if (typeof to !== "object") { - console.warn("'To' must be an object. Got " + to); - return same; - } - Object.keys(from).forEach(function (k) { - if (Object.isObject(from[k])) { - samediff(from[k], to[k]); - return; - } - - // if (Array.isArray(from[k])) { - // var d = valdiff(from[k], to[k]); - // if (!d) - // } - - var d = valdiff(from[k], to[k]); - if (!d) delete from[k]; - }); - - return same; -} - -samediff.doc = "Given a from and to object, returns an array of keys that are the same on from as on to."; - -return { - deep_copy, - ediff, - samediff, -}; diff --git a/scripts/engine.js b/scripts/engine.js index e7ff0ebd..645f8140 100644 --- a/scripts/engine.js +++ b/scripts/engine.js @@ -85,13 +85,13 @@ Resources.replstrs = function replstrs(path) { var stem = path.dir(); if (!console.enabled) script = Resources.rm_fn(/console\.(spam|info|warn|error)/, script); - if (!profile.enabled) script = Resources.rm_fn(/profile\.(cache|frame|endcache|endframe)/, script); +/* if (!profile.enabled) script = Resources.rm_fn(/profile\.(cache|frame|endcache|endframe)/, script); if (!debug.enabled) { script = Resources.rm_fn(/assert/, script); script = Resources.rm_fn(/debug\.(build|fn_break)/, script); } - +*/ script = script.replace(regexp, function (str) { var newstr = Resources.replpath(str.trimchr('"'), path); return `"${newstr}"`; @@ -188,15 +188,6 @@ function find_ext(file, ext) { return find; } -var hashhit = 0; -var hashmiss = 0; - -globalThis.hashifier = {}; -hashifier.stats = function() -{ - -} - Object.defineProperty(Function.prototype, "hashify", { value: function () { var hash = new Map(); @@ -227,6 +218,8 @@ Resources.find_font = function(file, root = "") { return find_ext(file, Resources.fonts, root); }.hashify(); +globalThis.console = os.use('console') + console.transcript = ""; console.say = function (msg) { console.print(msg); @@ -242,6 +235,8 @@ console.rec = function(category, priority, line, file, msg) return `${file}:${line}: [${category} ${priority}]: ${msg}` + "\n"; } +var io = os.use('io') + var logfile = io.open('.prosperon/log.txt') //logfile.buffer(1024*1024) // 1MB buffer @@ -304,6 +299,10 @@ console.panic = function (e) { os.quit(); }; +console.assert = function (op, str = `assertion failed [value '${op}']`) { + if (!op) console.panic(str); +}; + os.on('uncaught_exception', function(e) { console.error(e); }); console.stdout_lvl = 1; @@ -323,27 +322,6 @@ console.doc = { globalThis.global = globalThis; -var use_cache = {}; - -globalThis.use = function use(file) { - file = Resources.find_script(file); - - if (use_cache[file]) { - var ret = use_cache[file](); - return ret; - } - - var script = Resources.replstrs(file); - var fnname = file.replace(/[^a-zA-Z0-9_$]/g, "_"); - script = `(function ${fnname}() { var self = this; ${script}; } )`; - var fn = os.eval(file, script); - use_cache[file] = fn; - - var ret = fn(); - - return ret; -}; - var tmpslurp = io.slurp; io.slurp = function slurp(path) { @@ -360,6 +338,25 @@ io.slurpbytes = function(path) return ret; } + +function bare_use(file) { + try { + var script = io.slurp(file); + if (!script) return; + var fnname = file.replace(/[^a-zA-Z0-9_$]/g, "_"); + script = `(function ${fnname}() { var self = this; ${script}; })`; + Object.assign(globalThis, os.eval(file, script)()); + } catch(e) { + console.log(file) + console.error(e); + throw e + } +} + +bare_use("core/scripts/base.js"); + +var game = os.use('game') + var ignore = io.slurp('.prosperonignore').split('\n'); var allpaths; var tmpglob = io.glob; @@ -430,51 +427,387 @@ function matchPath(pathParts, patternParts) { return patternIndex === patternParts.length; } -function stripped_use(file, script) { - file = Resources.find_script(file); +globalThis.prosperon = os.use('prosperon') - if (use_cache[file]) { - var ret = use_cache[file](); - return ret; - } - script ??= Resources.replstrs(file); - - script = `(function () { var self = this; ${script}; })`; - var fn = os.eval(file, script); - var ret = fn(); - - return ret; +prosperon.SIGABRT = function() +{ + console.error(new Error('SIGABRT')); + os.exit(1); } -function bare_use(file) { +prosperon.SIGSEGV = function() +{ + console.error(new Error('SIGSEGV')); + os.exit(1); +} + +function add_timer(obj, fn, seconds) +{ + var timers = obj.timers; + + var stop = function () { + timers.delete(stop); + timer.fn = undefined; + timer = undefined; + }; + + function execute() { + if (fn) + timer.remain = fn(stop.seconds); + + if (!timer) return + if (!timer.remain) + stop(); + else + stop.seconds = timer.remain; + } + + var timer = os.make_timer(execute); + timer.remain = seconds; + + stop.remain = seconds; + stop.seconds = seconds; + + timers.push(stop); + return stop; +} + +var actor = {}; + +var use = function use(file) { try { - var script = io.slurp(file); - if (!script) return; - var fnname = file.replace(/[^a-zA-Z0-9_$]/g, "_"); - script = `(function ${fnname}() { var self = this; ${script}; })`; - Object.assign(globalThis, os.eval(file, script)()); + var par = script_fn(file) + if (par?.module_ret) + return par.module_ret } catch(e) { - console.log(file) - console.log(e); + console.error(e) + } + + try { + return os.use('./lib' + file + '.so') + } catch(e) { console.error(e)} + + return os.use(file) +}.hashify() + +var script_fn = function script_fn(path) { + var file = Resources.find_script(path) + if (!file) throw new Error(`File ${path} could not be found`) + var content = Resources.replstrs(file); + var parsed = parse_file(content) + var module_name = file.name() + + if (parsed.module) { + var mod_script = `(function setup_${module_name}_module(){ var self = this; var $ = this; ${parsed.module}})`; + var module_fn = os.eval(file, mod_script) + var module_ret = module_fn.call(); + if (module_ret === undefined || module_ret === null) + throw new Error(`Module ${module_name} must return a value`); + + parsed.module_fn = module_fn; + parsed.module_ret = module_ret; + } else + parsed.module_ret = {} + + if (parsed.program) { + var prog_script = `(function use_${module_name}() { var self = this; var $ = this.__proto__; ${parsed.program}})`; + parsed.prog_fn = os.eval(file, prog_script); + } + + return parsed; +}.hashify() + +function parse_file(content) { + if (!content) return {} + var parts = content.split(/\n\s*---\s*\n/) + if (parts.length === 1) { + var part = parts[0] + if (part.match(/return\s+[^;]+;?\s*$/)) + return { module: part } + return { program: part } + } + var module = parts[0] + if (!module.match(/return\s+[^;]+;?\s*$/)) + throw new Error("Module section must end with a return statement") + + var pad = '\n'.repeat(module.split('\n').length+2) // add 2 from the split search + return { + module, + program: pad+parts[1] } } -profile.enabled = true; -console.enabled = true; -debug.enabled = true; - -bare_use("core/scripts/base.js"); -bare_use("core/scripts/profile.js"); - -prosperon.release = function () { - profile.enabled = false; - console.enabled = false; - debug.enabled = false; +actor.__stats = function () { + var total = 0; + var stats = {}; + search.all_objects(obj => { + if (!actor_spawns[obj._file]) return; + stats[obj._file] ??= 0; + stats[obj._file]++; + total++; + }); +/* for (var i in actor_spawns) { + stats[i] = actor_spawns[i].length; + total += stats[i]; + }*/ +// stats.total = total; + return stats; }; -//bare_use("core/scripts/preconfig.js"); +actor.hotreload = function hotreload(file) { + var script = Resources.replstrs(file); + script = `(function() { var self = this; var $ = this.__proto__;${script};})`; + var fn = os.eval(file, script); +/* + for (var obj of actor_spawns[file]) { + var a = obj; + a.timers.forEachRight(t=>t()); + a.timers = []; + var save = json.decode(json.encode(a)); + fn.call(a); + Object.merge(a, save); + Register.check_registers(a); + } +*/ +}; -if (!profile.enabled) use = stripped_use; +////////// +/// EVENT +///////// + +var Event = { + events: {}, + + observe(name, obj, fn) { + this.events[name] ??= []; + this.events[name].push([obj, fn]); + }, + + unobserve(name, obj) { + this.events[name] = this.events[name].filter(x => x[0] !== obj); + }, + + rm_obj(obj) { + Object.keys(this.events).forEach(name => Event.unobserve(name, obj)); + }, + + notify(name, ...args) { + if (!this.events[name]) return; + this.events[name].forEach(function (x) { + x[1].call(x[0], ...args); + }); + }, +}; + +////////////////// +///////REGISTRANT +///////////////// +/* +Factory for creating registries. Register one with 'X.register', +which returns a function that, when invoked, cancels the registry. +*/ +var Register = { + registries: [], + + add_cb(name) { + var n = {}; + var fns = []; + + n.register = function (fn, oname) { + if (!(fn instanceof Function)) return; + + var dofn = function (...args) { + fn(...args); + }; + Object.defineProperty(dofn, 'name', {value:`do_${oname}`}); + + var left = 0; + var right = fns.length - 1; + dofn.layer = fn.layer; + dofn.layer ??= 0; + + while (left <= right) { + var mid = Math.floor((left + right) / 2); + if (fns[mid] === dofn.layer) { + left = mid; + break; + } else if (fns[mid].layer < dofn.layer) left = mid + 1; + else right = mid - 1; + } + + fns.splice(left, 0, dofn); + + return function () { + fns.remove(dofn); + }; + }; + + prosperon[name] = function (...args) { +// tracy.fiber_enter(vector.fib); + fns.forEach(fn => { + fn(...args) + }); +// tracy.fiber_leave(vector.fib); + }; + + Object.defineProperty(prosperon[name], 'name', {value:name}); + prosperon[name].fns = fns; + n.clear = function () { + fns = []; + }; + + Register[name] = n; + Register.registries[name] = n; + + return n; + }, +}; + +Register.pull_registers = function pull_registers(obj) +{ + var reggies = []; + for (var reg in Register.registries) { + if (typeof obj[reg] === "function") + reggies.push(reg); + } + return reggies; +} + +Register.register_obj = function register_obj(obj, reg) +{ + var fn = obj[reg].bind(obj); + fn.layer = obj[reg].layer; + var name = obj.ur ? obj.ur.name : obj.toString(); + obj.timers.push(Register.registries[reg].register(fn, name)); + if (!obj[reg].name) Object.defineProperty(obj[reg], 'name', {value:`${obj._file}_${reg}`}); +} + +Register.check_registers = function check_registers(obj) { + if (obj.__reggies) { + if (obj.__reggies.length == 0) return; + // fast path + for (var reg of obj.__reggies) + Register.register_obj(obj,reg); + + return; + } + + for (var reg in Register.registries) { + if (typeof obj[reg] === "function") + Register.register_obj(obj,reg); + } + +/* for (var k in obj) { + if (!k.startsWith("on_")) continue; + var signal = k.fromfirst("on_"); + Event.observe(signal, obj, obj[k]); + }*/ +}; + +Register.add_cb("appupdate"); +Register.add_cb("update").doc = "Called once per frame."; +Register.add_cb("physupdate"); +Register.add_cb("gui"); +Register.add_cb("hud"); +Register.add_cb("draw"); +Register.add_cb("imgui"); +Register.add_cb("app"); +Register.add_cb("prerender"); + +actor.spawn = function spawn(script, config, callback) { + var prog + if (!script) { + prog = Object.create(actor) + if (callback) callback(prog) + return prog + } + + prog = script_fn(script); + + if (!prog.prog_fn) throw new Error(`Script ${script} is not an actor script or has no actor component`) + + var underling; + prog.module_ret.__proto__ = actor; + underling = Object.create(prog.module_ret); + underling.overling = this; + + underling.timers = [] + underling.underlings = new Set() + + if (callback) callback(underling) +try{ + prog.prog_fn.call(underling) +} catch(e) { console.error(e); throw e} + if (typeof config === 'object') Object.assign(underling,config); + + if (!underling.__reggies) + underling.__proto__.__reggies = Register.pull_registers(underling) + + Register.check_registers(underling); + + if (underling.awake) underling.awake(); + + this.underlings.add(underling); + if (underling.tag) + search.tag_add(underling.tag, underling) + + return underling; +}; + +actor.spawn.doc = `Create a new actor, using this actor as the overling, initializing it with 'script' and with data (as a JSON or Nota file) from 'config'.`; + +actor.clear = function actor_clear() +{ + this.underlings.forEach(p => p.kill()) + this.underlings.clear() +} + +var input = use('input') + +actor.kill = function kill() { + if (this.__dead__) return; + this.__dead__ = true; + this.timers.forEachRight(t => t()) + delete this.timers + input.do_uncontrol(this); + Event.rm_obj(this); + + this.clear() + delete this.underlings + + this.__dead__ = true; + if (typeof this.garbage === "function") this.garbage(); + if (typeof this.then === "function") this.then(); +}; + +actor.kill.doc = `Remove this actor and all its underlings from existence.`; + +actor.delay = function (fn, seconds) { add_timer(this, fn, seconds) } +actor.delay.doc = `Call 'fn' after 'seconds' with 'this' set to the actor.`; + +actor.interval = function interval(fn, seconds) { + var self = this; + var stop; + var usefn = function () { + fn(); + stop = self.delay(usefn, seconds); + }; + stop = self.delay(usefn, seconds); + + return stop; +}; + +actor.underlings = new Set() + +globalThis.app = actor.spawn() +app.garbage = function () { + os.exit(0); +}; + +globalThis.world = app; + +var search = use('search') + +global.mixin("color"); +global.mixin("std") -Object.assign(globalThis, use("core/scripts/prosperon.js")); - diff --git a/scripts/entity.js b/scripts/entity.js deleted file mode 100644 index aa1a681d..00000000 --- a/scripts/entity.js +++ /dev/null @@ -1,755 +0,0 @@ -globalThis.entityreport = {}; - -function obj_unique_name(name, obj) { - name = name.replaceAll(".", "_"); - if (!(name in obj)) return name; - var t = 1; - var n = name + t; - while (n in obj) { - t++; - n = name + t; - } - return n; -} - -function unique_name(list, name = "new_object") { - var str = name.replaceAll(".", "_"); - var n = 1; - var t = str; - while (list.indexOf(t) !== -1) { - t = str + n; - n++; - } - return t; -} - -var entity = { - drawlayer: -1, - get_comp_by_name(name) { - var comps = []; - for (var c of Object.values(this.components)) if (c.comp === name) comps.push(c); - - if (comps.length) return comps; - return undefined; - }, - - path_from(o) { - var p = this.toString(); - var c = this.master; - while (c && c !== o && c !== world) { - p = c.toString() + "." + p; - c = c.master; - } - if (c === world) p = "world." + p; - return p; - }, - - drawlayer: 0, - - full_path() { - return this.path_from(world); - }, - - clear() { - for (var k in this.objects) - this.objects[k].kill(); - this.objects = {}; - }, - - delay(fn, seconds) { prosperon.add_timer(this, fn, seconds); }, - - cry(file) { - return audio.cry(file); - }, - - get pos() { - return this.transform.pos; - }, - set pos(x) { - this.transform.pos = x; - }, - get angle() { - return this.transform.angle; - }, - set angle(x) { - this.transform.angle = x; - }, - get scale() { - return this.transform.scale; - }, - set scale(x) { - this.transform.scale = x; - }, - - move(vec) { - this.pos = this.pos.add(vec); - }, - rotate(x) { - this.transform.rotate([0, 0, -1],x); - }, - grow(vec) { - if (typeof vec === "number") vec = [vec, vec]; - this.scale = this.scale.map((x, i) => x * vec[i]); - }, - - /* Reparent 'this' to be 'parent's child */ - reparent(parent) { - assert(parent, `Tried to reparent ${this.toString()} to nothing.`); - if (this.master === parent) { - console.warn(`not reparenting ... ${this.master} is the same as ${parent}`); - return; - } - - var name = unique_name(Object.keys(parent), this.name); - this.name = name; - - this.master?.remove_obj(this); - this.master = parent; - parent.objects[this.guid] = this; - parent[name] = this; - Object.hide(parent, name); - }, - - remove_obj(obj) { - if (this.objects) delete this.objects[obj.guid]; - else console.warn(`Object ${this.guid} has no objects file.`); - delete this[obj.name]; - Object.unhide(this, obj.name); - }, - - aspawn(text, config) { - var e = class_use(text,config, actor, undefined, this) - }, - - spawn(text, config, callback) { - var ent = class_use(text, config, entity, function (ent) { - ent.transform = os.make_transform(); -// ent.guid = prosperon.guid(); - ent.components = {}; - ent.objects = {}; - ent.timers = []; -// ent.ur = {}; -// ent.urname = text; - }, this); - /* - if (!text) - ent.ur = emptyur; - else if (typeof text === 'object') {// assume it's an ur - ent.ur = text; - text = ent.ur.text; - config = [ent.ur.data, config].filter(x => x).flat(); - } - else { - ent.ur = getur(text, config); - text = ent.ur.text; - config = [ent.ur.data, config]; - } - - if (typeof config === 'string') - Object.merge(ent, json.decode(Resources.replstrs(config))); - else if (Array.isArray(config)) - for (var path of config) { - if (typeof path === 'string') { - console.info(`ingesting ${path} ...`); - Object.merge(ent, json.decode(Resources.replstrs(path))); - } - else if (path instanceof Object) - Object.merge(ent,path); - }; - - if (typeof text === 'string') { - class_use( - use(text, ent); - } - else if (Array.isArray(text)) - for (var path of text) use(path,ent); -*/ -// ent.reparent(this); - -/* for (var [prop, p] of Object.entries(ent)) { - if (!p) continue; - if (typeof p !== "object") continue; - if (!p.comp) continue; - ent[prop] = component[p.comp](ent); - Object.merge(ent[prop], p); - ent.components[prop] = ent[prop]; - } -*/ - - check_registers(ent); - - if (typeof ent.awake === 'function') ent.awake(); - if (sim.playing()) { - ent._started = true; - if (typeof ent.start === 'function') ent.start(); - } - -/* ent._ed = { - selectable: true, - dirty: false, - inst: false, - urdiff: {}, - }; -*/ -// Object.hide(ent, "ur", "components", "objects", "timers", "guid", "master", "guid", "_ed"); - -/* if (!Object.empty(ent.objects)) { - var o = ent.objects; - delete ent.objects; - ent.objects = {}; - for (var i in o) { - console.info(`creating ${i} on ${ent.toString()}`); - var newur = o[i].ur; - delete o[i].ur; - var n = ent.spawn(ur[newur], o[i]); - ent.rename_obj(n.toString(), i); - } - } -*/ - if (ent.tag) game.tag_add(ent.tag, ent); - - if (callback) callback(ent); - -// ent.ur.fresh ??= json.decode(json.encode(ent)); -// ent.ur.fresh.objects = {}; -// for (var i in ent.objects) ent.ur.fresh.objects[i] = ent.objects[i].instance_obj(); - - return ent; - }, - - disable() { - for (var x of this.components) x.disable(); - }, - enable() { - for (var x of this.components) x.enable(); - }, - - this2screen(pos) { - return game.camera.world2view(this.this2world(pos)); - }, - screen2this(pos) { - return this.world2this(game.camera.view2world(pos)); - }, - - /* Make a unique object the same as its prototype */ - revert() { - Object.merge(this, this.ur.fresh); - }, - - name: "new_object", - toString() { - return this.name; - }, - width() { - var bb = this.boundingbox(); - return bb.r - bb.l; - }, - - height() { - var bb = this.boundingbox(); - return bb.t - bb.b; - }, - - flipx() { - return this.scale.x < 0; - }, - flipy() { - return this.scale.y < 0; - }, - - mirror(plane) { - this.scale = Vector.reflect(this.scale, plane); - }, - - /* Bounding box of the object in world dimensions */ - boundingbox() { - var boxes = []; - boxes.push({ - t: 0, - r: 0, - b: 0, - l: 0, - }); - - for (var key in this.components) { - if ("boundingbox" in this.components[key]) boxes.push(this.components[key].boundingbox()); - } - for (var key in this.objects) boxes.push(this.objects[key].boundingbox()); - - var bb = boxes.shift(); - - for (var x of boxes) bb = bbox.expand(bb, x); - - bb = bbox.move(bb, this.pos); - - return bb ? bb : bbox.fromcwh([0, 0], [0, 0]); - }, - - toJSON() { - return { guid: this.guid }; - }, - - /* The unique components of this object. Its diff. */ - json_obj(depth = 0) { - var fresh = this.ur.fresh; - var thiso = json.decode(json.encode(this)); // TODO: SLOW. Used to ignore properties in toJSON of components. - var d = ediff(thiso, fresh); - - d ??= {}; - - fresh.objects ??= {}; - var curobjs = {}; - for (var o in this.objects) curobjs[o] = this.objects[o].instance_obj(); - - var odiff = ediff(curobjs, fresh.objects); - if (odiff) d.objects = curobjs; - - delete d.pos; - delete d.angle; - delete d.scale; - delete d.velocity; - delete d.angularvelocity; - return d; - }, - - /* The object needed to store an object as an instance of a master */ - instance_obj() { - var t = os.make_transform(); - t = this.transform; - t.ur = this.ur.name; - return t; - }, - - transform() { - var t = {}; - t.pos = this.get_pos(this.master).map(x => Math.places(x, 0)); - t.angle = Math.places(this.get_angle(this.master), 4); - t.scale = this.get_scale(this.master).map(x => Math.places(x, 2)); - return t; - }, - - dup(diff) { - var n = this.master.spawn(this.ur); - Object.totalmerge(n, this.transform()); - return n; - }, - - kill() { - if (this.__kill) return; - this.__kill = true; - this.timers.forEachRight(x => x()); - delete this.timers; - Event.rm_obj(this); - input.do_uncontrol(this); - - if (this.master) { - this.master.remove_obj(this); - this.master = undefined; - } - - for (var key in this.components) { - this.components[key].kill?.(); - this.components[key].gameobject = undefined; - this.components[key].enabled = false; - delete this.components[key]; - } - - delete this.components; - - this.clear(); - if (typeof this.stop === 'function') this.stop(); - if (typeof this.garbage === "function") this.garbage(); - if (typeof this.then === "function") this.then(); - - game.tag_clear_guid(this.guid); - - rmactor(this); - -// for (var i in this) -// delete this[i] - }, - - make_objs(objs) { - for (var prop in objs) { - say(`spawning ${json.encode(objs[prop])}`); - var newobj = this.spawn(objs[prop]); - } - }, - - rename_obj(name, newname) { - if (!this.objects[name]) { - console.warn(`No object with name ${name}. Could not rename to ${newname}.`); - return; - } - if (name === newname) { - Object.hide(this, name); - return; - } - if (this.objects[newname]) return; - - this.objects[newname] = this.objects[name]; - this[newname] = this[name]; - this[newname].toString = function () { - return newname; - }; - Object.hide(this, newname); - delete this.objects[name]; - delete this[name]; - return this.objects[newname]; - }, - - add_component(comp, data) { - var name = prosperon.guid(); - this.components[name] = comp(this); - if (data) - Object.assign(this.components[name], data); - return this.components[name]; - }, -}; - -var gameobject = { - check_dirty() { - this._ed.urdiff = this.json_obj(); - this._ed.dirty = !Object.empty(this._ed.urdiff); - return; // TODO: IMPLEMENT - var lur = this.master.ur; - if (!lur) return; - var lur = lur.objects[this.toString()]; - var d = ediff(this._ed.urdiff, lur); - if (!d || Object.empty(d)) this._ed.inst = true; - else this._ed.inst = false; - }, - - namestr() { - var s = this.toString(); - if (this._ed?.dirty) - if (this._ed.inst) s += "#"; - else s += "*"; - return s; - }, - - urstr() { - var str = this.ur.name; - if (this._ed.dirty) str = "*" + str; - return str; - }, - - /* pin this object to the to object */ - pin(to) { - var p = joint.pin(this, to); - }, - slide(to, a = [0, 0], b = [0, 0], min = 0, max = 50) { - var p = joint.slide(this, to, a, b, min, max); - p.max_force = 500; - p.break(); - }, - pivot(to, piv = this.pos) { - var p = joint.pivot(this, to, piv); - }, - /* groove is on to, from local points a and b, anchored to this at local anchor */ - groove(to, a, b, anchor = [0, 0]) { - var p = joint.groove(to, this, a, b, anchor); - }, - damped_spring(to, length = Vector.length(this.pos, to.pos), stiffness = 1, damping = 1) { - var dc = 2 * Math.sqrt(stiffness * this.mass); - var p = joint.damped_spring(this, to, [0, 0], [0, 0], stiffness, damping * dc); - }, - damped_rotary_spring(to, angle = 0, stiffness = 1, damping = 1) { - /* calculate actual damping value from the damping ratio */ - /* damping = 1 is critical */ - var dc = 2 * Math.sqrt(stiffness * this.get_moi()); /* critical damping number */ - /* zeta = actual/critical */ - var p = joint.damped_rotary(this, to, angle, stiffness, damping * dc); - }, - rotary_limit(to, min, max) { - var p = joint.rotary(this, to, Math.turn2rad(min), Math.turn2rad(max)); - }, - ratchet(to, ratch) { - var phase = this.angle - to.angle; - var p = joint.ratchet(this, to, phase, Math.turn2rad(ratch)); - }, - gear(to, ratio = 1, phase = 0) { - var phase = this.angle - to.angle; - var p = joint.gear(this, to, phase, ratio); - }, - motor(to, rate) { - var p = joint.motor(this, to, rate); - }, - - set_pos(x, relative = world) { - var newpos = relative.this2world(x); - var move = newpos.sub(this.pos); - this.rpos = newpos; - for (var o of this.objects) o.move(move); - }, - - set_angle(x, relative = world) { - var newangle = relative.angle + x; - var diff = newangle - this.angle; - this.rangle = newangle; - for (var obj of this.objects) { - obj.rotate(diff); - obj.set_pos(Vector.rotate(obj.get_pos(obj.master), diff), obj.master); - } - }, - - set_scale(x, relative = world) { - if (typeof x === "number") x = [x, x, x]; - var newscale = relative.scale.map((s, i) => x[i] * s); - var pct = this.scale.map((s, i) => newscale[i] / s); - this.rscale = newscale; - for (var obj of this.objects) { - obj.grow(pct); - obj.set_pos( - obj.get_pos(obj.master).map((x, i) => x * pct[i]), - obj.master, - ); - } - }, - - get_pos(relative = world) { - if (relative === world) return this.pos; - return relative.world2this(this.pos); - //return this.pos.sub(relative.pos); - }, - - get_angle(relative = world) { - if (relative === world) return this.angle; - return this.angle - relative.angle; - }, - - get_scale(relative = world) { - if (relative === world) return this.scale; - var masterscale = relative.scale; - return this.scale.map((x, i) => x / masterscale[i]); - }, - - in_air() { - return this.in_air(); - }, - - /* Velocity and angular velocity of the object */ - phys_obj() { - var phys = {}; - phys.velocity = this.velocity; - phys.angularvelocity = this.angularvelocity; - return phys; - }, - - set category(n) { - if (n === 0) { - this.categories = n; - return; - } - var cat = 1 << (n - 1); - this.categories = cat; - }, - get category() { - if (this.categories === 0) return 0; - var pos = 0; - var num = this.categories; - while (num > 0) { - if (num & 1) { - break; - } - pos++; - num >>>= 1; - } - - return pos + 1; - }, -}; - -entity.spawn.doc = `Spawn an entity of type 'ur' on this entity. Returns the spawned entity.`; - -gameobject.doc = { - doc: "All objects in the game created through spawning have these attributes.", - pos: "Position of the object, relative to its master.", - angle: "Rotation of this object, relative to its master.", - velocity: "Velocity of the object, relative to world.", - angularvelocity: "Angular velocity of the object, relative to the world.", - scale: "Scale of the object, relative to its master.", - flipx: "Check if the object is flipped on its x axis.", - flipy: "Check if the object is flipped on its y axis.", - elasticity: `When two objects collide, their elasticities are multiplied together. Their velocities are then multiplied by this value to find √their resultant velocities.`, - friction: `When one object touches another, friction slows them down.`, - mass: `The higher the mass of the object, the less forces will affect it.`, - phys: `Set to 0, 1, or 2, representing dynamic, kinematic, and static.`, - worldpos: `Function returns the world position of the object.`, - set_pos: `Function to set the position of the object in world coordinates.`, - worldangle: `Function to get the angle of the entity in the world.`, - rotate: `Function to rotate this object by x degrees.`, - move: "Move an object by x,y,z. If the first parameter is an array, uses up to the first three array values.", - pulse: `Apply an impulse to this body in world coordinates. Impulse is a short force.`, - shove: `Apply a force to this body in world coordinates. Should be used over many frames.`, - shove_at: "Apply a force to this body, at a position relative to itself.", - max_velocity: "The max linear velocity this object can travel.", - max_angularvelocity: "The max angular velocity this object can rotate.", - on_ground: `Return true if the object is on the ground.`, - spawn: `Create an instance of a supplied ur-type on this object. Optionally provide a data object to modify the created entity.`, - hide: `Make this object invisible.`, - show: `Make this object visible.`, - width: `The total width of the object and all its components.`, - height: `The total height of the object.`, - move: `Move this object the given amount.`, - boundingbox: `The boundingbox of the object.`, - dup: `Make an exact copy of this object.`, - transform: `Return an object representing the transform state of this object.`, - kill: `Remove this object from the world.`, - master: "The entity this entity belongs to.", - delay: "Run the given function after the given number of seconds has elapsed.", - cry: "Make a sound. Can only make one at a time.", - add_component: "Add a component to the object by name.", - pin: "Pin joint to another object. Acts as if a rigid rod is between the two objects.", - slide: "Slide joint, similar to a pin but with min and max allowed distances.", - pivot: "Pivot joint to an object, with the pivot given in world coordinates.", - groove: "Groove joint. The groove is on to, from to local coordinates a and b, with this object anchored at anchor.", - damped_spring: "Damped spring to another object. Length is the distance it wants to be, stiffness is the spring constant, and damping is the damping ratio. 1 is critical, < 1 is underdamped, > 1 is overdamped.", - damped_rotary_spring: "Similar to damped spring but for rotation. Rest angle is the attempted angle.", - rotary_limit: "Limit the angle relative to the to body between min and max.", - ratchet: "Like a socket wrench, relative to to. ratch is the distance between clicks.", - gear: "Keeps the angular velocity ratio of this body and to constant. Ratio is the gear ratio.", - motor: "Keeps the relative angular velocity of this body to to at a constant rate. The most simple idea is for one of the bodies to be static, to the other is kept at rate.", - layer: "Bitmask for collision layers.", - drawlayer: "Layer for drawing. Higher numbers draw above lower ones.", - warp_filter: "Bitmask for selecting what warps should affect this entity.", -}; - -global.ur = {}; - -if (io.exists(`${io.dumpfolder}/ur.json`)) ur = json.decode(io.slurp(`${io.dumpfolder}/ur.json`)); -else { - ur = {}; - ur._list = []; -} - -/* UR OBJECT -ur { - name: fully qualified name of ur - text: file path to the script - data: file path to data - proto: resultant object of a freshly made entity -} -*/ - -/* Apply an ur u to an entity e */ -/* u is given as */ -function apply_ur(u, ent) { - if (typeof u !== "string") { - console.warn("Must give u as a string."); - return; - } - - var urs = u.split("."); - if (!urs.every(u => ur[u])) { - console.error(`Attempted to make ur combo ${u} but not every ur in the chain exists.`); - return; - } - - for (var u of urs) { - var text = u.text; - var data = u.data; - if (typeof text === "string") use(text, ent); - else if (Array.isArray(text)) for (var path of text) use(path, ent); - - if (typeof data === "string") Object.merge(ent, json.decode(Resources.replstrs(data))); - else if (Array.isArray(data)) { - for (var path of data) { - if (typeof path === "string") Object.merge(ent, json.decode(Resources.replstrs(data))); - else if (path instanceof Object) Object.merge(ent, path); - } - } - } -} - -var emptyur = { - name: "empty", -}; - -var getur = function (text, data) { - if (!text && !data) { - console.info("empty ur"); - return { - name: "empty", - }; - } - var urstr = text; - if (data) urstr += "+" + data; - - if (!ur[urstr]) { - ur[urstr] = { - name: urstr, - text: text, - data: data, - }; - } - return ur[urstr]; -}; - -var ur_from_file = function (file) { - var urname = file.name(); - if (ur[urname]) { - console.warn(`Tried to make another ur with the name ${urname} from ${file}, but it already exists.`); - return undefined; - } - var newur = { - name: urname, - }; - ur[urname] = newur; - ur._list.push(urname); - return newur; -}; - -game.loadurs = function () { - return; - ur = {}; - ur._list = []; - /* FIND ALL URS IN A PROJECT */ - for (var file of io.glob("**.ur")) { - var newur = ur_from_file(file); - if (!newur) continue; - var uur = Resources.replstrs(file); - var urjson = json.decode(uur); - Object.assign(newur, urjson); - } - - for (var file of io.glob("**.jso").filter(f => !ur[f.name()])) { - if (file[0] === "." || file[0] === "_") continue; - var newur = ur_from_file(file); - if (!newur) continue; - newur.text = file; - - var data = file.set_ext(".json"); - if (io.exists(data)) { - console.info(`Found matching json ${data} for ${file}`); - newur.data = data; - } - } -}; - -game.ur = {}; -game.ur.load = function (str) {}; -game.ur.add_data = function (str, data) { - var nur = ur[str]; - if (!nur) { - console.warn(`Cannot add data to the ur ${str}.`); - return; - } - if (!Array.isArray(ur.data)) { - var arr = []; - if (ur.data) arr.push(ur.data); - ur.data = arr; - } - - ur.data.push(data); -}; - -game.ur.save = function (str) { - var nur = ur[str]; - if (!nur) { - console.warn(`Cannot save ur ${str}.`); - return; - } -}; - -return { entity }; diff --git a/scripts/geometry.js b/scripts/geometry.js index 9db609e6..3d4d9c73 100644 --- a/scripts/geometry.js +++ b/scripts/geometry.js @@ -1,3 +1,5 @@ +var geometry = os.use('geometry') + var shape = {}; shape.box = {}; shape.box.points = function (ll, ur) { @@ -67,4 +69,7 @@ shape.corners2points = function (ll, ur) { return [ll, ll.add([ur.x, 0]), ur, ll.add([0, ur.y])]; }; -return { shape }; +for (var i in geometry) + shape[i] = geometry[i] + +return shape diff --git a/scripts/gizmos.js b/scripts/gizmos.js new file mode 100644 index 00000000..4756661d --- /dev/null +++ b/scripts/gizmos.js @@ -0,0 +1,26 @@ +var ex = {} + +ex.pick_gameobject_points = function pick_gameobject_points(worldpos, gameobject, points) { + var idx = Math.grab_from_points(worldpos, points.map(gameobject.this2world, gameobject), 25); + if (idx === -1) return undefined; + return idx; +} + +ex.normalizeSpacing = function normalizeSpacing(spacing) { + if (typeof spacing === 'number') { + return {l: spacing, r: spacing, t: spacing, b: spacing}; + } else if (Array.isArray(spacing)) { + if (spacing.length === 2) { + return {l: spacing[0], r: spacing[0], t: spacing[1], b: spacing[1]}; + } else if (spacing.length === 4) { + return {l: spacing[0], r: spacing[1], t: spacing[2], b: spacing[3]}; + } + } else if (typeof spacing === 'object') { + return {l: spacing.l || 0, r: spacing.r || 0, t: spacing.t || 0, b: spacing.b || 0}; + } else { + return {l:0, r:0, t:0, b:0}; + } +} + + +return ex diff --git a/scripts/graphics.js b/scripts/graphics.js new file mode 100644 index 00000000..ca199526 --- /dev/null +++ b/scripts/graphics.js @@ -0,0 +1,197 @@ +var graphics = {} + +function calc_image_size(img) +{ + if (!img.texture || !img.rect) return; + return [img.texture.width*img.rect.width, img.texture.height*img.rect.height]; +} + +function create_image(path) +{ + var data = io.slurpbytes(path); + var newimg; + switch(path.ext()) { + case 'gif': + newimg = os.make_gif(data); + if (newimg.surface) + newimg.texture = prosperon.gpu.load_texture(newimg.surface); + else + for (var frame of newimg.frames) + frame.texture = prosperon.gpu.load_texture(frame.surface); + break; + case 'ase': + case 'aseprite': + newimg = os.make_aseprite(data); + if (newimg.surface) + newimg.texture = prosperon.gpu.load_texture(newimg.surface); + else { + for (var anim in newimg) { + var a = newimg[anim]; + for (var frame of a.frames) + frame.texture = prosperon.gpu.load_texture(frame.surface); + } + } + break; + default: + newimg = { + surface: os.make_texture(data) + }; + newimg.texture = prosperon.gpu.load_texture(newimg.surface); + break; + } + return newimg; +} + +var image = {}; +image.dimensions = function() +{ + return [this.texture.width, this.texture.height].scale([this.rect[2], this.rect[3]]); +} + +var spritesheet; +var sheet_frames = []; +var sheetsize = 1024; + +function pack_into_sheet(images) +{ + return; + if (!Array.isArray(images)) images = [images]; + if (images[0].texture.width > 300 && images[0].texture.height > 300) return; + sheet_frames = sheet_frames.concat(images); + var sizes = sheet_frames.map(x => [x.rect.width*x.texture.width, x.rect.height*x.texture.height]); + var pos = os.rectpack(sheetsize, sheetsize, sizes); + if (!pos) { + console.error(`did not make spritesheet properly from images ${images}`); + console.info(sizes); + return; + } + + var newsheet = os.make_tex_data(sheetsize,sheetsize); + + for (var i = 0; i < pos.length; i++) { + // Copy the texture to the new sheet + newsheet.copy(sheet_frames[i].texture, pos[i], sheet_frames[i].rect); + + // Update the frame's rect to the new position in normalized coordinates + sheet_frames[i].rect.x = pos[i][0] / newsheet.width; + sheet_frames[i].rect.y = pos[i][1] / newsheet.height; + sheet_frames[i].rect.width = sizes[i][0] / newsheet.width; + sheet_frames[i].rect.height = sizes[i][1] / newsheet.height; + sheet_frames[i].texture = newsheet; + } + + newsheet.load_gpu(); + spritesheet = newsheet; + return spritesheet; +} + +graphics.is_image = function(obj) +{ + if (obj.texture && obj.rect) return true; +} + +// Any request to it returns an image, which is a texture and rect. +graphics.texture = function texture(path) { + if (typeof path !== 'string') { + return path; + throw new Error('need a string for graphics.texture') + } + var parts = path.split(':'); + var ipath = Resources.find_image(parts[0]); + + graphics.texture.cache[ipath] ??= create_image(ipath); + return graphics.texture.cache[ipath]; +} + +graphics.texture.cache = {}; +graphics.texture.time_cache = {}; + +graphics.texture.total_size = function() +{ + var size = 0; +// Object.values(graphics.texture.cache).forEach(x => size += x.texture.inram() ? x..texture.width*x.texture.height*4 : 0); + return size; +} + +graphics.texture.total_vram = function() +{ + var vram = 0; +// Object.values(graphics.texture.cache).forEach(x => vram += x.vram); + return vram; +} + +graphics.tex_hotreload = function tex_hotreload(file) { + if (!(file in graphics.texture.cache)) return; + + var img = create_image(file); + var oldimg = graphics.texture.cache[file]; + console.log(json.encode(img)) + + merge_objects(oldimg,img, ['surface', 'texture', 'loop', 'time']); + graphics.texture.cache[file] = img; +}; + +function make_spritesheet(paths, width, height) +{ + return + var textures = paths.map(path => graphics.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"); + + sheet.load_gpu(); +} + + +graphics.semver = {}; +graphics.semver.valid = function (v, range) { + v = v.split("."); + range = range.split("."); + if (v.length !== 3) return undefined; + if (range.length !== 3) return undefined; + + if (range[0][0] === "^") { + range[0] = range[0].slice(1); + if (parseInt(v[0]) >= parseInt(range[0])) return true; + + return false; + } + + if (range[0] === "~") { + range[0] = range[0].slice(1); + for (var i = 0; i < 2; i++) if (parseInt(v[i]) < parseInt(range[i])) return false; + return true; + } + + return graphics.semver.cmp(v.join("."), range.join(".")) === 0; +}; + +graphics.semver.cmp = function (v1, v2) { + var ver1 = v1.split("."); + var ver2 = v2.split("."); + + for (var i = 0; i < 3; i++) { + var n1 = parseInt(ver1[i]); + var n2 = parseInt(ver2[i]); + if (n1 > n2) return 1; + else if (n1 < n2) return -1; + } + + return 0; +}; + +graphics.semver.cmp.doc = "Compare two semantic version numbers, given like X.X.X."; +graphics.semver.valid.doc = `Test if semantic version v is valid, given a range. +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.`; + +return graphics diff --git a/scripts/input.js b/scripts/input.js index 351760cd..05764f7a 100644 --- a/scripts/input.js +++ b/scripts/input.js @@ -1,3 +1,5 @@ +var input = os.use('input') + var downkeys = {}; function keyname(key) @@ -314,6 +316,7 @@ var Player = { pawns: [], control(pawn) { + if (!pawn) return if (!pawn.inputs) { console.warn(`attempted to control a pawn without any input object.`); return; @@ -333,9 +336,8 @@ input.do_uncontrol = function input_do_uncontrol(pawn) { }); }; -for (var i = 0; i < 4; i++) { +for (var i = 0; i < 4; i++) Player.create(); -} Player.control.doc = "Control a provided object, if the object has an 'inputs' object."; Player.uncontrol.doc = "Uncontrol a previously controlled object."; @@ -345,6 +347,6 @@ Player.doc.players = "A list of current players."; var player = Player; -return { - player, -}; +input.player = Player + +return input diff --git a/scripts/layout.js b/scripts/layout.js index db37fcd7..7d62b074 100644 --- a/scripts/layout.js +++ b/scripts/layout.js @@ -1,6 +1,11 @@ // Layout code // Contain is for how it will treat its children. If they should be laid out as a row, or column, or in a flex style, etc. +var geometry = use('geometry') +var render = use('render') +var graphics = use('graphics') +var gizmo = use('gizmos') + var lay_ctx = layout.make_context(); var clay_base = { @@ -21,25 +26,9 @@ var clay_base = { var root_item; var root_config; var boxes = []; -globalThis.clay = {}; +var clay = {} -clay.normalizeSpacing = function normalizeSpacing(spacing) { - if (typeof spacing === 'number') { - return {l: spacing, r: spacing, t: spacing, b: spacing}; - } else if (Array.isArray(spacing)) { - if (spacing.length === 2) { - return {l: spacing[0], r: spacing[0], t: spacing[1], b: spacing[1]}; - } else if (spacing.length === 4) { - return {l: spacing[0], r: spacing[1], t: spacing[2], b: spacing[3]}; - } - } else if (typeof spacing === 'object') { - return {l: spacing.l || 0, r: spacing.r || 0, t: spacing.t || 0, b: spacing.b || 0}; - } else { - return {l:0, r:0, t:0, b:0}; - } -} - -clay.draw = function draw(size, fn, config = {}) +layout.draw = function draw(size, fn, config = {}) { lay_ctx.reset(); boxes = []; @@ -61,7 +50,7 @@ clay.draw = function draw(size, fn, config = {}) box.content = lay_ctx.get_rect(box.id); box.boundingbox = Object.assign({}, box.content); - var padding = clay.normalizeSpacing(box.config.padding || 0); + var padding = gizmo.normalizeSpacing(box.config.padding || 0); if (padding.l || padding.r || padding.t || padding.b) { // Adjust the boundingbox to include the padding box.boundingbox.x -= padding.l; @@ -70,7 +59,7 @@ clay.draw = function draw(size, fn, config = {}) box.boundingbox.height += padding.t + padding.b; } box.marginbox = Object.assign({}, box.content); - var margin = clay.normalizeSpacing(box.config.margin || 0); + var margin = gizmo.normalizeSpacing(box.config.margin || 0); box.marginbox.x -= margin.l; box.marginbox.y -= margin.t; box.marginbox.width += margin.l+margin.r; @@ -104,15 +93,15 @@ function create_view_fn(base_config) } } -clay.vstack = create_view_fn({ +layout.vstack = create_view_fn({ contain: layout.contain.column | layout.contain.start, }); -clay.hstack = create_view_fn({ +layout.hstack = create_view_fn({ contain: layout.contain.row | layout.contain.start, }); -clay.spacer = create_view_fn({ +layout.spacer = create_view_fn({ behave: layout.behave.hfill | layout.behave.vfill }); @@ -124,8 +113,8 @@ function image_size(img) function add_item(config) { // Normalize the child's margin - var margin = clay.normalizeSpacing(config.margin || 0); - var padding = clay.normalizeSpacing(config.padding || 0); + var margin = gizmo.normalizeSpacing(config.margin || 0); + var padding = gizmo.normalizeSpacing(config.padding || 0); var childGap = root_config.child_gap || 0; // Adjust for child_gap @@ -180,16 +169,16 @@ function rectify_configs(config_array) return cleanobj; } -clay.image = function image(path, ...configs) +layout.image = function image(path, ...configs) { var config = rectify_configs(configs); - var image = game.texture(path); + var image = graphics.texture(path); config.image = image; config.size ??= [image.texture.width, image.texture.height]; add_item(config); } -clay.text = function text(str, ...configs) +layout.text = function text(str, ...configs) { var config = rectify_configs(configs); config.size ??= [0,0]; @@ -211,7 +200,7 @@ var button_base = Object.assign(Object.create(clay_base), { hovered:{ } }); -clay.button = function button(str, action, config = {}) +layout.button = function button(str, action, config = {}) { config.__proto__ = button_base; config.size = render.text_size(str,config.font); @@ -222,6 +211,7 @@ clay.button = function button(str, action, config = {}) var hovered = undefined; layout.newframe = function() { hovered = undefined; } + // mousepos given in hud coordinates layout.draw_commands = function draw_commands(cmds, pos = [0,0], mousepos = prosperon.camera.screen2hud(input.mouse.screenpos())) { @@ -277,5 +267,5 @@ layout.inputs.mouse.left = function() layout.toString = _ => "layout" -return layout; +return layout diff --git a/scripts/nogame.js b/scripts/nogame.js index aed1e73d..5aed73c3 100644 --- a/scripts/nogame.js +++ b/scripts/nogame.js @@ -1,7 +1,7 @@ -var layout = use("layout.js"); +var clay = use("layout.js"); this.hud = function () { - layout.draw_commands(clay.draw([], _ => { + clay.draw_commands(clay.draw([], _ => { clay.text("No game yet! Make main.js to get started!"); })); }; diff --git a/scripts/particle.js b/scripts/particle.js index 81f16dd7..b765f8f5 100644 --- a/scripts/particle.js +++ b/scripts/particle.js @@ -1,3 +1,7 @@ +var Color = use('color') + +var ex = {} + var emitter = {}; emitter.life = 10; emitter.scale = 1; @@ -85,7 +89,7 @@ emitter.burst = function (count, t) { var emitters = []; -var make_emitter = function () { +ex.make = function make_emitter() { var e = Object.create(emitter); e.particles = []; e.dead = []; @@ -93,11 +97,11 @@ var make_emitter = function () { return e; }; -function update_emitters(dt) { +ex.update = function update_emitters(dt) { for (var e of emitters) e.step(dt); } -function stat_emitters() +ex.stat = function stat_emitters() { var stat = {}; stat.emitters = emitters.length; @@ -107,6 +111,6 @@ function stat_emitters() return stat; } -function all_emitters() { return emitters; } +ex.all = function all_emitters() { return emitters; } -return { make_emitter, update_emitters, stat_emitters, all_emitters }; +return ex diff --git a/scripts/physics.js b/scripts/physics.js index 5dc07394..ae81c6d3 100644 --- a/scripts/physics.js +++ b/scripts/physics.js @@ -9,7 +9,9 @@ var HIT = { }; */ -export function pos_query(pos, start = world, give = 10) { +var phys = {}; + +phys.pos_query = function pos_query(pos, start = world, give = 10) { var ret; ret = physics.point_query_nearest(pos, 0); @@ -21,12 +23,12 @@ export function pos_query(pos, start = world, give = 10) { }); }; -export function box_point_query(box, points) { - if (!box || !points) return []; +phys.box_point_query = function box_point_query(box, points) { +/* if (!box || !points) return []; var bbox = bbox.fromcwh(box.pos, box.wh); var inside = []; for (var i in points) if (bbox.pointin(bbox, points[i])) inside.push[i]; - return inside; + return inside;*/ }; Object.assign(physics, { diff --git a/scripts/profile.js b/scripts/profile.js index 34e92481..c4b6c7bb 100644 --- a/scripts/profile.js +++ b/scripts/profile.js @@ -1,3 +1,5 @@ +var profile = os.use('profile') + /* TYPES OF PROFILING report - can see specific events that happened. Includes inclusive vs noninclusive times. When used on top of each other, also generates a callstack. @@ -5,6 +7,8 @@ memory - can see how much memory is allocated and from where [not implemented yet] */ +var graphics = use('graphics') + function calc_cpu(fn, times, diff = 0) { var series = []; @@ -270,8 +274,8 @@ var get_snapshot = function() } snap.actors = actor.__stats(); - snap.memory.textures = game.texture.total_size(); - snap.memory.texture_vram = game.texture.total_vram(); + snap.memory.textures = graphics.texture.total_size(); + snap.memory.texture_vram = graphics.texture.total_vram(); snap.particles = stat_emitters(); } @@ -383,4 +387,4 @@ profile.print_gc = function () { profile.data.gc[profile.curframe] = gc; }; -return { profile }; +return profile diff --git a/scripts/prosperon.js b/scripts/prosperon.js deleted file mode 100644 index 9a7ff3b4..00000000 --- a/scripts/prosperon.js +++ /dev/null @@ -1,556 +0,0 @@ -globalThis.gamestate = {}; - -global.pull_registers = function(obj) -{ - var reggies = []; - for (var reg in Register.registries) { - if (typeof obj[reg] === "function") - reggies.push(reg); - } - return reggies; -} - -function register_obj(obj, reg) -{ - var fn = obj[reg].bind(obj); - fn.layer = obj[reg].layer; - var name = obj.ur ? obj.ur.name : obj.toString(); - obj.timers.push(Register.registries[reg].register(fn, name)); - if (!obj[reg].name) Object.defineProperty(obj[reg], 'name', {value:`${obj._file}_${reg}`}); -} - -global.check_registers = function check_registers(obj) { - if (obj.__reggies) { - if (obj.__reggies.length == 0) return; - // fast path - for (var reg of obj.__reggies) - register_obj(obj,reg); - - return; - } - - for (var reg in Register.registries) { - if (typeof obj[reg] === "function") - register_obj(obj,reg); - } - -/* for (var k in obj) { - if (!k.startsWith("on_")) continue; - var signal = k.fromfirst("on_"); - Event.observe(signal, obj, obj[k]); - }*/ -}; - -globalThis.timers = [] -global.setTimeout = function(fn,seconds, ...args) { - return prosperon.add_timer(globalThis, _ => fn.apply(undefined,args), seconds); -} -global.clearTimeout = function(id) { id(); } - -prosperon.delay = setTimeout; - -global.obscure("global"); -global.mixin("render"); -global.mixin("debug"); -global.mixin('layout') -globalThis.parseq = use('parseq'); - -globalThis.sim = {}; -sim.mode = "play"; -sim.play = function () { - this.mode = "play"; -// os.reindex_static(); - game.all_objects(o => { - if (!o._started) { - o._started = true; - o.start?.(); - } - }); -}; -sim.playing = function () { - return this.mode === "play"; -}; -sim.pause = function () { - this.mode = "pause"; -}; -sim.paused = function () { - return this.mode === "pause"; -}; -sim.step = function () { - this.mode = "step"; -}; -sim.stepping = function () { - return this.mode === "step"; -}; - -var frame_t = profile.now(); - -var physlag = 0; - -prosperon.SIGABRT = function() -{ - console.error(new Error('SIGABRT')); - os.exit(1); -} - -prosperon.SIGSEGV = function() -{ - console.error(new Error('SIGSEGV')); - os.exit(1); -} - -prosperon.exit = function() -{ - -} - -prosperon.init = function () { - render.init(); -// imgui.init(render._main); - tracy.gpu_init(); - - globalThis.audio = use("sound.js"); - world_start(); -/* shape.quad = { - pos: os.make_buffer([ - 0, 0, 0, - 0, 1, 0, - 1, 0, 0, - 1, 1, 0], 0), - verts: 4, - uv: os.make_buffer([ - 0, 1, - 0, 0, - 1, 1, - 1, 0], 2), - index: os.make_buffer([0, 1, 2, 2, 1, 3], 1), - count: 6, - }; - - shape.triangle = { - pos: os.make_buffer([0, 0, 0, 0.5, 1, 0, 1, 0, 0], 0), - uv: os.make_buffer([0, 0, 0.5, 1, 1, 0], 2), - verts: 3, - count: 3, - index: os.make_buffer([0, 1, 2], 1), - };*/ - if (io.exists("main.js")) global.app = actor.spawn("main.js"); - else global.app = actor.spawn("nogame.js"); -}; - -prosperon.release_mode = function () { - prosperon.debug = false; - debug.kill(); -}; -prosperon.debug = true; - -game.timescale = 1; - -var eachobj = function (obj, fn) { - var val = fn(obj); - if (val) return val; - for (var o in obj.objects) { - if (obj.objects[o] === obj) console.error(`Object ${obj.toString()} is referenced by itself.`); - val = eachobj(obj.objects[o], fn); - if (val) return val; - } -}; - -game.all_objects = function (fn, startobj = world) { - return eachobj(startobj, fn); -}; -game.find_object = function (fn, startobj = world) {}; - -game.tags = {}; -game.tag_add = function (tag, obj) { - game.tags[tag] ??= {}; - game.tags[tag][obj.guid] = obj; -}; - -game.tag_rm = function (tag, obj) { - delete game.tags[tag][obj.guid]; -}; - -game.tag_clear_guid = function (guid) { - for (var tag in game.tags) delete game.tags[tag][guid]; -}; - -game.objects_with_tag = function (tag) { - if (!game.tags[tag]) return []; - return Object.values(game.tags[tag]); -}; - -game.doc = {}; -game.doc.object = "Returns the entity belonging to a given id."; -game.doc.pause = "Pause game simulation."; -game.doc.play = "Resume or start game simulation."; - -function calc_image_size(img) -{ - if (!img.texture || !img.rect) return; - return [img.texture.width*img.rect.width, img.texture.height*img.rect.height]; -} - -function create_image(path) -{ - var data = io.slurpbytes(path); - var newimg; - switch(path.ext()) { - case 'gif': - newimg = os.make_gif(data); - if (newimg.surface) - newimg.texture = render._main.load_texture(newimg.surface); - else - for (var frame of newimg.frames) - frame.texture = render._main.load_texture(frame.surface); - break; - case 'ase': - case 'aseprite': - newimg = os.make_aseprite(data); - if (newimg.surface) - newimg.texture = render._main.load_texture(newimg.surface); - else { - for (var anim in newimg) { - var a = newimg[anim]; - for (var frame of a.frames) - frame.texture = render._main.load_texture(frame.surface); - } - } - break; - default: - newimg = { - surface: os.make_texture(data) - }; - newimg.texture = render._main.load_texture(newimg.surface); - break; - } - return newimg; -} - -function merge_objects(oldobj,newobj, properties) { - function recursive_merge(target,src) { - for (var key of Object.keys(src)) { - if (properties.includes(key)) { - target[key] = src[key]; - continue; - } - if (src[key] && typeof src[key] === 'object' && target[key] && typeof target[key] === 'object') - recursive_merge(target[key],src[key]) - } - } - recursive_merge(oldobj,newobj); -} - -game.tex_hotreload = function tex_hotreload(file) { - if (!(file in game.texture.cache)) return; - - var img = create_image(file); - var oldimg = game.texture.cache[file]; - console.log(json.encode(img)) - - merge_objects(oldimg,img, ['surface', 'texture', 'loop', 'time']); - game.texture.cache[file] = img; -}; - -var image = {}; -image.dimensions = function() -{ - return [this.texture.width, this.texture.height].scale([this.rect[2], this.rect[3]]); -} - -var spritesheet; -var sheet_frames = []; -var sheetsize = 1024; - -function pack_into_sheet(images) -{ - return; - if (!Array.isArray(images)) images = [images]; - if (images[0].texture.width > 300 && images[0].texture.height > 300) return; - sheet_frames = sheet_frames.concat(images); - var sizes = sheet_frames.map(x => [x.rect.width*x.texture.width, x.rect.height*x.texture.height]); - var pos = os.rectpack(sheetsize, sheetsize, sizes); - if (!pos) { - console.error(`did not make spritesheet properly from images ${images}`); - console.info(sizes); - return; - } - - var newsheet = os.make_tex_data(sheetsize,sheetsize); - - for (var i = 0; i < pos.length; i++) { - // Copy the texture to the new sheet - newsheet.copy(sheet_frames[i].texture, pos[i], sheet_frames[i].rect); - - // Update the frame's rect to the new position in normalized coordinates - sheet_frames[i].rect.x = pos[i][0] / newsheet.width; - sheet_frames[i].rect.y = pos[i][1] / newsheet.height; - sheet_frames[i].rect.width = sizes[i][0] / newsheet.width; - sheet_frames[i].rect.height = sizes[i][1] / newsheet.height; - sheet_frames[i].texture = newsheet; - } - - newsheet.load_gpu(); - spritesheet = newsheet; - return spritesheet; -} - -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. -game.texture = function texture(path) { - if (typeof path !== 'string') { - return path; - throw new Error('need a string for game.texture') - } - var parts = path.split(':'); - var ipath = Resources.find_image(parts[0]); - - game.texture.cache[ipath] ??= create_image(ipath); - return game.texture.cache[ipath]; -} - -game.texture.cache = {}; -game.texture.time_cache = {}; - -game.texture.total_size = function() -{ - var size = 0; -// Object.values(game.texture.cache).forEach(x => size += x.texture.inram() ? x..texture.width*x.texture.height*4 : 0); - return size; -} - -game.texture.total_vram = function() -{ - var vram = 0; -// Object.values(game.texture.cache).forEach(x => vram += x.vram); - return vram; -} - -prosperon.semver = {}; -prosperon.semver.valid = function (v, range) { - v = v.split("."); - range = range.split("."); - if (v.length !== 3) return undefined; - if (range.length !== 3) return undefined; - - if (range[0][0] === "^") { - range[0] = range[0].slice(1); - if (parseInt(v[0]) >= parseInt(range[0])) return true; - - return false; - } - - if (range[0] === "~") { - range[0] = range[0].slice(1); - for (var i = 0; i < 2; i++) if (parseInt(v[i]) < parseInt(range[i])) return false; - return true; - } - - return prosperon.semver.cmp(v.join("."), range.join(".")) === 0; -}; - -prosperon.semver.cmp = function (v1, v2) { - var ver1 = v1.split("."); - var ver2 = v2.split("."); - - for (var i = 0; i < 3; i++) { - var n1 = parseInt(ver1[i]); - var n2 = parseInt(ver2[i]); - if (n1 > n2) return 1; - else if (n1 < n2) return -1; - } - - return 0; -}; - -prosperon.semver.cmp.doc = "Compare two semantic version numbers, given like X.X.X."; -prosperon.semver.valid.doc = `Test if semantic version v is valid, given a range. -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.`; - -global.mixin("input"); -global.mixin("std"); -global.mixin("diff"); -global.mixin("color"); -global.mixin("tween"); -global.mixin("particle"); -//global.mixin("physics"); -global.mixin("geometry"); -/* -Factory for creating registries. Register one with 'X.register', -which returns a function that, when invoked, cancels the registry. -*/ -globalThis.Register = { - registries: [], - - add_cb(name) { - var n = {}; - var fns = []; - - n.register = function (fn, oname) { - if (!(fn instanceof Function)) return; - - var guid = prosperon.guid(); - - var dofn = function (...args) { - fn(...args); - }; - Object.defineProperty(dofn, 'name', {value:`do_${oname}`}); - - var left = 0; - var right = fns.length - 1; - dofn.layer = fn.layer; - dofn.layer ??= 0; - - while (left <= right) { - var mid = Math.floor((left + right) / 2); - if (fns[mid] === dofn.layer) { - left = mid; - break; - } else if (fns[mid].layer < dofn.layer) left = mid + 1; - else right = mid - 1; - } - - fns.splice(left, 0, dofn); - - return function () { - fns.remove(dofn); - }; - }; - - prosperon[name] = function (...args) { -// tracy.fiber_enter(vector.fib); - fns.forEach(fn => fn(...args)); -// tracy.fiber_leave(vector.fib); - }; - - Object.defineProperty(prosperon[name], 'name', {value:name}); - prosperon[name].fns = fns; - n.clear = function () { - fns = []; - }; - - Register[name] = n; - Register.registries[name] = n; - - return n; - }, -}; - -Register.add_cb("appupdate"); -Register.add_cb("update").doc = "Called once per frame."; -Register.add_cb("physupdate"); -Register.add_cb("gui"); -Register.add_cb("hud"); -Register.add_cb("draw"); -Register.add_cb("imgui"); -Register.add_cb("app"); -Register.add_cb("prerender"); - -global.mixin("components"); - -var Event = { - events: {}, - - observe(name, obj, fn) { - this.events[name] ??= []; - this.events[name].push([obj, fn]); - }, - - unobserve(name, obj) { - this.events[name] = this.events[name].filter(x => x[0] !== obj); - }, - - rm_obj(obj) { - Object.keys(this.events).forEach(name => Event.unobserve(name, obj)); - }, - - notify(name, ...args) { - if (!this.events[name]) return; - this.events[name].forEach(function (x) { - x[1].call(x[0], ...args); - }); - }, -}; - -prosperon.add_timer = function(obj, fn, seconds) -{ - var timers = obj.timers; - - var stop = function () { - timers.remove(stop); - timer.fn = undefined; - timer = undefined; - }; - - function execute() { - if (fn) - timer.remain = fn(stop.seconds); - - if (!timer.remain) - stop(); - else - stop.seconds = timer.remain; - } - - var timer = os.make_timer(execute); - timer.remain = seconds; - - stop.remain = seconds; - stop.seconds = seconds; - - timers.push(stop); - return stop; -} - -global.mixin("spline"); -global.mixin("actor"); -global.mixin("entity"); - -function world_start() { - globalThis.world = Object.create(entity); - world.transform = os.make_transform(); - world.objects = {}; - world.toString = function () { - return "world"; - }; - world.ur = "world"; - world.kill = function () { - this.clear(); - }; - world.phys = 2; - world.zoom = 1; - world._ed = { selectable: false }; - world.ur = {}; - world.ur.fresh = {}; -} - -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 { - sim, - frame_t, - physlag, - Event, -}; diff --git a/scripts/render.js b/scripts/render.js index c28d83f3..819b81d9 100644 --- a/scripts/render.js +++ b/scripts/render.js @@ -1,3 +1,35 @@ +var render = {} +var profile = use('profile') +var game = os.use('game') +var config = use('config.js') +var gizmo = use('gizmos') + +game.timescale = 1 + +prosperon.window = game.engine_start(config); + +var driver = "vulkan" +switch(os.sys()) { + case "Linux": + driver = "vulkan" + break + case "Windows": +// driver = "direct3d12" + driver = "vulkan" + break + case "macOS": + driver = "metal" + break +} + +render._main = prosperon.window.make_gpu(false,driver) +prosperon.gpu = render._main +render._main.window = prosperon.window +render._main.claim_window(prosperon.window) +render._main.set_swapchain('sdr', 'vsync') + +var graphics = use('graphics') + var unit_transform = os.make_transform(); render.doc = { @@ -469,6 +501,7 @@ function render_camera(cmds, camera) { var pass; try{ + delete camera.target // TODO: HORRIBLE if (!camera.target) { main_color.width = main_depth.width = camera.size.x; main_color.height = main_depth.height = camera.size.y; @@ -631,6 +664,18 @@ prosperon.camera = {}; // If camera viewport is defined, will draw to the screen // If target is defined, will render to a target, too +prosperon.camera.transform = os.make_transform(); +prosperon.camera.transform.unit(); +prosperon.camera.zoom = 1; +prosperon.camera.size = [640,360]; +prosperon.camera.mode = 'keep'; +prosperon.camera.viewport = {x:0,y:0,width:1,height:1} +prosperon.camera.fov = 45; +prosperon.camera.type = 'ortho'; +prosperon.camera.ortho = true +prosperon.camera.aspect = 16/9; +delete prosperon.camera.target; + prosperon.camera.draw_rect = function(size) { var mode = this.presentation || "letterbox" @@ -716,30 +761,6 @@ pipeline_model = Object.create(base_pipeline); pipeline_model.vertex = "model.vert" pipeline_model.fragment = "model.frag" -var quad_model; - -render.init = function () { - shader_type = render._main.shader_format()[0]; - prosperon.font = render.get_font('fonts/c64.ttf', 8); - - std_sampler = render._main.make_sampler({ - min_filter: "nearest", - mag_filter: "nearest", - mipmap_mode: "nearest", - address_mode_u: "repeat", - address_mode_v: "repeat", - address_mode_w: "repeat" - }); - quad_model = render._main.make_quad(); - io.mount("core"); - render._main.present = gpupresent; - var cmds = render._main.acquire_cmd_buffer(); - cmds.__proto__.upload_model = upload_model; - cmds.upload_model(quad_model); - cmds.submit(); - imgui.init(render._main, prosperon.window); -}; - render.draw_sprites = true; render.draw_particles = true; render.draw_hud = true; @@ -758,6 +779,8 @@ function insertion_sort(arr, cmp) return arr } +var sprite = use('sprite') + function sprites_to_queue(ysort = false) { var pos = prosperon.camera.transform.pos; @@ -768,13 +791,8 @@ function sprites_to_queue(ysort = false) width:size.x, height:size.y }; - var culled = sprite_qt.query(camrect) - if (globalThis.so_sprite_qt) { - var cull2 = so_sprite_qt.query(camrect) - culled = culled.concat(cull2) - } + var culled = sprite.tree.query(camrect) if (culled.length == 0) return []; - var cmd = render._main.make_sprite_queue(culled, prosperon.camera, sprite_pipeline, 1); return cmd; } @@ -912,12 +930,11 @@ render.text = function text(text, rect, font = prosperon.font, size = 0, color = os.make_text_buffer(str, pos, size, color, wrap, font); // this puts text into buffer }; -var tttsize = render.text_size; render.text_size = function(str, font, ...args) { if (typeof font === 'string') font = render.get_font(font); - return tttsize(str,font, ...args); + return font.text_size(str, ...args); } var stencil_write = { @@ -964,7 +981,7 @@ var stencil_invert = { render.mask = function mask(image, pos, scale, rotation = 0, ref = 1) { if (typeof image === 'string') - image = game.texture(image); + image = graphics.texture(image); var tex = image.texture; @@ -994,7 +1011,7 @@ function tile(image, rect = [0,0], color = Color.white, repeat = {}) { if (!image) throw Error ('Need an image to render.') if (typeof image === "string") - image = game.texture(image); + image = graphics.texture(image); render._main.tile(image, rect, undefined, 1); return; @@ -1019,7 +1036,7 @@ var std_sprite_cmd = { render.image = function image(image, rect = [0,0], rotation = 0, color, pipeline) { if (!image) throw Error ('Need an image to render.') if (typeof image === "string") - image = game.texture(image); + image = graphics.texture(image); rect.width ??= image.texture.width; rect.height ??= image.texture.height; @@ -1039,7 +1056,7 @@ render.image = function image(image, rect = [0,0], rotation = 0, color, pipeline render.images = function images(image, rects, config) { if (!image) throw Error ('Need an image to render.'); - if (typeof image === "string") image = game.texture(image); + if (typeof image === "string") image = graphics.texture(image); var bb = []; bb.width = image.texture.width; @@ -1075,7 +1092,7 @@ render.tile = function(image, rect, color = Color.white, tile = tile_def, pipeli { if (!image) throw Error ('Need an image to render.') if (typeof image === "string") - image = game.texture(image); + image = graphics.texture(image); var mesh = render._main.tile(image.texture, {x:0,y:0,width:image.texture.width,height:image.texture.height}, rect, tile); current_queue.push({ @@ -1100,9 +1117,9 @@ var slice9_info = { render.slice9 = function slice9(image, rect = [0,0], slice = 0, color = Color.white, info = slice9_info, pipeline = sprite_pipeline) { if (!image) throw Error ('Need an image to render.') if (typeof image === "string") - image = game.texture(image); + image = graphics.texture(image); - var mesh = render._main.slice9(image.texture, rect, clay.normalizeSpacing(slice), info); + var mesh = render._main.slice9(image.texture, rect, gizmo.normalizeSpacing(slice), info); current_queue.push({ type: 'geometry', mesh, @@ -1188,7 +1205,7 @@ screen2cam.doc = "Convert a screen space position in pixels to a normalized view prosperon.gizmos = function gizmos() { game.all_objects(o => { - if (o.gizmo) render.image(game.texture(o.gizmo), o.pos); + if (o.gizmo) render.image(graphics.texture(o.gizmo), o.pos); }); }; @@ -1260,7 +1277,7 @@ var imgui_fn = function imgui_fn() { prosperon.title = imgui.textinput("Title", prosperon.title); prosperon.icon = imgui.textinput("Icon", prosperon.icon); imgui.button("Refresh window", _ => { - prosperon.set_icon(game.texture(prosperon.icon)); + prosperon.set_icon(graphics.texture(prosperon.icon)); }); }); imgui.button("quit", os.exit); @@ -1307,8 +1324,8 @@ var imgui_fn = function imgui_fn() { } var texs = {}; - for (var path in game.texture.cache) { - var image = game.texture.cache[path]; + for (var path in graphics.texture.cache) { + var image = graphics.texture.cache[path]; if (image.texture && !texs[image.texture]) texs[image.texture] = image.texture; } @@ -1324,6 +1341,7 @@ var imgui_fn = function imgui_fn() { // imgui.endframe(render._main); }; +var dmon = use('dmon') if (dmon) dmon.watch('.'); function dmon_cb(e) @@ -1331,20 +1349,24 @@ function dmon_cb(e) try { io.invalidate(); if (e.file.startsWith('.')) return; - if (e.file.endsWith('.js')) - actor.hotreload(e.file); - else if (e.file.endsWith('.hlsl')) +// if (e.file.endsWith('.js')) +// actor.hotreload(e.file); + if (e.file.endsWith('.hlsl')) shader_hotreload(e.file); else if (Resources.is_image(e.file)) game.tex_hotreload(e.file); } catch(e) { console.error(e); } } +var sim = use('sim') +var emitters = use('particle') + var waittime = 1/240; var last_frame_time = 0; +var frame_t = 0; // Ran once per frame var fpses = []; -prosperon.process = function process() { +render.process = function process() { var now = profile.now(); var dt = now - last_frame_time; if (dt < waittime) os.sleep(waittime-dt); @@ -1357,7 +1379,6 @@ prosperon.process = function process() { try { game.engine_input(e => { - prosperon[e.type]?.(e); }); } catch(e) { console.error(e); } @@ -1367,7 +1388,7 @@ try { try { prosperon.appupdate(dt); } catch(e) { console.error(e) } input.procdown(); try { - update_emitters(dt * game.timescale); + emitters.update(dt * game.timescale); os.update_timers(dt * game.timescale); prosperon.update(dt*game.timescale); } catch(e) { console.error(e) } @@ -1387,7 +1408,7 @@ try { current_queue = render_queue; try { prosperon.draw(); } catch(e) { console.error(e) } - for (var e of all_emitters()) + for (var e of emitters.all()) render.particles(e); current_queue = hud_queue; try { prosperon.hud(); } catch(e) { console.error(e) } @@ -1398,4 +1419,22 @@ try { tracy.end_frame(); }; -return { render }; +// Some initialization +shader_type = render._main.shader_format()[0]; +prosperon.font = render.get_font('fonts/c64.ttf', 8); + +std_sampler = render._main.make_sampler({ + min_filter: "nearest", + mag_filter: "nearest", + mipmap_mode: "nearest", + address_mode_u: "repeat", + address_mode_v: "repeat", + address_mode_w: "repeat" +}); + +io.mount("core"); +render._main.present = gpupresent; +imgui.init(render._main, prosperon.window); +tracy.gpu_init() + +return render diff --git a/scripts/search.js b/scripts/search.js new file mode 100644 index 00000000..008c1388 --- /dev/null +++ b/scripts/search.js @@ -0,0 +1,37 @@ +var ex = {} + +var eachobj = function (obj, fn) { + var val = fn(obj); + if (val) return val; + for (var o in obj.objects) { + if (obj.objects[o] === obj) console.error(`Object ${obj.toString()} is referenced by itself.`); + val = eachobj(obj.objects[o], fn); + if (val) return val; + } +}; + +ex.all_objects = function (fn, startobj = world) { + return eachobj(startobj, fn); +}; +ex.find_object = function (fn, startobj = world) {}; + +var gtags = {}; +ex.tag_add = function (tag, obj) { + gtags[tag] ??= new Set(); + gtags[tag].add(obj) +}; + +ex.tag_rm = function (tag, obj) { + delete gtags[tag].delete(obj) +}; + +ex.tag_clear_guid = function (obj) { + for (var tag in gtags) gtags[tag].delete(obj) +}; + +ex.objects_with_tag = function (tag) { + if (!gtags[tag]) return []; + return Array.from(gtags[tag]) +}; + +return ex diff --git a/scripts/sim.js b/scripts/sim.js new file mode 100644 index 00000000..7135d187 --- /dev/null +++ b/scripts/sim.js @@ -0,0 +1,29 @@ +var sim = {}; +sim.mode = "play"; +sim.play = function () { + this.mode = "play"; +// os.reindex_static(); + game.all_objects(o => { + if (!o._started) { + o._started = true; + o.start?.(); + } + }); +}; +sim.playing = function () { + return this.mode === "play"; +}; +sim.pause = function () { + this.mode = "pause"; +}; +sim.paused = function () { + return this.mode === "pause"; +}; +sim.step = function () { + this.mode = "step"; +}; +sim.stepping = function () { + return this.mode === "step"; +}; + +return sim diff --git a/scripts/sound.js b/scripts/sound.js index 520b64b5..64870ba7 100644 --- a/scripts/sound.js +++ b/scripts/sound.js @@ -1,3 +1,5 @@ +//var soloud = use('soloud') +var tween = use('tween') soloud.init(); var audio = {}; @@ -46,7 +48,7 @@ audio.music = function music(file, fade = 0.5) { if (!song) { song = audio.play(file); song.volume = 1; - // tween(song,'volume', 1, fade); + // tween.tween(song,'volume', 1, fade); return; } @@ -55,8 +57,8 @@ audio.music = function music(file, fade = 0.5) { temp.volume = 1; var temp2 = song; - // tween(temp, 'volume', 1, fade); - // tween(temp2, 'volume', 0, fade); + // tween.tween(temp, 'volume', 1, fade); + // tween.tween(temp2, 'volume', 0, fade); song = temp; song.loop = true; }; diff --git a/scripts/sprite.js b/scripts/sprite.js index 678ea679..89488004 100644 --- a/scripts/sprite.js +++ b/scripts/sprite.js @@ -1,11 +1,8 @@ -globalThis.so_sprite_qt = os.make_rtree(); - var sprite = { +var graphics = use('graphics') + +var sprite = { image: undefined, - get diffuse() { return this.image; }, - set diffuse(x) {}, - set color(x) { - this._sprite.color = x; - }, + set color(x) { this._sprite.color = x; }, get color() { return this._sprite.color; }, anim_speed: 1, play(str, loop = true, reverse = false, fn) { @@ -27,9 +24,8 @@ globalThis.so_sprite_qt = os.make_rtree(); var stop; this.del_anim?.(); - self.del_anim = function () { - self.del_anim = undefined; - self = undefined; + this.del_anim = () => { + this.del_anim = undefined; advance = undefined; stop?.(); }; @@ -37,9 +33,7 @@ globalThis.so_sprite_qt = os.make_rtree(); var f = 0; if (reverse) f = playing.frames.length - 1; - function advance(time) { - if (!self) return; - + var advance = (time) => { var done = false; if (reverse) { f = (((f - 1) % playing.frames.length) + playing.frames.length) % playing.frames.length; @@ -49,27 +43,27 @@ globalThis.so_sprite_qt = os.make_rtree(); if (f === 0) done = true; } - self.image = playing.frames[f]; + this.image = playing.frames[f]; if (done) { // notify requestor fn?.(); if (!loop) { - self?.stop(); + this?.stop(); return; } } - return playing.frames[f].time/self.anim_speed; + return playing.frames[f].time/this.anim_speed; } - stop = self.delay(advance, playing.frames[f].time/self.anim_speed); + stop = this.delay(advance, playing.frames[f].time/this.anim_speed); advance(); }, stop() { this.del_anim?.(); }, set path(p) { - var image = game.texture(p); + var image = graphics.texture(p); if (!image) { console.warn(`Could not find image ${p}.`); return; @@ -106,10 +100,14 @@ globalThis.so_sprite_qt = os.make_rtree(); return this._p; }, garbage: function() { - console.log("KILLING SPRITE") this.del_anim?.(); this.anim = undefined; - so_sprite_qt.remove(this._sprite) + tree.delete(this._sprite) + this.transform.parent = undefined + for (var t of this.transform.children()) + t.parent = undefined + delete this.transform + delete this._sprite }, anchor: [0, 0], set layer(v) { this._sprite.layer = v; }, @@ -118,9 +116,7 @@ globalThis.so_sprite_qt = os.make_rtree(); return this; }, boundingbox() { - var dim = this.dimensions(); - var realpos = dim.scale(0.5).add(this.pos); - return bbox.fromcwh(realpos, dim); + return Object.freeze(this._sprite.rect) // freeze so it can't be modified on the outside } }; @@ -187,25 +183,34 @@ sprite.inputs.kp1 = function () { this.setanchor("ul"); }; +var tree = os.make_rtree() +sprite.tree = tree; + sprite.t_hook = function() { var msp = this.sprite; - so_sprite_qt.remove(msp); + if (this.__in) + tree.delete(msp); msp.rect = this.torect() msp.set_affine(this) - so_sprite_qt.insert(msp) + tree.add(msp) + this.__in = true } +Object.mixin(sprite,use("transform")) + return sprite; --- -if (!this.overling.transform) throw new Error("Overling must have a transform to have a sprite") +var Color = use('color') this.transform = os.make_transform(); -this.transform.parent = this.overling.transform; +if (this.overling.transform) + this.transform.parent = this.overling.transform; + this.transform.change_hook = $.t_hook; var msp = os.make_sprite(); this._sprite = msp; msp.color = Color.white; this.transform.sprite = msp -so_sprite_qt.insert(msp) + diff --git a/scripts/std.js b/scripts/std.js index 5a755478..dbd65c71 100644 --- a/scripts/std.js +++ b/scripts/std.js @@ -3,6 +3,9 @@ os.env.doc = "Return the value of the environment variable v."; if (os.sys() === "windows") os.user = os.env("USERNAME"); else os.user = os.env("USER"); +var sim = use('sim') +var io = use('io') + /*var ignore; if (ignore = io.slurp('.prosperonignore')) { ignore = ignore.split('\n'); @@ -44,7 +47,9 @@ appy.inputs.f11.doc = "Toggle window fullscreen."; appy.inputs.f11.title = "Toggle Fullscreen"; appy.inputs["M-f4"] = os.exit; -player[0].control(appy); +var input = use('input') + +input.player[0].control(appy); os.home = os.env("HOME"); @@ -70,7 +75,6 @@ os.openurl = function (url) { else os.system(`open ${url}`); }; -var projectfile = "project.prosperon"; io.dumpfolder = ".prosperon"; Resources.texture = {}; @@ -164,16 +168,7 @@ Cmdline.register_order( say("No game to edit. Try making one with 'prosperon init'."); return; } - sim.pause(); - - game.engine_start(function () { - global.mixin("editor.js"); - use("editorconfig.js"); - use("config.js"); - render.set_font("fonts/c64.ttf", 8); - editor.enter_editor(); - }); }, "Edit the project in this folder. Give it the name of an UR to edit that specific object.", "?UR?", @@ -217,65 +212,12 @@ Cmdline.register_order( function (argv) { if (argv[0]) io.chdir(argv[0]); - // game.loadurs(); + if (io.exists("main.js")) app.spawn("main.js") + else app.spawn("nogame.js"); - if (!io.exists(projectfile)) { - console.log("No game to play. Try making one with 'prosperon init'."); - return; - } + var ren = use('render') - var project = json.decode(io.slurp(projectfile)); - - prosperon.title = project.title; - prosperon.width = 1280; - prosperon.height = 720; - prosperon.size = [1280, 720]; - prosperon.icon = os.make_texture(io.slurpbytes('core/icons/moon.gif')); - prosperon.high_dpi = 0; - prosperon.alpha = 1; - prosperon.fullscreen = 0; - prosperon.sample_count = 1; - prosperon.enable_clipboard = true; - prosperon.enable_dragndrop=true; - prosperon.max_dropped_files=1; - prosperon.swap_interval = 1; - prosperon.title = "Prosperon"; - prosperon.name = prosperon.title; - prosperon.version = "432r23a"; - prosperon.identifier = "world.pockle.prosperon"; - prosperon.creator = "Pockle World LLC" - prosperon.copyright = "Copyright Pockle World 2025" - prosperon.type = "application" - prosperon.url = "https://github.com/johnbrethauer/prosperon" - - if (io.exists("config.js")) global.mixin("config.js"); - else console.warn("No config.js file found. Starting with default parameters."); - - prosperon.window = game.engine_start(prosperon); - var driver = "vulkan" - switch(os.sys()) { - case "Linux": - driver = "vulkan" - break - case "Windows": -// driver = "direct3d12" - driver = "vulkan" - break - case "macOS": - driver = "metal" - break - } - - render._main = prosperon.window.make_gpu(false, driver); - render._main.window = prosperon.window; - render._main.claim_window(prosperon.window); - render._main.set_swapchain("sdr", "vsync"); - var tt = game.texture('moon'); - tt.texture.__proto__.toString = function() { return os.value_id(this); } - - prosperon.init(); - - while(1) prosperon.process(); + while(1) ren.process(); }, "Play the game present in this folder.", ); @@ -606,9 +548,4 @@ function convertYAMLtoJSON(yamlString) { return jsonObj; } -return { - Resources, - Cmdline, - cmd_args, - convertYAMLtoJSON, -}; +return {cmd_args} diff --git a/scripts/transform.js b/scripts/transform.js new file mode 100644 index 00000000..84bd88bd --- /dev/null +++ b/scripts/transform.js @@ -0,0 +1,32 @@ +var ret = { + get pos() { + return this.transform.pos; + }, + set pos(x) { + this.transform.pos = x; + }, + get angle() { + return this.transform.angle; + }, + set angle(x) { + this.transform.angle = x; + }, + get scale() { + return this.transform.scale; + }, + set scale(x) { + this.transform.scale = x; + }, + move(vec) { + this.pos = this.pos.add(vec); + }, + rotate(x) { + this.transform.rotate([0, 0, -1],x); + }, + grow(vec) { + if (typeof vec === "number") vec = [vec, vec]; + this.scale = this.scale.map((x, i) => x * vec[i]); + }, +} + +return ret diff --git a/scripts/tween.js b/scripts/tween.js index 1fb13b92..b080728e 100644 --- a/scripts/tween.js +++ b/scripts/tween.js @@ -1,3 +1,5 @@ +var profile = use('profile') + /* Take numbers from 0 to 1 and remap them to easing functions */ var Ease = { linear(t) { diff --git a/source/jsffi.c b/source/jsffi.c index 4386bd45..fd0957ef 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -42,6 +42,8 @@ typedef struct rtree rtree; #include #include #include +#include + #ifdef __APPLE__ #include @@ -49,8 +51,6 @@ typedef struct rtree rtree; //#include #endif -#define RT_DEPTH SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT - static JSAtom width_atom; static JSAtom height_atom; static JSAtom l_atom; @@ -1126,8 +1126,6 @@ static void js_transform_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark QJSCLASSMARK(transform) QJSCLASS(font) -//QJSCLASS(warp_gravity) -//QJSCLASS(warp_damp) QJSCLASS(datastream) static JSClassID js_timer_id; @@ -1559,87 +1557,6 @@ int point2segindex(HMM_Vec2 p, HMM_Vec2 *segs, double slop) { return best; } -/*JSC_GETSET(warp_gravity, strength, number) -JSC_GETSET(warp_gravity, decay, number) -JSC_GETSET(warp_gravity, spherical, bool) -JSC_GETSET(warp_gravity, mask, bitmask) -JSC_GETSET(warp_gravity, planar_force, vec3) - -static const JSCFunctionListEntry js_warp_gravity_funcs [] = { - CGETSET_ADD(warp_gravity, strength), - CGETSET_ADD(warp_gravity, decay), - CGETSET_ADD(warp_gravity, spherical), - CGETSET_ADD(warp_gravity, mask), - CGETSET_ADD(warp_gravity, planar_force), -}; - -JSC_GETSET(warp_damp, damp, vec3) - -static const JSCFunctionListEntry js_warp_damp_funcs [] = { - CGETSET_ADD(warp_damp, damp) -}; -*/ - -HMM_Mat4 transform2view(transform *t) -{ - HMM_Vec3 look = HMM_AddV3(t->pos, transform_direction(t, vFWD)); - HMM_Mat4 ret = HMM_LookAt_RH(t->pos, look, vUP); - ret = HMM_MulM4(ret, HMM_Scale(t->scale)); - return ret; -} - -JSC_CCALL(render_set_projection_ortho, - lrtb extents = js2lrtb(js, argv[0]); - float nearme = js2number(js,argv[1]); - float farme = js2number(js,argv[2]); - globalview.p = HMM_Orthographic_RH_ZO( - extents.l, - extents.r, - extents.b, - extents.t, - nearme, - farme - ); - globalview.vp = HMM_MulM4(globalview.p, globalview.v); -) - -JSC_CCALL(render_set_projection_perspective, - float fov = js2number(js,argv[0]); - float aspect = js2number(js,argv[1]); - float nearme = js2number(js,argv[2]); - float farme = js2number(js,argv[3]); - globalview.p = HMM_Perspective_RH_NO(fov, aspect, nearme, farme); - globalview.vp = HMM_MulM4(globalview.p, globalview.v); -) - -JSC_CCALL(render_set_view, - globalview.v = transform2view(js2transform(js,argv[0])); - globalview.vp = HMM_MulM4(globalview.p, globalview.v); -) - -JSC_SCALL(render_text_size, - font *f = js2font(js,argv[1]); - float size = js2number(js,argv[2]); - if (!size) size = f->height; - float letterSpacing = js2number(js,argv[3]); - float wrap = js2number(js,argv[4]); - ret = vec22js(js,measure_text(str, f, size, letterSpacing, wrap)); -) - -JSC_CCALL(render_draw_color, - SDL_Renderer *renderer = js2SDL_Renderer(js,self); - colorf rgba = js2color(js,argv[0]); - SDL_SetRenderDrawColorFloat(renderer, rgba.r, rgba.g, rgba.b, rgba.a); -) - -static const JSCFunctionListEntry js_render_funcs[] = { - MIST_FUNC_DEF(render, text_size, 5), - MIST_FUNC_DEF(render, set_projection_ortho, 3), - MIST_FUNC_DEF(render, set_projection_perspective, 4), - MIST_FUNC_DEF(render, set_view, 1), - MIST_FUNC_DEF(render, draw_color, 1), -}; - static JSValue idx_buffer = JS_UNDEFINED; static int idx_count = 0; @@ -2738,8 +2655,21 @@ JSC_CCALL(game_engine_input, JS_FreeValue(js,ret); } ) +#include "wildmatch.h" +JSC_SSCALL(game_glob, + if (wildmatch(str, str2, WM_PATHNAME | WM_PERIOD | WM_WILDSTAR) == WM_MATCH) + ret = JS_NewBool(js,1); + else + ret = JS_NewBool(js, 0); +) -JSC_CCALL(game_cameras, +static const JSCFunctionListEntry js_game_funcs[] = { + MIST_FUNC_DEF(game, engine_start, 1), + MIST_FUNC_DEF(game, engine_input,1), + MIST_FUNC_DEF(game, glob, 2), +}; + +JSC_CCALL(camera_list, int num; SDL_CameraID *ids = SDL_GetCameras(&num); if (num == 0) return JS_UNDEFINED; @@ -2750,7 +2680,7 @@ JSC_CCALL(game_cameras, return jsids; ) -JSC_CCALL(game_open_camera, +JSC_CCALL(camera_open, int id = js2number(js,argv[0]); SDL_Camera *cam = SDL_OpenCamera(id, NULL); if (!cam) ret = JS_ThrowReferenceError(js, "Could not open camera %d: %s\n", id, SDL_GetError()); @@ -2758,22 +2688,14 @@ JSC_CCALL(game_open_camera, ret = SDL_Camera2js(js,cam); ) -#include "wildmatch.h" -JSC_SSCALL(game_glob, - if (wildmatch(str, str2, WM_PATHNAME | WM_PERIOD | WM_WILDSTAR) == WM_MATCH) - ret = JS_NewBool(js,1); - else - ret = JS_NewBool(js, 0); -) - -JSC_CCALL(game_camera_name, +JSC_CCALL(camera_name, const char *name = SDL_GetCameraName(js2number(js,argv[0])); if (!name) return JS_ThrowReferenceError(js, "Could not get camera name from id %d.", (int)js2number(js,argv[0])); return JS_NewString(js, name); ) -JSC_CCALL(game_camera_position, +JSC_CCALL(camera_position, SDL_CameraPosition pos = SDL_GetCameraPosition(js2number(js,argv[0])); switch(pos) { case SDL_CAMERA_POSITION_UNKNOWN: return JS_NewString(js,"unknown"); @@ -2782,14 +2704,11 @@ JSC_CCALL(game_camera_position, } ) -static const JSCFunctionListEntry js_game_funcs[] = { - MIST_FUNC_DEF(game, engine_start, 1), - MIST_FUNC_DEF(game, engine_input,1), - MIST_FUNC_DEF(game, cameras, 0), - MIST_FUNC_DEF(game, open_camera, 1), - MIST_FUNC_DEF(game, camera_name,1), - MIST_FUNC_DEF(game, camera_position,1), - MIST_FUNC_DEF(game, glob, 2), +static const JSCFunctionListEntry js_camera_funcs[] = { + MIST_FUNC_DEF(camera, list, 0), + MIST_FUNC_DEF(camera, open, 1), + MIST_FUNC_DEF(camera, name, 1), + MIST_FUNC_DEF(camera, position, 1), }; JSC_SCALL(SDL_Window_make_renderer, @@ -5665,8 +5584,8 @@ static const JSCFunctionListEntry js_debug_funcs[] = { MIST_FUNC_DEF(debug, build_backtrace, 0), MIST_FUNC_DEF(debug, closure_vars, 1), MIST_FUNC_DEF(debug, local_vars, 1), - MIST_FUNC_DEF(debug,fn_info, 1), - MIST_FUNC_DEF(debug,backtrace_fns,0), + MIST_FUNC_DEF(debug, fn_info, 1), + MIST_FUNC_DEF(debug, backtrace_fns,0), MIST_FUNC_DEF(debug, dump_obj, 1), }; @@ -5991,7 +5910,7 @@ static JSValue js_transform_get_parent(JSContext *js, JSValueConst self) static JSValue js_transform_set_parent(JSContext *js, JSValueConst self, JSValue v) { transform *p = js2transform(js,v); - if (!p) + if (!JS_IsUndefined(v) && !p) return JS_ThrowReferenceError(js,"Parent must be another transform."); transform *t = js2transform(js,self); @@ -6014,6 +5933,7 @@ static JSValue js_transform_set_parent(JSContext *js, JSValueConst self, JSValue } } + if (!p) return JS_UNDEFINED; t->parent = p; t->jsparent = JS_DupValue(js,v); @@ -6031,6 +5951,13 @@ JSC_CCALL(transform_torect, return rect2js(js,transform2rect(t)); ) +JSC_CCALL(transform_children, + transform *t = js2transform(js,self); + ret = JS_NewArray(js); + for (int i = 0; i < arrlen(t->jschildren); i++) + JS_SetPropertyUint32(js,ret,i,JS_DupValue(js,t->jschildren[i])); +) + static const JSCFunctionListEntry js_transform_funcs[] = { CGETSET_ADD(transform, pos), CGETSET_ADD(transform, scale), @@ -6048,6 +5975,7 @@ static const JSCFunctionListEntry js_transform_funcs[] = { MIST_FUNC_DEF(transform, rect, 1), MIST_FUNC_DEF(transform, array, 0), MIST_FUNC_DEF(transform, torect, 0), + MIST_FUNC_DEF(transform, children, 0), }; JSC_CCALL(datastream_time, return number2js(js,plm_get_time(js2datastream(js,self)->plm)); ) @@ -6080,11 +6008,21 @@ 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_GET(font, descent), + MIST_FUNC_DEF(font, text_size, 3), }; const char *STRTEST = "TEST STRING"; @@ -7251,6 +7189,53 @@ JSC_CCALL(os_clean_transforms, clean_all(); ) +typedef struct { + const char *name; + const JSCFunctionListEntry *fn; + size_t fn_count; +} ModuleEntry; + +#define MISTLINE(NAME) { #NAME, js_##NAME##_funcs, countof(js_##NAME##_funcs) } + +static ModuleEntry module_registry[] = { + MISTLINE(game), + MISTLINE(io), + MISTLINE(input), + MISTLINE(prosperon), + MISTLINE(time), + MISTLINE(console), + MISTLINE(profile), + MISTLINE(debug), + MISTLINE(vector), + MISTLINE(spline), + MISTLINE(performance), + MISTLINE(geometry), + MISTLINE(camera), +}; + +JSC_SCALL(os_use, + SDL_SharedObject *ptr = SDL_LoadObject(str); + if (!ptr) { + for (int i = 0; i < sizeof(module_registry)/sizeof(module_registry[0]); i++) { + if (strcmp(str,module_registry[i].name) == 0) { + JSValue mod = JS_NewObject(js); + JS_SetPropertyFunctionList(js,mod,module_registry[i].fn, module_registry[i].fn_count); + return mod; + } + } + return JS_ThrowReferenceError(js, "%s\nAnd could not find a static module.", 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); +) + static const JSCFunctionListEntry js_os_funcs[] = { MIST_FUNC_DEF(os, turbulence, 4), MIST_FUNC_DEF(os, model_buffer, 1), @@ -7319,6 +7304,7 @@ static const JSCFunctionListEntry js_os_funcs[] = { MIST_FUNC_DEF(os, cull_sprites, 2), MIST_FUNC_DEF(os, rects_to_sprites,2), MIST_FUNC_DEF(os, on, 2), + MIST_FUNC_DEF(os, use, 1), }; JSC_CCALL(qtree_insert, @@ -7356,7 +7342,7 @@ static const JSCFunctionListEntry js_qtree_funcs[] = { MIST_FUNC_DEF(qtree, query, 2), }; -JSC_CCALL(rtree_insert, +JSC_CCALL(rtree_add, rtree *tree = js2rtree(js,self); JSValue v = argv[0]; rect r; @@ -7386,7 +7372,7 @@ int rtree_cmp(const JSValue *a, const JSValue *b, JSContext *js) return !same; } -JSC_CCALL(rtree_remove, +JSC_CCALL(rtree_delete, rtree *tree = js2rtree(js,self); JSValue v = argv[0]; rect r; @@ -7451,16 +7437,68 @@ JSC_CCALL(rtree_query, */ ) -JSC_CCALL(rtree_count, +struct rtree_each +{ + JSValue fn; + JSContext *js; +}; + +int rtree_foreach(const NUMTYPE *min, const NUMTYPE *max, const JSValue *value, struct rtree_each *each) +{ + JSValue ret = JS_Call(each->js, each->fn, JS_UNDEFINED, 0, NULL); + uncaught_exception(each->js, ret); + return 1; +} + +JSC_CCALL(rtree_forEach, rtree *tree = js2rtree(js,self); - return number2js(js, rtree_count(tree)); + struct rtree_each each; + each.fn = JS_DupValue(js,argv[0]); + each.js = js; + + rtree_scan(tree, rtree_foreach, &each); + JS_FreeValue(js,each.fn); ) +typedef struct { + JSContext *js; + JSValue v; + int has; +} rtree_has; + +int rtree_hasfn(const NUMTYPE *min, const NUMTYPE *max, const JSValue *value, rtree_has *has) +{ + if (JS_SameValue(has->js, has->v, *value)) { + has->has = 1; + return 0; + } + return 1; +} + +JSC_CCALL(rtree_has, + rtree *tree = js2rtree(js,self); + rtree_has has; + has.js = js; + has.v = JS_DupValue(js,argv[0]); + has.has = 0; + rtree_scan(tree, rtree_hasfn, &has); + JS_FreeValue(js,argv[0]); + return JS_NewBool(js,has.has); +) + +JSValue js_rtree_get_size(JSContext *js, JSValue self, int magic) +{ + rtree *tree = js2rtree(js,self); + return number2js(js,rtree_count(tree)); +} + static const JSCFunctionListEntry js_rtree_funcs[] = { - MIST_FUNC_DEF(rtree, insert, 1), - MIST_FUNC_DEF(rtree, remove, 1), + MIST_FUNC_DEF(rtree, add, 1), + MIST_FUNC_DEF(rtree, delete, 1), MIST_FUNC_DEF(rtree, query, 1), - MIST_FUNC_DEF(rtree, count, 0), + JS_CGETSET_DEF("size", js_rtree_get_size,NULL), + MIST_FUNC_DEF(rtree, forEach, 1), + MIST_FUNC_DEF(rtree, has, 1), }; JSC_GETSET(sprite, layer, number) @@ -7503,9 +7541,7 @@ JS_SetPropertyFunctionList(js, js_##NAME, js_##NAME##_funcs, countof(js_##NAME## JS_SetPrototype(js, js_##NAME, PARENT); \ JSValue js_layout_use(JSContext *js); -JSValue js_miniz_use(JSContext *js); JSValue js_soloud_use(JSContext *js); -JSValue js_chipmunk2d_use(JSContext *js); #ifdef TRACY_ENABLE JSValue js_tracy_use(JSContext *js); @@ -7513,7 +7549,6 @@ JSValue js_tracy_use(JSContext *js); #ifndef NEDITOR JSValue js_imgui(JSContext *js); -JSValue js_dmon_use(JSContext *js); #endif static void signal_handler(int sig) { @@ -7581,57 +7616,26 @@ void ffi_load(JSContext *js) { // QJSCLASSPREP_FUNCS(SDL_GPUShader) QJSCLASSPREP_FUNCS(SDL_GPUBuffer) // QJSCLASSPREP_FUNCS(SDL_GPUTransferBuffer) - QJSCLASSPREP_FUNCS(PHYSFS_File) - - QJSGLOBALCLASS(os); - QJSCLASSPREP_FUNCS(transform); -// QJSCLASSPREP_FUNCS(warp_gravity); -// QJSCLASSPREP_FUNCS(warp_damp); QJSCLASSPREP_FUNCS(font); QJSCLASSPREP_FUNCS(datastream); QJSCLASSPREP_FUNCS(timer); - QJSGLOBALCLASS(input); - QJSGLOBALCLASS(io); - QJSGLOBALCLASS(prosperon); - QJSGLOBALCLASS(time); - QJSGLOBALCLASS(console); - QJSGLOBALCLASS(profile); - QJSGLOBALCLASS(debug); - QJSGLOBALCLASS(game); - QJSGLOBALCLASS(render); - QJSGLOBALCLASS(vector); - QJSGLOBALCLASS(spline); - QJSGLOBALCLASS(performance); - QJSGLOBALCLASS(geometry); - - JS_SetPropertyStr(js, prosperon, "version", JS_NewString(js,"ver")); - JS_SetPropertyStr(js, prosperon, "revision", JS_NewString(js,"com")); - JS_SetPropertyStr(js, prosperon, "date", JS_NewString(js,"date")); + QJSGLOBALCLASS(os); JSValue array_proto = js_getpropertystr(js,globalThis, "Array"); array_proto = js_getpropertystr(js,array_proto, "prototype"); JS_SetPropertyFunctionList(js, array_proto, js_array_funcs, countof(js_array_funcs)); JS_SetPropertyStr(js, globalThis, "layout", js_layout_use(js)); - JS_SetPropertyStr(js, globalThis, "miniz", js_miniz_use(js)); JS_SetPropertyStr(js, globalThis, "soloud", js_soloud_use(js)); - JS_SetPropertyStr(js, globalThis, "chipmunk2d", js_chipmunk2d_use(js)); #ifdef TRACY_ENABLE JS_SetPropertyStr(js, globalThis, "tracy", js_tracy_use(js)); #endif - signal(SIGINT, signal_handler); - signal(SIGTERM, signal_handler); - signal(SIGSEGV, signal_handler); - signal(SIGABRT, signal_handler); - atexit(exit_handler); - #ifndef NEDITOR - JS_SetPropertyStr(js, globalThis, "dmon", js_dmon_use(js)); JS_SetPropertyStr(js, globalThis, "imgui", js_imgui(js)); #endif @@ -7729,5 +7733,11 @@ void ffi_load(JSContext *js) { global_js = js; + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + signal(SIGSEGV, signal_handler); + signal(SIGABRT, signal_handler); + atexit(exit_handler); + JS_FreeValue(js,globalThis); } diff --git a/source/qjs_macros.h b/source/qjs_macros.h index 9d6697d7..77bac67d 100644 --- a/source/qjs_macros.h +++ b/source/qjs_macros.h @@ -125,7 +125,6 @@ JSValue TYPE##2js(JSContext *js, TYPE *n) { \ return j; }\ \ - #define QJSGLOBALCLASS(NAME) \ JSValue NAME = JS_NewObject(js); \ JS_SetPropertyFunctionList(js, NAME, js_##NAME##_funcs, countof(js_##NAME##_funcs)); \ diff --git a/source/transform.c b/source/transform.c index e0cf4abf..329dbebd 100644 --- a/source/transform.c +++ b/source/transform.c @@ -16,7 +16,6 @@ static transform model = { .jsparent = JS_UNDEFINED, .change_hook = JS_UNDEFINED }; - transform *make_transform() { diff --git a/source/transform.h b/source/transform.h index 6d5538e1..42026fef 100644 --- a/source/transform.h +++ b/source/transform.h @@ -12,10 +12,11 @@ typedef struct transform { HMM_Mat4 cache; HMM_Mat4 gcache; int dirty; - JSValue self; struct transform *parent; - JSValue jsparent; struct transform **children; + + JSValue self; + JSValue jsparent; JSValue *jschildren; JSValue change_hook; } transform; diff --git a/source/warp.c b/source/warp.c deleted file mode 100644 index fd4906c9..00000000 --- a/source/warp.c +++ /dev/null @@ -1,62 +0,0 @@ -#include "warp.h" -#include "stb_ds.h" - -static warp_gravity **warps = NULL; - -warp_damp *warp_damp_make() -{ - warp_damp *d = calloc(sizeof(*d),1); - return d; -} - -void warp_damp_free(warp_damp *d) { free(d); } - -warp_gravity *warp_gravity_make() -{ - warp_gravity *n = calloc(sizeof(*n),1); - n->strength = 9.8; - n->t.scale = (HMM_Vec3){0,-1,0}; - n->planar_force = (HMM_Vec3){0,-1,0}; - arrput(warps, n); - return n; -} - -void warp_gravity_free(warp_gravity *n) { - for (int i = 0; i < arrlen(warps); i++) { - if (warps[i] == n) { - arrdelswap(warps, i); - break; - } - } - free(n); -} - -HMM_Vec3 warp_damp_force(warp_damp *d, HMM_Vec3 pos, HMM_Vec3 vel) -{ - return HMM_MulV3(vel, d->damp); -} - -HMM_Vec3 warp_gravity_force(warp_gravity *g, HMM_Vec3 pos) -{ - HMM_Vec3 f = (HMM_Vec3){0,0,0}; - if (g->strength == 0) return f; - if (g->spherical) { - HMM_Vec3 dir = HMM_SubV3(g->t.pos, pos); - float len = HMM_LenV3(dir); - if (len == 0) return f; - HMM_Vec3 norm = HMM_NormV3(HMM_SubV3(g->t.pos, pos)); - return HMM_MulV3F(norm,g->strength); - } else { - return HMM_MulV3F(g->planar_force, g->strength); - } -} - -HMM_Vec3 warp_force(HMM_Vec3 pos, warpmask mask) -{ - HMM_Vec3 f = (HMM_Vec3){0,0,0}; - for (int i = 0; i < arrlen(warps); i++) { - if (!(mask & warps[i]->mask)) continue; - f = HMM_AddV3(f, warp_gravity_force(warps[i], pos)); - } - return f; -} diff --git a/source/warp.h b/source/warp.h deleted file mode 100644 index a48496e6..00000000 --- a/source/warp.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef WARP_H -#define WARP_H - -#include "stdint.h" -#include "transform.h" - -typedef uint32_t warpmask; - -#define gravmask 1U - -typedef struct { - transform t; - float strength; - float decay; - int spherical; - HMM_Vec3 planar_force; - warpmask mask; -} warp_gravity; - -typedef struct { - transform t; - int unlimited_range; - HMM_Vec3 range; - HMM_Vec3 falloff; - HMM_Vec3 damp; - warpmask mask; -} warp_damp; - -typedef struct { - transform t; - float strength; - float decay; - float pulse; /* strength of random variance in the wind effect */ - float frequency; /* when 0, pulse is smooth. Increase for very pulse over time */ - float turbulence; /* When 0, pulsing is smooth and regular. Increase for more chaos. */ - int spherical; -} warp_wind; - -/* returns the total force for an object at pos */ -HMM_Vec3 warp_force(HMM_Vec3 pos, warpmask mask); - -warp_gravity *warp_gravity_make(); -warp_damp *warp_damp_make(); -void warp_gravity_free(warp_gravity *g); -void warp_damp_free(warp_damp *d); - -#endif