@moood/render 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/engine/engine.js +73 -50
- package/engine/engine.json +1 -1
- package/engine/gl.js +8 -12
- package/engine/scenestate.js +12 -7
- package/engine/select.js +5 -5
- package/package.json +1 -1
package/engine/engine.js
CHANGED
|
@@ -352,8 +352,9 @@ const Moood = (() => {
|
|
|
352
352
|
const STATE = { values: {}, ver: 0, was: key => STATE.values[key], sample: null };
|
|
353
353
|
// what the shown scene's code shares (kit.js): the world's modules it lists and the scene's lib
|
|
354
354
|
let SHARED = { kit: {}, lib: {} };
|
|
355
|
-
// Where an element is now, as its painting code says (e.at):
|
|
356
|
-
//
|
|
355
|
+
// Where an element is now, as its painting code says (e.at): the point it marks, in metres from
|
|
356
|
+
// its anchor as painted — speech bubbles and widgets follow it (stage.screenOf) wherever the
|
|
357
|
+
// element is put (its `place`)
|
|
357
358
|
const FOCUS = new Map();
|
|
358
359
|
// A platformer's hero as it moves now (camera.mode platform): its painting code reads e.hero —
|
|
359
360
|
// { vx, vy (m/s), ground } — to move its legs; the engine moves and turns the painting itself.
|
|
@@ -376,22 +377,19 @@ const Moood = (() => {
|
|
|
376
377
|
compiled.set(code, fn);
|
|
377
378
|
}
|
|
378
379
|
if (!fn) return;
|
|
379
|
-
|
|
380
|
-
// seen from the start (x 0…W/H, y 0…1, with margins); others in metres from their anchor
|
|
381
|
-
const flat = o.sky || o.ground;
|
|
382
|
-
const ppm = flat ? H : o.size * H / (o.height || 2), x = flat ? 0 : o.x * W, y = flat ? 0 : o.y * H;
|
|
380
|
+
const [x, y, ppm] = anchorOf(o, W, H);
|
|
383
381
|
ctx.save();
|
|
384
382
|
ctx.translate(x, y);
|
|
385
383
|
ctx.scale(ppm, ppm);
|
|
386
384
|
ctx.fillStyle = ctx.strokeStyle = o.color || '#000000';
|
|
387
385
|
ctx.lineCap = ctx.lineJoin = 'round';
|
|
388
386
|
const period = o.period || 2;
|
|
389
|
-
// a point the code names, as painted — through whatever it did to
|
|
390
|
-
// scale, rotate): [x, y, how much it is scaled]
|
|
387
|
+
// a point the code names, in its units from the anchor as painted — through whatever it did to
|
|
388
|
+
// the canvas since (translate, scale, rotate): [x, y, how much it is scaled]
|
|
391
389
|
const base = ctx.getTransform(), unbase = base.inverse();
|
|
392
390
|
const painted = (lx, ly) => {
|
|
393
391
|
const now = ctx.getTransform(), q = unbase.transformPoint(now.transformPoint({ x: lx, y: ly }));
|
|
394
|
-
return [
|
|
392
|
+
return [q.x, q.y, Math.hypot(now.a, now.b) / Math.hypot(base.a, base.b)];
|
|
395
393
|
};
|
|
396
394
|
const read = { time: false, state: false };
|
|
397
395
|
READS.set(o, read);
|
|
@@ -404,11 +402,11 @@ const Moood = (() => {
|
|
|
404
402
|
light(lx, ly, r, color = '#ffd08a', strength = 0.6) {
|
|
405
403
|
try {
|
|
406
404
|
const [px, py, k] = painted(lx, ly);
|
|
407
|
-
lightsOut.push({ x: px, y: py, r: r * k * ppm, col: rgb(color), a: strength, flicker: o.flicker ?? 0.3 });
|
|
405
|
+
lightsOut.push({ x: x + px * ppm, y: y + py * ppm, r: r * k * ppm, col: rgb(color), a: strength, flicker: o.flicker ?? 0.3 });
|
|
408
406
|
} catch (err) { reportError(o, err); }
|
|
409
407
|
},
|
|
410
408
|
// where this element is now (its head, say): what speaks or hovers over it follows this point
|
|
411
|
-
at(lx, ly) { if (o.id) { const [px, py] = painted(lx, ly); FOCUS.set(o.id, { x: px
|
|
409
|
+
at(lx, ly) { if (o.id) { const [px, py] = painted(lx, ly); FOCUS.set(o.id, { x: px, y: py }); } },
|
|
412
410
|
anim: MooodKit.anim, kit: SHARED.kit, lib: SHARED.lib,
|
|
413
411
|
was: (key, ago = 0) => { read.state = true; return STATE.was(key, ago); },
|
|
414
412
|
speed: key => { read.state = true; const a = STATE.was(key, 0), b = STATE.was(key, 0.05); return typeof a === 'number' && typeof b === 'number' ? (a - b) / 0.05 : 0; },
|
|
@@ -417,6 +415,12 @@ const Moood = (() => {
|
|
|
417
415
|
try { fn(ctx, e); } catch (err) { reportError(o, err); }
|
|
418
416
|
ctx.restore();
|
|
419
417
|
}
|
|
418
|
+
// Where a custom element's painting has its origin on the W × H stage, and its scale: [x, y, px a
|
|
419
|
+
// unit]. Sky and ground elements are painted in stage heights from the stage's top-left corner, as
|
|
420
|
+
// seen from the start (x 0…W/H, y 0…1, with margins); others in metres from their anchor.
|
|
421
|
+
function anchorOf(o, W, H) {
|
|
422
|
+
return o.sky || o.ground ? [0, 0, H] : [o.x * W, o.y * H, o.size * H / (o.height || 2)];
|
|
423
|
+
}
|
|
420
424
|
// The box an object's painting fits in (pixels of the W × H stage): built-in kinds a fixed frame
|
|
421
425
|
// around the base, custom ones their `box` in metres (default: a little wider than tall).
|
|
422
426
|
function objectBox(o, W, H) {
|
|
@@ -545,6 +549,15 @@ const Moood = (() => {
|
|
|
545
549
|
const land = (spec.layers || []).filter(L => LAND.has(L.kind));
|
|
546
550
|
return land.length ? Math.min(...land.map(L => L.y)) : 0.6;
|
|
547
551
|
}
|
|
552
|
+
// An element that moves through the world has `place`: its across, distance, elevation or x are the
|
|
553
|
+
// names of numbers in the state (metres) — it is where they say now, the rest as the spec has it.
|
|
554
|
+
const PLACE = ['across', 'distance', 'elevation', 'x'];
|
|
555
|
+
function placed(o) {
|
|
556
|
+
if (!o.place) return o;
|
|
557
|
+
const n = { ...o };
|
|
558
|
+
for (const f of PLACE) { const k = o.place[f]; if (typeof k === 'string' && typeof STATE.values[k] === 'number') n[f] = STATE.values[k]; }
|
|
559
|
+
return n;
|
|
560
|
+
}
|
|
548
561
|
function depths(spec) {
|
|
549
562
|
const layers = spec.layers || [];
|
|
550
563
|
// An object stands where its base is — unless it is attached to a bigger one (a window on
|
|
@@ -589,21 +602,27 @@ const Moood = (() => {
|
|
|
589
602
|
const paintZ = o => (o.plane ? planeZ(o) : custom(o) ? sightZ(o) : own(host(o) || o));
|
|
590
603
|
const objectX = o => (bearing(o) ? 0.5 : typeof o.across === 'number' && !o.sky ? 0.5 + o.across / EYE / STAGE / paintZ(o) : o.x ?? 0.5);
|
|
591
604
|
const objectY = o => (o.sky || o.ground ? 0 : custom(o) ? yh + (1 - (o.elevation || 0) / EYE) / sightZ(o) : o.kind === 'bird' ? o.y : o.y + shift(o));
|
|
605
|
+
const objectSize = o => (o.sky || o.ground ? 1 : custom(o) ? (o.height || 2) / EYE / sightZ(o) : o.size);
|
|
606
|
+
// where it stands in the world (eye heights: X to the right, Z ahead, Y its anchor's height) and
|
|
607
|
+
// the distance Zp it is painted for
|
|
608
|
+
const objectAt = o => {
|
|
609
|
+
const Zp = paintZ(o), Y = 1 - (objectY(o) - yh) * Zp;
|
|
610
|
+
if (!bearing(o)) return { X: (objectX(o) - 0.5) * STAGE * Zp, Y, Z: Zp, Zp };
|
|
611
|
+
const b = o.bearing * Math.PI / 180, d = o.distance / EYE;
|
|
612
|
+
return { X: d * Math.sin(b), Y, Z: d * Math.cos(b), Zp };
|
|
613
|
+
};
|
|
614
|
+
// ground planes are drawn right after the sky, under everything standing on them
|
|
615
|
+
// a horizontal plane (gl.js) is ordered by its far edge, before what stands on it
|
|
616
|
+
const objectZ = o => (o.sky ? Infinity : o.ground ? Z_FAR * 4 : o.plane ? clamp(planeZ(o) + (o.depth || 4) / 2 / EYE, Z_NEAR, Z_FAR) : custom(o) ? customZ(o) : o.kind === 'bird' ? 30 : own(host(o) || o));
|
|
592
617
|
return {
|
|
593
|
-
yh, Z, objectX,
|
|
594
|
-
// where it
|
|
595
|
-
//
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
return { X: d * Math.sin(b), Y, Z: d * Math.cos(b), Zp };
|
|
618
|
+
yh, Z, objectX, objectAt, objectZ,
|
|
619
|
+
// an element where it is now (its `place`), the one truth the renderers, the picking and
|
|
620
|
+
// whatever follows it go by: { o: as painted on the stage (x, y, size: fractions of it),
|
|
621
|
+
// Z: the depth it is drawn at, at: where it stands in the world }
|
|
622
|
+
now: o => {
|
|
623
|
+
const n = placed(o);
|
|
624
|
+
return { o: { ...o, x: objectX(n), y: objectY(n), size: objectSize(n), _yh: yh }, Z: objectZ(n), at: objectAt(n) };
|
|
601
625
|
},
|
|
602
|
-
// ground planes are drawn right after the sky, under everything standing on them
|
|
603
|
-
// a horizontal plane (gl.js) is ordered by its far edge, before what stands on it
|
|
604
|
-
objectZ: o => (o.sky ? Infinity : o.ground ? Z_FAR * 4 : o.plane ? clamp(planeZ(o) + (o.depth || 4) / 2 / EYE, Z_NEAR, Z_FAR) : custom(o) ? customZ(o) : o.kind === 'bird' ? 30 : own(host(o) || o)),
|
|
605
|
-
objectY,
|
|
606
|
-
objectSize: o => (o.sky || o.ground ? 1 : custom(o) ? (o.height || 2) / EYE / sightZ(o) : o.size),
|
|
607
626
|
};
|
|
608
627
|
}
|
|
609
628
|
// Screen transform [a b c d e f] for a plane at distance Z (Infinity = the sky): maps the
|
|
@@ -635,10 +654,11 @@ const Moood = (() => {
|
|
|
635
654
|
return { m: [s, 0, 0, s, q[0] - s * ax, q[1] - s * ay], D: q[2] };
|
|
636
655
|
}
|
|
637
656
|
// Where an element's painting (as depths() places it on the SW-wide stage) goes on the frame from
|
|
638
|
-
// camera cam: { m: the transform, D: its distance along the gaze }, or null behind the eye
|
|
657
|
+
// camera cam, where it is now: { m: the transform, D: its distance along the gaze }, or null behind the eye
|
|
639
658
|
function placeOf(o, cam, W, H, dz, round) {
|
|
640
|
-
|
|
641
|
-
|
|
659
|
+
const n = dz.now(o);
|
|
660
|
+
if (!round) return { m: view(cam, n.Z, W, H, dz.yh), D: n.Z - cam.z };
|
|
661
|
+
return billboard(cam, n.at, n.o.x * STAGE * H, n.o.y * H, W, H, dz.yh);
|
|
642
662
|
}
|
|
643
663
|
// The hero of a platformer h = { dx, dy (metres from where it is painted), flip } as a transform
|
|
644
664
|
// of its painting: its anchor painted at x = ax, `ppm` painted px per metre
|
|
@@ -779,9 +799,12 @@ const Moood = (() => {
|
|
|
779
799
|
if (L.kind === 'water') return { L, Z };
|
|
780
800
|
return { L, Z, ...staticLayer(L, SW, H, seed + i * 101) };
|
|
781
801
|
}).sort((a, b) => b.Z - a.Z);
|
|
782
|
-
// looking around, each also stands somewhere in the world (p, depths().objectAt)
|
|
783
|
-
|
|
784
|
-
const
|
|
802
|
+
// looking around, each also stands somewhere in the world (p, depths().objectAt); what moves
|
|
803
|
+
// through the world (`place`) is put where the state has it as each frame starts (move)
|
|
804
|
+
const round = camSetup(spec).round, farFirst = (a, b) => (b.Z - a.Z) || 0;
|
|
805
|
+
const at = o => { const n = dz.now(o); return { o: n.o, Z: n.Z, p: round && !o.sky && !o.ground ? n.at : null }; };
|
|
806
|
+
const objects = (spec.objects || []).map(o => ({ from: o, ...at(o) })).sort(farFirst), moving = objects.filter(ob => ob.from.place);
|
|
807
|
+
const move = () => { if (!moving.length) return; for (const ob of moving) Object.assign(ob, at(ob.from)); objects.sort(farFirst); };
|
|
785
808
|
const sky = !spec.sky ? '#000000' : (() => {
|
|
786
809
|
const c = document.createElement('canvas').getContext('2d');
|
|
787
810
|
const g = c.createLinearGradient(0, 0, 0, H);
|
|
@@ -795,7 +818,7 @@ const Moood = (() => {
|
|
|
795
818
|
v.addColorStop(0, 'rgba(0,0,0,0)');
|
|
796
819
|
v.addColorStop(1, `rgba(0,0,0,${0.75 * clamp(spec.light?.vignette ?? 0.5, 0, 1)})`);
|
|
797
820
|
vg.fillStyle = v; vg.fillRect(0, 0, W, H);
|
|
798
|
-
return { W, H, SW, u, x0, span, yh: dz.yh, round, lim: limits(spec, W, H), sky, stars, layers, particles, objects, vignette, scratch: layer(W, H) };
|
|
821
|
+
return { W, H, SW, u, x0, span, yh: dz.yh, round, lim: limits(spec, W, H), sky, stars, layers, particles, objects, move, vignette, scratch: layer(W, H) };
|
|
799
822
|
}
|
|
800
823
|
|
|
801
824
|
// Looking around, with Canvas 2D (the fallback): the sky turned as a flat band (not bent into a
|
|
@@ -852,6 +875,7 @@ const Moood = (() => {
|
|
|
852
875
|
// S.out: where the frame lies in the canvas (pixels); the scene goes on around it as painted
|
|
853
876
|
function frame(ctx, S, spec, cam, t, dt) {
|
|
854
877
|
const { W, H, SW, u } = S, out = S.out;
|
|
878
|
+
S.move();
|
|
855
879
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
856
880
|
ctx.fillStyle = '#000000';
|
|
857
881
|
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
@@ -1048,7 +1072,7 @@ const Moood = (() => {
|
|
|
1048
1072
|
const pick = (clientX, clientY) => {
|
|
1049
1073
|
const [cx, cy] = local(clientX, clientY), Hc = box.h, Wc = box.w, yh = S?.yh ?? 0.6, dz = spec ? depths(spec) : null;
|
|
1050
1074
|
const at = D => ({ Z: target.z + D, x: target.x + (cx - Wc / 2) / Hc * D });
|
|
1051
|
-
const near = (spec?.objects || []).filter(o => o.kind !== 'bird' && !o.sky && !o.ground && !o.plane).map(o =>
|
|
1075
|
+
const near = (spec?.objects || []).filter(o => o.kind !== 'bird' && !o.sky && !o.ground && !o.plane).map(o => dz.now(o))
|
|
1052
1076
|
.filter(({ Z }) => fadeNear(Z, target) > 0).sort((a, b) => a.Z - b.Z);
|
|
1053
1077
|
for (const { o, Z } of near) {
|
|
1054
1078
|
const m = view(target, Z, Wc, Hc, yh), b = objectBox(o, STAGE * Hc, Hc);
|
|
@@ -1114,32 +1138,31 @@ const Moood = (() => {
|
|
|
1114
1138
|
glide.vy += ((target.y - y0) / dt - glide.vy) * k;
|
|
1115
1139
|
lastMove = now;
|
|
1116
1140
|
};
|
|
1117
|
-
// where in the canvas (CSS px) an element is from camera c —
|
|
1118
|
-
// else its anchor (its top, with over) — lifted by `lift` metres:
|
|
1119
|
-
// when out of sight
|
|
1141
|
+
// where in the canvas (CSS px) an element is from camera c — where its `place` has it now: the
|
|
1142
|
+
// point its code marks (e.at), else its anchor (its top, with over) — lifted by `lift` metres:
|
|
1143
|
+
// [x, y, its distance]; null when out of sight
|
|
1120
1144
|
function seenFrom(c, id, lift = 0, over = false) {
|
|
1121
1145
|
const o = (spec?.objects || []).find(o => o.id === id);
|
|
1122
1146
|
if (!o || o.sky) return null;
|
|
1123
1147
|
const W = box.w, H = box.h, dz = depths(spec), yh = dz.yh;
|
|
1124
|
-
if (
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
const q = project(c, p.X, p.Y + (lift + (over && !o.plane ? o.height ?? EYE : 0)) / EYE, p.Z, W, H, yh);
|
|
1148
|
+
if (o.plane) {
|
|
1149
|
+
const p = dz.objectAt(o), Z = p.Zp, up = lift / EYE;
|
|
1150
|
+
if (round()) {
|
|
1151
|
+
const q = project(c, p.X, p.Y + up, p.Z, W, H, yh);
|
|
1129
1152
|
return q && q[2] > 0.3 ? [box.x + q[0], box.y + q[1], q[2]] : null;
|
|
1130
1153
|
}
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
return [box.x +
|
|
1154
|
+
if (fadeNear(Z, c) < 0.3) return null;
|
|
1155
|
+
const m = view(c, Z, W, H, yh), px = dz.objectX(o) * STAGE * H, py = (yh + (1 - (o.elevation || 0) / EYE) / Z - up / Z) * H;
|
|
1156
|
+
return [box.x + m[0] * px + m[4], box.y + m[3] * py + m[5], Z - c.z];
|
|
1134
1157
|
}
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
|
|
1158
|
+
const at = placeOf(o, c, W, H, dz, round());
|
|
1159
|
+
// behind the eye, or faded out as it is passed (fadeNear)
|
|
1160
|
+
if (!at || at.D < 0.48) return null;
|
|
1161
|
+
const n = dz.now(o), [ax, ay, ppm] = anchorOf(n.o, STAGE * H, H), f = FOCUS.get(id) || { x: 0, y: 0 };
|
|
1138
1162
|
if (over && !FOCUS.has(id)) lift += o.height ?? EYE;
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
return [box.x + m[0] * px + m[4], box.y + m[3] * py + m[5], Z - c.z];
|
|
1163
|
+
let px = ax + f.x * ppm, py = ay + f.y * ppm - lift / EYE * H / n.at.Zp;
|
|
1164
|
+
if (c.hero && id === c.hero.id) { const hm = heroMatrix(c.hero, ax, ppm); px = hm[0] * px + hm[4]; py += hm[5]; }
|
|
1165
|
+
return [box.x + at.m[0] * px + at.m[4], box.y + at.m[3] * py + at.m[5], at.D];
|
|
1143
1166
|
}
|
|
1144
1167
|
// the camera keeps the elements it follows in the visible part of the screen: together when
|
|
1145
1168
|
// they fit, else the first of them
|
package/engine/engine.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"engine":"
|
|
1
|
+
{"engine":"8bdd9745d005"}
|
package/engine/gl.js
CHANGED
|
@@ -453,7 +453,7 @@ const MooodGL = (() => {
|
|
|
453
453
|
const objects = (spec.objects || []).map(o0 => {
|
|
454
454
|
if (o0.plane) return planeOf(o0, dz, tex);
|
|
455
455
|
// looking around, the sky is the full circle, 2π stage heights wide (engine.js)
|
|
456
|
-
const
|
|
456
|
+
const n = dz.now(o0), o = { ...n.o, _sw: ROUND && o0.sky ? I.TAU * H : SW }, Z = n.Z;
|
|
457
457
|
const x = o.x * SW, y = o.y * H, s = Math.max(2, o.size * H);
|
|
458
458
|
if (o.kind === 'bird') {
|
|
459
459
|
const canvas = paint(s * 1.2 * SS, s * 0.6 * SS, (g) => {
|
|
@@ -472,7 +472,7 @@ const MooodGL = (() => {
|
|
|
472
472
|
const live = (o.kind === 'custom' || !o.kind) ? liveOf(o, o.frames, tex) : null;
|
|
473
473
|
return {
|
|
474
474
|
o, Z, x, y, s, bx, by, bw, bh, tex: frames[0], frames, period: o.period || 2, detail: null,
|
|
475
|
-
p: ROUND && !o.sky ?
|
|
475
|
+
p: ROUND && !o.sky ? n.at : null,
|
|
476
476
|
live, ground: !!o.ground, from: o0.place ? o0 : null,
|
|
477
477
|
reflect: o.reflect ?? (o.kind === 'boat' || o.kind === 'bridge'),
|
|
478
478
|
lights: lights.map(l => ({ x: l.x, y: l.y, r: l.r, c: col(l.col), a: l.a, dot: l.dot, fire: o.kind === 'campfire', flicker: l.flicker })),
|
|
@@ -873,22 +873,18 @@ const MooodGL = (() => {
|
|
|
873
873
|
}
|
|
874
874
|
}
|
|
875
875
|
|
|
876
|
-
// An element that moves through the world (its `place
|
|
877
|
-
//
|
|
878
|
-
// its haze and what it is drawn in front of or behind follow.
|
|
876
|
+
// An element that moves through the world (its `place`): put where the state has it now
|
|
877
|
+
// (depths().now) — its size, its depth, its haze and what it is drawn in front of or behind follow.
|
|
879
878
|
function placeMoving(S) {
|
|
880
|
-
const v = I.STATE.values;
|
|
881
879
|
let moved = false;
|
|
882
880
|
for (const ob of S.objects) {
|
|
883
881
|
if (!ob.from) continue;
|
|
884
|
-
const
|
|
885
|
-
for (const [f, k] of Object.entries(ob.from.place)) if (typeof k === 'string' && typeof v[k] === 'number') o1[f] = v[k];
|
|
886
|
-
const key = `${o1.x}|${o1.across}|${o1.distance}|${o1.elevation}`;
|
|
882
|
+
const n = S.dz.now(ob.from), key = `${n.o.x}|${n.o.y}|${n.o.size}`;
|
|
887
883
|
if (key === ob.placed) continue;
|
|
888
884
|
ob.placed = key; moved = true;
|
|
889
|
-
const o = { ...
|
|
890
|
-
Object.assign(ob, { o, Z:
|
|
891
|
-
if (ROUND && !o.sky) ob.p =
|
|
885
|
+
const o = { ...n.o, _sw: ob.o._sw };
|
|
886
|
+
Object.assign(ob, { o, Z: n.Z, x: o.x * SW, y: o.y * H, s: Math.max(2, o.size * H) }, I.objectBox(o, SW, H));
|
|
887
|
+
if (ROUND && !o.sky) ob.p = n.at;
|
|
892
888
|
}
|
|
893
889
|
if (!moved) return;
|
|
894
890
|
for (const item of S.order) if (item.ob) item.Z = item.ob.Z;
|
package/engine/scenestate.js
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
// widgets — MooodState is global: get / set / on.
|
|
8
8
|
// The painting code reads the current values as e.state and is repainted when they change. A
|
|
9
9
|
// number may be set to move there over some seconds, along an easing curve (kit.js: smooth by
|
|
10
|
-
// default — it starts and stops gently; linear, in, out, back, spring, anticipate…).
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
10
|
+
// default — it starts and stops gently; linear, in, out, back, spring, anticipate…). A value set
|
|
11
|
+
// is heard once, where it is set to (on('change')); a value moving there is heard each frame as it
|
|
12
|
+
// moves (on('move')). What a value was a moment ago stays known for a while (was): what lags
|
|
13
|
+
// behind — a coat's tail, hair, a head that follows — reads it. A script may tell that something
|
|
14
|
+
// happened (scene.tell): the game decides what it means.
|
|
14
15
|
const MooodState = (() => {
|
|
15
16
|
let stage = null, tell = null, save = null, t0 = performance.now();
|
|
16
17
|
let values = {}, cur = {}, tweens = new Map(), listeners = [], timers = [], raf = 0, pending = {}, flushTimer = 0;
|
|
@@ -44,12 +45,14 @@ const MooodState = (() => {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
function publish() { cur = { ...values }; for (const [k, tw] of tweens) cur[k] = along(tw, performance.now()); stage?.setState(cur, was, sample); }
|
|
47
|
-
// the moving values, where they are now (the stage asks as it paints a frame)
|
|
48
|
+
// the moving values, where they are now (the stage asks as it paints a frame): each heard moving
|
|
49
|
+
// from where it was last seen
|
|
48
50
|
function sample() {
|
|
49
51
|
if (!tweens.size) return;
|
|
50
|
-
const now = performance.now();
|
|
52
|
+
const now = performance.now(), before = cur, moving = [...tweens.keys()];
|
|
51
53
|
for (const [k, tw] of tweens) if (now >= tw.end) tweens.delete(k);
|
|
52
54
|
publish();
|
|
55
|
+
for (const k of moving) if (cur[k] !== before[k]) emit('move', k, cur[k], before[k], k);
|
|
53
56
|
}
|
|
54
57
|
function tick() {
|
|
55
58
|
raf = 0;
|
|
@@ -167,7 +170,9 @@ const MooodState = (() => {
|
|
|
167
170
|
}
|
|
168
171
|
return end;
|
|
169
172
|
},
|
|
170
|
-
// on('change', fn(value, old, key)) · on('change', key, fn)
|
|
173
|
+
// on('change', fn(value, old, key)) · on('change', key, fn) — a value set (with `over`: where it
|
|
174
|
+
// goes, once); on('move', [key,] fn(value, last, key)) — a value moving, each frame (set with
|
|
175
|
+
// `over`, path), till it stops; on('load', fn(state))
|
|
171
176
|
on(ev, a, b) {
|
|
172
177
|
const fn = typeof a === 'function' ? a : b, key = typeof a === 'function' ? null : String(a);
|
|
173
178
|
listeners.push({ ev, key, fn, who });
|
package/engine/select.js
CHANGED
|
@@ -51,12 +51,12 @@ const MooodSelect = (() => {
|
|
|
51
51
|
// so a plane's view() maps painted coordinates straight to where they are in the frame;
|
|
52
52
|
// the frame lies at (x, y) in the canvas.
|
|
53
53
|
const size = () => { const f = stage.frame(); return { W: f.w, H: f.h, SW: I.STAGE * f.h, x: f.x, y: f.y }; };
|
|
54
|
-
// objects stand where the engine puts them (
|
|
55
|
-
const placed = (dz, o) =>
|
|
54
|
+
// objects stand where the engine puts them now (its depths().now: their real distance, their `place`)
|
|
55
|
+
const placed = (dz, o) => dz.now(o).o;
|
|
56
56
|
// a platformer's hero is drawn moved and turned (engine.js heroMatrix): so is its outline
|
|
57
57
|
function heroed(m, o, cam, SW, H) {
|
|
58
58
|
if (!o || !cam.hero || o.id !== cam.hero.id) return m;
|
|
59
|
-
const
|
|
59
|
+
const p = placed(I.depths(spec), o), h = I.heroMatrix(cam.hero, p.x * SW, p.size * H / (o.height || 2));
|
|
60
60
|
return [m[0] * h[0], 0, 0, m[3], m[0] * h[4] + m[4], m[3] * h[5] + m[5]];
|
|
61
61
|
}
|
|
62
62
|
const layerSeed = i => I.hash(JSON.stringify(spec.layers || []) + (spec.title || '')) + i * 101;
|
|
@@ -125,7 +125,7 @@ const MooodSelect = (() => {
|
|
|
125
125
|
const c = I.layer(b.bw * k, b.bh * k), g = c.getContext('2d');
|
|
126
126
|
g.setTransform(k, 0, 0, k, -b.bx * k, -b.by * k);
|
|
127
127
|
I.drawObject(g, o, SW, H, 0, []);
|
|
128
|
-
return { c, x: b.bx, y: b.by, k, Z: dz.
|
|
128
|
+
return { c, x: b.bx, y: b.by, k, Z: dz.now(it).Z, obj: it };
|
|
129
129
|
}
|
|
130
130
|
if (sel.startsWith('layers') && LAND.has(it.kind)) {
|
|
131
131
|
if (it.kind === 'water') {
|
|
@@ -239,7 +239,7 @@ const MooodSelect = (() => {
|
|
|
239
239
|
if (!coverage.has(path)) {
|
|
240
240
|
const { W, H, SW } = size(), dz = I.depths(spec), it = item(spec, path), n = 64, c = I.layer(n, n * H / W), g = c.getContext('2d');
|
|
241
241
|
// from the start — looking around, turned toward it
|
|
242
|
-
const round = I.camSetup(spec).round, p = round && !it.sky ? dz.
|
|
242
|
+
const round = I.camSetup(spec).round, p = round && !it.sky ? dz.now(it).at : null;
|
|
243
243
|
const at = I.placeOf(it, { x: 0, y: 0, z: 0, h: 1, yaw: p ? Math.atan2(p.X, p.Z) : 0 }, W, H, dz, round && !it.sky), k = n / W;
|
|
244
244
|
const m = at ? at.m : [0, 0, 0, 0, 0, 0];
|
|
245
245
|
g.setTransform(m[0] * k, 0, 0, m[3] * k, m[4] * k, m[5] * k);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moood/render",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "A moood scene rendered locally — the site's own engine, in a browser already installed — at a moment, with a state: a PNG and the errors its code threw.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|