From 2f086947b9563258c68958f673eca85edf2d89ac Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Sun, 24 Dec 2023 15:14:46 +0000 Subject: [PATCH] scaling --- Makefile | 4 +- docs/entities.md | 19 +++ docs/scene.md | 23 +++ docs/scripting.md | 3 +- scripts/base.js | 21 ++- scripts/components.js | 19 +-- scripts/diff.js | 46 ++---- scripts/editor.js | 78 +++++----- scripts/engine.js | 1 + scripts/entity.js | 288 ++++++++++++++++++------------------- source/engine/gameobject.c | 10 +- source/engine/gameobject.h | 3 +- source/engine/jsffi.c | 5 +- source/engine/yugine.c | 2 +- 14 files changed, 260 insertions(+), 262 deletions(-) create mode 100644 docs/scene.md diff --git a/Makefile b/Makefile index 4d95d4c..4700b65 100755 --- a/Makefile +++ b/Makefile @@ -53,7 +53,7 @@ endif CPPFLAGS += -ffast-math ifeq ($(DBG),1) - CPPFLAGS += -g -fsanitize=address + CPPFLAGS += -g #-fsanitize=address INFO += _dbg else CPPFLAGS += -DNDEBUG @@ -78,7 +78,7 @@ else endif endif -CPPFLAGS += -DHAVE_CEIL -DCP_USE_CGTYPES=0 -DCP_USE_DOUBLES=0 -DTINYSPLINE_FLOAT_PRECISION -DHAVE_FLOOR -DHAVE_FMOD -DHAVE_LRINT -DHAVE_LRINTF $(includeflag) -MD $(WARNING_FLAGS) -I. -DVER=\"$(VER)\" -DINFO=\"$(INFO)\" #-DENABLE_SINC_MEDIUM_CONVERTER -DENABLE_SINC_FAST_CONVERTER -DCP_COLLISION_TYPE_TYPE=uintptr_t -DCP_BITMASK_TYPE=uintptr_t +CPPFLAGS += -DHAVE_CEIL -DCP_USE_CGTYPES=0 -DCP_USE_DOUBLES=0 -DHAVE_FLOOR -DHAVE_FMOD -DHAVE_LRINT -DHAVE_LRINTF $(includeflag) -MD $(WARNING_FLAGS) -I. -DVER=\"$(VER)\" -DINFO=\"$(INFO)\" #-DENABLE_SINC_MEDIUM_CONVERTER -DENABLE_SINC_FAST_CONVERTER -DCP_COLLISION_TYPE_TYPE=uintptr_t -DCP_BITMASK_TYPE=uintptr_t # ENABLE_SINC_[BEST|FAST|MEDIUM]_CONVERTER # default, fast and medium available in game at runtime; best available in editor diff --git a/docs/entities.md b/docs/entities.md index 3f76520..dc08a6f 100644 --- a/docs/entities.md +++ b/docs/entities.md @@ -15,3 +15,22 @@ Components, if they have a defined transform, are relative to the entity they re Entities are defined via a typical scene graph system. This allows for easy reuse of components when designing levels. When the game is run, the movement relationships are broken, and the physics system takes over. In the physics engine, each entity is a direct child of the world. Objects can still be constrained to other objects via the physics system. + +The transform properties have local, global, and screen space accessors. For example, for position .. + +- pos: the relative position of the object to its master +- worldpos: the position of the object in the world +- screenpos: the position of the object in screen space + +The core engine only remembers each object's world position. When its position is requested, it figures its position based on its parent. + +## Inheritence? Composition? + +The idea behind how the engine is oragnized is to encourage composition. Compisition creates cleaner objects than does inheritence, and is easier to understand. And yet, inheritence is useful for specific circumstances. + +- Creating a specific unique object in a level + +There are only two cases where inheritence can be utilized in this engine: +1. Whole entities can be subtyped. Only values can be substituted. An ur-type must define all functions and behaviors of an object. but then that can be subtyped to change only a handful of properties. + +2. Entities can be subtyped when thrall to another entity. This can only be done once. Thrall objects, like components, can be defined only once on an ur-type. Sub types can only change parameters, and not of the sub objects. \ No newline at end of file diff --git a/docs/scene.md b/docs/scene.md new file mode 100644 index 0000000..88e7535 --- /dev/null +++ b/docs/scene.md @@ -0,0 +1,23 @@ +# The Scene + +There are a multitude of representations of your entities in the engine. The two most prominent are the entity system, where entities can be master or thrall to a master. And then there is the physics view, which is mostly flat, where objects are all part of a "world", but can be connected to one another via various physics constraints: + +- Pin joints, rigidly connected +- Slide joints, with a mix or max distance +- Pivot points, stuck to position but allow rotation +- Groove joint +- Damped spring +- Damped rotary spring +- Rotary limit +- Ratchet joint +- Gear joint +- Simple motor + +The most common sort of connection will be master and thrall. When editing, this relationship allows for simple + +## Entity vs Global +The physics engine stores all objects on the global level. The center of the world is [0,0]. Physics does not handle scaling at all. + +The master/thrall relationship is closer to a scene graph. When the master moves, its thralls move, and their thralls move, etc. When a master scales, it is the same. + +When the game starts simulating, objects ignore their parent relationship after they are created. They become only part of the world and simulate as such. \ No newline at end of file diff --git a/docs/scripting.md b/docs/scripting.md index 924c079..7f8cd22 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -27,7 +27,8 @@ Computation takes place in turns. Each entity has functions called, if they exis |function|description| |---|---| -|start|called when the object is loaded| +|load|called after the object is loaded, including in editor| +|start|called when the object is loaded during gameplay| |update(dt)|called once per game frame tick| |physupdate(dt)|called once per physics tick| |stop|called when the object is killed| diff --git a/scripts/base.js b/scripts/base.js index 9c0915b..076fca9 100644 --- a/scripts/base.js +++ b/scripts/base.js @@ -1041,6 +1041,13 @@ Object.defineProperty(Array.prototype, 'mapc', { } }); +Object.defineProperty(Array.prototype, 'mapvec', { + value: function(fn, b) { + return this.map((x,i) => fn(x,b[i])); + } +}); + + Object.defineProperty(Array.prototype, 'remove', { value: function(b) { var idx = this.indexOf(b); @@ -1201,11 +1208,6 @@ Math.nearest = function(n, incr) return Math.round(n/incr)*incr; } -Number.prec = function(num) -{ - return parseFloat(num.toFixed(3)); -} - Number.hex = function(n) { var s = Math.floor(n).toString(16); @@ -1257,9 +1259,13 @@ Math.angledist = function (a1, a2) { return wrap; }; Math.angledist.doc = "Find the shortest angle between two angles."; -Math.patan2 = function(p) { return Math.atan2(p.y,p.x); }; +Math.TAU = Math.PI*2; Math.deg2rad = function(deg) { return deg * 0.0174533; }; Math.rad2deg = function(rad) { return rad / 0.0174533; }; +Math.deg2rad = function(x) { return x; }; +Math.rad2deg = function(x) { return x; }; +Math.turn2rad = function(x) { return x*Math.TAU; }; +Math.rad2turn = function(x) { return x/Math.TAU; }; Math.randomint = function(max) { return Math.clamp(Math.floor(Math.random() * max), 0, max-1); }; /* BOUNDINGBOXES */ @@ -1397,12 +1403,13 @@ var Vector = { }, angle(v) { - return Math.atan2(v.y, v.x); + return Math.rad2turn(Math.atan2(v.y, v.x)); }, rotate(v,angle) { var r = Vector.length(v); var p = Vector.angle(v) + angle; + p = Math.turn2rad(angle); return [r*Math.cos(p), r*Math.sin(p)]; }, diff --git a/scripts/components.js b/scripts/components.js index 9ae24ae..ac31331 100644 --- a/scripts/components.js +++ b/scripts/components.js @@ -97,7 +97,7 @@ component.sprite.impl = { set layer(x) { cmd(60, this.id, x); }, get layer() { return undefined; }, emissive(x) { cmd(170, this.id, x); }, - pick() { return this; }, + pickm() { return this; }, move(d) { this.pos = this.pos.add(d); }, boundingbox() { @@ -183,10 +183,6 @@ var SpriteAnim = { } anim.dim = cmd(64,path); anim.dim.x /= frames; - anim.toJSON = function() - { - return anim.path; - } return anim; }, @@ -332,19 +328,6 @@ component.char2dimpl = { this.play_anim(anim); } }, - - post() { - /* - this.timer = timer.make(this.advance.bind(this), 1); - this.timer.loop = true; - Object.hide(this,'timer'); - for (var k in this.anims) { - var path = this.anims[k]; - this.anims[k] = run_env(path + ".asset", path); - this.add_anim(this.anims[k], k); - } - Object.hide(this, 'acur');*/ - }, }; Object.assign(component.char2d, component.char2dimpl); diff --git a/scripts/diff.js b/scripts/diff.js index b207bcc..0a0fce7 100644 --- a/scripts/diff.js +++ b/scripts/diff.js @@ -1,20 +1,4 @@ -function deep_copy(from) { - if (typeof from !== 'object') - return from; - - if (Array.isArray(from)) { - var c = []; - from.forEach(function(x,i) { c[i] = deep_copy(x); }); - return c; - } - - var obj = {}; - for (var key in from) - obj[key] = deep_copy(from[key]); - - return obj; -}; - +function deep_copy(from) { return json.decode(json.encode(from)); } var walk_up_get_prop = function(obj, prop, endobj) { var props = []; @@ -58,8 +42,7 @@ function deep_merge(target, source) function equal(x,y) { if (typeof x === 'object') - for (var key in x) - return equal(x[key],y[key]); + return json.encode(x) === json.encode(y); return x === y; }; @@ -83,11 +66,6 @@ function diffassign(target, from) { } }; -function diffkey(from,to,key) -{ - -} - function objdiff(from,to) { var ret = {}; @@ -118,9 +96,8 @@ function objdiff(from,to) } if (typeof v === 'number') { - var a = Number.prec(v); - if (!to || a !== to[key]) - ret[key] = a; + if (!to || v !== to[key]) + ret[key] = v; return; } @@ -139,7 +116,6 @@ function valdiff(from,to) if (typeof from === 'undefined') return undefined; if (typeof from === 'number') { - if (Number.prec(from) !== Number.prec(to)) return to; return undefined; @@ -153,7 +129,16 @@ function valdiff(from,to) return undefined; } +/* Returns the json encoded object, assuming it has an implementation it must check through */ +function impl_json(obj) +{ + +} +function ndiff(from,to) +{ + +} function ediff(from,to) { @@ -189,9 +174,8 @@ function ediff(from,to) } if (typeof v === 'number') { - var a = Number.prec(v); - if (!to || a !== to[key]) - ret[key] = a; + if (!to || v !== to[key]) + ret[key] = v; return; } diff --git a/scripts/editor.js b/scripts/editor.js index 8fbbc52..7642386 100644 --- a/scripts/editor.js +++ b/scripts/editor.js @@ -14,7 +14,6 @@ var editor = { machine: undefined, device_test: undefined, selectlist: [], - grablist: [], scalelist: [], rotlist: [], camera: undefined, @@ -43,9 +42,8 @@ var editor = { if (obj.level !== this.edit_level) { var testlevel = obj.level; - while (testlevel && testlevel.level !== this.edit_level) { + while (testlevel && testlevel.level !== this.edit_level && testlevel !== testlevel.level) testlevel = testlevel.level; - } return testlevel; } @@ -458,7 +456,6 @@ var editor = { var p = x[1].namestr(); GUI.text(p, x[1].screenpos().add([0,16]),1,editor.color_depths[depth]); Shape.circle(x[1].screenpos(),10,Color.blue.alpha(0.3)); -// Shape.arrow(x[1].screenpos(), x[1].screenpos().add(x[1].up().scale(15)), Color.red, 10); }); var mg = physics.pos_query(Mouse.worldpos,10); @@ -514,25 +511,7 @@ var editor = { this.curpanels.forEach(function(x) { if (x.on) x.gui(); - }); -/* - var o = editor.get_that(); - - var pos = [6,600]; - var offset = [0,0]; - var os = Object.entries(o.objects); - - GUI.text(o.toString(), pos.add(offset)); - offset = offset.add([5,-16]); - - os.forEach(function(x) { - GUI.text(x.toString(), pos.add(offset)); - offset = offset.add([0,-16]); - }); - - GUI.text(JSON.stringify(o._ed.urdiff,null,1), [500,500]); -*/ - + }); }, ed_debug() { @@ -540,17 +519,6 @@ var editor = { this.selectlist.forEach(function(x) { Debug.draw_obj_phys(x); }); }, - viewasset(path) { - Log.info(path); - var fn = function(x) { return path.endsWith(x); }; - if (Resources.images.any(fn)) { - var newtex = Object.copy(texgui, { path: path }); - this.addpanel(newtex); - } - else if (sounds.any(fn)) - Log.info("selected a sound"); - }, - killring: [], killcom: [], @@ -614,6 +582,10 @@ editor.inputs.drop = function(str) { console.warn("NOT AN IMAGE"); return; } + + if (this.selected.length === 0) { + + } if (this.sel_comp?.comp === 'sprite') { this.sel_comp.path = str; @@ -727,8 +699,8 @@ editor.inputs.m = function() { return; } - - editor.selectlist.forEach(x => x.mirror([1,0])); + + editor.selectlist.forEach(obj => obj.scale = [-obj.scale[0], obj.scale[1]]); }; editor.inputs.m.doc = "Mirror selected objects on the X axis."; @@ -897,6 +869,8 @@ editor.inputs['M-t'] = function() { editor.edit_level.objects.forEach(function(x editor.inputs['M-t'].doc = "Unlock all objects in current level."; editor.inputs['C-n'] = function() { + gameobject.make(editor.edit_level); + console.warn("MADE A NEW OBJECT"); /* if (editor.edit_level._ed.dirty) { Log.info("Level has changed; save before starting a new one."); editor.openpanel(gen_notify("Level is changed. Are you sure you want to close it?", _ => editor.clear_level())); @@ -967,14 +941,18 @@ editor.inputs['C-f1'].doc = "Enter basic edit mode."; editor.inputs['C-f2'] = function() { editor.edit_mode = "brush"; }; editor.inputs['C-f2'].doc = "Enter brush mode."; - editor.inputs.f2 = function() { - objectexplorer.on_close = save_configs; - objectexplorer.obj = configs; + if (this.selectlist.length !== 1) return; + objectexplorer.obj = this.selectlist[0]; this.openpanel(objectexplorer); }; editor.inputs.f2.doc = "Open configurations object."; +editor.inputs.f3 = function() { + if (this.selectlist.length !== 1) return; + this.openpanel(componentexplorer); +}; + editor.inputs.lm = function() { editor.sel_start = Mouse.worldpos; }; editor.inputs.lm.doc = "Selection box."; @@ -1144,9 +1122,9 @@ editor.inputs.mouse.move = function(pos, dpos) editor.rotlist?.forEach(function(x) { var anglediff = Math.atan2(relpos.y, relpos.x) - x.rotoffset; - x.obj.angle = x.angle + Math.rad2deg(anglediff); + x.obj.angle = x.angle + Math.rad2turn(anglediff); if (Keys.shift()) - x.obj.angle = Math.nearest(x.obj.angle, 45); + x.obj.angle = Math.nearest(x.obj.angle, (1/24)); if (x.pos) x.obj.pos = x.pos.sub(x.offset).add(x.offset.rotate(anglediff)); }); @@ -1289,7 +1267,7 @@ editor.inputs['C-c'] = function() { this.killcom = physics.com(this.selectlist.map(x=>x.pos)); this.selectlist.forEach(function(x) { - this.killring.push(x.make_ur()); + this.killring.push(x.instance_obj()); },this); }; editor.inputs['C-c'].doc = "Copy selected objects to killring."; @@ -1686,7 +1664,6 @@ var objectexplorer = Object.copy(inputpanel, { previous: [], start() { this.previous = []; - Input.setnuke(); }, goto_obj(obj) { @@ -1701,7 +1678,8 @@ var objectexplorer = Object.copy(inputpanel, { guibody() { var items = []; - items.push(Mum.text({str:"Examining " + this.obj.toString()})); + items.push(Mum.text({str:"Examining " + this.obj.toString() + " entity"})); + items.push(Mum.text({str: JSON.stringify(this.obj,undefined,1)})); return items; var n = 0; @@ -1936,6 +1914,18 @@ var assetexplorer = Object.copy(openlevelpanel, { }, }); +var componentexplorer = Object.copy(inputpanel, { + title: "component menu", + assets: ['sprite', 'model', 'edge2d', 'polygon2d', 'circle2d'], + click(name) { + if (editor.selectlist.length !== 1) return; + editor.selectlist[0].add_component(component[name]); + }, + guibody() { + return componentexplorer.assets.map(x => Mum.text({str:x, action:this.click, color: Color.blue, hovered:{Color:Color.red}, selectable:true})); + }, +}); + function tab_complete(val, list) { if (!val) return val; list.dofilter(function(x) { return x.startsWith(val); }); diff --git a/scripts/engine.js b/scripts/engine.js index f678c9b..ad09678 100644 --- a/scripts/engine.js +++ b/scripts/engine.js @@ -524,6 +524,7 @@ function world_start() { Primum.toString = function() { return "Primum"; }; Primum.ur = undefined; Primum.kill = function() { this.clear(); }; + gameobject.level = Primum; } load("scripts/physics.js"); diff --git a/scripts/entity.js b/scripts/entity.js index b87be21..51e7b44 100644 --- a/scripts/entity.js +++ b/scripts/entity.js @@ -1,3 +1,16 @@ +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; +} + var actor = {}; actor.spawn = function(script, config){ if (typeof script !== 'string') return; @@ -24,10 +37,7 @@ actor.kill = function(){ this.padawans.forEach(p => p.kill()); this.__dead__ = true; }; -actor.toJSON = function() { - if (this.__dead__) return undefined; - return this; -} + actor.delay = function(fn, seconds) { var t = Object.create(timer); t.remain = seconds; @@ -55,6 +65,93 @@ actor.remaster = function(to){ to.padawans.push(this); }; +var gameobject_impl = { + get pos() { + Debug.assert(this.level, `Entity ${this.toString()} has no level.`); + return this.level.world2this(this.worldpos()); + }, + + set pos(x) { + Debug.assert(this.level, `Entity ${this.toString()} has no level.`); + this.set_worldpos(this.level.this2world(x)); + }, + + get angle() { + Debug.assert(this.level, `No level set on ${this.toString()}`); + return this.worldangle() - this.level.worldangle(); + }, + set angle(x) { + var diff = x - this.angle; + + this.objects.forEach(function(x) { + x.rotate(diff); + x.pos = Vector.rotate(x.pos, diff); + }); + + this.sworldangle(x-this.level.worldangle()); + }, + + get scale() { + Debug.assert(this.level, `No level set on ${this.toString()}`); + return this.gscale().map((x,i) => x/this.level.gscale()[i]); + }, + + set scale(x) { + if (typeof x === 'number') + x = [x,x]; + + var pct = this.scale.map((s,i) => x[i]/s); + this.spread(pct); + + /* TRANSLATE ALL SUB OBJECTS */ + this.objects.forEach(obj => { + obj.spread(pct); + obj.pos = obj.pos.map((x,i)=>x*pct[i]); + }); + }, + + get draw_layer() { return cmd(171, this.body); }, + set draw_layer(x) { cmd(172, this.body, x); }, + set layer(x) { cmd(75,this.body,x); }, + get layer() { cmd(77,this.body); }, + + set mass(x) { set_body(7,this.body,x); }, + get mass() { + if (!(this.phys === Physics.dynamic)) + return this.__proto__.mass; + + return q_body(5, this.body); + }, + get elasticity() { return cmd(107,this.body); }, + set elasticity(x) { cmd(106,this.body,x); }, + get friction() { return cmd(109,this.body); }, + set friction(x) { cmd(108,this.body,x); }, + set gravity(x) { cmd(167,this.body, x); }, + get gravity() { return cmd(159,this.body); }, + set timescale(x) { cmd(168,this.body,x); }, + get timescale() { return cmd(169,this.body); }, + set phys(x) { set_body(1, this.body, x); }, + get phys() { return q_body(0,this.body); }, + get velocity() { return q_body(3, this.body); }, + set velocity(x) { set_body(9, this.body, x); }, + get damping() { return cmd(157,this.body); }, + set damping(x) { cmd(156, this.body, x); }, + get angularvelocity() { return Math.rad2turn(q_body(4,this.body)); }, + set angularvelocity(x) { set_body(8, this.body, Math.turn2rad(x)); }, + get max_velocity() { return cmd(152, this.body); }, + set max_velocity(x) { cmd(151, this.body, x); }, + get max_angularvelocity() { return cmd(155,this.body); }, + set max_angularvelocity(x) { cmd(154,this.body,x); }, + get_moi() { return q_body(6, this.body); }, + set_moi(x) { + if(x <= 0) { + Log.error("Cannot set moment of inertia to 0 or less."); + return; + } + set_body(13, this.body, x); + }, +}; + var gameobject = { get_comp_by_name(name) { var comps = []; @@ -128,10 +225,6 @@ var gameobject = { return killfn; }, - set max_velocity(x) { cmd(151, this.body, x); }, - get max_velocity() { return cmd(152, this.body); }, - set max_angularvelocity(x) { cmd(154, this.body, Math.deg2rad(x)); }, - get max_angularvelocity() { return Math.rad2deg(cmd(155, this.body)); }, set torque(x) { if (!(x >= 0 && x <= Infinity)) return; cmd(153, this.body, x); }, gscale() { return cmd(103,this.body); }, sgscale(x) { @@ -139,65 +232,14 @@ var gameobject = { x = [x,x]; cmd(36,this.body,x) }, - get scale() { - Debug.assert(this.level, `No level set on ${this.toString()}`); - return this.gscale().map((x,i) => x/this.level.gscale()[i]); - }, - set scale(x) { - if (typeof x === 'number') - x = [x,x]; - - if (this.level) { - var g = this.level.gscale(); - x = x.map((y,i) => y * g[i]); - } - - var pct = x.map(function(y,i) { return y/this.gscale()[i]; }, this); - - this.sgscale(x); - /* TRANSLATE ALL SUB OBJECTS */ + phys_material() { + var mat = {}; + mat.elasticity = this.elasticity; + mat.friction = this.friction; + return mat; }, - set pos(x) { - Debug.assert(this.level, `Entity ${this.toString()} has no level.`); - this.set_worldpos(this.level.this2world(x)); - }, - - get pos() { - Debug.assert(this.level, `Entity ${this.toString()} has no level.`); - return this.level.world2this(this.worldpos()); - }, - - get draw_layer() { return cmd(171, this.body); }, - set draw_layer(x) { cmd(172, this.body, x); }, - - get elasticity() { return cmd(107,this.body); }, - set elasticity(x) { cmd(106,this.body,x); }, - - get friction() { return cmd(109,this.body); }, - set friction(x) { cmd(108,this.body,x); }, - - set mass(x) { set_body(7,this.body,x); }, - get mass() { - if (!(this.phys === Physics.dynamic)) - return this.__proto__.mass; - - return q_body(5, this.body); - }, - set gravity(x) { cmd(158,this.body, x); }, - get gravity() { return cmd(159,this.body); }, - set_gravity(x) { cmd(167, this.body, x); }, - set timescale(x) { cmd(168,this.body,x); }, - get timescale() { return cmd(169,this.body); }, - set phys(x) { set_body(1, this.body, x); }, - get phys() { return q_body(0,this.body); }, - get velocity() { return q_body(3, this.body); }, - set velocity(x) { set_body(9, this.body, x); }, - get damping() { return cmd(157,this.body); }, - set_damping(x) { cmd(156, this.body, x); }, - get angularvelocity() { return Math.rad2deg(q_body(4, this.body)); }, - set angularvelocity(x) { set_body(8, this.body, Math.deg2rad(x)); }, worldpos() { return q_body(1,this.body); }, set_worldpos(x) { var poses = this.objects.map(x => x.pos); @@ -206,34 +248,9 @@ var gameobject = { }, screenpos() { return world2screen(this.worldpos()); }, - worldangle() { return Math.rad2deg(q_body(2,this.body))%360; }, - sworldangle(x) { set_body(0,this.body,Math.deg2rad(x)); }, - get angle() { - Debug.assert(this.level, `No level set on ${this.toString()}`); - - return this.worldangle() - this.level.worldangle(); - }, - set angle(x) { - var diff = x - this.angle; - var thatpos = this.pos; - this.objects.forEach(function(x) { - x.rotate(diff); - var opos = x.pos; - var r = Vector.length(opos); - var p = Math.rad2deg(Math.atan2(opos.y, opos.x)); - p += diff; - p = Math.deg2rad(p); - x.pos = [r*Math.cos(p), r*Math.sin(p)]; - }); + worldangle() { return Math.rad2turn(q_body(2,this.body)); }, + sworldangle(x) { set_body(0,this.body,Math.turn2rad(x)); }, - if (this.level) - set_body(0,this.body, Math.deg2rad(x - this.level.worldangle())); - else - set_body(0,this.body,x); - }, - - rotate(x) { this.sworldangle(this.worldangle()+x); }, - spawn_from_instance(inst) { return this.spawn(inst.ur, inst); }, @@ -290,16 +307,7 @@ var gameobject = { components: {}, objects: {}, level: undefined, - get_moi() { return q_body(6, this.body); }, - set_moi(x) { - if(x <= 0) { - Log.error("Cannot set moment of inertia to 0 or less."); - return; - } - set_body(13, this.body, x); - }, - pulse(vec) { set_body(4, this.body, vec);}, shove(vec) { set_body(12,this.body,vec);}, shove_at(vec, at) { set_body(14,this.body,vec,at); }, @@ -310,19 +318,12 @@ var gameobject = { dir_world2this(dir) { return cmd(160, this.body, dir); }, dir_this2world(dir) { return cmd(161, this.body, dir); }, - set layer(x) { cmd(75,this.body,x); }, - get layer() { cmd(77,this.body); }, alive() { return this.body >= 0; }, in_air() { return q_body(7, this.body);}, hide() { this.components.forEach(x=>x.hide()); this.objects.forEach(x=>x.hide());}, show() { this.components.forEach(function(x) { x.show(); }); this.objects.forEach(function(x) { x.show(); }); }, - get_relangle() { - if (!this.level) return this.angle; - return this.angle - this.level.angle; - }, - width() { var bb = this.boundingbox(); return bb.r - bb.l; @@ -334,15 +335,11 @@ var gameobject = { }, move(vec) { this.pos = this.pos.add(vec); }, - - rotate(amt) { this.angle += amt; }, + rotate(x) { this.sworldangle(this.worldangle()+x); }, + spread(vec) { this.sgscale(this.gscale().map((x,i)=>x*vec[i])); }, /* Make a unique object the same as its prototype */ revert() { -// var keys = Object.samenewkeys(this, this.__proto__); -// keys.unique.forEach(x => delete this[x]); -// keys.same.forEach(x => this[x] = this.__proto__[x]); - var jobj = this.json_obj(); var lobj = this.level.__proto__.objects[this.toString()]; delete jobj.objects; @@ -433,8 +430,10 @@ var gameobject = { return bb ? bb : cwh2bb([0,0], [0,0]); }, + /* The unique components of this object. Its diff. */ json_obj() { var d = ediff(this,this.__proto__); + d ??= {}; var objects = {}; @@ -454,51 +453,34 @@ var gameobject = { return d; }, - transform_obj() { - var t = this.json_obj(); - Object.assign(t, this.transform()); - return t; - }, - + /* The object needed to store an object as an instance of a level */ instance_obj() { - var t = this.transform_obj(); + var t = this.transform(); + var j = this.json_obj(); + Object.assign(t,j); t.ur = this.ur; return t; }, - ur_obj() { - var ur = this.json_obj(); - for (var k in ur) - if (ur[k].ur) - delete ur[k]; - - return ur; - }, - - make_ur() { - var thisur = this.json_obj(); - thisur.pos = this.pos; - thisur.angle = this.angle; - return thisur; - }, - transform() { var t = {}; - t.pos = this.pos.map(Number.prec); - t.angle = Number.prec(this.angle); + t.pos = this.pos; + t.angle = this.angle; + t.scale = this.scale; return t; }, + /* Velocity and angular velocity of the object */ phys_obj() { var phys = {}; - phys.velocity = this.velocity.map(Number.prec); - phys.angularvelocity = Number.prec(this.angularvelocity); + phys.velocity = this.velocity; + phys.angularvelocity = this.angularvelocity; return phys; }, dup(diff) { var n = this.level.spawn(this.__proto__); - Object.totalmerge(n, this.make_ur()); + Object.totalmerge(n, this.instance_obj()); return n; }, @@ -531,14 +513,16 @@ var gameobject = { this.stop(); }, - up() { return [0,1].rotate(Math.deg2rad(this.angle));}, - down() { return [0,-1].rotate(Math.deg2rad(this.angle));}, - right() { return [1,0].rotate(Math.deg2rad(this.angle));}, - left() { return [-1,0].rotate(Math.deg2rad(this.angle));}, + up() { return [0,1].rotate(this.angle);}, + down() { return [0,-1].rotate(this.angle);}, + right() { return [1,0].rotate(this.angle);}, + left() { return [-1,0].rotate(this.angle); }, make(level, data) { var obj = Object.create(this); + obj.make = undefined; + Object.mixin(obj,gameobject_impl); if (this.instances) this.instances.push(obj); @@ -561,7 +545,7 @@ var gameobject = { obj.reparent(level); cmd(113, obj.body, obj); // set the internal obj reference to this obj - + for (var [prop,p] of Object.entries(this)) { if (!p) continue; if (typeof p.make === 'function') { @@ -582,7 +566,7 @@ var gameobject = { if (data) Object.dainty_assign(obj,data); - if (typeof obj.warmup === 'function') obj.warmup(); + if (typeof obj.load === 'function') obj.load(); if (Game.playing() && typeof obj.start === 'function') obj.start(); return obj; @@ -617,9 +601,13 @@ var gameobject = { return this.objects[newname]; }, - add_component(comp) { + add_component(comp, data) { + data ??= undefined; if (typeof comp.make !== 'function') return; - return {comp:comp.toString()}; + var name = obj_unique_name(comp.toString(), this); + this[name] = comp.make(this.body); + Object.assign(this[name], data); + return this[name]; }, register_hit(fn, obj) { @@ -632,6 +620,7 @@ var gameobject = { Signal.obj_separate(fn,obj,this); }, } +Object.mixin(gameobject,gameobject_impl); gameobject.body = make_gameobject(); cmd(113,gameobject.body, gameobject); @@ -678,6 +667,7 @@ gameobject.doc = { level: "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.', }; /* Default objects */ diff --git a/source/engine/gameobject.c b/source/engine/gameobject.c index c7e51dc..25856eb 100644 --- a/source/engine/gameobject.c +++ b/source/engine/gameobject.c @@ -4,6 +4,7 @@ #include #include "debugdraw.h" #include "log.h" +#include "math.h" #include "stb_ds.h" @@ -129,7 +130,9 @@ static void velocityFn(cpBody *body, cpVect gravity, cpFloat damping, cpFloat dt } cpFloat d = isnan(go->damping) ? damping : d; - cpVect g = go->gravity ? gravity : go->cgravity.cp; + cpVect g = gravity; + if (isfinite(go->gravity.x) && isfinite(go->gravity.y)) + g = go->gravity.cp; cpBodyUpdateVelocity(body,g,d,dt*go->timescale); @@ -154,9 +157,8 @@ gameobject *MakeGameobject() { .next = -1, .drawlayer = 0, .shape_cbs = NULL, - .gravity = 1, - .cgravity = (HMM_Vec2){0,0}, - .damping = NAN, + .gravity = (HMM_Vec2){INFINITY,INFINITY}, + .damping = INFINITY, .timescale = 1.0, .ref = JS_UNDEFINED, }; diff --git a/source/engine/gameobject.h b/source/engine/gameobject.h index 351bb5f..96059f4 100644 --- a/source/engine/gameobject.h +++ b/source/engine/gameobject.h @@ -36,8 +36,7 @@ struct gameobject { float timescale; float maxvelocity; float maxangularvelocity; - int gravity; - HMM_Vec2 cgravity; + HMM_Vec2 gravity; /* its own gravity */ float damping; unsigned int layer; cpShapeFilter filter; diff --git a/source/engine/jsffi.c b/source/engine/jsffi.c index a16268d..e6818d9 100644 --- a/source/engine/jsffi.c +++ b/source/engine/jsffi.c @@ -1173,10 +1173,9 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) ret = num2js(js2gameobject(argv[1])->damping); break; case 158: - js2gameobject(argv[1])->gravity = js2bool(argv[2]); break; case 159: - ret = bool2js(js2gameobject(argv[1])->gravity); + ret = vec2js(js2gameobject(argv[1])->gravity); break; case 160: ret = vec2js(mat_t_dir(t_world2go(js2gameobject(argv[1])), js2vec2(argv[2]))); @@ -1205,7 +1204,7 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) ret = int2js(cp(str, str2)); break; case 167: - js2gameobject(argv[1])->cgravity = js2vec2(argv[2]); + js2gameobject(argv[1])->gravity = js2vec2(argv[2]); break; case 168: js2gameobject(argv[1])->timescale = js2number(argv[2]); diff --git a/source/engine/yugine.c b/source/engine/yugine.c index 1ba80ae..94ceb75 100644 --- a/source/engine/yugine.c +++ b/source/engine/yugine.c @@ -123,8 +123,8 @@ static void process_frame() if (stm_sec(stm_diff(frame_t, updatelast)) > updateMS) { double dt = stm_sec(stm_diff(frame_t, updatelast)); updatelast = frame_t; + // prof_start(&prof_update); - call_updates(dt * timescale); // prof_lap(&prof_update);