prosperon/scripts/components.js

902 lines
23 KiB
JavaScript
Raw Normal View History

2024-05-28 13:39:02 -05:00
var component = {};
2024-09-26 11:36:09 -05:00
var make_point_obj = function (o, p) {
2024-02-25 17:31:48 -06:00
return {
pos: p,
move(d) {
d = o.gameobject.dir_world2this(d);
p.x += d.x;
p.y += d.y;
},
2024-09-26 11:36:09 -05:00
sync: o.sync.bind(o),
};
};
2024-02-25 17:31:48 -06:00
2024-09-26 11:36:09 -05:00
var sprite_addbucket = function (sprite) {
2024-10-05 08:43:51 -05:00
if (!sprite.image) return;
2024-09-26 23:12:30 -05:00
var layer = 1000000 + sprite.gameobject.drawlayer * 1000 - sprite.gameobject.pos.y;
sprite_buckets[layer] ??= {};
2024-10-05 08:43:51 -05:00
sprite_buckets[layer][sprite.image.texture.path] ??= [];
sprite_buckets[layer][sprite.image.texture.path].push(sprite);
2024-09-26 23:12:30 -05:00
sprite._oldlayer = layer;
2024-10-05 08:43:51 -05:00
sprite._oldpath = sprite.image.texture.path;
2024-09-26 11:36:09 -05:00
};
2024-07-23 17:21:27 -05:00
2024-09-26 11:36:09 -05:00
var sprite_rmbucket = function (sprite) {
2024-09-26 23:12:30 -05:00
if (sprite._oldlayer && sprite._oldpath) sprite_buckets[sprite._oldlayer][sprite._oldpath].remove(sprite);
else for (var layer of Object.values(sprite_buckets)) for (var path of Object.values(layer)) path.remove(sprite);
2024-09-26 11:36:09 -05:00
};
2024-07-23 17:21:27 -05:00
2024-10-05 08:43:51 -05:00
/* an anim is simply an array of images */
/* an anim set is like this
frog = {
walk: [],
hop: [],
...etc
}
*/
2024-05-08 11:07:19 -05:00
var sprite = {
2024-10-05 08:43:51 -05:00
image: undefined,
get diffuse() { return this.image.texture; },
set diffuse(x) {},
2024-09-10 14:33:49 -05:00
anim_speed: 1,
2024-10-05 08:43:51 -05:00
play(str, loop = true, reverse = false) {
str ??= this.anim;
if (!str) return;
if (typeof str === 'string')
str = this.animset[str];
var playing = str;
this.del_anim?.();
2024-10-05 08:43:51 -05:00
var self = this;
2024-03-27 15:00:59 -05:00
var stop;
2024-10-05 08:43:51 -05:00
2024-09-26 11:36:09 -05:00
self.del_anim = function () {
self.del_anim = undefined;
self = undefined;
advance = undefined;
2024-04-03 00:44:08 -05:00
stop?.();
2024-09-26 11:36:09 -05:00
};
2024-10-05 08:43:51 -05:00
var f = 0;
if (reverse) f = playing.length - 1;
2024-09-26 11:36:09 -05:00
2024-09-30 10:14:56 -05:00
function advance(time) {
if (!self) return;
if (!self.gameobject) return;
2024-10-05 08:43:51 -05:00
2024-09-08 11:24:21 -05:00
var done = false;
if (reverse) {
2024-10-05 08:43:51 -05:00
f = (((f - 1) % playing.length) + playing.length) % playing.length;
if (f === playing.length - 1) done = true;
2024-09-26 11:36:09 -05:00
} else {
2024-10-05 08:43:51 -05:00
f = (f + 1) % playing.length;
2024-09-26 11:36:09 -05:00
if (f === 0) done = true;
2024-09-08 11:24:21 -05:00
}
2024-09-26 11:36:09 -05:00
2024-10-05 08:43:51 -05:00
self.image = playing[f];
2024-09-08 11:24:21 -05:00
if (done) {
2024-10-05 08:43:51 -05:00
self.anim_done?.();
2024-09-26 11:36:09 -05:00
if (!loop) {
self?.stop();
return;
}
2024-03-27 15:00:59 -05:00
}
2024-09-30 10:14:56 -05:00
2024-10-05 08:43:51 -05:00
return playing[f].time/self.anim_speed;
2023-12-29 19:08:53 -06:00
}
2024-10-05 08:43:51 -05:00
stop = self.gameobject.delay(advance, playing[f].time/self.anim_speed);
advance();
},
2024-08-22 14:41:01 -05:00
tex_sync() {
if (this.anim) this.stop();
this.sync();
this.play();
},
2024-03-22 09:02:10 -05:00
stop() {
this.del_anim?.();
},
set path(p) {
2024-10-05 08:43:51 -05:00
var image = game.texture(p);
if (!image) {
console.warn(`Could not find image ${p}.`);
return;
}
2024-08-22 14:41:01 -05:00
2024-07-23 17:21:27 -05:00
this._p = p;
2024-09-26 11:36:09 -05:00
this.del_anim?.();
2024-10-05 08:43:51 -05:00
this.image = image;
if (Array.isArray(image)) {
this.anim = image;
this.image = image[0];
}
if (Object.values(image)[0][0]) {
// it is an anims set
this.animset = image;
this.image = Object.values(image)[0][0];
}
2024-08-22 14:41:01 -05:00
this.tex_sync();
2023-09-26 17:07:51 -05:00
},
get path() {
return this._p;
2023-12-29 19:08:53 -06:00
},
kill() {
sprite_rmbucket(this);
this.del_anim?.();
this.anim = undefined;
this.gameobject = undefined;
2024-04-03 00:44:08 -05:00
this.anim_done = undefined;
2024-09-26 23:12:30 -05:00
allsprites.remove(this);
},
2024-09-26 11:36:09 -05:00
move(d) {
this.pos = this.pos.add(d);
},
grow(x) {
this.scale = this.scale.scale(x);
this.pos = this.pos.scale(x);
},
2024-09-26 11:36:09 -05:00
anchor: [0, 0],
sync() {
2024-09-26 23:12:30 -05:00
var layer = 1000000 + this.gameobject.drawlayer * 1000 - this.gameobject.pos.y;
if (layer === this._oldlayer && this.path === this._oldpath) return;
sprite_rmbucket(this);
sprite_addbucket(this);
},
2024-09-26 11:36:09 -05:00
pick() {
return this;
},
boundingbox() {
var dim = this.dimensions();
2024-04-22 08:47:04 -05:00
dim = dim.scale(this.gameobject.scale);
2023-12-20 17:20:29 -06:00
var realpos = dim.scale(0.5).add(this.pos);
2024-09-26 11:36:09 -05:00
return bbox.fromcwh(realpos, dim);
},
2024-05-08 11:07:19 -05:00
};
2024-09-26 23:12:30 -05:00
globalThis.allsprites = [];
var sprite_buckets = [];
2024-09-26 11:36:09 -05:00
component.sprite_buckets = function () {
return sprite_buckets;
};
2024-09-26 23:12:30 -05:00
component.dynamic_sprites = [];
sprite.doc = {
path: "Path to the texture.",
color: "Color to mix with the sprite.",
2024-09-26 11:36:09 -05:00
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;
2024-03-22 09:02:10 -05:00
this.pos = this.dimensions().scale(off);
2024-09-26 11:36:09 -05:00
};
2024-03-22 09:02:10 -05:00
sprite.inputs = {};
2024-09-26 11:36:09 -05:00
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");
};
component.sprite = function (obj) {
2024-05-28 13:39:02 -05:00
var sp = Object.create(sprite);
sp.gameobject = obj;
2024-07-22 19:07:02 -05:00
sp.transform = obj.transform;
2024-05-28 13:39:02 -05:00
sp.guid = prosperon.guid();
2024-09-26 23:12:30 -05:00
allsprites.push(sp);
sprite_addbucket(sp);
2024-05-28 13:39:02 -05:00
return sp;
2024-09-26 11:36:09 -05:00
};
2024-07-23 17:21:27 -05:00
2024-09-26 11:36:09 -05:00
sprite.shade = [1, 1, 1, 1];
2024-05-08 11:07:19 -05:00
2024-05-28 13:39:02 -05:00
Object.mixin(os.make_seg2d(), {
2024-09-26 11:36:09 -05:00
sync() {
this.set_endpoints(this.points[0], this.points[1]);
},
2024-05-28 13:39:02 -05:00
});
2023-12-29 19:08:53 -06:00
/* sprite anim returns a data structure for the given file path
frames: array of frames
rect: frame rectangle
time: miliseconds to hold the frame for
loop: true if it should be looped
*/
var animcache = {};
2024-04-10 16:21:46 -05:00
var SpriteAnim = {};
2024-10-02 09:55:32 -05:00
SpriteAnim.hotreload = function(path) {
delete animcache[path];
}
2024-10-05 08:43:51 -05:00
SpriteAnim.make = function (path, tex) {
2024-04-10 16:21:46 -05:00
var anim;
2024-10-02 09:55:32 -05:00
if (animcache[path]) return animcache[path];
2024-10-05 08:43:51 -05:00
if (!tex) return;
2024-10-02 09:55:32 -05:00
profile.report(`animation_${path}`);
2024-10-05 08:43:51 -05:00
if (io.exists(path.set_ext(".ase"))) anim = SpriteAnim.aseprite(path.set_ext(".ase"), tex);
else if (io.exists(path.set_ext(".json"))) anim = SpriteAnim.aseprite(path.set_ext(".json"), tex);
else if (path.ext() === "ase") anim = SpriteAnim.aseprite(path, tex);
else if (path.ext() === "gif") anim = SpriteAnim.gif(path, tex);
2024-09-26 11:36:09 -05:00
else anim = undefined;
2024-10-02 09:55:32 -05:00
profile.endreport(`animation_${path}`);
2024-09-26 11:36:09 -05:00
animcache[path] = anim;
2024-04-10 16:21:46 -05:00
return animcache[path];
};
2024-10-05 08:43:51 -05:00
SpriteAnim.gif = function (path, tex) {
2024-04-10 16:21:46 -05:00
var anim = {};
anim.frames = [];
anim.path = path;
var frames = tex.frames;
if (frames === 1) return undefined;
2024-09-26 11:36:09 -05:00
var yslice = 1 / frames;
2024-04-10 16:21:46 -05:00
for (var f = 0; f < frames; f++) {
var frame = {};
2024-10-05 08:43:51 -05:00
frame.rect = [
0,
yslice * f,
1,
yslice,
];
2024-04-10 16:21:46 -05:00
frame.time = 0.05;
anim.frames.push(frame);
}
var times = tex.delays;
2024-09-26 11:36:09 -05:00
for (var i = 0; i < frames; i++) anim.frames[i].time = times[i] / 1000;
2024-04-10 16:21:46 -05:00
anim.loop = true;
2024-09-26 11:36:09 -05:00
var dim = [tex.width, tex.height];
2024-04-10 16:21:46 -05:00
dim.y /= frames;
anim.dim = dim;
2024-09-26 11:36:09 -05:00
return { 0: anim };
2024-04-10 16:21:46 -05:00
};
2024-09-26 11:36:09 -05:00
SpriteAnim.strip = function (path, frames, time = 0.05) {
2024-04-10 16:21:46 -05:00
var anim = {};
anim.frames = [];
anim.path = path;
2024-09-26 11:36:09 -05:00
var xslice = 1 / frames;
2024-04-10 16:21:46 -05:00
for (var f = 0; f < frames; f++) {
var frame = {};
2024-09-26 11:36:09 -05:00
frame.rect = { s0: xslice * f, s1: xslice * (f + 1), t0: 0, t1: 1 };
2024-04-10 16:21:46 -05:00
frame.time = time;
anim.frames.push(frame);
}
anim.dim = Resources.texture.dimensions(path);
anim.dim.x /= frames;
return anim;
};
2024-09-26 11:36:09 -05:00
SpriteAnim.aseprite = function (path) {
2024-04-10 16:21:46 -05:00
function aseframeset2anim(frameset, meta) {
2023-11-01 15:33:22 -05:00
var anim = {};
anim.frames = [];
anim.path = path.folder() + meta.image;
2024-04-10 16:21:46 -05:00
var dim = meta.size;
2024-09-26 11:36:09 -05:00
var ase_make_frame = function (ase_frame) {
2024-04-10 16:21:46 -05:00
var f = ase_frame.frame;
2023-11-01 15:33:22 -05:00
var frame = {};
2024-10-05 08:43:51 -05:00
frame.rect = [
f.x / dim.w,
f.y / dim.h,
f.w / dim.w,
f.h / dim.h,
];
2024-04-10 16:21:46 -05:00
frame.time = ase_frame.duration / 1000;
2023-11-01 15:33:22 -05:00
anim.frames.push(frame);
2024-04-10 16:21:46 -05:00
};
2023-09-25 08:21:02 -05:00
2024-04-10 16:21:46 -05:00
frameset.forEach(ase_make_frame);
anim.dim = frameset[0].sourceSize;
anim.loop = true;
2023-11-01 15:33:22 -05:00
return anim;
2024-09-26 11:36:09 -05:00
}
2024-04-10 16:21:46 -05:00
var data = json.decode(io.slurp(path));
if (!data?.meta?.app.includes("aseprite")) return;
var anims = {};
var frames = Array.isArray(data.frames) ? data.frames : Object.values(data.frames);
var f = 0;
2024-06-19 18:52:41 -05:00
if (!data.meta.frameTags || data.meta.frameTags.length === 0) {
anims[0] = aseframeset2anim(frames, data.meta);
return anims;
}
2024-04-10 16:21:46 -05:00
for (var tag of data.meta.frameTags) {
2024-09-26 11:36:09 -05:00
anims[tag.name] = aseframeset2anim(frames.slice(tag.from, tag.to + 1), data.meta);
2024-04-10 16:21:46 -05:00
anims[f] = anims[tag.name];
f++;
}
2023-11-01 15:33:22 -05:00
2024-04-10 16:21:46 -05:00
return anims;
};
2024-09-26 11:36:09 -05:00
SpriteAnim.validate = function (anim) {
2024-04-10 16:21:46 -05:00
if (!Object.isObject(anim)) return false;
2024-09-26 11:36:09 -05:00
if (typeof anim.path !== "string") return false;
if (typeof anim.dim !== "object") return false;
2024-04-10 16:21:46 -05:00
return true;
};
2024-09-26 11:36:09 -05:00
SpriteAnim.find = function (path) {
2024-04-10 16:21:46 -05:00
if (!io.exists(path + ".asset")) return;
var asset = JSON.parse(io.slurp(path + ".asset"));
2023-11-01 15:33:22 -05:00
};
2024-09-26 11:36:09 -05:00
SpriteAnim.doc = "Functions to create Primum animations from varying sources.";
SpriteAnim.gif.doc = "Convert a gif.";
SpriteAnim.strip.doc = "Given a path and number of frames, converts a horizontal strip animation, where each cell is the same width.";
SpriteAnim.aseprite.doc = "Given an aseprite json metadata, returns an object of animations defined in the aseprite file.";
SpriteAnim.find.doc = "Given a path, find the relevant animation for the file.";
2024-05-28 13:39:02 -05:00
var collider2d = {};
collider2d.inputs = {};
2024-09-26 11:36:09 -05:00
collider2d.inputs["M-s"] = function () {
this.sensor = !this.sensor;
};
collider2d.inputs["M-s"].doc = "Toggle if this collider is a sensor.";
2024-09-26 11:36:09 -05:00
collider2d.inputs["M-t"] = function () {
this.enabled = !this.enabled;
};
collider2d.inputs["M-t"].doc = "Toggle if this collider is enabled.";
2024-05-28 13:39:02 -05:00
Object.mix(os.make_poly2d(), {
boundingbox() {
return bbox.frompoints(this.spoints());
},
2024-09-26 11:36:09 -05:00
/* EDITOR */
2023-12-19 15:34:36 -06:00
spoints() {
var spoints = this.points.slice();
2024-09-26 11:36:09 -05:00
if (this.flipx) {
2024-09-26 11:36:09 -05:00
spoints.forEach(function (x) {
var newpoint = x.slice();
2024-09-26 11:36:09 -05:00
newpoint.x = -newpoint.x;
spoints.push(newpoint);
});
}
2024-09-26 11:36:09 -05:00
if (this.flipy) {
2024-09-26 11:36:09 -05:00
spoints.forEach(function (x) {
var newpoint = x.slice();
2024-09-26 11:36:09 -05:00
newpoint.y = -newpoint.y;
spoints.push(newpoint);
});
}
return spoints;
},
2024-09-26 11:36:09 -05:00
gizmo() {
2024-02-25 17:31:48 -06:00
this.spoints().forEach(x => render.point(this.gameobject.this2screen(x), 3, Color.green));
2024-09-26 11:36:09 -05:00
this.points.forEach((x, i) => render.coordinate(this.gameobject.this2screen(x)));
},
pick(pos) {
2024-09-26 11:36:09 -05:00
if (!Object.hasOwn(this, "points")) this.points = deep_copy(this.__proto__.points);
2023-12-18 06:45:27 -06:00
var i = Gizmos.pick_gameobject_points(pos, this.gameobject, this.points);
var p = this.points[i];
2024-09-26 11:36:09 -05:00
if (p) return make_point_obj(this, p);
2023-09-27 12:36:32 -05:00
return undefined;
},
2023-10-05 17:30:17 -05:00
});
function pointscaler(x) {
2024-09-26 11:36:09 -05:00
if (typeof x === "number") return;
this.points = this.points.map(p => p.mult(x));
}
2024-05-28 13:39:02 -05:00
Object.mixin(os.make_poly2d(), {
2024-09-26 11:36:09 -05:00
sync() {
2024-05-28 13:39:02 -05:00
this.setverts(this.points);
},
grow: pointscaler,
});
2024-05-28 13:39:02 -05:00
var polyinputs = Object.create(collider2d.inputs);
os.make_poly2d().inputs = polyinputs;
2024-05-28 13:39:02 -05:00
polyinputs = {};
2024-09-26 11:36:09 -05:00
polyinputs.f10 = function () {
2024-02-25 17:31:48 -06:00
this.points = Math.sortpointsccw(this.points);
};
2024-05-28 13:39:02 -05:00
polyinputs.f10.doc = "Sort all points to be CCW order.";
2024-09-26 11:36:09 -05:00
polyinputs["C-lm"] = function () {
2024-04-03 00:44:08 -05:00
this.points.push(this.gameobject.world2this(input.mouse.worldpos()));
};
2024-09-26 11:36:09 -05:00
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);
};
2024-09-26 11:36:09 -05:00
polyinputs["C-M-lm"].doc = "Remove point under mouse.";
2024-09-26 11:36:09 -05:00
polyinputs["C-b"] = function () {
this.points = this.spoints;
this.flipx = false;
this.flipy = false;
};
2024-09-26 11:36:09 -05:00
polyinputs["C-b"].doc = "Freeze mirroring in place.";
2024-05-28 13:39:02 -05:00
var edge2d = {
2024-09-26 11:36:09 -05:00
dimensions: 2,
thickness: 1,
2023-12-13 08:06:24 -06:00
/* if type === -1, point to point */
2023-12-12 08:46:27 -06:00
type: Spline.type.catmull,
2024-09-26 11:36:09 -05:00
C: 1 /* when in bezier, continuity required. 0, 1 or 2. */,
looped: false,
2024-09-26 11:36:09 -05:00
angle: 0.5 /* smaller for smoother bezier */,
2024-05-28 13:39:02 -05:00
elasticity: 0,
friction: 0,
sync() {
var ppp = this.sample();
this.segs ??= [];
2024-09-26 11:36:09 -05:00
var count = ppp.length - 1;
2024-05-28 13:39:02 -05:00
this.segs.length = count;
for (var i = 0; i < count; i++) {
this.segs[i] ??= os.make_seg2d(this.body);
2024-09-26 11:36:09 -05:00
this.segs[i].set_endpoints(ppp[i], ppp[i + 1]);
this.segs[i].set_neighbors(ppp[i], ppp[i + 1]);
2024-05-28 13:39:02 -05:00
this.segs[i].radius = this.thickness;
this.segs[i].elasticity = this.elasticity;
this.segs[i].friction = this.friction;
this.segs[i].collide = this.collide;
}
},
2024-09-26 11:36:09 -05:00
flipx: false,
flipy: false,
2024-09-26 11:36:09 -05:00
hollow: false,
2023-05-27 07:01:17 -05:00
hollowt: 0,
2023-10-12 17:05:49 -05:00
spoints() {
if (!this.points) return [];
var spoints = this.points.slice();
2024-09-26 11:36:09 -05:00
if (this.flipx) {
2024-09-26 11:36:09 -05:00
if (Spline.is_bezier(this.type)) spoints.push(Vector.reflect_point(spoints.at(-2), spoints.at(-1)));
2023-12-20 17:20:29 -06:00
2024-09-26 11:36:09 -05:00
for (var i = spoints.length - 1; i >= 0; i--) {
var newpoint = spoints[i].slice();
2024-09-26 11:36:09 -05:00
newpoint.x = -newpoint.x;
spoints.push(newpoint);
}
}
2024-09-26 11:36:09 -05:00
if (this.flipy) {
2024-09-26 11:36:09 -05:00
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();
2024-04-03 17:17:32 -05:00
newpoint.y = -newpoint.y;
spoints.push(newpoint);
}
}
if (this.hollow) {
2024-03-20 14:32:35 -05:00
var hpoints = vector.inflate(spoints, this.hollowt);
if (hpoints.length === spoints.length) return spoints;
2024-09-26 11:36:09 -05:00
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());
}
2024-09-26 11:36:09 -05:00
if (this.looped) spoints = spoints.wrapped(1);
return spoints;
},
post() {
this.points = [];
},
2023-12-13 08:06:24 -06:00
sample() {
2023-10-12 17:05:49 -05:00
var spoints = this.spoints();
2024-03-09 18:22:06 -06:00
if (spoints.length === 0) return [];
2024-09-26 11:36:09 -05:00
2023-12-13 08:06:24 -06:00
if (this.type === -1) {
if (this.looped) spoints.push(spoints[0]);
return spoints;
}
2023-12-13 19:53:09 -06:00
if (this.type === Spline.type.catmull) {
2024-09-26 11:36:09 -05:00
if (this.looped) spoints = Spline.catmull_loop(spoints);
else spoints = Spline.catmull_caps(spoints);
2023-12-18 06:45:27 -06:00
2024-09-26 11:36:09 -05:00
return Spline.sample_angle(this.type, spoints, this.angle);
2023-12-12 19:35:34 -06:00
}
2023-12-13 19:53:09 -06:00
2024-09-26 11:36:09 -05:00
if (this.looped && Spline.is_bezier(this.type)) spoints = Spline.bezier_loop(spoints);
2023-12-20 17:20:29 -06:00
2023-12-12 08:46:27 -06:00
return Spline.sample_angle(this.type, spoints, this.angle);
},
2024-09-26 11:36:09 -05:00
boundingbox() {
return bbox.frompoints(this.points.map(x => x.scale(this.gameobject.scale)));
},
2023-05-27 07:01:17 -05:00
/* EDITOR */
gizmo() {
2023-12-27 10:34:14 -06:00
if (this.type === Spline.type.catmull || this.type === -1) {
2024-02-25 17:31:48 -06:00
this.spoints().forEach(x => render.point(this.gameobject.this2screen(x), 3, Color.teal));
2024-09-26 11:36:09 -05:00
this.points.forEach((x, i) => render.coordinate(this.gameobject.this2screen(x)));
2023-12-15 12:45:09 -06:00
} else {
2024-09-26 11:36:09 -05:00
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) {
2024-04-04 17:28:11 -05:00
render.coordinate(this.gameobject.this2screen(this.points[i]), 1, Color.green);
2024-09-26 11:36:09 -05:00
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);
2023-12-15 12:45:09 -06:00
}
}
},
2024-09-26 11:36:09 -05:00
finish_center(change) {
this.points = this.points.map(function (x) {
return x.sub(change);
});
},
2023-05-27 07:01:17 -05:00
pick(pos) {
var i = Gizmos.pick_gameobject_points(pos, this.gameobject, this.points);
var p = this.points[i];
2023-12-18 17:12:05 -06:00
if (!p) return undefined;
2024-09-26 11:36:09 -05:00
if (Spline.is_catmull(this.type) || this.type === -1) return make_point_obj(this, p);
2023-12-18 06:45:27 -06:00
var that = this.gameobject;
var me = this;
if (p) {
var o = {
pos: p,
2024-09-26 11:36:09 -05:00
sync: me.sync.bind(me),
2023-12-18 06:45:27 -06:00
};
2024-09-26 11:36:09 -05:00
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);
};
2023-12-18 06:45:27 -06:00
else
2024-09-26 11:36:09 -05:00
o.move = function (d) {
2024-04-03 17:17:32 -05:00
d = that.dir_world2this(d);
p.x += d.x;
p.y += d.y;
2024-09-26 11:36:09 -05:00
var pp = Spline.bezier_point_handles(me.points, i);
pp.forEach(ph => (me.points[ph] = me.points[ph].add(d)));
};
2023-12-18 06:45:27 -06:00
return o;
}
},
2023-12-19 15:34:36 -06:00
rm_node(idx) {
if (idx < 0 || idx >= this.points.length) return;
2024-09-26 11:36:09 -05:00
if (Spline.is_catmull(this.type)) this.points.splice(idx, 1);
2023-12-19 15:34:36 -06:00
if (Spline.is_bezier(this.type)) {
2024-09-26 11:36:09 -05:00
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);
2023-12-19 15:34:36 -06:00
}
},
add_node(pos) {
pos = this.gameobject.world2this(pos);
var idx = 0;
2023-12-27 10:34:14 -06:00
if (Spline.is_catmull(this.type) || this.type === -1) {
2024-09-26 11:36:09 -05:00
if (this.points.length >= 2) idx = physics.closest_point(pos, this.points, 400);
2023-12-19 15:34:36 -06:00
2024-09-26 11:36:09 -05:00
if (idx === this.points.length) this.points.push(pos);
else this.points.splice(idx, 0, pos);
2023-12-19 15:34:36 -06:00
}
if (Spline.is_bezier(this.type)) {
2024-09-26 11:36:09 -05:00
idx = physics.closest_point(pos, Spline.bezier_nodes(this.points), 400);
2023-12-21 10:49:44 -06:00
2023-12-19 15:34:36 -06:00
if (idx < 0) return;
2024-09-26 11:36:09 -05:00
2023-12-21 10:49:44 -06:00
if (idx === 0) {
2024-09-26 11:36:09 -05:00
this.points.unshift(pos.slice(), pos.add([-100, 0]), Vector.reflect_point(this.points[1], this.points[0]));
return;
2023-12-21 10:49:44 -06:00
}
if (idx === Spline.bezier_node_count(this.points)) {
2024-09-26 11:36:09 -05:00
this.points.push(Vector.reflect_point(this.points.at(-2), this.points.at(-1)), pos.add([-100, 0]), pos.slice());
return;
2023-12-21 10:49:44 -06:00
}
2024-09-26 11:36:09 -05:00
idx = 2 + (idx - 1) * 3;
var adds = [pos.add([100, 0]), pos.slice(), pos.add([-100, 0])];
this.points.splice(idx, 0, ...adds);
2023-12-19 15:34:36 -06:00
}
},
pick_all() {
var picks = [];
2024-09-26 11:36:09 -05:00
this.points.forEach(x => picks.push(make_point_obj(this, x)));
return picks;
},
2024-05-28 13:39:02 -05:00
};
2024-09-26 11:36:09 -05:00
component.edge2d = function (obj) {
2024-05-28 13:39:02 -05:00
if (!obj.body) obj.rigidify();
var edge = Object.create(edge2d);
edge.body = obj.body;
return edge;
2024-09-26 11:36:09 -05:00
};
2023-10-05 17:30:17 -05:00
2024-05-28 13:39:02 -05:00
edge2d.spoints.doc = "Returns the controls points after modifiers are applied, such as it being hollow or mirrored on its axises.";
edge2d.inputs = {};
2024-09-26 11:36:09 -05:00
edge2d.inputs.h = function () {
this.hollow = !this.hollow;
};
2024-05-28 13:39:02 -05:00
edge2d.inputs.h.doc = "Toggle hollow.";
2024-09-26 11:36:09 -05:00
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;
2024-09-26 11:36:09 -05:00
edge2d.inputs["C-f"] = function () {
this.hollowt++;
};
edge2d.inputs["C-f"].doc = "Increase the hollow thickness.";
edge2d.inputs["C-f"].rep = true;
2024-09-26 11:36:09 -05:00
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;
2024-09-26 11:36:09 -05:00
edge2d.inputs["C-y"] = function () {
this.points = this.spoints();
this.flipx = false;
this.flipy = false;
this.hollow = false;
};
2024-09-26 11:36:09 -05:00
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;
2024-09-26 11:36:09 -05:00
edge2d.inputs.plus = function () {
2023-12-13 08:06:24 -06:00
if (this.angle <= 1) {
this.angle = 1;
return;
}
this.angle *= 0.9;
};
2024-05-28 13:39:02 -05:00
edge2d.inputs.plus.doc = "Increase the number of samples of this spline.";
edge2d.inputs.plus.rep = true;
2024-09-26 11:36:09 -05:00
edge2d.inputs.minus = function () {
this.angle *= 1.1;
};
2024-05-28 13:39:02 -05:00
edge2d.inputs.minus.doc = "Decrease the number of samples on this spline.";
edge2d.inputs.minus.rep = true;
2024-09-26 11:36:09 -05:00
edge2d.inputs["C-r"] = function () {
this.points = this.points.reverse();
};
edge2d.inputs["C-r"].doc = "Reverse the order of the spline's points.";
2024-09-26 11:36:09 -05:00
edge2d.inputs["C-l"] = function () {
this.looped = !this.looped;
};
edge2d.inputs["C-l"].doc = "Toggle spline being looped.";
2024-09-26 11:36:09 -05:00
edge2d.inputs["C-c"] = function () {
switch (this.type) {
2023-12-18 06:45:27 -06:00
case Spline.type.bezier:
this.points = Spline.bezier2catmull(this.points);
2023-12-18 06:45:27 -06:00
break;
}
this.type = Spline.type.catmull;
};
2024-09-26 11:36:09 -05:00
edge2d.inputs["C-c"].doc = "Set type of spline to catmull-rom.";
2024-09-26 11:36:09 -05:00
edge2d.inputs["C-b"] = function () {
switch (this.type) {
2023-12-18 06:45:27 -06:00
case Spline.type.catmull:
this.points = Spline.catmull2bezier(Spline.catmull_caps(this.points));
2023-12-18 06:45:27 -06:00
break;
}
this.type = Spline.type.bezier;
};
2023-12-13 19:53:09 -06:00
2024-09-26 11:36:09 -05:00
edge2d.inputs["C-o"] = function () {
this.type = -1;
};
edge2d.inputs["C-o"].doc = "Set spline to linear.";
2024-09-26 11:36:09 -05:00
edge2d.inputs["C-M-lm"] = function () {
2023-12-19 15:34:36 -06:00
if (Spline.is_catmull(this.type)) {
2024-09-26 11:36:09 -05:00
var idx = Math.grab_from_points(
input.mouse.worldpos(),
this.points.map(p => this.gameobject.this2world(p)),
25,
);
2023-12-19 15:34:36 -06:00
if (idx === -1) return;
} else {
}
this.points = this.points.newfirst(idx);
};
2024-09-26 11:36:09 -05:00
edge2d.inputs["C-M-lm"].doc = "Select the given point as the '0' of this spline.";
2024-09-26 11:36:09 -05:00
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.";
2024-09-26 11:36:09 -05:00
edge2d.inputs["C-M-lm"] = function () {
2023-12-19 15:34:36 -06:00
var idx = -1;
if (Spline.is_catmull(this.type))
2024-09-26 11:36:09 -05:00
idx = Math.grab_from_points(
input.mouse.worldpos(),
this.points.map(p => this.gameobject.this2world(p)),
25,
);
2023-12-19 15:34:36 -06:00
else {
var nodes = Spline.bezier_nodes(this.points);
2024-09-26 11:36:09 -05:00
idx = Math.grab_from_points(
input.mouse.worldpos(),
nodes.map(p => this.gameobject.this2world(p)),
25,
);
2023-12-19 15:34:36 -06:00
idx *= 3;
}
2023-12-19 15:34:36 -06:00
this.rm_node(idx);
};
2024-09-26 11:36:09 -05:00
edge2d.inputs["C-M-lm"].doc = "Remove point from the spline.";
2024-09-26 11:36:09 -05:00
edge2d.inputs.lm = function () {};
edge2d.inputs.lm.released = function () {};
2024-09-26 11:36:09 -05:00
edge2d.inputs.lb = function () {
var np = [];
2024-09-26 11:36:09 -05:00
this.points.forEach(function (c) {
np.push(Vector.rotate(c, Math.deg2rad(-1)));
});
this.points = np;
};
2024-05-28 13:39:02 -05:00
edge2d.inputs.lb.doc = "Rotate the points CCW.";
edge2d.inputs.lb.rep = true;
2024-09-26 11:36:09 -05:00
edge2d.inputs.rb = function () {
var np = [];
2024-09-26 11:36:09 -05:00
this.points.forEach(function (c) {
np.push(Vector.rotate(c, Math.deg2rad(1)));
});
this.points = np;
};
2024-05-28 13:39:02 -05:00
edge2d.inputs.rb.doc = "Rotate the points CW.";
edge2d.inputs.rb.rep = true;
2023-10-10 17:37:58 -05:00
2024-05-28 13:39:02 -05:00
/* CIRCLE */
2024-05-28 13:39:02 -05:00
function shape_maker(maker) {
2024-09-26 11:36:09 -05:00
return function (obj) {
if (!obj.body) obj.rigidify();
return maker(obj.body);
};
2024-05-28 13:39:02 -05:00
}
component.circle2d = shape_maker(os.make_circle2d);
component.poly2d = shape_maker(os.make_poly2d);
component.seg2d = shape_maker(os.make_seg2d);
2023-10-11 17:22:41 -05:00
2024-05-28 13:39:02 -05:00
Object.mix(os.make_circle2d(), {
2024-09-26 11:36:09 -05:00
boundingbox() {
return bbox.fromcwh(this.offset, [this.radius, this.radius]);
},
2023-10-11 17:22:41 -05:00
2024-09-26 11:36:09 -05:00
set scale(x) {
this.radius = x;
},
get scale() {
return this.radius;
},
get pos() {
return this.offset;
},
set pos(x) {
this.offset = x;
},
grow(x) {
2024-09-26 11:36:09 -05:00
if (typeof x === "number") this.scale *= x;
else if (typeof x === "object") this.scale *= x[0];
},
2024-05-28 13:39:02 -05:00
});
2024-09-26 11:36:09 -05:00
return { component, SpriteAnim };