summaryrefslogtreecommitdiff
path: root/src/engine/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/components')
-rw-r--r--src/engine/components/BoundingBox.ts122
-rw-r--r--src/engine/components/ComponentNames.ts3
-rw-r--r--src/engine/components/FacingDirection.ts12
-rw-r--r--src/engine/components/GridPosition.ts13
-rw-r--r--src/engine/components/Sprite.ts96
-rw-r--r--src/engine/components/index.ts4
6 files changed, 250 insertions, 0 deletions
diff --git a/src/engine/components/BoundingBox.ts b/src/engine/components/BoundingBox.ts
new file mode 100644
index 0000000..d64041f
--- /dev/null
+++ b/src/engine/components/BoundingBox.ts
@@ -0,0 +1,122 @@
+import { Component, ComponentNames } from ".";
+import type { Coord2D, Dimension2D } from "../interfaces";
+import { dotProduct, rotateVector } from "../utils";
+
+export class BoundingBox extends Component {
+ public center: Coord2D;
+ public dimension: Dimension2D;
+ public rotation: number;
+
+ constructor(center: Coord2D, dimension: Dimension2D, rotation?: number) {
+ super(ComponentNames.BoundingBox);
+
+ this.center = center;
+ this.dimension = dimension;
+ this.rotation = rotation ?? 0;
+ }
+
+ public isCollidingWith(box: BoundingBox): boolean {
+ // optimization; when neither rotates just check if they overlap
+ if (this.rotation == 0 && box.rotation == 0) {
+ const thisTopLeft = this.getTopLeft();
+ const thisBottomRight = this.getBottomRight();
+
+ const thatTopLeft = box.getTopLeft();
+ const thatBottomRight = box.getBottomRight();
+
+ if (
+ thisBottomRight.x <= thatTopLeft.x ||
+ thisTopLeft.x >= thatBottomRight.x ||
+ thisBottomRight.y <= thatTopLeft.y ||
+ thisTopLeft.y >= thatBottomRight.y
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ // https://en.wikipedia.org/wiki/Hyperplane_separation_theorem
+ const boxes = [this.getVertices(), box.getVertices()];
+ for (const poly of boxes) {
+ for (let i = 0; i < poly.length; i++) {
+ const [A, B] = [poly[i], poly[(i + 1) % poly.length]];
+ const normal: Coord2D = { x: B.y - A.y, y: A.x - B.x };
+
+ const [[minThis, maxThis], [minBox, maxBox]] = boxes.map((box) =>
+ box.reduce(
+ ([min, max], vertex) => {
+ const projection = dotProduct(normal, vertex);
+ return [Math.min(min, projection), Math.max(max, projection)];
+ },
+ [Infinity, -Infinity],
+ ),
+ );
+
+ if (maxThis < minBox || maxBox < minThis) return false;
+ }
+ }
+
+ return true;
+ }
+
+ public getVertices(): Coord2D[] {
+ return [
+ { x: -this.dimension.width / 2, y: -this.dimension.height / 2 },
+ { x: -this.dimension.width / 2, y: this.dimension.height / 2 },
+ { x: this.dimension.width / 2, y: this.dimension.height / 2 },
+ { x: this.dimension.width / 2, y: -this.dimension.height / 2 },
+ ]
+ .map((vertex) => rotateVector(vertex, this.rotation)) // rotate
+ .map((vertex) => {
+ // translate
+ return {
+ x: vertex.x + this.center.x,
+ y: vertex.y + this.center.y,
+ };
+ });
+ }
+
+ public getRotationInPiOfUnitCircle(): number {
+ let rads = this.rotation * (Math.PI / 180);
+ if (rads >= Math.PI) {
+ // Physics system guarantees rotation \in [0, 360)
+ rads -= Math.PI;
+ }
+ return rads;
+ }
+
+ public getOutscribedBoxDims(): Dimension2D {
+ let rads = this.getRotationInPiOfUnitCircle();
+ const { width, height } = this.dimension;
+
+ if (rads == 0) return this.dimension;
+
+ if (rads <= Math.PI / 2) {
+ return {
+ width: Math.abs(height * Math.sin(rads) + width * Math.cos(rads)),
+ height: Math.abs(width * Math.sin(rads) + height * Math.cos(rads)),
+ };
+ }
+
+ rads -= Math.PI / 2;
+ return {
+ width: Math.abs(height * Math.cos(rads) + width * Math.sin(rads)),
+ height: Math.abs(width * Math.cos(rads) + height * Math.sin(rads)),
+ };
+ }
+
+ public getTopLeft(): Coord2D {
+ return {
+ x: this.center.x - this.dimension.width / 2,
+ y: this.center.y - this.dimension.height / 2,
+ };
+ }
+
+ public getBottomRight(): Coord2D {
+ return {
+ x: this.center.x + this.dimension.width / 2,
+ y: this.center.y + this.dimension.height / 2,
+ };
+ }
+}
diff --git a/src/engine/components/ComponentNames.ts b/src/engine/components/ComponentNames.ts
index 90dfb90..0f1200a 100644
--- a/src/engine/components/ComponentNames.ts
+++ b/src/engine/components/ComponentNames.ts
@@ -1,3 +1,6 @@
export namespace ComponentNames {
export const Sprite = "Sprite";
+ export const FacingDirection = "FacingDirection";
+ export const GridPosition = "GridPosition";
+ export const BoundingBox = "BoundingBox";
}
diff --git a/src/engine/components/FacingDirection.ts b/src/engine/components/FacingDirection.ts
new file mode 100644
index 0000000..a449d21
--- /dev/null
+++ b/src/engine/components/FacingDirection.ts
@@ -0,0 +1,12 @@
+import { Component, ComponentNames, Sprite } from ".";
+import { type Direction } from "../interfaces";
+
+export class FacingDirection extends Component {
+ public readonly directionSprites: Map<Direction, Sprite>;
+
+ constructor() {
+ super(ComponentNames.FacingDirection);
+
+ this.directionSprites = new Map<Direction, Sprite>();
+ }
+}
diff --git a/src/engine/components/GridPosition.ts b/src/engine/components/GridPosition.ts
new file mode 100644
index 0000000..b5acf3b
--- /dev/null
+++ b/src/engine/components/GridPosition.ts
@@ -0,0 +1,13 @@
+import { Component, ComponentNames } from ".";
+
+export class GridPosition extends Component {
+ public x: number;
+ public y: number;
+
+ constructor(x: number, y: number) {
+ super(ComponentNames.GridPosition);
+
+ this.x = x;
+ this.y = y;
+ }
+}
diff --git a/src/engine/components/Sprite.ts b/src/engine/components/Sprite.ts
new file mode 100644
index 0000000..6a66a5c
--- /dev/null
+++ b/src/engine/components/Sprite.ts
@@ -0,0 +1,96 @@
+import { Component, ComponentNames } from ".";
+import type { Dimension2D, DrawArgs, Coord2D } from "../interfaces";
+
+export class Sprite extends Component {
+ private sheet: HTMLImageElement;
+
+ private spriteImgPos: Coord2D;
+ private spriteImgDimensions: Dimension2D;
+
+ private msPerFrame: number;
+ private msSinceLastFrame: number;
+ private currentFrame: number;
+ private numFrames: number;
+
+ constructor(
+ sheet: HTMLImageElement,
+ spriteImgPos: Coord2D,
+ spriteImgDimensions: Dimension2D,
+ msPerFrame: number,
+ numFrames: number,
+ ) {
+ super(ComponentNames.Sprite);
+
+ this.sheet = sheet;
+ this.spriteImgPos = spriteImgPos;
+ this.spriteImgDimensions = spriteImgDimensions;
+ this.msPerFrame = msPerFrame;
+ this.numFrames = numFrames;
+
+ this.msSinceLastFrame = 0;
+ this.currentFrame = 0;
+ }
+
+ public update(dt: number) {
+ this.msSinceLastFrame += dt;
+ if (this.msSinceLastFrame >= this.msPerFrame) {
+ this.currentFrame = (this.currentFrame + 1) % this.numFrames;
+ this.msSinceLastFrame = 0;
+ }
+ }
+
+ public draw(ctx: CanvasRenderingContext2D, drawArgs: DrawArgs) {
+ const { center, rotation, tint, opacity } = drawArgs;
+
+ ctx.save();
+ ctx.translate(center.x, center.y);
+ if (rotation != undefined && rotation != 0) {
+ ctx.rotate(rotation * (Math.PI / 180));
+ }
+ ctx.translate(-center.x, -center.y);
+
+ if (opacity) {
+ ctx.globalAlpha = opacity;
+ }
+
+ ctx.drawImage(
+ this.sheet,
+ ...this.getSpriteArgs(),
+ ...this.getDrawArgs(drawArgs),
+ );
+
+ if (tint) {
+ ctx.globalAlpha = 0.5;
+ ctx.globalCompositeOperation = "source-atop";
+ ctx.fillStyle = tint;
+ ctx.fillRect(...this.getDrawArgs(drawArgs));
+ }
+
+ ctx.restore();
+ }
+
+ private getSpriteArgs(): [sx: number, sy: number, sw: number, sh: number] {
+ return [
+ this.spriteImgPos.x + this.currentFrame * this.spriteImgDimensions.width,
+ this.spriteImgPos.y,
+ this.spriteImgDimensions.width,
+ this.spriteImgDimensions.height,
+ ];
+ }
+
+ private getDrawArgs({
+ center,
+ dimension,
+ }: DrawArgs): [dx: number, dy: number, dw: number, dh: number] {
+ return [
+ center.x - dimension.width / 2,
+ center.y - dimension.height / 2,
+ dimension.width,
+ dimension.height,
+ ];
+ }
+
+ public getSpriteDimensions() {
+ return this.spriteImgDimensions;
+ }
+}
diff --git a/src/engine/components/index.ts b/src/engine/components/index.ts
index a2fd5d1..30fe50a 100644
--- a/src/engine/components/index.ts
+++ b/src/engine/components/index.ts
@@ -1,2 +1,6 @@
export * from "./Component";
export * from "./ComponentNames";
+export * from "./Sprite";
+export * from "./FacingDirection";
+export * from "./GridPosition";
+export * from "./BoundingBox";