spline editing works; hollow, etc; add M-g for move all points

This commit is contained in:
John Alanbrook 2023-10-29 21:39:45 +00:00
parent 0fcc2286fa
commit 6eefa95546
10 changed files with 137 additions and 131 deletions

View file

@ -2,10 +2,11 @@
Script hooks exist to allow to modification of the game. Script hooks exist to allow to modification of the game.
|config.js|called before any game play| |config.js|called before any game play, including play from editor|
|game.js|called to start the game| |game.js|called to start the game|
|editorconfig.js|called when the editor is loaded, used to personalize| |editorconfig.js|called when the editor is loaded, used to personalize|
|debug.js|called when play in editor is selected| |debug.js|called when play in editor is selected|
|dbgret.js|called when play in editor returns to editor|
All objects in the Yugine can have an associated script. This script can perform setup, teardown, and handles responses for the object. All objects in the Yugine can have an associated script. This script can perform setup, teardown, and handles responses for the object.

View file

@ -460,7 +460,6 @@ polygon2d.inputs['C-b'].doc = "Freeze mirroring in place.";
//Object.freeze(polygon2d); //Object.freeze(polygon2d);
component.edge2d = Object.copy(collider2d, { component.edge2d = Object.copy(collider2d, {
degrees:2,
dimensions:2, dimensions:2,
thickness:0, thickness:0,
/* open: 0 /* open: 0
@ -468,13 +467,8 @@ component.edge2d = Object.copy(collider2d, {
beziers: 2 beziers: 2
looped: 3 looped: 3
*/ */
type: 3, type: Spline.type.clamped,
typeid: { looped: false,
open: 0,
clamped: 1,
beziers: 2,
looped: 3
},
flipx: false, flipx: false,
flipy: false, flipy: false,
@ -483,7 +477,7 @@ component.edge2d = Object.copy(collider2d, {
hollow: false, hollow: false,
hollowt: 0, hollowt: 0,
spoints() { spoints() {
if (!this.cpoints) return []; if (!this.cpoints) return [];
var spoints = this.cpoints.slice(); var spoints = this.cpoints.slice();
@ -504,15 +498,12 @@ component.edge2d = Object.copy(collider2d, {
} }
} }
return spoints;
if (this.hollow) { if (this.hollow) {
var hpoints = []; var hpoints = inflate_cpv(spoints, spoints.length, this.hollowt);
var inflatep = inflate_cpv(spoints, spoints.length, this.hollowt); if (hpoints.length === spoints.length) return spoints;
inflatep[0].slice().reverse().forEach(function(x) { hpoints.push(x); }); var arr1 = hpoints.filter(function(x,i) { return i % 2 === 0; });
var arr2 = hpoints.filter(function(x,i) { return i % 2 !== 0; });
inflatep[1].forEach(function(x) { hpoints.push(x); }); return arr1.concat(arr2.reverse());
return hpoints;
} }
return spoints; return spoints;
@ -521,29 +512,29 @@ component.edge2d = Object.copy(collider2d, {
sample(n) { sample(n) {
var spoints = this.spoints(); var spoints = this.spoints();
this.degrees = Math.clamp(this.degrees, 1, spoints.length-1); var degrees = 2;
if (n < spoints.length) n = spoints.length;
if (spoints.length === 2) if (spoints.length === 2)
return spoints; return spoints;
if (spoints.length < 2) if (spoints.length < 2)
return []; return [];
if (this.degrees < 2) { if (this.samples === spoints.length) {
if (this.type === 3) if (this.looped) return spoints.wrapped(1);
return spoints.wrapped(1);
return spoints; return spoints;
} }
/* /*
order = degrees+1 order = degrees+1
knots = spoints.length + order knots = spoints.length + order
assert knots%order != 0 assert knots%order != 0
*/ */
if (this.type === component.edge2d.typeid.this.looped) if (this.looped)
return spline_cmd(0, this.degrees, this.dimensions, 0, spoints.wrapped(this.degrees), n); return Spline.sample(degrees, this.dimensions, Spline.type.open, spoints.wrapped(this.degrees), n);
return spline_cmd(0, this.degrees, this.dimensions, this.type, spoints, n); return Spline.sample(degrees, this.dimensions, this.type, spoints, n);
}, },
samples: 10, samples: 10,
@ -580,6 +571,18 @@ component.edge2d = Object.copy(collider2d, {
return undefined; return undefined;
}, },
pick_all() {
var picks = [];
this.cpoints.forEach(function(x) {
picks.push({
set pos(n) { x.x = n.x; x.y = n.y; },
get pos() { return x; },
sync: this.sync.bind(this),
});
}, this);
return picks;
},
}); });
component.edge2d.impl = { component.edge2d.impl = {
@ -591,6 +594,7 @@ component.edge2d.impl = {
}, },
get thickness() { return cmd(112,this.id); }, get thickness() { return cmd(112,this.id); },
sync() { sync() {
if (this.samples < this.spoints().length) this.samples = this.spoints().length;
var sensor = this.sensor; var sensor = this.sensor;
var points = this.sample(this.samples); var points = this.sample(this.samples);
cmd_edge2d(0,this.id,points); cmd_edge2d(0,this.id,points);
@ -599,64 +603,58 @@ component.edge2d.impl = {
}; };
var bucket = component.edge2d; var bucket = component.edge2d;
bucket.spoints.doc = "Returns the controls points after modifiers are applied, such as it being hollow or mirrored on its axises.";
bucket.inputs = {}; bucket.inputs = {};
bucket.inputs.post = function() { this.sync(); };
bucket.inputs.h = function() { this.hollow = !this.hollow; }; bucket.inputs.h = function() { this.hollow = !this.hollow; };
bucket.inputs.h.doc = "Toggle hollow."; bucket.inputs.h.doc = "Toggle hollow.";
bucket.inputs['C-g'] = function() { bucket.inputs['C-g'] = function() { if (this.hollowt > 0) this.hollowt--; };
this.hollowt--;
if (this.hollowt < 0) this.hollowt = 0;
};
bucket.inputs['C-g'].doc = "Thin the hollow thickness."; bucket.inputs['C-g'].doc = "Thin the hollow thickness.";
bucket.inputs['C-g'].rep = true;
bucket.inputs['C-f'] = function() { this.hollowt++; }; bucket.inputs['C-f'] = function() { this.hollowt++; };
bucket.inputs['C-f'].doc = "Increase the hollow thickness."; bucket.inputs['C-f'].doc = "Increase the hollow thickness.";
bucket.inputs['C-f'].rep = true;
bucket.inputs['M-v'] = function() { this.thickness--; }; bucket.inputs['M-v'] = function() { if (this.thickness > 0) this.thickness--; };
bucket.inputs['M-v'].doc = "Decrease spline thickness."; bucket.inputs['M-v'].doc = "Decrease spline thickness.";
bucket.inputs['M-v'].rep = true; bucket.inputs['M-v'].rep = true;
bucket.inputs['C-b'] = function() { bucket.inputs['C-y'] = function() {
this.cpoints = this.spoints(); this.cpoints = this.spoints();
this.flipx = false; this.flipx = false;
this.flipy = false; this.flipy = false;
this.hollow = false;
}; };
bucket.inputs['C-b'].doc = "Freeze mirroring,"; bucket.inputs['C-y'].doc = "Freeze mirroring,";
bucket.inputs['M-b'] = function() { this.thickness++; }; bucket.inputs['M-b'] = function() { this.thickness++; };
bucket.inputs['M-b'].doc = "Increase spline thickness."; bucket.inputs['M-b'].doc = "Increase spline thickness.";
bucket.inputs['M-b'].rep = true; bucket.inputs['M-b'].rep = true;
bucket.inputs['C-plus'] = function() { this.degrees++; };
bucket.inputs['C-plus'].doc = "Increase the degrees of this spline.";
bucket.inputs['C-plus'].rep = true;
bucket.inputs.plus = function() { this.samples++; }; bucket.inputs.plus = function() { this.samples++; };
bucket.inputs.plus.doc = "Increase the number of samples of this spline."; bucket.inputs.plus.doc = "Increase the number of samples of this spline.";
bucket.inputs.plus.rep = true; bucket.inputs.plus.rep = true;
bucket.inputs.minus = function() { bucket.inputs.minus = function() { if (this.samples > this.spoints().length) this.samples--;};
this.samples--;
if (this.samples < 1) this.samples = 1;
};
bucket.inputs.minus.doc = "Decrease the number of samples on this spline."; bucket.inputs.minus.doc = "Decrease the number of samples on this spline.";
bucket.inputs.minus.rep = true; bucket.inputs.minus.rep = true;
bucket.inputs['C-minus'] = function() { this.degrees--; };
bucket.inputs['C-minus'].doc = "Decrease the number of degrees of this spline.";
bucket.inputs['C-minus'].rep = true;
bucket.inputs['C-r'] = function() { this.cpoints = this.cpoints.reverse(); }; bucket.inputs['C-r'] = function() { this.cpoints = this.cpoints.reverse(); };
bucket.inputs['C-r'].doc = "Reverse the order of the spline's points."; bucket.inputs['C-r'].doc = "Reverse the order of the spline's points.";
bucket.inputs['C-l'] = function() { this.type = 3; }; bucket.inputs['C-l'] = function() { this.looped = !this.looped};
bucket.inputs['C-l'].doc = "Set type of spline to clamped."; bucket.inputs['C-l'].doc = "Toggle spline being looped.";
bucket.inputs['C-c'] = function() { this.type = 1; }; bucket.inputs['C-c'] = function() { this.type = Spline.type.clamped; };
bucket.inputs['C-c'].doc = "Set type of spline to closed."; bucket.inputs['C-c'].doc = "Set type of spline to clamped.";
bucket.inputs['C-o'] = function() { this.type = 0; }; bucket.inputs['C-o'] = function() { this.type = Spline.type.open; };
bucket.inputs['C-o'].doc = "Set spline to open."; bucket.inputs['C-o'].doc = "Set spline to open.";
bucket.inputs['C-b'] = function() { this.type = Spline.type.bezier; };
bucket.inputs['C-b'].doc = "Set spline to bezier.";
bucket.inputs['C-M-lm'] = function() { bucket.inputs['C-M-lm'] = function() {
var idx = grab_from_points(Mouse.worldpos, this.cpoints.map(this.gameobject.world2this,this.gameobject), 25); var idx = grab_from_points(Mouse.worldpos, this.cpoints.map(this.gameobject.world2this,this.gameobject), 25);
if (idx === -1) return; if (idx === -1) return;

View file

@ -568,6 +568,7 @@ var editor = {
GUI.text(JSON.stringify(o._ed.urdiff,null,1), [500,500]); GUI.text(JSON.stringify(o._ed.urdiff,null,1), [500,500]);
*/ */
}, },
ed_debug() { ed_debug() {
@ -662,6 +663,9 @@ var editor = {
} }
editor.inputs = {}; editor.inputs = {};
editor.inputs.post = function() {
if (editor.sel_comp && 'sync' in editor.sel_comp) editor.sel_comp.sync();
};
editor.inputs.release_post = function() { editor.inputs.release_post = function() {
editor.snapshot(); editor.snapshot();
editor.edit_level.check_dirty(); editor.edit_level.check_dirty();
@ -710,7 +714,7 @@ editor.inputs['C-d'].doc = "Duplicate all selected objects.";
editor.inputs['C-m'] = function() { editor.inputs['C-m'] = function() {
if (editor.sel_comp) { if (editor.sel_comp) {
if (editor.sel_comp.flipy) if ('flipy' in editor.sel_comp)
editor.sel_comp.flipy = !editor.sel_comp.flipy; editor.sel_comp.flipy = !editor.sel_comp.flipy;
return; return;
@ -722,7 +726,7 @@ editor.inputs['C-m'].doc = "Mirror selected objects on the Y axis.";
editor.inputs.m = function() { editor.inputs.m = function() {
if (editor.sel_comp) { if (editor.sel_comp) {
if (editor.sel_comp.flipx) if ('flipx' in editor.sel_comp)
editor.sel_comp.flipx = !editor.sel_comp.flipx; editor.sel_comp.flipx = !editor.sel_comp.flipx;
return; return;
@ -1187,17 +1191,22 @@ editor.inputs['M-u'].doc = "Make selected objects unique.";
editor.inputs['C-S-g'] = function() { editor.openpanel(groupsaveaspanel); }; editor.inputs['C-S-g'] = function() { editor.openpanel(groupsaveaspanel); };
editor.inputs['C-S-g'].doc = "Save selected objects as a new level."; editor.inputs['C-S-g'].doc = "Save selected objects as a new level.";
editor.inputs.g = editor.inputs.mm;/*
editor.inputs.g = function() { editor.inputs.g = function() {
if (editor.selectlist.length === 0) { if (editor.selectlist.length === 0) {
var o = editor.try_pick(); var o = editor.try_pick();
if (!o) return; if (!o) return;
editor.selectlist = [o]; editor.selectlist = [o];
} }
if (editor.sel_comp && 'pick' in editor.sel_comp) {
var o = editor.sel_comp.pick(Mouse.worldpos);
if (o) editor.grabselect = [o];
return;
}
editor.grabselect = editor.selectlist.slice(); editor.grabselect = editor.selectlist.slice();
}; };
*/
editor.inputs.g.doc = "Move selected objects."; editor.inputs.g.doc = "Move selected objects.";
editor.inputs.g.released = function() { editor.grabselect = []; Mouse.normal(); }; editor.inputs.g.released = function() { editor.grabselect = []; Mouse.normal(); };
@ -1238,6 +1247,13 @@ editor.inputs['C-g'] = function() {
}; };
editor.inputs['C-g'].doc = "Duplicate selected objects, then move them."; editor.inputs['C-g'].doc = "Duplicate selected objects, then move them.";
editor.inputs['M-g'] = function()
{
if (this.sel_comp && 'pick_all' in this.sel_comp)
this.grabselect = this.sel_comp.pick_all();
}
editor.inputs['M-g'].doc = "Move all.";
editor.inputs['C-lb'] = function() { editor.inputs['C-lb'] = function() {
editor_config.grid_size -= Keys.shift() ? 10 : 1; editor_config.grid_size -= Keys.shift() ? 10 : 1;
if (editor_config.grid_size <= 0) editor_config.grid_size = 1; if (editor_config.grid_size <= 0) editor_config.grid_size = 1;
@ -1989,6 +2005,7 @@ limited_editor.inputs['C-q'] = function()
{ {
Sound.killall(); Sound.killall();
Primum.clear(); Primum.clear();
load("dbgret.js");
editor.enter_editor(); editor.enter_editor();
} }

View file

@ -595,6 +595,18 @@ function Color(from) {
}; };
*/ */
var Spline = {};
Spline.sample = function(degrees, dimensions, type, ctrl_points, nsamples)
{
var s = spline_cmd(0, degrees,dimensions,type,ctrl_points,nsamples);
return s;
}
Spline.type = {
open: 0,
clamped: 1,
beziers: 2
};
load("scripts/components.js"); load("scripts/components.js");
function find_com(objects) function find_com(objects)
@ -853,3 +865,4 @@ Game.view_camera = function(cam)
Window.name = "Primum Machinam (V0.1)"; Window.name = "Primum Machinam (V0.1)";
Window.width = 1280; Window.width = 1280;
Window.height = 720; Window.height = 720;

View file

@ -414,7 +414,7 @@ var gameobject = {
this.level = undefined; this.level = undefined;
} }
Player.uncontrol(this); Player.do_uncontrol(this);
Register.unregister_obj(this); Register.unregister_obj(this);
// ur[this.ur].instances.remove(this); // ur[this.ur].instances.remove(this);
this.body = -1; this.body = -1;

View file

@ -165,7 +165,7 @@ var Player = {
} }
}, },
uncontrol(pawn) { do_uncontrol(pawn) {
this.players.forEach(function(p) { this.players.forEach(function(p) {
p.pawns = p.pawns.filter(x => x !== pawn); p.pawns = p.pawns.filter(x => x !== pawn);
}); });

View file

@ -370,51 +370,45 @@ float vecs2m(cpVect a, cpVect b)
return (b.y-a.y)/(b.x-a.x); return (b.y-a.y)/(b.x-a.x);
} }
cpVect inflatepoint(cpVect a, cpVect b, cpVect c, float d) cpVect *inflatepoints(cpVect *p, float d, int n)
{
cpVect ba = cpvnormalize(cpvsub(a,b));
cpVect bc = cpvnormalize(cpvsub(c,b));
cpVect avg = cpvadd(ba, bc);
avg = cpvmult(avg, 0.5);
float dot = cpvdot(ba, bc);
dot /= cpvlength(ba);
dot /= cpvlength(bc);
float mid = acos(dot)/2;
avg = cpvnormalize(avg);
return cpvadd(b, cpvmult(avg, d/sin(mid)));
}
void inflatepoints(cpVect *r, cpVect *p, float d, int n)
{ {
if (d == 0) { if (d == 0) {
cpVect *ret = NULL;
arraddn(ret,n);
for (int i = 0; i < n; i++) for (int i = 0; i < n; i++)
r[i] = p[i]; ret[i] = p[i];
return; return ret;
} }
if (cpveql(p[0], p[n-1])) { parsl_position par_v[n];
r[0] = inflatepoint(p[n-2],p[0],p[1],d); uint16_t spine_lens[] = {n};
r[n-1] = r[0]; for (int i = 0; i < n; i++) {
} else { par_v[i].x = p[i].x;
cpVect outdir = cpvmult(cpvnormalize(cpvsub(p[0],p[1])),fabs(d)); par_v[i].y = p[i].y;
cpVect perp; };
if (d > 0)
perp = cpvperp(outdir);
else
perp = cpvrperp(outdir);
r[0] = cpvadd(p[0],cpvadd(outdir,perp));
outdir = cpvmult(cpvnormalize(cpvsub(p[n-1],p[n-2])),fabs(d));
if (d > 0)
perp = cpvrperp(outdir);
else
perp = cpvperp(outdir);
r[n-1] = cpvadd(p[n-1],cpvadd(outdir,perp));
}
for (int i = 0; i < n-2; i++) parsl_context *par_ctx = parsl_create_context((parsl_config){
r[i+1] = inflatepoint(p[i],p[i+1],p[i+2], d); .thickness = d,
.flags= PARSL_FLAG_ANNOTATIONS,
.u_mode = PAR_U_MODE_DISTANCE
});
parsl_mesh *mesh = parsl_mesh_from_lines(par_ctx, (parsl_spine_list){
.num_vertices = n,
.num_spines = 1,
.vertices = par_v,
.spine_lengths = spine_lens,
.closed = 0,
});
cpVect *ret = NULL;
arraddn(ret,mesh->num_vertices);
for (int i = 0; i < mesh->num_vertices; i++) {
ret[i].x = mesh->positions[i].x;
ret[i].y = mesh->positions[i].y;
};
return ret;
} }
void draw_edge(cpVect *points, int n, struct rgba color, int thickness, int closed, int flags, struct rgba line_color, float line_seg) void draw_edge(cpVect *points, int n, struct rgba color, int thickness, int closed, int flags, struct rgba line_color, float line_seg)
@ -443,7 +437,6 @@ void draw_edge(cpVect *points, int n, struct rgba color, int thickness, int clos
parsl_context *par_ctx = parsl_create_context((parsl_config){ parsl_context *par_ctx = parsl_create_context((parsl_config){
.thickness = thickness, .thickness = thickness,
.flags = PARSL_FLAG_ANNOTATIONS, .flags = PARSL_FLAG_ANNOTATIONS,
.u_mode = PAR_U_MODE_DISTANCE,
}); });
parsl_mesh *mesh = parsl_mesh_from_lines(par_ctx, (parsl_spine_list){ parsl_mesh *mesh = parsl_mesh_from_lines(par_ctx, (parsl_spine_list){

View file

@ -24,7 +24,6 @@ void debug_flush(HMM_Mat4 *view);
void debug_newframe(); void debug_newframe();
void debug_nextpass(); void debug_nextpass();
cpVect inflatepoint(cpVect a, cpVect b, cpVect c, float d); cpVect *inflatepoints(cpVect *p, float d, int n);
void inflatepoints(cpVect *r, cpVect *p, float d, int n);
#endif #endif

View file

@ -237,7 +237,7 @@ cpBitmask js2bitmask(JSValue v) {
cpVect *cpvecarr = NULL; cpVect *cpvecarr = NULL;
/* Must be freed */ /* Does not need to be freed by returning; but not reentrant */
cpVect *js2cpvec2arr(JSValue v) { cpVect *js2cpvec2arr(JSValue v) {
if (cpvecarr) if (cpvecarr)
arrfree(cpvecarr); arrfree(cpvecarr);
@ -362,30 +362,24 @@ JSValue duk_spline_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst
tsBSpline spline; tsBSpline spline;
int d = js2int(argv[2]); /* dimensions */
int degrees = js2int(argv[1]); int degrees = js2int(argv[1]);
int d = js2int(argv[2]); /* dimensions */
int type = js2int(argv[3]); int type = js2int(argv[3]);
JSValue ctrl_pts = argv[4]; cpVect *points = js2cpvec2arr(argv[4]);
int n = js_arrlen(ctrl_pts);
size_t nsamples = js2int(argv[5]); size_t nsamples = js2int(argv[5]);
cpVect points[n];
tsStatus status; tsStatus status;
ts_bspline_new(n, d, degrees, type, &spline, &status); ts_bspline_new(arrlen(points), d, degrees, type, &spline, &status);
if (status.code) if (status.code)
YughCritical("Spline creation error %d: %s", status.code, status.message); YughCritical("Spline creation error %d: %s", status.code, status.message);
for (int i = 0; i < n; i++) ts_bspline_set_control_points(&spline, (tsReal*)points, &status);
points[i] = js2vec2(js_getpropidx( ctrl_pts, i));
ts_bspline_set_control_points(&spline, (tsReal *)points, &status);
if (status.code) if (status.code)
YughCritical("Spline creation error %d: %s", status.code, status.message); YughCritical("Spline creation error %d: %s", status.code, status.message);
cpVect samples[nsamples]; cpVect *samples = malloc(nsamples*sizeof(cpVect));
size_t rsamples; size_t rsamples;
/* TODO: This does not work with Clang/GCC due to UB */ /* TODO: This does not work with Clang/GCC due to UB */
@ -394,16 +388,10 @@ JSValue duk_spline_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst
if (status.code) if (status.code)
YughCritical("Spline creation error %d: %s", status.code, status.message); YughCritical("Spline creation error %d: %s", status.code, status.message);
JSValue arr = JS_NewArray(js); JSValue arr = vecarr2js(samples, nsamples);
for (int i = 0; i < nsamples; i++) {
JSValue psample = JS_NewArray(js);
js_setprop_num(psample, 0, float2js(samples[i].x));
js_setprop_num(psample, 1, float2js(samples[i].y));
js_setprop_num(arr, i, psample);
}
ts_bspline_free(&spline); ts_bspline_free(&spline);
free(samples);
return arr; return arr;
} }
@ -1587,16 +1575,9 @@ JSValue duk_inflate_cpv(JSContext *js, JSValueConst this, int argc, JSValueConst
cpVect *points = js2cpvec2arr(argv[0]); cpVect *points = js2cpvec2arr(argv[0]);
int n = js2int(argv[1]); int n = js2int(argv[1]);
double d = js2number(argv[2]); double d = js2number(argv[2]);
cpVect *infl = inflatepoints(points,d,n);
cpVect inflate_out[n]; JSValue arr = vecarr2js(infl,arrlen(infl));
cpVect inflate_in[n]; arrfree(infl);
inflatepoints(inflate_out, points, d, n);
inflatepoints(inflate_in, points, -d, n);
JSValue arr = JS_NewArray(js);
js_setprop_num(arr, 0, vecarr2js(inflate_out, n));
js_setprop_num(arr, 1, vecarr2js(inflate_in, n));
return arr; return arr;
} }

View file

@ -263,6 +263,10 @@ const char *keyname_extd(int key) {
return "space"; return "space";
case SAPP_KEYCODE_KP_ADD: case SAPP_KEYCODE_KP_ADD:
return "plus"; return "plus";
case '=':
return "plus";
case '-':
return "minus";
case SAPP_KEYCODE_KP_SUBTRACT: case SAPP_KEYCODE_KP_SUBTRACT:
return "minus"; return "minus";
case SAPP_KEYCODE_GRAVE_ACCENT: case SAPP_KEYCODE_GRAVE_ACCENT: