diff options
Diffstat (limited to 'src/engine/components')
-rw-r--r-- | src/engine/components/BoundingBox.ts | 122 | ||||
-rw-r--r-- | src/engine/components/ComponentNames.ts | 3 | ||||
-rw-r--r-- | src/engine/components/FacingDirection.ts | 12 | ||||
-rw-r--r-- | src/engine/components/GridPosition.ts | 13 | ||||
-rw-r--r-- | src/engine/components/Sprite.ts | 96 | ||||
-rw-r--r-- | src/engine/components/index.ts | 4 |
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"; |