This commit is contained in:
2025-01-21 16:46:18 -06:00
parent e628256f44
commit 3d7ea7d358
32 changed files with 1209 additions and 3067 deletions

View File

@@ -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(

View File

@@ -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 };

View File

@@ -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,
};

View File

@@ -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 };

View File

@@ -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}

View File

@@ -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,
};

View File

@@ -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"));

View File

@@ -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 };

View File

@@ -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

26
scripts/gizmos.js Normal file
View File

@@ -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

197
scripts/graphics.js Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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!");
}));
};

View File

@@ -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

View File

@@ -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, {

View File

@@ -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

View File

@@ -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,
};

View File

@@ -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

37
scripts/search.js Normal file
View File

@@ -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

29
scripts/sim.js Normal file
View File

@@ -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

View File

@@ -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;
};

View File

@@ -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)

View File

@@ -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}

32
scripts/transform.js Normal file
View File

@@ -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

View File

@@ -1,3 +1,5 @@
var profile = use('profile')
/* Take numbers from 0 to 1 and remap them to easing functions */
var Ease = {
linear(t) {

View File

@@ -42,6 +42,8 @@ typedef struct rtree rtree;
#include <SDL3/SDL_gpu.h>
#include <SDL3/SDL_error.h>
#include <SDL3/SDL_properties.h>
#include <SDL3/SDL_loadso.h>
#ifdef __APPLE__
#include <Accelerate/Accelerate.h>
@@ -49,8 +51,6 @@ typedef struct rtree rtree;
//#include <cblas.h>
#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);
}

View File

@@ -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)); \

View File

@@ -16,7 +16,6 @@ static transform model = {
.jsparent = JS_UNDEFINED,
.change_hook = JS_UNDEFINED
};
transform *make_transform()
{

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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