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