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}