A browser source overlay for winter vibes for your Live Streams or Videos

accumulation and the start of the plow

+12 -4
src/config.js
···
export const CANVAS_HEIGHT = 180;
// number of collisions required to shift the snowlevel up
-
export const FLOOR_RAISE_THRESHOLD = 100;
+
export const FLOOR_RAISE_THRESHOLD = 4;
+
+
// how many sections the floor is divided into
+
export const FLOOR_SICE_COUNT = 16;
// number of snowflakes to render
-
export const MAX_PARTICAL_COUNT = 200;
+
export const MAX_PARTICAL_COUNT = 30;
+
+
export const MAX_FLOOR_HEIGHT = 10;
// minimum size of snowflake
export const MIN_SIZE = 1;
···
export const MAX_SIZE = 4;
// maximum speed of a falling snowflake
-
export const MAX_SPEED = 0.1;
+
export const MAX_SPEED = 0.07;
// minimum speed of a snowflake falling
-
export const MIN_SPEED = 0.08;
+
export const MIN_SPEED = 0.04;
+
+
// speed of the plow
+
export const PLOW_SPEED = 0.1;
+44 -9
src/main.js
···
import "./style.css";
-
import { CANVAS_HEIGHT, CANVAS_WIDTH, MAX_PARTICAL_COUNT } from "./config";
+
import {
+
CANVAS_HEIGHT,
+
CANVAS_WIDTH,
+
FLOOR_SICE_COUNT,
+
MAX_FLOOR_HEIGHT,
+
MAX_PARTICAL_COUNT,
+
} from "./config";
import { createSnowflake, drawSnowflake, moveSnowflake } from "./snowflake";
import {
+
accumulateSnow,
checkCollision,
createSnowAccumulator,
-
drawSnowAccumulator,
+
drawSnowAccumulators,
} from "./snow_accumulator";
/**
···
const snowFlakes = [];
/**
-
* @type SnowAccumulator
+
* @type SnowAccumulator[]
*/
-
const floor = createSnowAccumulator(CANVAS_HEIGHT);
+
const floor = [];
+
const sectionSize = CANVAS_WIDTH / FLOOR_SICE_COUNT;
/**
* @type {HTMLCanvasElement}
*/
···
let lastSpawn = 0;
let lastFrame = 0;
-
let fps = 0;
+
+
let isPlowing = false;
window.onload = init;
···
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
+
for (let i = 0; i < FLOOR_SICE_COUNT; i++) {
+
floor.push(
+
createSnowAccumulator(i * sectionSize, CANVAS_HEIGHT, sectionSize),
+
);
+
}
+
window.requestAnimationFrame(gameLoop);
}
···
*/
function gameLoop(time) {
const delta = time - lastFrame;
-
fps = Math.round(1 / (delta / 1000));
ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
if (time - lastSpawn > 500 && snowFlakes.length < MAX_PARTICAL_COUNT) {
···
snowFlakes.forEach((flake, idx) => {
moveSnowflake(flake, delta);
-
if (checkCollision(floor, flake)) {
+
const floorIdx = Math.floor(flake.x / sectionSize);
+
+
if (checkCollision(floor[floorIdx], flake)) {
+
if (!isPlowing) {
+
accumulateSnow(floor[floorIdx], flake);
+
}
snowFlakes[idx] = createSnowflake();
}
drawSnowflake(flake, ctx);
});
-
drawSnowAccumulator(ctx, floor);
-
ctx.fillText("FPS: " + fps, 10, 30);
+
const avgHeight =
+
floor.reduce((acc, sa) => acc + sa.height, 0) / floor.length;
+
+
if (avgHeight > MAX_FLOOR_HEIGHT) {
+
isPlowing = true;
+
}
+
+
if (isPlowing) {
+
// plow the snow and reset the accumulators
+
floor.forEach((sa) => {
+
sa.height = 0;
+
sa.accumulator = 0;
+
});
+
isPlowing = false;
+
}
+
+
drawSnowAccumulators(ctx, floor);
+
lastFrame = time;
window.requestAnimationFrame(gameLoop);
}
+7
src/plow.js
···
+
export function spawnPlow(direction) {
+
return {
+
x: 0,
+
y: 0,
+
speed: 0.1,
+
};
+
}
+32 -10
src/snow_accumulator.js
···
/**
* @param {number} y
+
* @param {number} x
+
* @param {number} width
* @returns {SnowAccumulator}
*/
-
export function createSnowAccumulator(y) {
+
export function createSnowAccumulator(x, y, width, height = 0) {
return {
accumulator: 0,
y,
+
x,
+
width,
+
height,
};
}
···
* @param {number} item.y
*/
export function checkCollision(sa, { y }) {
-
if (y >= sa.y) {
-
sa.accumulator++;
-
-
if (sa.accumulator % FLOOR_RAISE_THRESHOLD == 0) {
-
sa.y -= 1;
-
}
+
if (y >= sa.y - sa.height) {
return true;
}
+
return false;
}
/**
-
* @param {CanvasRenderingContext2D} ctx
* @param {SnowAccumulator} sa
+
* @param {Object} item
+
* @param {number} item.size
*/
-
export function drawSnowAccumulator(ctx, sa) {
+
export function accumulateSnow(sa, { size }) {
+
sa.accumulator += size;
+
+
if (sa.accumulator % FLOOR_RAISE_THRESHOLD == 0) {
+
sa.height += 1;
+
}
+
}
+
+
/**
+
* @param {CanvasRenderingContext2D} ctx
+
* @param {SnowAccumulator[]} accumulators
+
*/
+
export function drawSnowAccumulators(ctx, accumulators) {
+
const points = accumulators.map((sa) => [sa.x, sa.y - sa.height]);
+
const avgHeight =
+
accumulators.reduce((acc, sa) => acc + sa.height, 0) / accumulators.length;
+
const noramlized = points.map(([x, y]) => [x, y - avgHeight]);
+
ctx.beginPath();
-
ctx.rect(0, sa.y, CANVAS_WIDTH, CANVAS_HEIGHT - sa.y);
+
noramlized.forEach(([x, y]) => ctx.lineTo(x, y));
+
ctx.lineTo(CANVAS_WIDTH, noramlized[noramlized.length - 1][1]);
+
ctx.lineTo(CANVAS_WIDTH, CANVAS_HEIGHT);
+
ctx.lineTo(0, CANVAS_HEIGHT);
ctx.fillStyle = "#fff";
ctx.fill();
}
+7
src/types.d.ts
···
type SnowAccumulator = {
accumulator: number;
y: number;
+
height: number;
+
x: number;
+
width: number;
+
};
+
+
type Plow = {
+
left: boolean;
};
}