This commit is contained in:
kolaflash
2025-11-25 19:32:27 +01:00
commit 104f0998e1
36 changed files with 1274 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
import type { Vector } from "./Vector.ts";
export type Node<T> = T;
export type Edge<T> = [Node<T>, Node<T>];
export type Graph<T> = Map<Node<T>, Array<Node<T>>>;
type PositionPartial = {
position: Vector;
};
export function drawNode(
node: Node<PositionPartial>,
ctx: CanvasRenderingContext2D,
color: string,
): void {
ctx.lineWidth = 2;
ctx.strokeStyle = color;
ctx.beginPath();
ctx.arc(node.position.x, node.position.y, 8, 0, 2 * Math.PI);
ctx.stroke();
}
export function getNodeByPosition(
nodes: Array<Node<PositionPartial>>,
position: Vector,
): Node<PositionPartial> | null {
for (const node of nodes) {
if (
node.position.x <= position.x + 16 &&
node.position.x >= position.x - 16 &&
node.position.y <= position.y + 16 &&
node.position.y >= position.y - 16
) {
return node;
}
}
return null;
}
export function drawEdge(
edge: Edge<PositionPartial>,
ctx: CanvasRenderingContext2D,
color: string,
): void {
ctx.lineWidth = 2;
ctx.strokeStyle = color;
ctx.beginPath();
ctx.moveTo(edge[0].position.x, edge[0].position.y);
ctx.lineTo(edge[1].position.x, edge[1].position.y);
ctx.stroke();
}
export function createGraph<T>(
nodes: Array<Node<T>>,
edges: Array<Edge<T>>,
): Graph<T> {
const graph: Graph<T> = new Map();
for (const node of nodes) {
const neighbours = edges.reduce(
(acc, edge) => {
if (edge[0] === node) {
acc.push(edge[1]);
return acc;
}
if (edge[1] === node) {
acc.push(edge[0]);
return acc;
}
return acc;
},
[] as Array<Node<T>>,
);
if (neighbours.length > 0) {
graph.set(node, neighbours);
}
}
return graph;
}
export function originDFS<T>(origin: Node<T>, graph: Graph<T>): Array<Node<T>> {
const visited: Array<Node<T>> = [origin];
if (!graph.has(origin)) {
return visited;
}
innerDFS(origin, graph, visited);
return visited;
}
function innerDFS<T>(
node: Node<T>,
graph: Graph<T>,
visited: Array<Node<T>>,
): void {
const neighbours = graph.get(node)!;
for (const node of neighbours) {
if (visited.includes(node)) {
continue;
}
visited.push(node);
innerDFS(node, graph, visited);
}
}
export function getNeighbours<T>(
graph: Graph<T>,
node: Node<T>,
): Array<Node<T>> {
return graph.has(node) ? graph.get(node)! : [];
}
+72
View File
@@ -0,0 +1,72 @@
import { lerp } from "./lerp.ts";
import type { Vector } from "./Vector.ts";
export type Segment = [Vector, Vector];
export type SegmentIntersection = {
position: Vector;
offset: number;
};
export function createSegmentIntersection(): SegmentIntersection {
return {
position: {
x: Number.NaN,
y: Number.NaN,
},
offset: Number.NaN,
};
}
export function setSegmentIntersection(
segmentIntersection: SegmentIntersection,
a: Segment,
b: Segment,
): void {
const bottom =
(b[1].y - b[0].y) * (a[1].x - a[0].x) -
(b[1].x - b[0].x) * (a[1].y - a[0].y);
if (bottom === 0) {
segmentIntersection.position.x = Number.NaN;
segmentIntersection.position.y = Number.NaN;
segmentIntersection.offset = Number.NaN;
return;
}
const tTop =
(b[1].x - b[0].x) * (a[0].y - b[0].y) -
(b[1].y - b[0].y) * (a[0].x - b[0].x);
const uTop =
(b[0].y - a[0].y) * (a[0].x - a[1].x) -
(b[0].x - a[0].x) * (a[0].y - a[1].y);
const t = tTop / bottom;
const u = uTop / bottom;
if (t < 0 || t > 1 || u < 0 || u > 1) {
segmentIntersection.position.x = Number.NaN;
segmentIntersection.position.y = Number.NaN;
segmentIntersection.offset = Number.NaN;
return;
}
segmentIntersection.position.x = lerp(a[0].x, a[1].x, t);
segmentIntersection.position.y = lerp(a[0].y, a[1].y, t);
segmentIntersection.offset = t;
}
export function drawSegment(
segment: Segment,
ctx: CanvasRenderingContext2D,
color: string,
): void {
ctx.lineWidth = 2;
ctx.strokeStyle = color;
ctx.beginPath();
ctx.moveTo(segment[0].x, segment[0].y);
ctx.lineTo(segment[1].x, segment[1].y);
ctx.stroke();
}
+46
View File
@@ -0,0 +1,46 @@
export type Sprite = {
image: HTMLImageElement;
xFrames: number;
yFrames: number;
frameWidth: number;
frameHeight: number;
x: number;
y: number;
};
export function createSprite(
image: HTMLImageElement,
xFrames: number,
yFrames: number,
): Sprite {
return {
image,
xFrames,
yFrames,
frameWidth: image.width / xFrames,
frameHeight: image.height / yFrames,
x: 0,
y: 0,
};
}
export function drawSprite(
ctx: CanvasRenderingContext2D,
sprite: Sprite,
x: number,
y: number,
width: number,
height: number,
): void {
ctx.drawImage(
sprite.image,
sprite.x,
sprite.y,
sprite.frameWidth,
sprite.frameHeight,
x,
y,
width,
height,
);
}
+80
View File
@@ -0,0 +1,80 @@
export type Vector = {
x: number;
y: number;
};
export function createVector(x: number = 0, y: number = 0): Vector {
return { x, y };
}
export function fromPolar(direction: number, magnitude: number): Vector {
return {
x: Math.cos(direction) * magnitude,
y: Math.sin(direction) * magnitude,
};
}
export function getDirection(vector: Vector): number {
return Math.atan2(vector.y, vector.x);
}
export function getMagnitude(vector: Vector): number {
return Math.sqrt(vector.x * vector.x + vector.y * vector.y);
}
export function getDistance(a: Vector, b: Vector): number {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.sqrt(dx * dx + dy * dy);
}
export function getSquared(vector: Vector): number {
return vector.x * vector.x + vector.y * vector.y;
}
export function setDistanceNormal(normal: Vector, a: Vector, b: Vector): void {
const dx = a.x - b.x;
const dy = a.y - b.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
normal.x = dx / distance;
normal.y = dy / distance;
}
}
export function getDotProduct(a: Vector, b: Vector): number {
return a.x * b.x + a.y * b.y;
}
export function getPerpDotProduct(a: Vector, b: Vector): number {
return a.x * b.x - a.y * b.y;
}
export function scale(vector: Vector, scalar: number): void {
vector.x *= scalar;
vector.y *= scalar;
}
export function normalize(vector: Vector): void {
const magnitude = Math.sqrt(vector.x * vector.x + vector.y * vector.y);
if (magnitude > 0) {
const scalar = 1 / magnitude;
vector.x *= scalar;
vector.y *= scalar;
}
}
export function isZero(vector: Vector): boolean {
return vector.x === 0 && vector.y === 0;
}
export function isBelowThreshold(vector: Vector, threshold: number): boolean {
return Math.abs(vector.x) < threshold && Math.abs(vector.y) < threshold;
}
export function equals(a: Vector, b: Vector): boolean {
return a.x === b.x && a.y === b.y;
}
+7
View File
@@ -0,0 +1,7 @@
export function lerp(a: number, b: number, t: number): number {
return a + (b - a) * t;
}
export function lerpPrecice(a: number, b: number, t: number): number {
return a * (1 - t) + b * t;
}
+12
View File
@@ -0,0 +1,12 @@
export function loadImage(src: string): Promise<HTMLImageElement> {
return new Promise((resolve) => {
const image = new Image();
image.onload = () => resolve(image);
image.onerror = () => {
console.error(image.src);
return resolve(image);
};
image.src = src;
});
}