Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/lib/canvas/Animator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export abstract class Animator {
ctx: CanvasRenderingContext2D;

constructor(context: CanvasRenderingContext2D) {
this.ctx = context;
}

abstract animate(): void;
}
97 changes: 97 additions & 0 deletions src/lib/canvas/weather/animate/Bluebody.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Animator } from "$lib/canvas/Animator";

export class Bluebody extends Animator {
radialGradient: CanvasGradient;
orbitPosX: number;
orbitPosY: number;
orbitCenX: number;
orbitCenY: number;
orbitRadius: number;
orbitBodyRadius: number;
canvasWidth: number;
canvasHeight: number;

constructor(
context: CanvasRenderingContext2D,
time: Date,
orbitCenX: number,
orbitCenY: number,
orbitRadius: number,
orbitBodyRadius: number,
canvasWidth: number,
canvasHeight: number,
) {
super(context);
this.canvasHeight = canvasHeight;
this.canvasWidth = canvasWidth;
// ctx.beginPath();
// ctx.arc(
// orbitCentreX,
// orbitCentreY,
// orbitRadius,
// Math.PI, Math.PI * 2, false);
// ctx.stroke();
this.orbitPosX = 0;
this.orbitPosY = 0;
this.orbitCenY = orbitCenY;
this.orbitCenX = orbitCenX;

this.orbitRadius = orbitRadius;
this.orbitBodyRadius = orbitBodyRadius;
this.updateTime(time);
this.radialGradient = context.createRadialGradient(0, 0, 0, 0, 0, 0);
this.ctx = context;
// sun or moon

// context.fillRect(orbitPositionX, orbitPositionY, orbitBodyRadius * 2, orbitBodyRadius * 2);
}

updateTime(time: Date) {
const hours = time.getHours();
const minutes = time.getMinutes();

const decimal = hours + (minutes / 60);
const moduloTime = decimal + (6 % 24);
const orbitAngle = Math.PI - ((((2 * Math.PI) / 24) * moduloTime) % Math.PI) - Math.PI / 2;


const orbitLenX = Math.sin(orbitAngle) * this.orbitRadius;
const orbitLenY = Math.cos(orbitAngle) * this.orbitRadius;
this.orbitPosX = this.orbitCenX - orbitLenX;
this.orbitPosY = this.orbitCenY - orbitLenY;

this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)

if (hours >= 6 && hours < 18) {
this.radialGradient = this.getSunRadialGradient(this.ctx, this.orbitPosX, this.orbitPosY, this.orbitBodyRadius);
} else {
this.radialGradient = this.getMoonRadialGradient(this.ctx, this.orbitPosX, this.orbitPosY, this.orbitBodyRadius);
}
this.draw();

}

animate = () => {
this.draw();
};

draw = () => {
this.ctx.fillStyle = this.radialGradient;
this.ctx.arc(this.orbitPosX, this.orbitPosY, this.orbitBodyRadius, 0, 2 * Math.PI, false);
this.ctx.fill();
};

getSunRadialGradient(context: CanvasRenderingContext2D, x: number, y: number, r: number) {
const radialGradient = context.createRadialGradient(x, y, r * 0.8, x, y, r);
radialGradient.addColorStop(0, "rgba(255, 240, 210, 1)");
radialGradient.addColorStop(1, "rgba(255, 255, 0, 0)");
return radialGradient;
}

getMoonRadialGradient(context: CanvasRenderingContext2D, x: number, y: number, r: number) {
const radialGradient = context.createRadialGradient(x, y, r * 0.5, x, y, r * 0.8);
radialGradient.addColorStop(0, "rgba(255, 255, 255, 1)");
radialGradient.addColorStop(1, "rgba(50, 50, 50, 0)");
return radialGradient;
}
}
95 changes: 95 additions & 0 deletions src/lib/canvas/weather/animate/Cloud.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Animator } from "$lib/canvas/Animator";
import { Vector2D } from "$lib/mechanics/vector";
import { randBetween } from "$lib/utils/maths";

const TWO_PI = Math.PI * 2;
const ORB_SIZE_VARIANCE = [0.8, 1.2];
const ORB_X_SPREAD_FACTOR = [-2, 2];
const ORB_Y_SPREAD_FACTOR = [-0.5, 1];

export class Cloud extends Animator {
position: Vector2D;
speed: number;
orbs: Orb[] = [];
width: number;
radius: number;
size: number;
color: string;

constructor(context: CanvasRenderingContext2D, x: number, y: number, radius: number, size: number, speed: number, color: string) {
super(context);
this.position = new Vector2D(x, y);
this.speed = speed;
this.size = size;
this.ctx = context;
this.width = 0;
this.color = color;
this.radius = radius;
}

animate = () => {
this.position.x += this.speed;
// regenerate once cloud has left screen
if (this.position.x > this.ctx.canvas.width + this.width) {
this.generate();
this.position.x = -this.width;
// vary height, keep within top half of canvas
this.position.y += randBetween(-this.width, this.width);
this.position.y = Math.max(0, this.position.y);
this.position.y = Math.min(this.ctx.canvas.height / 2, this.position.y);
}
this.draw();
};

draw = () => {
this.orbs.forEach((orb) => orb.draw(this.ctx, this.position.x, this.position.y));
};

generate = () => {
let smallestX = 0;
let largestX = 0;

this.orbs.splice(0, this.orbs.length);

for (let i = 0; i < this.size; i++) {
const orbRadius = randBetween(this.radius * ORB_SIZE_VARIANCE[0], this.radius * ORB_SIZE_VARIANCE[1]);
const orbX = randBetween(ORB_X_SPREAD_FACTOR[0] * this.radius, ORB_X_SPREAD_FACTOR[1] * this.radius);
const orbY = randBetween(ORB_Y_SPREAD_FACTOR[0] * this.radius, ORB_Y_SPREAD_FACTOR[1] * this.radius);
if (orbX < smallestX) {
smallestX = orbX;
}
if (orbX > largestX) {
largestX = orbX;
}
this.orbs.push(new Orb(orbX, orbY, orbRadius, this.color));
}

this.width = largestX - smallestX;
};
}

class Orb {
pos: Vector2D;
color: string;
radius: number;

constructor(x: number, y: number, radius: number, color: string) {
this.color = color;
this.radius = radius;
this.pos = new Vector2D(x, y);
}

draw = (ctx: CanvasRenderingContext2D, x: number, y: number) => {
const absX = x + this.pos.x;
const absY = y + this.pos.y;

const radialGradient = ctx.createRadialGradient(absX, absY, 0, absX, absY, this.radius);
radialGradient.addColorStop(0, this.color);
radialGradient.addColorStop(1, "rgba(255, 255, 255, 0)");
ctx.beginPath();
ctx.fillStyle = radialGradient;
ctx.arc(absX, absY, this.radius, 0, TWO_PI, true);
ctx.fill();
ctx.closePath();
};
}
58 changes: 58 additions & 0 deletions src/lib/canvas/weather/animate/Precipitater.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Animator } from "$lib/canvas/Animator";
import { Vector2D } from "$lib/mechanics/vector";

export abstract class Precipitator extends Animator {
canvasWidth: number;
canvasHeight: number;
drops: Drop[] = [];
dropSpeed: number;
windSpeed: number;
size: number;

constructor(context: CanvasRenderingContext2D, speed: number, density: number, windSpeed: number, size: number) {
super(context);
this.canvasWidth = context.canvas.width;
this.canvasHeight = context.canvas.height;
this.dropSpeed = speed;
this.size = size;
this.windSpeed = windSpeed;
const dropSpacing = this.size / density;
const columns = Math.floor(this.canvasWidth / (this.size + dropSpacing));

for (let i = 0; i < columns; i++) {
this.drops.push(this.generateDrop(i * (dropSpacing + this.size), Math.random() * this.canvasHeight));
}
}

abstract generateDrop(x: number, y: number): Drop;

animate = () => {
this.drops.forEach((drop) => {
drop.position.y += this.dropSpeed;
drop.position.x += this.windSpeed / 10;

if (drop.position.y > this.canvasHeight) {
// vary starting position for variety of y position
drop.position.y = 0 - Math.random() * this.canvasHeight;
// shift drop position left or right up to drop width
drop.position.x = drop.position.x + (Math.random() * 2 - 1.0) * 3 * this.size;
}
if (drop.position.x > this.canvasWidth) {
drop.position.x = 0;
}
drop.draw(this.ctx);
});
};
}

export abstract class Drop {
position: Vector2D;
size: number;

constructor(size: number, initPos: Vector2D) {
this.position = initPos;
this.size = size;
}

abstract draw(context: CanvasRenderingContext2D): void;
}
37 changes: 37 additions & 0 deletions src/lib/canvas/weather/animate/Rain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Vector2D } from "$lib/mechanics/vector";
import { Drop, Precipitator } from "./Precipitater";

const RAIN_COLOR = "rgb(90, 140, 210)";
const DROP_SIZE = 10;

export class Rain extends Precipitator {
constructor(context: CanvasRenderingContext2D, speed: number, density: number, windSpeed: number, size: number = DROP_SIZE) {
super(context, speed, density, windSpeed, size);
}

generateDrop(x: number, y: number): Drop {
return new RainDrop(this.size, new Vector2D(x, y));
}
}

class RainDrop extends Drop {
height: number;

constructor(size: number, initPos: Vector2D) {
super(size, initPos);
this.height = size * 2.5;
}

draw(context: CanvasRenderingContext2D) {
context.beginPath();
context.fillStyle = RAIN_COLOR;
context.moveTo(this.position.x, this.position.y);
context.lineTo(this.position.x - this.size, this.position.y + this.height);
context.lineTo(this.position.x + this.size, this.position.y + this.height);
context.lineTo(this.position.x, this.position.y);
context.fill();
context.arc(this.position.x, this.position.y + this.height, this.size, 1.9 * Math.PI, 1.1 * Math.PI);
context.fill();
context.closePath();
}
}
71 changes: 71 additions & 0 deletions src/lib/canvas/weather/animate/Snow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Vector2D } from "$lib/mechanics/vector";
import { Drop, Precipitator } from "./Precipitater";

const SNOW_COLOR = "rgb(180, 190, 255)";
// const FLAKE_SIZE = 40;
// const FLAKE_BRANCH_LENGTH = FLAKE_SIZE / 2;

export class Snow extends Precipitator {
constructor(context: CanvasRenderingContext2D, speed: number, density: number, windSpeed: number, size: number) {
super(context, speed, density, windSpeed, size);
}

generateDrop(x: number, y: number): Drop {
return new SnowFlake(this.size, new Vector2D(x, y));
}

draw = () => {
this.drops.forEach((drop) => drop.draw(this.ctx));
};
}

class SnowFlake extends Drop {
constructor(size: number, initPos: Vector2D) {
super(size, initPos);
}

draw(context: CanvasRenderingContext2D) {
context.beginPath();
context.strokeStyle = SNOW_COLOR;
context.lineCap = "round";
context.lineWidth = this.size / 20;

this.drawBranch(context, this.size / 2, Math.PI / 6);
this.drawBranch(context, this.size / 2, Math.PI / 2);
this.drawBranch(context, this.size / 2, (5 * Math.PI) / 6);
this.drawBranch(context, this.size / 2, (7 * Math.PI) / 6);
this.drawBranch(context, this.size / 2, (3 * Math.PI) / 2);
this.drawBranch(context, this.size / 2, (11 * Math.PI) / 6);

context.stroke();
context.closePath();
}

drawBranch = (context: CanvasRenderingContext2D, radius: number, angle: number) => {
const offsetX = radius * Math.cos(angle);
const offsetY = radius * Math.sin(angle);

// draw main branch
context.moveTo(this.position.x, this.position.y);
context.lineTo(this.position.x - offsetX, this.position.y - offsetY);

const r2 = radius * 0.5; // length from origin to start of sub branch
const r3 = radius * 1.1; // length from
const subBranchAngle = Math.PI / 6;
const r4 = (r2 + r3) * Math.tan(subBranchAngle);

const x2 = this.position.x - r2 * Math.cos(angle);
const y2 = this.position.y - r2 * Math.sin(angle);

const x3 = this.position.x - r4 * Math.cos(subBranchAngle + angle);
const y3 = this.position.y - r4 * Math.sin(subBranchAngle + angle);

const x4 = this.position.x - r4 * Math.cos(angle - subBranchAngle);
const y4 = this.position.y - r4 * Math.sin(angle - subBranchAngle);

context.moveTo(x2, y2);
context.lineTo(x3, y3);
context.moveTo(x2, y2);
context.lineTo(x4, y4);
};
}
Loading
Loading