@arcane-engine/runtime 0.1.0 → 0.2.0
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/package.json +4 -2
- package/src/agent/protocol.ts +35 -1
- package/src/agent/types.ts +98 -13
- package/src/particles/emitter.test.ts +323 -0
- package/src/particles/emitter.ts +409 -0
- package/src/particles/index.ts +25 -0
- package/src/particles/types.ts +236 -0
- package/src/pathfinding/astar.ts +27 -0
- package/src/pathfinding/types.ts +39 -0
- package/src/physics/aabb.ts +55 -8
- package/src/rendering/animation.ts +73 -0
- package/src/rendering/audio.ts +29 -9
- package/src/rendering/camera.ts +28 -4
- package/src/rendering/input.ts +45 -9
- package/src/rendering/lighting.ts +29 -3
- package/src/rendering/loop.ts +16 -3
- package/src/rendering/sprites.ts +24 -1
- package/src/rendering/text.ts +52 -6
- package/src/rendering/texture.ts +22 -4
- package/src/rendering/tilemap.ts +36 -4
- package/src/rendering/types.ts +37 -19
- package/src/rendering/validate.ts +48 -3
- package/src/state/error.ts +21 -2
- package/src/state/observe.ts +40 -9
- package/src/state/prng.ts +88 -10
- package/src/state/query.ts +115 -15
- package/src/state/store.ts +42 -11
- package/src/state/transaction.ts +116 -12
- package/src/state/types.ts +31 -5
- package/src/systems/system.ts +77 -5
- package/src/systems/types.ts +52 -6
- package/src/testing/harness.ts +103 -5
- package/src/testing/mock-renderer.test.ts +16 -20
- package/src/tweening/chain.test.ts +191 -0
- package/src/tweening/chain.ts +103 -0
- package/src/tweening/easing.test.ts +134 -0
- package/src/tweening/easing.ts +288 -0
- package/src/tweening/helpers.test.ts +185 -0
- package/src/tweening/helpers.ts +166 -0
- package/src/tweening/index.ts +76 -0
- package/src/tweening/tween.test.ts +322 -0
- package/src/tweening/tween.ts +296 -0
- package/src/tweening/types.ts +134 -0
- package/src/ui/colors.ts +129 -0
- package/src/ui/index.ts +1 -0
- package/src/ui/primitives.ts +44 -5
- package/src/ui/types.ts +41 -2
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for easing functions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, assert } from "../testing/harness.ts";
|
|
6
|
+
import {
|
|
7
|
+
linear,
|
|
8
|
+
easeInQuad,
|
|
9
|
+
easeOutQuad,
|
|
10
|
+
easeInOutQuad,
|
|
11
|
+
easeInCubic,
|
|
12
|
+
easeOutCubic,
|
|
13
|
+
easeInOutCubic,
|
|
14
|
+
easeInSine,
|
|
15
|
+
easeOutSine,
|
|
16
|
+
easeInOutSine,
|
|
17
|
+
easeInExpo,
|
|
18
|
+
easeOutExpo,
|
|
19
|
+
easeInOutExpo,
|
|
20
|
+
easeInCirc,
|
|
21
|
+
easeOutCirc,
|
|
22
|
+
easeInOutCirc,
|
|
23
|
+
easeInBack,
|
|
24
|
+
easeOutBack,
|
|
25
|
+
easeInOutBack,
|
|
26
|
+
easeInElastic,
|
|
27
|
+
easeOutElastic,
|
|
28
|
+
easeInOutElastic,
|
|
29
|
+
easeInBounce,
|
|
30
|
+
easeOutBounce,
|
|
31
|
+
easeInOutBounce,
|
|
32
|
+
Easing,
|
|
33
|
+
} from "./easing.ts";
|
|
34
|
+
|
|
35
|
+
describe("Easing Functions", () => {
|
|
36
|
+
// Helper to test that easing function has correct boundary behavior
|
|
37
|
+
function testEasingBoundaries(name: string, easingFn: (t: number) => number) {
|
|
38
|
+
it(`${name} should return 0 at t=0`, () => {
|
|
39
|
+
const result = easingFn(0);
|
|
40
|
+
assert.ok(Math.abs(result - 0) < 0.001, `Expected ~0, got ${result}`);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it(`${name} should return 1 at t=1`, () => {
|
|
44
|
+
const result = easingFn(1);
|
|
45
|
+
assert.ok(Math.abs(result - 1) < 0.001, `Expected ~1, got ${result}`);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it(`${name} should return values in range [0,1] for t in [0,1]`, () => {
|
|
49
|
+
for (let t = 0; t <= 1; t += 0.1) {
|
|
50
|
+
const result = easingFn(t);
|
|
51
|
+
// Some easing functions (like back, elastic) can go slightly outside [0,1] during animation
|
|
52
|
+
// But they should end at the right place
|
|
53
|
+
assert.ok(!isNaN(result), `Result should not be NaN for t=${t}`);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Test all easing functions have correct boundaries
|
|
59
|
+
testEasingBoundaries("linear", linear);
|
|
60
|
+
testEasingBoundaries("easeInQuad", easeInQuad);
|
|
61
|
+
testEasingBoundaries("easeOutQuad", easeOutQuad);
|
|
62
|
+
testEasingBoundaries("easeInOutQuad", easeInOutQuad);
|
|
63
|
+
testEasingBoundaries("easeInCubic", easeInCubic);
|
|
64
|
+
testEasingBoundaries("easeOutCubic", easeOutCubic);
|
|
65
|
+
testEasingBoundaries("easeInOutCubic", easeInOutCubic);
|
|
66
|
+
testEasingBoundaries("easeInSine", easeInSine);
|
|
67
|
+
testEasingBoundaries("easeOutSine", easeOutSine);
|
|
68
|
+
testEasingBoundaries("easeInOutSine", easeInOutSine);
|
|
69
|
+
testEasingBoundaries("easeInExpo", easeInExpo);
|
|
70
|
+
testEasingBoundaries("easeOutExpo", easeOutExpo);
|
|
71
|
+
testEasingBoundaries("easeInOutExpo", easeInOutExpo);
|
|
72
|
+
testEasingBoundaries("easeInCirc", easeInCirc);
|
|
73
|
+
testEasingBoundaries("easeOutCirc", easeOutCirc);
|
|
74
|
+
testEasingBoundaries("easeInOutCirc", easeInOutCirc);
|
|
75
|
+
testEasingBoundaries("easeInBack", easeInBack);
|
|
76
|
+
testEasingBoundaries("easeOutBack", easeOutBack);
|
|
77
|
+
testEasingBoundaries("easeInOutBack", easeInOutBack);
|
|
78
|
+
testEasingBoundaries("easeInElastic", easeInElastic);
|
|
79
|
+
testEasingBoundaries("easeOutElastic", easeOutElastic);
|
|
80
|
+
testEasingBoundaries("easeInOutElastic", easeInOutElastic);
|
|
81
|
+
testEasingBoundaries("easeInBounce", easeInBounce);
|
|
82
|
+
testEasingBoundaries("easeOutBounce", easeOutBounce);
|
|
83
|
+
testEasingBoundaries("easeInOutBounce", easeInOutBounce);
|
|
84
|
+
|
|
85
|
+
it("linear should return t", () => {
|
|
86
|
+
assert.equal(linear(0.5), 0.5);
|
|
87
|
+
assert.equal(linear(0.25), 0.25);
|
|
88
|
+
assert.equal(linear(0.75), 0.75);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("easeInQuad should be slower at start", () => {
|
|
92
|
+
// At t=0.5, quadratic should be at 0.25 (slower than linear)
|
|
93
|
+
assert.ok(easeInQuad(0.5) < 0.5);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("easeOutQuad should be faster at start", () => {
|
|
97
|
+
// At t=0.5, ease-out should be past 0.5 (faster than linear)
|
|
98
|
+
assert.ok(easeOutQuad(0.5) > 0.5);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("easeInOutQuad should be symmetric", () => {
|
|
102
|
+
// At t=0.5, should be at 0.5
|
|
103
|
+
const mid = easeInOutQuad(0.5);
|
|
104
|
+
assert.ok(Math.abs(mid - 0.5) < 0.01);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("Easing map contains all functions", () => {
|
|
108
|
+
assert.equal(typeof Easing.linear, "function");
|
|
109
|
+
assert.equal(typeof Easing.easeInQuad, "function");
|
|
110
|
+
assert.equal(typeof Easing.easeOutQuad, "function");
|
|
111
|
+
assert.equal(typeof Easing.easeInOutQuad, "function");
|
|
112
|
+
assert.equal(typeof Easing.easeInCubic, "function");
|
|
113
|
+
assert.equal(typeof Easing.easeOutCubic, "function");
|
|
114
|
+
assert.equal(typeof Easing.easeInOutCubic, "function");
|
|
115
|
+
assert.equal(typeof Easing.easeInSine, "function");
|
|
116
|
+
assert.equal(typeof Easing.easeOutSine, "function");
|
|
117
|
+
assert.equal(typeof Easing.easeInOutSine, "function");
|
|
118
|
+
assert.equal(typeof Easing.easeInExpo, "function");
|
|
119
|
+
assert.equal(typeof Easing.easeOutExpo, "function");
|
|
120
|
+
assert.equal(typeof Easing.easeInOutExpo, "function");
|
|
121
|
+
assert.equal(typeof Easing.easeInCirc, "function");
|
|
122
|
+
assert.equal(typeof Easing.easeOutCirc, "function");
|
|
123
|
+
assert.equal(typeof Easing.easeInOutCirc, "function");
|
|
124
|
+
assert.equal(typeof Easing.easeInBack, "function");
|
|
125
|
+
assert.equal(typeof Easing.easeOutBack, "function");
|
|
126
|
+
assert.equal(typeof Easing.easeInOutBack, "function");
|
|
127
|
+
assert.equal(typeof Easing.easeInElastic, "function");
|
|
128
|
+
assert.equal(typeof Easing.easeOutElastic, "function");
|
|
129
|
+
assert.equal(typeof Easing.easeInOutElastic, "function");
|
|
130
|
+
assert.equal(typeof Easing.easeInBounce, "function");
|
|
131
|
+
assert.equal(typeof Easing.easeOutBounce, "function");
|
|
132
|
+
assert.equal(typeof Easing.easeInOutBounce, "function");
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 30 easing functions for tweens, organized into 10 families.
|
|
3
|
+
*
|
|
4
|
+
* All functions take `t` (0 to 1) and return an eased value.
|
|
5
|
+
* Most return 0..1, but back and elastic easings may overshoot.
|
|
6
|
+
*
|
|
7
|
+
* Families: linear, quad, cubic, quart, quint, sine, expo, circ, back, elastic, bounce.
|
|
8
|
+
* Each family (except linear) has easeIn (slow start), easeOut (slow end),
|
|
9
|
+
* and easeInOut (slow start + end) variants.
|
|
10
|
+
*
|
|
11
|
+
* Based on https://easings.net/ and Robert Penner's easing equations.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { easeOutQuad } from "./easing.ts";
|
|
16
|
+
* tween(sprite, { x: 100 }, 0.5, { easing: easeOutQuad });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { EasingFunction } from "./types.ts";
|
|
21
|
+
|
|
22
|
+
// --- Linear ---
|
|
23
|
+
|
|
24
|
+
/** No easing: constant speed from start to end. */
|
|
25
|
+
export const linear: EasingFunction = (t) => t;
|
|
26
|
+
|
|
27
|
+
// --- Quadratic ---
|
|
28
|
+
|
|
29
|
+
/** Quadratic ease-in: slow start, accelerating. t^2 curve. */
|
|
30
|
+
export const easeInQuad: EasingFunction = (t) => t * t;
|
|
31
|
+
|
|
32
|
+
/** Quadratic ease-out: fast start, decelerating. Reverse t^2 curve. */
|
|
33
|
+
export const easeOutQuad: EasingFunction = (t) => t * (2 - t);
|
|
34
|
+
|
|
35
|
+
/** Quadratic ease-in-out: slow start and end, fast in the middle. */
|
|
36
|
+
export const easeInOutQuad: EasingFunction = (t) =>
|
|
37
|
+
t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
|
38
|
+
|
|
39
|
+
// --- Cubic ---
|
|
40
|
+
|
|
41
|
+
/** Cubic ease-in: slow start with t^3 curve. Slightly more pronounced than quad. */
|
|
42
|
+
export const easeInCubic: EasingFunction = (t) => t * t * t;
|
|
43
|
+
|
|
44
|
+
/** Cubic ease-out: fast start, decelerating with t^3 curve. */
|
|
45
|
+
export const easeOutCubic: EasingFunction = (t) => {
|
|
46
|
+
const t1 = t - 1;
|
|
47
|
+
return t1 * t1 * t1 + 1;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** Cubic ease-in-out: smooth acceleration then deceleration. */
|
|
51
|
+
export const easeInOutCubic: EasingFunction = (t) =>
|
|
52
|
+
t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
|
|
53
|
+
|
|
54
|
+
// --- Quartic ---
|
|
55
|
+
|
|
56
|
+
/** Quartic ease-in: very slow start with t^4 curve. */
|
|
57
|
+
export const easeInQuart: EasingFunction = (t) => t * t * t * t;
|
|
58
|
+
|
|
59
|
+
/** Quartic ease-out: fast start, strong deceleration. */
|
|
60
|
+
export const easeOutQuart: EasingFunction = (t) => {
|
|
61
|
+
const t1 = t - 1;
|
|
62
|
+
return 1 - t1 * t1 * t1 * t1;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** Quartic ease-in-out: pronounced slow start/end, fast middle. */
|
|
66
|
+
export const easeInOutQuart: EasingFunction = (t) => {
|
|
67
|
+
if (t < 0.5) {
|
|
68
|
+
return 8 * t * t * t * t;
|
|
69
|
+
}
|
|
70
|
+
const t1 = t - 1;
|
|
71
|
+
return 1 - 8 * t1 * t1 * t1 * t1;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// --- Quintic ---
|
|
75
|
+
|
|
76
|
+
/** Quintic ease-in: very slow start with t^5 curve. Most dramatic polynomial ease-in. */
|
|
77
|
+
export const easeInQuint: EasingFunction = (t) => t * t * t * t * t;
|
|
78
|
+
|
|
79
|
+
/** Quintic ease-out: fast start, very strong deceleration. */
|
|
80
|
+
export const easeOutQuint: EasingFunction = (t) => {
|
|
81
|
+
const t1 = t - 1;
|
|
82
|
+
return 1 + t1 * t1 * t1 * t1 * t1;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** Quintic ease-in-out: extremely slow start/end, very fast middle. */
|
|
86
|
+
export const easeInOutQuint: EasingFunction = (t) => {
|
|
87
|
+
if (t < 0.5) {
|
|
88
|
+
return 16 * t * t * t * t * t;
|
|
89
|
+
}
|
|
90
|
+
const t1 = t - 1;
|
|
91
|
+
return 1 + 16 * t1 * t1 * t1 * t1 * t1;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// --- Sine ---
|
|
95
|
+
|
|
96
|
+
/** Sine ease-in: gentle slow start following a sine curve. */
|
|
97
|
+
export const easeInSine: EasingFunction = (t) =>
|
|
98
|
+
1 - Math.cos((t * Math.PI) / 2);
|
|
99
|
+
|
|
100
|
+
/** Sine ease-out: gentle deceleration following a sine curve. */
|
|
101
|
+
export const easeOutSine: EasingFunction = (t) =>
|
|
102
|
+
Math.sin((t * Math.PI) / 2);
|
|
103
|
+
|
|
104
|
+
/** Sine ease-in-out: smooth sinusoidal acceleration and deceleration. */
|
|
105
|
+
export const easeInOutSine: EasingFunction = (t) =>
|
|
106
|
+
-(Math.cos(Math.PI * t) - 1) / 2;
|
|
107
|
+
|
|
108
|
+
// --- Exponential ---
|
|
109
|
+
|
|
110
|
+
/** Exponential ease-in: near-zero at start, rapidly accelerating. Returns 0 when t=0. */
|
|
111
|
+
export const easeInExpo: EasingFunction = (t) =>
|
|
112
|
+
t === 0 ? 0 : Math.pow(2, 10 * t - 10);
|
|
113
|
+
|
|
114
|
+
/** Exponential ease-out: fast start, asymptotically approaching 1. Returns 1 when t=1. */
|
|
115
|
+
export const easeOutExpo: EasingFunction = (t) =>
|
|
116
|
+
t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
|
|
117
|
+
|
|
118
|
+
/** Exponential ease-in-out: dramatic acceleration/deceleration with near-flat start/end. */
|
|
119
|
+
export const easeInOutExpo: EasingFunction = (t) => {
|
|
120
|
+
if (t === 0) return 0;
|
|
121
|
+
if (t === 1) return 1;
|
|
122
|
+
if (t < 0.5) {
|
|
123
|
+
return Math.pow(2, 20 * t - 10) / 2;
|
|
124
|
+
}
|
|
125
|
+
return (2 - Math.pow(2, -20 * t + 10)) / 2;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// --- Circular ---
|
|
129
|
+
|
|
130
|
+
/** Circular ease-in: quarter-circle curve, slow start. */
|
|
131
|
+
export const easeInCirc: EasingFunction = (t) =>
|
|
132
|
+
1 - Math.sqrt(1 - t * t);
|
|
133
|
+
|
|
134
|
+
/** Circular ease-out: quarter-circle curve, fast start. */
|
|
135
|
+
export const easeOutCirc: EasingFunction = (t) =>
|
|
136
|
+
Math.sqrt(1 - Math.pow(t - 1, 2));
|
|
137
|
+
|
|
138
|
+
/** Circular ease-in-out: half-circle curve, slow at both ends. */
|
|
139
|
+
export const easeInOutCirc: EasingFunction = (t) => {
|
|
140
|
+
if (t < 0.5) {
|
|
141
|
+
return (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2;
|
|
142
|
+
}
|
|
143
|
+
return (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// --- Back ---
|
|
147
|
+
|
|
148
|
+
/** Back ease-in: pulls back slightly before accelerating forward. Overshoots below 0. */
|
|
149
|
+
export const easeInBack: EasingFunction = (t) => {
|
|
150
|
+
const c1 = 1.70158;
|
|
151
|
+
const c3 = c1 + 1;
|
|
152
|
+
return c3 * t * t * t - c1 * t * t;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/** Back ease-out: overshoots past 1 then settles back. */
|
|
156
|
+
export const easeOutBack: EasingFunction = (t) => {
|
|
157
|
+
const c1 = 1.70158;
|
|
158
|
+
const c3 = c1 + 1;
|
|
159
|
+
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/** Back ease-in-out: pulls back, accelerates, overshoots, then settles. */
|
|
163
|
+
export const easeInOutBack: EasingFunction = (t) => {
|
|
164
|
+
const c1 = 1.70158;
|
|
165
|
+
const c2 = c1 * 1.525;
|
|
166
|
+
if (t < 0.5) {
|
|
167
|
+
return (Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2;
|
|
168
|
+
}
|
|
169
|
+
return (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// --- Elastic ---
|
|
173
|
+
|
|
174
|
+
/** Elastic ease-in: spring-like oscillation at the start. Overshoots below 0. */
|
|
175
|
+
export const easeInElastic: EasingFunction = (t) => {
|
|
176
|
+
const c4 = (2 * Math.PI) / 3;
|
|
177
|
+
if (t === 0) return 0;
|
|
178
|
+
if (t === 1) return 1;
|
|
179
|
+
return -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * c4);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/** Elastic ease-out: spring-like oscillation at the end. Overshoots above 1. */
|
|
183
|
+
export const easeOutElastic: EasingFunction = (t) => {
|
|
184
|
+
const c4 = (2 * Math.PI) / 3;
|
|
185
|
+
if (t === 0) return 0;
|
|
186
|
+
if (t === 1) return 1;
|
|
187
|
+
return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/** Elastic ease-in-out: spring oscillation at both start and end. */
|
|
191
|
+
export const easeInOutElastic: EasingFunction = (t) => {
|
|
192
|
+
const c5 = (2 * Math.PI) / 4.5;
|
|
193
|
+
if (t === 0) return 0;
|
|
194
|
+
if (t === 1) return 1;
|
|
195
|
+
if (t < 0.5) {
|
|
196
|
+
return -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * c5)) / 2;
|
|
197
|
+
}
|
|
198
|
+
return (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * c5)) / 2 + 1;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// --- Bounce ---
|
|
202
|
+
|
|
203
|
+
/** Bounce ease-out: simulates a ball bouncing to rest. Stays within 0..1. */
|
|
204
|
+
export const easeOutBounce: EasingFunction = (t) => {
|
|
205
|
+
const n1 = 7.5625;
|
|
206
|
+
const d1 = 2.75;
|
|
207
|
+
|
|
208
|
+
if (t < 1 / d1) {
|
|
209
|
+
return n1 * t * t;
|
|
210
|
+
} else if (t < 2 / d1) {
|
|
211
|
+
const t2 = t - 1.5 / d1;
|
|
212
|
+
return n1 * t2 * t2 + 0.75;
|
|
213
|
+
} else if (t < 2.5 / d1) {
|
|
214
|
+
const t2 = t - 2.25 / d1;
|
|
215
|
+
return n1 * t2 * t2 + 0.9375;
|
|
216
|
+
} else {
|
|
217
|
+
const t2 = t - 2.625 / d1;
|
|
218
|
+
return n1 * t2 * t2 + 0.984375;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/** Bounce ease-in: reverse bouncing at the start. Stays within 0..1. */
|
|
223
|
+
export const easeInBounce: EasingFunction = (t) =>
|
|
224
|
+
1 - easeOutBounce(1 - t);
|
|
225
|
+
|
|
226
|
+
/** Bounce ease-in-out: bouncing at both start and end. */
|
|
227
|
+
export const easeInOutBounce: EasingFunction = (t) => {
|
|
228
|
+
if (t < 0.5) {
|
|
229
|
+
return (1 - easeOutBounce(1 - 2 * t)) / 2;
|
|
230
|
+
}
|
|
231
|
+
return (1 + easeOutBounce(2 * t - 1)) / 2;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// --- Convenience map ---
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Lookup object containing all 30 easing functions keyed by name.
|
|
238
|
+
* Useful for selecting an easing function dynamically (e.g., from config or UI).
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```ts
|
|
242
|
+
* const easingName = "easeOutQuad";
|
|
243
|
+
* tween(obj, { x: 100 }, 1, { easing: Easing[easingName] });
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
export const Easing = {
|
|
247
|
+
linear,
|
|
248
|
+
|
|
249
|
+
easeInQuad,
|
|
250
|
+
easeOutQuad,
|
|
251
|
+
easeInOutQuad,
|
|
252
|
+
|
|
253
|
+
easeInCubic,
|
|
254
|
+
easeOutCubic,
|
|
255
|
+
easeInOutCubic,
|
|
256
|
+
|
|
257
|
+
easeInQuart,
|
|
258
|
+
easeOutQuart,
|
|
259
|
+
easeInOutQuart,
|
|
260
|
+
|
|
261
|
+
easeInQuint,
|
|
262
|
+
easeOutQuint,
|
|
263
|
+
easeInOutQuint,
|
|
264
|
+
|
|
265
|
+
easeInSine,
|
|
266
|
+
easeOutSine,
|
|
267
|
+
easeInOutSine,
|
|
268
|
+
|
|
269
|
+
easeInExpo,
|
|
270
|
+
easeOutExpo,
|
|
271
|
+
easeInOutExpo,
|
|
272
|
+
|
|
273
|
+
easeInCirc,
|
|
274
|
+
easeOutCirc,
|
|
275
|
+
easeInOutCirc,
|
|
276
|
+
|
|
277
|
+
easeInBack,
|
|
278
|
+
easeOutBack,
|
|
279
|
+
easeInOutBack,
|
|
280
|
+
|
|
281
|
+
easeInElastic,
|
|
282
|
+
easeOutElastic,
|
|
283
|
+
easeInOutElastic,
|
|
284
|
+
|
|
285
|
+
easeInBounce,
|
|
286
|
+
easeOutBounce,
|
|
287
|
+
easeInOutBounce,
|
|
288
|
+
} as const;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for tweening helpers (camera shake, screen flash)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, assert } from "../testing/harness.ts";
|
|
6
|
+
import {
|
|
7
|
+
shakeCamera,
|
|
8
|
+
getCameraShakeOffset,
|
|
9
|
+
isCameraShaking,
|
|
10
|
+
stopCameraShake,
|
|
11
|
+
flashScreen,
|
|
12
|
+
getScreenFlash,
|
|
13
|
+
isScreenFlashing,
|
|
14
|
+
stopScreenFlash,
|
|
15
|
+
} from "./helpers.ts";
|
|
16
|
+
import { updateTweens, stopAllTweens } from "./tween.ts";
|
|
17
|
+
|
|
18
|
+
describe("Camera Shake", () => {
|
|
19
|
+
it("should start inactive", () => {
|
|
20
|
+
stopAllTweens();
|
|
21
|
+
stopCameraShake();
|
|
22
|
+
|
|
23
|
+
assert.equal(isCameraShaking(), false);
|
|
24
|
+
const offset = getCameraShakeOffset();
|
|
25
|
+
assert.equal(offset.x, 0);
|
|
26
|
+
assert.equal(offset.y, 0);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should activate when shake is triggered", () => {
|
|
30
|
+
stopAllTweens();
|
|
31
|
+
stopCameraShake();
|
|
32
|
+
|
|
33
|
+
shakeCamera(10, 1.0);
|
|
34
|
+
assert.equal(isCameraShaking(), true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should produce non-zero offsets during shake", () => {
|
|
38
|
+
stopAllTweens();
|
|
39
|
+
stopCameraShake();
|
|
40
|
+
|
|
41
|
+
shakeCamera(10, 1.0);
|
|
42
|
+
updateTweens(0.1);
|
|
43
|
+
|
|
44
|
+
const offset = getCameraShakeOffset();
|
|
45
|
+
// Offset should be non-zero (with high probability due to randomness)
|
|
46
|
+
const hasOffset = Math.abs(offset.x) > 0 || Math.abs(offset.y) > 0;
|
|
47
|
+
assert.equal(hasOffset, true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should decay intensity over time", () => {
|
|
51
|
+
stopAllTweens();
|
|
52
|
+
stopCameraShake();
|
|
53
|
+
|
|
54
|
+
shakeCamera(100, 1.0);
|
|
55
|
+
updateTweens(0.1);
|
|
56
|
+
|
|
57
|
+
const offset1 = getCameraShakeOffset();
|
|
58
|
+
const magnitude1 = Math.sqrt(offset1.x ** 2 + offset1.y ** 2);
|
|
59
|
+
|
|
60
|
+
updateTweens(0.5);
|
|
61
|
+
|
|
62
|
+
const offset2 = getCameraShakeOffset();
|
|
63
|
+
const magnitude2 = Math.sqrt(offset2.x ** 2 + offset2.y ** 2);
|
|
64
|
+
|
|
65
|
+
// Later magnitude should generally be smaller (decay)
|
|
66
|
+
// Note: Due to randomness, this might occasionally fail, but with high intensity
|
|
67
|
+
// and sufficient time, it should be reliable
|
|
68
|
+
assert.ok(magnitude2 < magnitude1 + 50, "Shake should decay over time");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should stop after duration completes", () => {
|
|
72
|
+
stopAllTweens();
|
|
73
|
+
stopCameraShake();
|
|
74
|
+
|
|
75
|
+
shakeCamera(10, 1.0);
|
|
76
|
+
assert.equal(isCameraShaking(), true);
|
|
77
|
+
|
|
78
|
+
updateTweens(1.0);
|
|
79
|
+
assert.equal(isCameraShaking(), false);
|
|
80
|
+
|
|
81
|
+
const offset = getCameraShakeOffset();
|
|
82
|
+
assert.equal(offset.x, 0);
|
|
83
|
+
assert.equal(offset.y, 0);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should stop immediately when stopCameraShake is called", () => {
|
|
87
|
+
stopAllTweens();
|
|
88
|
+
stopCameraShake();
|
|
89
|
+
|
|
90
|
+
shakeCamera(10, 1.0);
|
|
91
|
+
updateTweens(0.5);
|
|
92
|
+
assert.equal(isCameraShaking(), true);
|
|
93
|
+
|
|
94
|
+
stopCameraShake();
|
|
95
|
+
assert.equal(isCameraShaking(), false);
|
|
96
|
+
|
|
97
|
+
const offset = getCameraShakeOffset();
|
|
98
|
+
assert.equal(offset.x, 0);
|
|
99
|
+
assert.equal(offset.y, 0);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("Screen Flash", () => {
|
|
104
|
+
it("should start inactive", () => {
|
|
105
|
+
stopAllTweens();
|
|
106
|
+
stopScreenFlash();
|
|
107
|
+
|
|
108
|
+
assert.equal(isScreenFlashing(), false);
|
|
109
|
+
assert.equal(getScreenFlash(), null);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("should activate when flash is triggered", () => {
|
|
113
|
+
stopAllTweens();
|
|
114
|
+
stopScreenFlash();
|
|
115
|
+
|
|
116
|
+
flashScreen(1, 1, 1, 1.0);
|
|
117
|
+
assert.equal(isScreenFlashing(), true);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should return flash color and opacity", () => {
|
|
121
|
+
stopAllTweens();
|
|
122
|
+
stopScreenFlash();
|
|
123
|
+
|
|
124
|
+
flashScreen(1, 0.5, 0.2, 1.0, 0.8);
|
|
125
|
+
const flash = getScreenFlash();
|
|
126
|
+
|
|
127
|
+
assert.ok(flash !== null);
|
|
128
|
+
assert.equal(flash!.r, 1);
|
|
129
|
+
assert.equal(flash!.g, 0.5);
|
|
130
|
+
assert.equal(flash!.b, 0.2);
|
|
131
|
+
assert.equal(flash!.opacity, 0.8);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should fade opacity over time", () => {
|
|
135
|
+
stopAllTweens();
|
|
136
|
+
stopScreenFlash();
|
|
137
|
+
|
|
138
|
+
flashScreen(1, 1, 1, 1.0, 0.8);
|
|
139
|
+
updateTweens(0.5);
|
|
140
|
+
|
|
141
|
+
const flash = getScreenFlash();
|
|
142
|
+
assert.ok(flash !== null);
|
|
143
|
+
assert.ok(flash!.opacity < 0.8, "Opacity should decrease over time");
|
|
144
|
+
assert.ok(flash!.opacity > 0, "Opacity should still be positive");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should stop after duration completes", () => {
|
|
148
|
+
stopAllTweens();
|
|
149
|
+
stopScreenFlash();
|
|
150
|
+
|
|
151
|
+
flashScreen(1, 1, 1, 1.0);
|
|
152
|
+
assert.equal(isScreenFlashing(), true);
|
|
153
|
+
|
|
154
|
+
updateTweens(1.0);
|
|
155
|
+
assert.equal(isScreenFlashing(), false);
|
|
156
|
+
assert.equal(getScreenFlash(), null);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("should stop immediately when stopScreenFlash is called", () => {
|
|
160
|
+
stopAllTweens();
|
|
161
|
+
stopScreenFlash();
|
|
162
|
+
|
|
163
|
+
flashScreen(1, 1, 1, 1.0);
|
|
164
|
+
updateTweens(0.5);
|
|
165
|
+
assert.equal(isScreenFlashing(), true);
|
|
166
|
+
|
|
167
|
+
stopScreenFlash();
|
|
168
|
+
assert.equal(isScreenFlashing(), false);
|
|
169
|
+
assert.equal(getScreenFlash(), null);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("should preserve color during fade", () => {
|
|
173
|
+
stopAllTweens();
|
|
174
|
+
stopScreenFlash();
|
|
175
|
+
|
|
176
|
+
flashScreen(0.5, 0.3, 0.7, 1.0);
|
|
177
|
+
updateTweens(0.5);
|
|
178
|
+
|
|
179
|
+
const flash = getScreenFlash();
|
|
180
|
+
assert.ok(flash !== null);
|
|
181
|
+
assert.equal(flash!.r, 0.5);
|
|
182
|
+
assert.equal(flash!.g, 0.3);
|
|
183
|
+
assert.equal(flash!.b, 0.7);
|
|
184
|
+
});
|
|
185
|
+
});
|