@guinetik/gcanvas 1.0.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/.github/workflows/release.yaml +70 -0
- package/.jshintrc +4 -0
- package/.vscode/settings.json +22 -0
- package/CLAUDE.md +310 -0
- package/blackhole.jpg +0 -0
- package/demo.png +0 -0
- package/demos/CNAME +1 -0
- package/demos/animations.html +31 -0
- package/demos/basic.html +38 -0
- package/demos/baskara.html +31 -0
- package/demos/bezier.html +35 -0
- package/demos/beziersignature.html +29 -0
- package/demos/blackhole.html +28 -0
- package/demos/blob.html +35 -0
- package/demos/demos.css +289 -0
- package/demos/easing.html +28 -0
- package/demos/events.html +195 -0
- package/demos/fluent.html +647 -0
- package/demos/fractals.html +36 -0
- package/demos/genart.html +26 -0
- package/demos/gendream.html +26 -0
- package/demos/group.html +36 -0
- package/demos/home.html +587 -0
- package/demos/index.html +364 -0
- package/demos/isometric.html +34 -0
- package/demos/js/animations.js +452 -0
- package/demos/js/basic.js +204 -0
- package/demos/js/baskara.js +751 -0
- package/demos/js/bezier.js +692 -0
- package/demos/js/beziersignature.js +241 -0
- package/demos/js/blackhole/accretiondisk.obj.js +379 -0
- package/demos/js/blackhole/blackhole.obj.js +318 -0
- package/demos/js/blackhole/index.js +409 -0
- package/demos/js/blackhole/particle.js +56 -0
- package/demos/js/blackhole/starfield.obj.js +218 -0
- package/demos/js/blob.js +2263 -0
- package/demos/js/easing.js +477 -0
- package/demos/js/fluent.js +183 -0
- package/demos/js/fractals.js +931 -0
- package/demos/js/fractalworker.js +93 -0
- package/demos/js/genart.js +268 -0
- package/demos/js/gendream.js +209 -0
- package/demos/js/group.js +140 -0
- package/demos/js/info-toggle.js +25 -0
- package/demos/js/isometric.js +863 -0
- package/demos/js/kerr.js +1556 -0
- package/demos/js/lavalamp.js +590 -0
- package/demos/js/layout.js +354 -0
- package/demos/js/mondrian.js +285 -0
- package/demos/js/opacity.js +275 -0
- package/demos/js/painter.js +484 -0
- package/demos/js/particles-showcase.js +514 -0
- package/demos/js/particles.js +299 -0
- package/demos/js/patterns.js +397 -0
- package/demos/js/penrose/artifact.js +69 -0
- package/demos/js/penrose/blackhole.js +121 -0
- package/demos/js/penrose/constants.js +73 -0
- package/demos/js/penrose/game.js +943 -0
- package/demos/js/penrose/lore.js +278 -0
- package/demos/js/penrose/penrosescene.js +892 -0
- package/demos/js/penrose/ship.js +216 -0
- package/demos/js/penrose/sounds.js +211 -0
- package/demos/js/penrose/voidparticle.js +55 -0
- package/demos/js/penrose/voidscene.js +258 -0
- package/demos/js/penrose/voidship.js +144 -0
- package/demos/js/penrose/wormhole.js +46 -0
- package/demos/js/pipeline.js +555 -0
- package/demos/js/scene.js +304 -0
- package/demos/js/scenes.js +320 -0
- package/demos/js/schrodinger.js +410 -0
- package/demos/js/schwarzschild.js +1023 -0
- package/demos/js/shapes.js +628 -0
- package/demos/js/space/alien.js +171 -0
- package/demos/js/space/boom.js +98 -0
- package/demos/js/space/boss.js +353 -0
- package/demos/js/space/buff.js +73 -0
- package/demos/js/space/bullet.js +102 -0
- package/demos/js/space/constants.js +85 -0
- package/demos/js/space/game.js +1884 -0
- package/demos/js/space/hud.js +112 -0
- package/demos/js/space/laserbeam.js +179 -0
- package/demos/js/space/lightning.js +277 -0
- package/demos/js/space/minion.js +192 -0
- package/demos/js/space/missile.js +212 -0
- package/demos/js/space/player.js +430 -0
- package/demos/js/space/powerup.js +90 -0
- package/demos/js/space/starfield.js +58 -0
- package/demos/js/space/starpower.js +90 -0
- package/demos/js/spacetime.js +559 -0
- package/demos/js/svgtween.js +204 -0
- package/demos/js/tde/accretiondisk.js +418 -0
- package/demos/js/tde/blackhole.js +219 -0
- package/demos/js/tde/blackholescene.js +209 -0
- package/demos/js/tde/config.js +59 -0
- package/demos/js/tde/index.js +695 -0
- package/demos/js/tde/jets.js +290 -0
- package/demos/js/tde/lensedstarfield.js +147 -0
- package/demos/js/tde/tdestar.js +317 -0
- package/demos/js/tde/tidalstream.js +356 -0
- package/demos/js/tde_old/blackhole.obj.js +354 -0
- package/demos/js/tde_old/debris.obj.js +791 -0
- package/demos/js/tde_old/flare.obj.js +239 -0
- package/demos/js/tde_old/index.js +448 -0
- package/demos/js/tde_old/star.obj.js +812 -0
- package/demos/js/tiles.js +312 -0
- package/demos/js/tweendemo.js +79 -0
- package/demos/js/visibility.js +102 -0
- package/demos/kerr.html +28 -0
- package/demos/lavalamp.html +27 -0
- package/demos/layouts.html +37 -0
- package/demos/logo.svg +4 -0
- package/demos/loop.html +84 -0
- package/demos/mondrian.html +32 -0
- package/demos/og_image.png +0 -0
- package/demos/opacity.html +36 -0
- package/demos/painter.html +39 -0
- package/demos/particles-showcase.html +28 -0
- package/demos/particles.html +24 -0
- package/demos/patterns.html +33 -0
- package/demos/penrose-game.html +31 -0
- package/demos/pipeline.html +737 -0
- package/demos/scene.html +33 -0
- package/demos/scenes.html +96 -0
- package/demos/schrodinger.html +27 -0
- package/demos/schwarzschild.html +27 -0
- package/demos/shapes.html +16 -0
- package/demos/space.html +85 -0
- package/demos/spacetime.html +27 -0
- package/demos/svgtween.html +29 -0
- package/demos/tde.html +28 -0
- package/demos/tiles.html +28 -0
- package/demos/transforms.html +400 -0
- package/demos/tween.html +45 -0
- package/demos/visibility.html +33 -0
- package/disk_example.png +0 -0
- package/docs/README.md +222 -0
- package/docs/concepts/architecture-overview.md +204 -0
- package/docs/concepts/lifecycle.md +255 -0
- package/docs/concepts/rendering-pipeline.md +279 -0
- package/docs/concepts/tde-zorder.md +106 -0
- package/docs/concepts/two-layer-architecture.md +229 -0
- package/docs/getting-started/first-game.md +354 -0
- package/docs/getting-started/hello-world.md +269 -0
- package/docs/getting-started/installation.md +157 -0
- package/docs/modules/collision/README.md +453 -0
- package/docs/modules/fluent/README.md +1075 -0
- package/docs/modules/game/README.md +303 -0
- package/docs/modules/isometric-camera.md +210 -0
- package/docs/modules/isometric.md +275 -0
- package/docs/modules/painter/README.md +328 -0
- package/docs/modules/particle/README.md +559 -0
- package/docs/modules/shapes/README.md +221 -0
- package/docs/modules/shapes/base/euclidian.md +123 -0
- package/docs/modules/shapes/base/geometry2d.md +204 -0
- package/docs/modules/shapes/base/renderable.md +215 -0
- package/docs/modules/shapes/base/shape.md +262 -0
- package/docs/modules/shapes/base/transformable.md +243 -0
- package/docs/modules/shapes/hierarchy.md +218 -0
- package/docs/modules/state/README.md +577 -0
- package/docs/modules/util/README.md +99 -0
- package/docs/modules/util/camera3d.md +412 -0
- package/docs/modules/util/scene3d.md +395 -0
- package/index.html +17 -0
- package/jsdoc.json +50 -0
- package/package.json +55 -0
- package/readme.md +599 -0
- package/scripts/build-demo.js +69 -0
- package/scripts/bundle4llm.js +276 -0
- package/scripts/clearconsole.js +48 -0
- package/src/collision/collision-system.js +332 -0
- package/src/collision/collision.js +303 -0
- package/src/collision/index.js +10 -0
- package/src/fluent/fluent-game.js +430 -0
- package/src/fluent/fluent-go.js +1060 -0
- package/src/fluent/fluent-layer.js +152 -0
- package/src/fluent/fluent-scene.js +291 -0
- package/src/fluent/index.js +98 -0
- package/src/fluent/sketch.js +380 -0
- package/src/game/game.js +467 -0
- package/src/game/index.js +49 -0
- package/src/game/objects/go.js +220 -0
- package/src/game/objects/imagego.js +30 -0
- package/src/game/objects/index.js +54 -0
- package/src/game/objects/isometric-scene.js +260 -0
- package/src/game/objects/layoutscene.js +549 -0
- package/src/game/objects/scene.js +175 -0
- package/src/game/objects/scene3d.js +118 -0
- package/src/game/objects/text.js +221 -0
- package/src/game/objects/wrapper.js +232 -0
- package/src/game/pipeline.js +243 -0
- package/src/game/ui/button.js +396 -0
- package/src/game/ui/cursor.js +93 -0
- package/src/game/ui/fps.js +91 -0
- package/src/game/ui/index.js +5 -0
- package/src/game/ui/togglebutton.js +93 -0
- package/src/game/ui/tooltip.js +249 -0
- package/src/index.js +25 -0
- package/src/io/events.js +20 -0
- package/src/io/index.js +86 -0
- package/src/io/input.js +70 -0
- package/src/io/keys.js +152 -0
- package/src/io/mouse.js +61 -0
- package/src/io/touch.js +39 -0
- package/src/logger/debugtab.js +138 -0
- package/src/logger/index.js +3 -0
- package/src/logger/loggable.js +47 -0
- package/src/logger/logger.js +113 -0
- package/src/math/complex.js +37 -0
- package/src/math/constants.js +1 -0
- package/src/math/fractal.js +1271 -0
- package/src/math/gr.js +201 -0
- package/src/math/heat.js +202 -0
- package/src/math/index.js +12 -0
- package/src/math/noise.js +433 -0
- package/src/math/orbital.js +191 -0
- package/src/math/patterns.js +1339 -0
- package/src/math/penrose.js +259 -0
- package/src/math/quantum.js +115 -0
- package/src/math/random.js +195 -0
- package/src/math/tensor.js +1009 -0
- package/src/mixins/anchor.js +131 -0
- package/src/mixins/draggable.js +72 -0
- package/src/mixins/index.js +2 -0
- package/src/motion/bezier.js +132 -0
- package/src/motion/bounce.js +58 -0
- package/src/motion/easing.js +349 -0
- package/src/motion/float.js +130 -0
- package/src/motion/follow.js +125 -0
- package/src/motion/hop.js +52 -0
- package/src/motion/index.js +82 -0
- package/src/motion/motion.js +1124 -0
- package/src/motion/orbit.js +49 -0
- package/src/motion/oscillate.js +39 -0
- package/src/motion/parabolic.js +141 -0
- package/src/motion/patrol.js +147 -0
- package/src/motion/pendulum.js +48 -0
- package/src/motion/pulse.js +88 -0
- package/src/motion/shake.js +83 -0
- package/src/motion/spiral.js +144 -0
- package/src/motion/spring.js +150 -0
- package/src/motion/swing.js +47 -0
- package/src/motion/tween.js +92 -0
- package/src/motion/tweenetik.js +139 -0
- package/src/motion/waypoint.js +210 -0
- package/src/painter/index.js +8 -0
- package/src/painter/painter.colors.js +331 -0
- package/src/painter/painter.effects.js +230 -0
- package/src/painter/painter.img.js +229 -0
- package/src/painter/painter.js +295 -0
- package/src/painter/painter.lines.js +189 -0
- package/src/painter/painter.opacity.js +41 -0
- package/src/painter/painter.shapes.js +277 -0
- package/src/painter/painter.text.js +273 -0
- package/src/particle/emitter.js +124 -0
- package/src/particle/index.js +11 -0
- package/src/particle/particle-system.js +322 -0
- package/src/particle/particle.js +71 -0
- package/src/particle/updaters.js +170 -0
- package/src/shapes/arc.js +43 -0
- package/src/shapes/arrow.js +33 -0
- package/src/shapes/bezier.js +42 -0
- package/src/shapes/circle.js +62 -0
- package/src/shapes/clouds.js +56 -0
- package/src/shapes/cone.js +219 -0
- package/src/shapes/cross.js +70 -0
- package/src/shapes/cube.js +244 -0
- package/src/shapes/cylinder.js +254 -0
- package/src/shapes/diamond.js +48 -0
- package/src/shapes/euclidian.js +111 -0
- package/src/shapes/figure.js +115 -0
- package/src/shapes/geometry.js +220 -0
- package/src/shapes/group.js +375 -0
- package/src/shapes/heart.js +42 -0
- package/src/shapes/hexagon.js +26 -0
- package/src/shapes/image.js +192 -0
- package/src/shapes/index.js +111 -0
- package/src/shapes/line.js +29 -0
- package/src/shapes/pattern.js +90 -0
- package/src/shapes/pin.js +44 -0
- package/src/shapes/poly.js +31 -0
- package/src/shapes/prism.js +226 -0
- package/src/shapes/rect.js +35 -0
- package/src/shapes/renderable.js +333 -0
- package/src/shapes/ring.js +26 -0
- package/src/shapes/roundrect.js +95 -0
- package/src/shapes/shape.js +117 -0
- package/src/shapes/slice.js +26 -0
- package/src/shapes/sphere.js +314 -0
- package/src/shapes/sphere3d.js +537 -0
- package/src/shapes/square.js +15 -0
- package/src/shapes/star.js +99 -0
- package/src/shapes/svg.js +408 -0
- package/src/shapes/text.js +553 -0
- package/src/shapes/traceable.js +83 -0
- package/src/shapes/transform.js +357 -0
- package/src/shapes/transformable.js +172 -0
- package/src/shapes/triangle.js +26 -0
- package/src/sound/index.js +17 -0
- package/src/sound/sound.js +473 -0
- package/src/sound/synth.analyzer.js +149 -0
- package/src/sound/synth.effects.js +207 -0
- package/src/sound/synth.envelope.js +59 -0
- package/src/sound/synth.js +229 -0
- package/src/sound/synth.musical.js +160 -0
- package/src/sound/synth.noise.js +85 -0
- package/src/sound/synth.oscillators.js +293 -0
- package/src/state/index.js +10 -0
- package/src/state/state-machine.js +371 -0
- package/src/util/camera3d.js +438 -0
- package/src/util/index.js +6 -0
- package/src/util/isometric-camera.js +235 -0
- package/src/util/layout.js +317 -0
- package/src/util/position.js +147 -0
- package/src/util/tasks.js +47 -0
- package/src/util/zindex.js +287 -0
- package/src/webgl/index.js +9 -0
- package/src/webgl/shaders/sphere-shaders.js +994 -0
- package/src/webgl/webgl-renderer.js +388 -0
- package/tde.png +0 -0
- package/test/math/orbital.test.js +61 -0
- package/test/math/tensor.test.js +114 -0
- package/test/particle/emitter.test.js +204 -0
- package/test/particle/particle-system.test.js +310 -0
- package/test/particle/particle.test.js +116 -0
- package/test/particle/updaters.test.js +386 -0
- package/test/setup.js +120 -0
- package/test/shapes/euclidian.test.js +44 -0
- package/test/shapes/geometry.test.js +86 -0
- package/test/shapes/group.test.js +86 -0
- package/test/shapes/rectangle.test.js +64 -0
- package/test/shapes/transform.test.js +379 -0
- package/test/util/camera3d.test.js +428 -0
- package/test/util/scene3d.test.js +352 -0
- package/types/collision.d.ts +249 -0
- package/types/common.d.ts +155 -0
- package/types/game.d.ts +497 -0
- package/types/index.d.ts +309 -0
- package/types/io.d.ts +188 -0
- package/types/logger.d.ts +127 -0
- package/types/math.d.ts +268 -0
- package/types/mixins.d.ts +92 -0
- package/types/motion.d.ts +678 -0
- package/types/painter.d.ts +378 -0
- package/types/shapes.d.ts +864 -0
- package/types/sound.d.ts +672 -0
- package/types/state.d.ts +251 -0
- package/types/util.d.ts +253 -0
- package/vite.config.js +50 -0
- package/vitest.config.js +13 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Motion } from "./motion";
|
|
2
|
+
import { Tween } from "./tween";
|
|
3
|
+
/**
|
|
4
|
+
* Spiral motion animation
|
|
5
|
+
*
|
|
6
|
+
* @param {number} centerX - X coordinate of spiral center
|
|
7
|
+
* @param {number} centerY - Y coordinate of spiral center
|
|
8
|
+
* @param {number} startRadius - Starting radius of the spiral
|
|
9
|
+
* @param {number} endRadius - Ending radius of the spiral
|
|
10
|
+
* @param {number} startAngle - Starting angle in radians
|
|
11
|
+
* @param {number} revolutions - Number of complete revolutions
|
|
12
|
+
* @param {number} elapsedTime - Total elapsed time in seconds
|
|
13
|
+
* @param {number} duration - Duration of animation in seconds
|
|
14
|
+
* @param {boolean} [loop=false] - Whether animation should loop (restart from beginning)
|
|
15
|
+
* @param {boolean} [yoyo=false] - Whether animation should reverse direction at the end
|
|
16
|
+
* @param {Function} [easingFn=null] - Optional easing function to apply
|
|
17
|
+
* @param {Object} [callbacks] - Optional callback functions
|
|
18
|
+
* @param {Object} [state] - Internal state tracking for callbacks
|
|
19
|
+
* @returns {Object} Animation result with x, y coordinates and metadata
|
|
20
|
+
*/
|
|
21
|
+
export function spiralV1(
|
|
22
|
+
centerX,
|
|
23
|
+
centerY,
|
|
24
|
+
startRadius,
|
|
25
|
+
endRadius,
|
|
26
|
+
startAngle,
|
|
27
|
+
revolutions,
|
|
28
|
+
elapsedTime,
|
|
29
|
+
duration,
|
|
30
|
+
loop = false,
|
|
31
|
+
yoyo = false,
|
|
32
|
+
easingFn = null,
|
|
33
|
+
callbacks = {},
|
|
34
|
+
state = null
|
|
35
|
+
) {
|
|
36
|
+
// Initialize state if needed
|
|
37
|
+
if (!state) {
|
|
38
|
+
state = {
|
|
39
|
+
started: false,
|
|
40
|
+
loopCount: 0,
|
|
41
|
+
direction: 1, // 1 = forward, -1 = backward (for yoyo)
|
|
42
|
+
lastDirection: 1,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// Calculate normalized time (0-1)
|
|
46
|
+
let t = duration > 0 ? elapsedTime / duration : 1;
|
|
47
|
+
let completed = false;
|
|
48
|
+
let activeCallbacks = { ...callbacks };
|
|
49
|
+
// Handle yoyo and loop logic
|
|
50
|
+
if (yoyo || loop) {
|
|
51
|
+
// For yoyo + loop, or just loop: calculate cycle
|
|
52
|
+
if (loop) {
|
|
53
|
+
if (yoyo) {
|
|
54
|
+
// For yoyo + loop: full cycle is 2x duration (forward + backward)
|
|
55
|
+
const fullCycle = duration * 2;
|
|
56
|
+
const cycleTime = elapsedTime % fullCycle;
|
|
57
|
+
const cycleCount = Math.floor(elapsedTime / fullCycle);
|
|
58
|
+
// Determine direction and adjusted t
|
|
59
|
+
const newDirection = cycleTime < duration ? 1 : -1;
|
|
60
|
+
t =
|
|
61
|
+
newDirection === 1 ? cycleTime / duration : 2 - cycleTime / duration;
|
|
62
|
+
// Check for direction change for callbacks
|
|
63
|
+
if (newDirection !== state.direction) {
|
|
64
|
+
state.direction = newDirection;
|
|
65
|
+
if (state.direction === 1 && activeCallbacks.onLoop) {
|
|
66
|
+
activeCallbacks.onLoop(cycleCount);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Track loop count
|
|
70
|
+
if (cycleCount > state.loopCount) {
|
|
71
|
+
state.loopCount = cycleCount;
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
// Just loop, no yoyo: reset t at each cycle
|
|
75
|
+
t = t % 1;
|
|
76
|
+
// Track loop count for callbacks
|
|
77
|
+
const newLoopCount = Math.floor(elapsedTime / duration);
|
|
78
|
+
if (newLoopCount > state.loopCount && activeCallbacks.onLoop) {
|
|
79
|
+
activeCallbacks.onLoop(newLoopCount);
|
|
80
|
+
state.loopCount = newLoopCount;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} else if (yoyo && !loop) {
|
|
84
|
+
// Yoyo without loop: complete one cycle then stop
|
|
85
|
+
if (t <= 1) {
|
|
86
|
+
// Forward part of the cycle
|
|
87
|
+
state.direction = 1;
|
|
88
|
+
} else if (t <= 2) {
|
|
89
|
+
// Backward part of the cycle
|
|
90
|
+
t = 2 - t;
|
|
91
|
+
state.direction = -1;
|
|
92
|
+
} else {
|
|
93
|
+
// Complete
|
|
94
|
+
t = 0;
|
|
95
|
+
completed = true;
|
|
96
|
+
state.direction = 1;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
// No loop or yoyo: standard behavior
|
|
101
|
+
if (t >= 1) {
|
|
102
|
+
t = 1;
|
|
103
|
+
completed = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Call onStart callback once
|
|
107
|
+
if (!state.started && activeCallbacks.onStart) {
|
|
108
|
+
activeCallbacks.onStart();
|
|
109
|
+
state.started = true;
|
|
110
|
+
}
|
|
111
|
+
// Call onComplete callback once when non-looping animation completes
|
|
112
|
+
if (completed && !state.completed && activeCallbacks.onComplete) {
|
|
113
|
+
activeCallbacks.onComplete();
|
|
114
|
+
state.completed = true;
|
|
115
|
+
}
|
|
116
|
+
// Apply easing if provided
|
|
117
|
+
const easedT = easingFn ? easingFn(t) : t;
|
|
118
|
+
// Calculate current radius using linear interpolation
|
|
119
|
+
const radius = Tween.lerp(startRadius, endRadius, easedT);
|
|
120
|
+
// Calculate current angle (start angle + number of revolutions)
|
|
121
|
+
const angle = startAngle + easedT * revolutions * Math.PI * 2;
|
|
122
|
+
// Convert polar coordinates to Cartesian
|
|
123
|
+
const x = centerX + radius * Math.cos(angle);
|
|
124
|
+
const y = centerY + radius * Math.sin(angle);
|
|
125
|
+
// Update state for next frame
|
|
126
|
+
const newState = {
|
|
127
|
+
...state,
|
|
128
|
+
lastDirection: state.direction,
|
|
129
|
+
};
|
|
130
|
+
// Return standardized result
|
|
131
|
+
return Motion.animationResult(
|
|
132
|
+
{
|
|
133
|
+
x,
|
|
134
|
+
y,
|
|
135
|
+
radius,
|
|
136
|
+
angle,
|
|
137
|
+
direction: state.direction, // Include current direction (1 = forward, -1 = backward)
|
|
138
|
+
},
|
|
139
|
+
t,
|
|
140
|
+
loop || (yoyo && !completed),
|
|
141
|
+
completed,
|
|
142
|
+
newState
|
|
143
|
+
);
|
|
144
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Easing } from "./easing";
|
|
2
|
+
import { Motion } from "./motion";
|
|
3
|
+
import { Tween } from "./tween";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Spring animation that uses elastic easing functions for a bouncy spring effect
|
|
7
|
+
* Stateless implementation that doesn't require previous frame state
|
|
8
|
+
*
|
|
9
|
+
* @param {number} initial - Initial value (starting position)
|
|
10
|
+
* @param {number} target - Target value (ending position)
|
|
11
|
+
* @param {number} elapsedTime - Total elapsed time in seconds
|
|
12
|
+
* @param {number} duration - Duration of one complete cycle in seconds
|
|
13
|
+
* @param {boolean} [loop=false] - Whether animation should loop
|
|
14
|
+
* @param {boolean} [yoyo=false] - Whether animation should return to initial value
|
|
15
|
+
* @param {Object} [springParams] - Spring parameters
|
|
16
|
+
* @param {number} [springParams.stiffness=0.3] - Spring stiffness (0-1)
|
|
17
|
+
* @param {number} [springParams.damping=0.6] - Damping factor (0-1)
|
|
18
|
+
* @param {Object} [callbacks] - Optional callback functions
|
|
19
|
+
* @param {Function} [callbacks.onStart] - Called when animation starts
|
|
20
|
+
* @param {Function} [callbacks.onComplete] - Called when animation completes
|
|
21
|
+
* @param {Function} [callbacks.onLoop] - Called when animation loops
|
|
22
|
+
* @param {Function} [callbacks.onYoyoTurn] - Called when yoyo animation changes direction
|
|
23
|
+
* @returns {Object} Animation result with value, velocity and metadata
|
|
24
|
+
*/
|
|
25
|
+
export function springV1(
|
|
26
|
+
initial,
|
|
27
|
+
target,
|
|
28
|
+
elapsedTime,
|
|
29
|
+
duration,
|
|
30
|
+
loop = false,
|
|
31
|
+
yoyo = false,
|
|
32
|
+
springParams = {},
|
|
33
|
+
callbacks = {}
|
|
34
|
+
) {
|
|
35
|
+
// Early return for zero duration
|
|
36
|
+
if (duration <= 0) {
|
|
37
|
+
return this.animationResult(
|
|
38
|
+
{ value: target, velocity: 0, done: true, phase: "complete" },
|
|
39
|
+
1,
|
|
40
|
+
false,
|
|
41
|
+
true
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
// Normalize time (0-1) within current cycle
|
|
45
|
+
let t = elapsedTime / duration;
|
|
46
|
+
let yoyoPhase = "forward";
|
|
47
|
+
let loopCount = 0;
|
|
48
|
+
// Handle looping vs. non-looping
|
|
49
|
+
if (loop) {
|
|
50
|
+
// Calculate loop count (for callbacks)
|
|
51
|
+
loopCount = Math.floor(t);
|
|
52
|
+
// Use only the fractional part for looping (0-1 repeating)
|
|
53
|
+
t = t % 1;
|
|
54
|
+
// Call onLoop callback if provided and we crossed a loop boundary
|
|
55
|
+
if (loopCount > 0 && callbacks.onLoop) {
|
|
56
|
+
callbacks.onLoop(loopCount);
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
// Clamp to 1 for non-looping animations
|
|
60
|
+
if (t > 1) t = 1;
|
|
61
|
+
}
|
|
62
|
+
// Call onStart callback if needed (only when animation begins)
|
|
63
|
+
if (t > 0 && elapsedTime <= duration && callbacks.onStart) {
|
|
64
|
+
callbacks.onStart();
|
|
65
|
+
}
|
|
66
|
+
// For yoyo, adjust the target value based on which half of the cycle we're in
|
|
67
|
+
let currentTarget, currentInitial, adjustedT;
|
|
68
|
+
if (yoyo) {
|
|
69
|
+
// If in the second half of the animation, we're going back
|
|
70
|
+
if (t >= 0.5) {
|
|
71
|
+
// Swap target and initial
|
|
72
|
+
currentTarget = initial;
|
|
73
|
+
currentInitial = target;
|
|
74
|
+
// Rescale t to 0-1 for the second half
|
|
75
|
+
adjustedT = (t - 0.5) * 2;
|
|
76
|
+
yoyoPhase = "return";
|
|
77
|
+
// Call onYoyoTurn callback at the turning point
|
|
78
|
+
if (t >= 0.5 && t < 0.51 && callbacks.onYoyoTurn) {
|
|
79
|
+
callbacks.onYoyoTurn();
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
// First half of animation
|
|
83
|
+
currentTarget = target;
|
|
84
|
+
currentInitial = initial;
|
|
85
|
+
adjustedT = t * 2;
|
|
86
|
+
yoyoPhase = "forward";
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
currentTarget = target;
|
|
90
|
+
currentInitial = initial;
|
|
91
|
+
adjustedT = t;
|
|
92
|
+
}
|
|
93
|
+
// Get spring parameters with defaults
|
|
94
|
+
const stiffness =
|
|
95
|
+
springParams.stiffness !== undefined ? springParams.stiffness : 0.3;
|
|
96
|
+
const damping =
|
|
97
|
+
springParams.damping !== undefined ? springParams.damping : 0.6;
|
|
98
|
+
// Convert stiffness and damping to elastic easing parameters
|
|
99
|
+
const amplitude = Math.max(0.1, 1.0 / (damping * 1.5)); // Higher damping = lower amplitude
|
|
100
|
+
const period = Math.max(0.1, 0.8 / (stiffness * 1.5 + 0.5)); // Higher stiffness = higher frequency
|
|
101
|
+
// Use the elastic easing to generate spring-like motion
|
|
102
|
+
// For springs, easeOutElastic works best (starts fast, then oscillates to rest)
|
|
103
|
+
let easedT;
|
|
104
|
+
if (adjustedT < 0.99) {
|
|
105
|
+
// Use elastic easing for most of the animation
|
|
106
|
+
easedT = Easing.easeOutElastic(adjustedT, amplitude, period);
|
|
107
|
+
} else {
|
|
108
|
+
// Smoothly settle to the exact target value at the end
|
|
109
|
+
// This prevents small oscillations at t=1
|
|
110
|
+
const blendFactor = (adjustedT - 0.99) / 0.01;
|
|
111
|
+
const elasticT = Easing.easeOutElastic(0.99, amplitude, period);
|
|
112
|
+
easedT = elasticT * (1 - blendFactor) + 1 * blendFactor;
|
|
113
|
+
}
|
|
114
|
+
// Interpolate between initial and target values using the eased time
|
|
115
|
+
const position = Tween.lerp(currentInitial, currentTarget, easedT);
|
|
116
|
+
// Calculate approximate velocity by comparing positions at t and t+dt
|
|
117
|
+
const dt = 0.01;
|
|
118
|
+
const nextT = Math.min(adjustedT + dt, 1);
|
|
119
|
+
// Calculate next position for velocity estimation
|
|
120
|
+
let nextEasedT;
|
|
121
|
+
if (nextT < 0.99) {
|
|
122
|
+
nextEasedT = Easing.easeOutElastic(nextT, amplitude, period);
|
|
123
|
+
} else {
|
|
124
|
+
const blendFactor = (nextT - 0.99) / 0.01;
|
|
125
|
+
const elasticT = Easing.easeOutElastic(0.99, amplitude, period);
|
|
126
|
+
nextEasedT = elasticT * (1 - blendFactor) + 1 * blendFactor;
|
|
127
|
+
}
|
|
128
|
+
const nextPosition = Tween.lerp(currentInitial, currentTarget, nextEasedT);
|
|
129
|
+
// Calculate velocity (scaled by duration to account for time)
|
|
130
|
+
const velocity = ((nextPosition - position) / dt) * duration;
|
|
131
|
+
// Check if non-looping animation is complete
|
|
132
|
+
const isDone = !loop && t >= 1;
|
|
133
|
+
// Call onComplete if animation has completed
|
|
134
|
+
if (isDone && callbacks.onComplete) {
|
|
135
|
+
callbacks.onComplete();
|
|
136
|
+
}
|
|
137
|
+
// Return standardized result with additional spring metadata
|
|
138
|
+
return Motion.animationResult(
|
|
139
|
+
{
|
|
140
|
+
value: position,
|
|
141
|
+
velocity: velocity,
|
|
142
|
+
delta: yoyoPhase === "forward" ? target - position : initial - position,
|
|
143
|
+
done: isDone,
|
|
144
|
+
phase: yoyoPhase,
|
|
145
|
+
},
|
|
146
|
+
t,
|
|
147
|
+
loop,
|
|
148
|
+
isDone
|
|
149
|
+
);
|
|
150
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Motion } from "./motion";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Swing animation - oscillates around a fixed center angle
|
|
5
|
+
*
|
|
6
|
+
* @param {number} centerX - X pivot (not used for angle, just for semantic)
|
|
7
|
+
* @param {number} centerY - Y pivot (not used for angle, just for semantic)
|
|
8
|
+
* @param {number} maxAngle - Maximum swing angle in radians (e.g. Math.PI / 6)
|
|
9
|
+
* @param {number} elapsedTime - Total elapsed time in seconds
|
|
10
|
+
* @param {number} duration - Duration of one full swing cycle
|
|
11
|
+
* @param {boolean} [loop=true] - Whether the animation loops
|
|
12
|
+
* @param {boolean} [yoyo=true] - Whether the swing reverses
|
|
13
|
+
* @param {Function} [easingFn=null] - Optional easing function
|
|
14
|
+
* @param {Object} [callbacks={}] - Optional callbacks (onStart, onComplete, etc)
|
|
15
|
+
* @param {Object} [state=null] - Internal state tracking
|
|
16
|
+
* @returns {Object} Animation result with angle and metadata
|
|
17
|
+
*/
|
|
18
|
+
export function swingV1(
|
|
19
|
+
centerX,
|
|
20
|
+
centerY,
|
|
21
|
+
maxAngle,
|
|
22
|
+
elapsedTime,
|
|
23
|
+
duration,
|
|
24
|
+
loop = true,
|
|
25
|
+
yoyo = true,
|
|
26
|
+
easingFn = null,
|
|
27
|
+
callbacks = {},
|
|
28
|
+
state = null
|
|
29
|
+
) {
|
|
30
|
+
// Process time and easing
|
|
31
|
+
const {
|
|
32
|
+
t,
|
|
33
|
+
easedT,
|
|
34
|
+
completed,
|
|
35
|
+
state: newState,
|
|
36
|
+
} = Motion._frame(elapsedTime, duration, loop, easingFn, callbacks, state);
|
|
37
|
+
|
|
38
|
+
// Swing back and forth using sine curve
|
|
39
|
+
const phase = yoyo
|
|
40
|
+
? Math.sin(easedT * Math.PI * 2)
|
|
41
|
+
: Math.sin(easedT * Math.PI);
|
|
42
|
+
|
|
43
|
+
// Calculate angle: center ± maxAngle
|
|
44
|
+
const angle = phase * maxAngle;
|
|
45
|
+
|
|
46
|
+
return Motion.animationResult({ angle }, t, loop, completed, newState);
|
|
47
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tween module providing core interpolation utilities for animations.
|
|
3
|
+
*
|
|
4
|
+
* @class Tween
|
|
5
|
+
* @description
|
|
6
|
+
* Tween is a stateless utility class for performing basic and advanced interpolation operations.
|
|
7
|
+
* These methods are low-level math tools used across both simulation-driven animations (like {@link Motion})
|
|
8
|
+
* and UI-driven transitions (like {@link Tweenetik}).
|
|
9
|
+
*
|
|
10
|
+
* The class includes:
|
|
11
|
+
* - Scalar and angle interpolation
|
|
12
|
+
* - Color blending in RGB and HSL spaces
|
|
13
|
+
* - Angle-safe easing
|
|
14
|
+
*
|
|
15
|
+
* All functions are deterministic, pure, and suitable for use in render loops or transition systems.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const x = Tween.lerp(0, 100, 0.25); // 25
|
|
19
|
+
* const angle = Tween.lerpAngle(Math.PI, -Math.PI, 0.5); // shortest-path rotation
|
|
20
|
+
* const rgb = Tween.tweenColor([255, 0, 0], [0, 0, 255], 0.5); // purple
|
|
21
|
+
*
|
|
22
|
+
* @see {@link Motion} for game-loop-driven animation patterns
|
|
23
|
+
* @see {@link Tweenetik} for time-based UI transitions
|
|
24
|
+
*/
|
|
25
|
+
export class Tween {
|
|
26
|
+
/**
|
|
27
|
+
* Linear interpolation between two scalar values.
|
|
28
|
+
* Also known as Lerp.
|
|
29
|
+
* @param {number} start
|
|
30
|
+
* @param {number} end
|
|
31
|
+
* @param {number} t - Normalized interpolation factor (0 to 1)
|
|
32
|
+
* @returns {number} Interpolated scalar
|
|
33
|
+
*/
|
|
34
|
+
static lerp(start, end, t) {
|
|
35
|
+
return start + (end - start) * t;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Smoothly interpolates between two angles (in radians),
|
|
40
|
+
* always taking the shortest path around the circle.
|
|
41
|
+
*
|
|
42
|
+
* Prevents snapping when angles wrap across -π to π boundaries.
|
|
43
|
+
*
|
|
44
|
+
* @param {number} a - Starting angle in radians
|
|
45
|
+
* @param {number} b - Target angle in radians
|
|
46
|
+
* @param {number} t - Interpolation factor (0 to 1)
|
|
47
|
+
* @returns {number} Interpolated angle in radians
|
|
48
|
+
*/
|
|
49
|
+
static lerpAngle(a, b, t) {
|
|
50
|
+
// Calculate raw difference
|
|
51
|
+
let diff = b - a;
|
|
52
|
+
// Wrap difference to range [-PI, PI] for shortest rotation
|
|
53
|
+
while (diff < -Math.PI) diff += Math.PI * 2;
|
|
54
|
+
while (diff > Math.PI) diff -= Math.PI * 2;
|
|
55
|
+
// Interpolate from a toward b using the wrapped difference
|
|
56
|
+
return a + diff * t;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Interpolate between two colors in RGB space
|
|
61
|
+
* @param {Array<number>} color1 - Start color [r, g, b] (0-255)
|
|
62
|
+
* @param {Array<number>} color2 - End color [r, g, b] (0-255)
|
|
63
|
+
* @param {number} t - Interpolation factor (0-1)
|
|
64
|
+
* @returns {Array<number>} Interpolated color [r, g, b]
|
|
65
|
+
*/
|
|
66
|
+
static tweenColor(color1, color2, t) {
|
|
67
|
+
return color1.map((c, i) => Tween.lerp(c, color2[i], t));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Interpolate between two HSL colors
|
|
72
|
+
* @param {Array<number>} color1 - Start color [h, s, l]
|
|
73
|
+
* @param {Array<number>} color2 - End color [h, s, l]
|
|
74
|
+
* @param {number} t - Interpolation factor (0-1)
|
|
75
|
+
* @returns {Array<number>} Interpolated color [h, s, l]
|
|
76
|
+
*/
|
|
77
|
+
static tweenGradient(color1, color2, t) {
|
|
78
|
+
// Special handling for hue to handle the circular nature
|
|
79
|
+
let h1 = color1[0];
|
|
80
|
+
let h2 = color2[0];
|
|
81
|
+
// Find the shortest path around the circle
|
|
82
|
+
if (Math.abs(h2 - h1) > 180) {
|
|
83
|
+
if (h1 < h2) h1 += 360;
|
|
84
|
+
else h2 += 360;
|
|
85
|
+
}
|
|
86
|
+
// tween each channel individually
|
|
87
|
+
const h = Tween.lerp(h1, h2, t) % 360;
|
|
88
|
+
const s = Tween.lerp(color1[1], color2[1], t);
|
|
89
|
+
const l = Tween.lerp(color1[2], color2[2], t);
|
|
90
|
+
return [h, s, l];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Tweenetik.js
|
|
2
|
+
import { Easing } from "./easing.js";
|
|
3
|
+
import { Tween } from "./tween.js";
|
|
4
|
+
/**
|
|
5
|
+
* Tweenetik module for declarative, time-managed UI animations.
|
|
6
|
+
*
|
|
7
|
+
* @class Tweenetik
|
|
8
|
+
* @description
|
|
9
|
+
* Tweenetik provides a lightweight, self-managed tweening system that animates object properties over time.
|
|
10
|
+
* Unlike {@link Motion}, which returns stateless values for use in real-time game loops,
|
|
11
|
+
* Tweenetik mutates the target object directly and manages its own internal time progression.
|
|
12
|
+
* <br/>
|
|
13
|
+
* It is ideal for UI transitions, hover effects, attention animations, or any element
|
|
14
|
+
* that animates independently of the simulation/game loop logic.
|
|
15
|
+
* <br/>
|
|
16
|
+
* Tweenetik is declarative: you configure a tween once and let it run in the background.
|
|
17
|
+
* You must call {@link Tweenetik.updateAll} on each frame to drive all active tweens.
|
|
18
|
+
* <br/>
|
|
19
|
+
* @example
|
|
20
|
+
* Tweenetik.to(button, { scaleX: 1.2, scaleY: 1.2 }, 0.5, Easing.easeOutBack);
|
|
21
|
+
*
|
|
22
|
+
* @see {@link Tween} for interpolation utilities
|
|
23
|
+
* @see {@link Motion} for stateless simulation-driven animations
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export class Tweenetik {
|
|
27
|
+
/**
|
|
28
|
+
* @param {Object} target - The object whose properties will be tweened.
|
|
29
|
+
* @param {Object} toProps - An object containing the property values we want to end up at (e.g. { x: 100, y: 200 }).
|
|
30
|
+
* @param {number} duration - How long (in seconds) the tween should take.
|
|
31
|
+
* @param {Function} easingFn - One of the easing functions from the Tween class (e.g. Easing.easeOutBounce).
|
|
32
|
+
* @param {Object} [options]
|
|
33
|
+
* @param {number} [options.delay=0] - Delay in seconds before starting.
|
|
34
|
+
* @param {Function} [options.onStart] - Callback invoked once when the tween actually begins.
|
|
35
|
+
* @param {Function} [options.onComplete] - Callback invoked once when the tween finishes.
|
|
36
|
+
* @param {Function} [options.onUpdate] - Callback invoked every frame (after the object’s properties are updated).
|
|
37
|
+
*/
|
|
38
|
+
constructor(target, toProps, duration, easingFn, options = {}) {
|
|
39
|
+
this.target = target;
|
|
40
|
+
this.toProps = { ...toProps };
|
|
41
|
+
this.duration = duration;
|
|
42
|
+
this.easingFn = easingFn || Easing.easeOutQuad;
|
|
43
|
+
|
|
44
|
+
// Options
|
|
45
|
+
this.delay = options.delay || 0;
|
|
46
|
+
this.onStart = options.onStart || null;
|
|
47
|
+
this.onComplete = options.onComplete || null;
|
|
48
|
+
this.onUpdate = options.onUpdate || null;
|
|
49
|
+
|
|
50
|
+
// Internal
|
|
51
|
+
this._elapsed = 0;
|
|
52
|
+
this._started = false;
|
|
53
|
+
this._finished = false;
|
|
54
|
+
this._startProps = {};
|
|
55
|
+
|
|
56
|
+
for (const prop in this.toProps) {
|
|
57
|
+
if (prop in this.target) {
|
|
58
|
+
this._startProps[prop] = this.target[prop];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Creates and registers a new Tweenetik instance in the global list.
|
|
65
|
+
* @param {Object} target
|
|
66
|
+
* @param {Object} toProps
|
|
67
|
+
* @param {number} duration
|
|
68
|
+
* @param {Function} easingFn
|
|
69
|
+
* @param {Object} [options]
|
|
70
|
+
*/
|
|
71
|
+
static to(target, toProps, duration, easingFn, options) {
|
|
72
|
+
const tween = new Tweenetik(target, toProps, duration, easingFn, options);
|
|
73
|
+
Tweenetik._active.push(tween);
|
|
74
|
+
return tween;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Updates this tween by the given delta time (in seconds).
|
|
79
|
+
* @param {number} dt
|
|
80
|
+
*/
|
|
81
|
+
update(dt) {
|
|
82
|
+
if (this._finished) return;
|
|
83
|
+
|
|
84
|
+
this._elapsed += dt;
|
|
85
|
+
|
|
86
|
+
// Wait until the delay has passed
|
|
87
|
+
if (this._elapsed < this.delay) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Time in tween (after delay)
|
|
92
|
+
const timeSinceDelay = this._elapsed - this.delay;
|
|
93
|
+
const t = Math.min(timeSinceDelay / this.duration, 1);
|
|
94
|
+
|
|
95
|
+
// onStart callback (once)
|
|
96
|
+
if (!this._started && t > 0) {
|
|
97
|
+
this._started = true;
|
|
98
|
+
if (this.onStart) {
|
|
99
|
+
this.onStart();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Calculate eased factor
|
|
104
|
+
const eased = this.easingFn(t);
|
|
105
|
+
//console.log("Tweenetik.update", this._startProps);
|
|
106
|
+
// Apply interpolations for each property
|
|
107
|
+
for (const prop in this._startProps) {
|
|
108
|
+
const startVal = this._startProps[prop];
|
|
109
|
+
const endVal = this.toProps[prop];
|
|
110
|
+
// Use Tween.go(...) to interpolate
|
|
111
|
+
this.target[prop] = Tween.lerp(startVal, endVal, eased);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// onUpdate callback (each frame)
|
|
115
|
+
if (this.onUpdate) {
|
|
116
|
+
this.onUpdate();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Check if finished
|
|
120
|
+
if (t >= 1) {
|
|
121
|
+
this._finished = true;
|
|
122
|
+
if (this.onComplete) {
|
|
123
|
+
this.onComplete();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Update all active tweens by dt. Call this once each frame in your main loop.
|
|
130
|
+
* @param {number} dt
|
|
131
|
+
*/
|
|
132
|
+
static updateAll(dt) {
|
|
133
|
+
for (const tween of Tweenetik._active) {
|
|
134
|
+
tween.update(dt);
|
|
135
|
+
}
|
|
136
|
+
// Remove finished tweens from the list
|
|
137
|
+
Tweenetik._active = Tweenetik._active.filter((t) => !t._finished);
|
|
138
|
+
}
|
|
139
|
+
}
|