A browser source overlay for winter vibes for your Live Streams or Videos
1import Config from "./config.js";
2import { createSnowflake, drawSnowflake, moveSnowflake } from "./snowflake.js";
3import {
4 accumulateSnow,
5 snowAccumulatorCollisionY,
6 createSnowAccumulator,
7 drawSnowAccumulators,
8 resetSnowAccumulator,
9} from "./snow_accumulator.js";
10import { createPlow, drawPlow, movePlow, plowDone } from "./plow.js";
11import "./contextMenu.js";
12
13/**
14 * @type{Array.<Snowflake>}
15 */
16const snowFlakes = [];
17
18/**
19 * @type SnowAccumulator[]
20 */
21const floor = [];
22
23const sectionSize = Config.canvas.width / Config.groundAccumulator.slices;
24/**
25 * @type {HTMLCanvasElement}
26 */
27let canvas;
28
29/**
30 * @type {CanvasRenderingContext2D}
31 */
32let ctx;
33
34/**
35 * @type {Plow}
36 */
37let plow;
38
39let lastSpawn = 0;
40let lastFrame = 0;
41
42let isPlowing = false;
43
44function init() {
45 // @ts-ignore -- I dunno how to get ts / jsdoc to be ok with this
46 canvas = document.getElementById("main");
47
48 ctx = canvas.getContext("2d");
49 canvas.width = Config.canvas.width;
50 canvas.height = Config.canvas.height;
51 ctx.imageSmoothingEnabled = false;
52
53 for (let i = 0; i < Config.groundAccumulator.slices; i++) {
54 floor.push(
55 createSnowAccumulator(i * sectionSize, Config.canvas.height, sectionSize),
56 );
57 }
58
59 window.requestAnimationFrame(gameLoop);
60}
61
62/**
63 * @param time {DOMHighResTimeStamp}
64 */
65function gameLoop(time) {
66 const delta = time - lastFrame;
67
68 ctx.clearRect(0, 0, Config.canvas.width, Config.canvas.height);
69 if (time - lastSpawn > 500 && snowFlakes.length < Config.snow.max) {
70 for (let i = 0; i < Math.floor(Math.random() * 2); i++) {
71 snowFlakes.push(createSnowflake());
72 }
73 }
74
75 snowFlakes.forEach((flake, idx) => {
76 moveSnowflake(flake, delta);
77 const floorIdx = Math.floor(flake.x / sectionSize);
78
79 if (snowAccumulatorCollisionY(floor[floorIdx], flake)) {
80 if (!isPlowing) {
81 accumulateSnow(floor[floorIdx], flake);
82 }
83 snowFlakes[idx] = createSnowflake();
84 }
85 drawSnowflake(flake, ctx);
86 });
87
88 const avgHeight =
89 floor.reduce((acc, sa) => acc + sa.height, 0) / floor.length;
90
91 if (avgHeight > Config.groundAccumulator.max) {
92 isPlowing = true;
93 }
94
95 drawSnowAccumulators(ctx, floor, plow);
96
97 if (isPlowing) {
98 plow ??= createPlow();
99 movePlow(plow, delta);
100 drawPlow(plow, ctx, time);
101 if (plowDone(plow)) {
102 isPlowing = false;
103 plow = null;
104 floor.forEach((sa) => resetSnowAccumulator(sa));
105 }
106 }
107
108 lastFrame = time;
109 window.requestAnimationFrame(gameLoop);
110}
111
112init();