182 lines
4.7 KiB
TypeScript
182 lines
4.7 KiB
TypeScript
import { Position, Size, Velocity } from './types';
|
|
import { between } from './utils';
|
|
|
|
export abstract class Element {
|
|
instance: HTMLElement;
|
|
elements: Element[] = [];
|
|
style: CSSStyleDeclaration;
|
|
isDestroyed = false;
|
|
|
|
protected constructor(public size: Size, public pos: Position, public vel: Velocity = Velocity.create()) {
|
|
this.instance = document.createElement('div');
|
|
this.style = this.instance.style;
|
|
this.style.transition = 'transform linear 20ms';
|
|
this.translate = this.translate.bind(this);
|
|
this.bounce = this.bounce.bind(this);
|
|
this.update = this.update.bind(this);
|
|
this.reset = this.reset.bind(this);
|
|
this.destroy = this.destroy.bind(this);
|
|
this.intersects = this.intersects.bind(this);
|
|
}
|
|
|
|
destroy(): this {
|
|
this.isDestroyed = true;
|
|
return this;
|
|
}
|
|
|
|
get top(): number {
|
|
return this.pos.y;
|
|
}
|
|
get right(): number {
|
|
return this.pos.x + this.size.width;
|
|
}
|
|
get bottom(): number {
|
|
return this.pos.y + this.size.height;
|
|
}
|
|
get left(): number {
|
|
return this.pos.x;
|
|
}
|
|
|
|
appendElement(element: Element): this {
|
|
this.elements.push(element);
|
|
this.instance.appendChild(element.instance);
|
|
return this;
|
|
}
|
|
removeElement(element: Element): this {
|
|
this.elements.splice(this.elements.indexOf(element), 1);
|
|
this.instance.removeChild(element.instance);
|
|
return this;
|
|
}
|
|
removeAllElements(): this {
|
|
while (this.instance.childNodes.length) {
|
|
this.instance.removeChild(this.instance.childNodes[0]);
|
|
}
|
|
this.elements = [];
|
|
return this;
|
|
}
|
|
|
|
move(pos: Position): this {
|
|
this.pos = pos;
|
|
return this;
|
|
}
|
|
|
|
translate(): this {
|
|
this.style.transform = `translate(${this.pos.x}px, ${this.pos.y}px)`;
|
|
return this;
|
|
}
|
|
|
|
isInBoundsX(x: number, w: number): boolean {
|
|
return x >= 0 && x + w <= this.size.width;
|
|
}
|
|
|
|
isInBoundsY(y: number, h: number): boolean {
|
|
return y >= 0 && y + h <= this.size.height;
|
|
}
|
|
|
|
isInBounds(pos: Position, size: Size): boolean {
|
|
return this.isInBoundsX(pos.x, size.width) && this.isInBoundsY(pos.y, size.height);
|
|
}
|
|
|
|
intersectsTop(element: Element): boolean {
|
|
let yIntersects = false;
|
|
let xIntersects = false;
|
|
|
|
if (between(element.bottom, this.top, this.top + Math.round(this.size.height / 2))) {
|
|
yIntersects = true;
|
|
}
|
|
|
|
if (between(element.left, this.left, this.right) || between(element.right, this.left, this.right)) {
|
|
xIntersects = true;
|
|
}
|
|
|
|
return yIntersects && xIntersects;
|
|
}
|
|
|
|
intersectsBottom(element: Element): boolean {
|
|
let yIntersects = false;
|
|
let xIntersects = false;
|
|
|
|
if (between(element.top, this.bottom, this.bottom - Math.round(this.size.height / 2))) {
|
|
yIntersects = true;
|
|
}
|
|
|
|
if (between(element.left, this.left, this.right) || between(element.right, this.left, this.right)) {
|
|
xIntersects = true;
|
|
}
|
|
|
|
return yIntersects && xIntersects;
|
|
}
|
|
|
|
intersectsLeft(element: Element): boolean {
|
|
let yIntersects = false;
|
|
let xIntersects = false;
|
|
|
|
if (between(element.right, this.left, this.left + Math.round(this.size.width / 2))) {
|
|
xIntersects = true;
|
|
}
|
|
|
|
if (between(element.top, this.top, this.bottom) || between(element.bottom, this.top, this.bottom)) {
|
|
yIntersects = true;
|
|
}
|
|
|
|
return yIntersects && xIntersects;
|
|
}
|
|
|
|
intersectsRight(element: Element): boolean {
|
|
let yIntersects = false;
|
|
let xIntersects = false;
|
|
|
|
if (between(element.left, this.right - Math.round(this.size.width / 2), this.right)) {
|
|
xIntersects = true;
|
|
}
|
|
|
|
if (between(element.top, this.top, this.bottom) || between(element.bottom, this.top, this.bottom)) {
|
|
yIntersects = true;
|
|
}
|
|
|
|
return yIntersects && xIntersects;
|
|
}
|
|
|
|
intersects(element: Element): boolean {
|
|
return (
|
|
this.intersectsTop(element) ||
|
|
this.intersectsRight(element) ||
|
|
this.intersectsLeft(element) ||
|
|
this.intersectsBottom(element)
|
|
);
|
|
}
|
|
|
|
bounce(element: Element): boolean {
|
|
if (this.intersectsTop(element)) {
|
|
element.vel.y = Math.abs(element.vel.y) * -1;
|
|
return true;
|
|
} else if (this.intersectsBottom(element)) {
|
|
element.vel.y = Math.abs(element.vel.y);
|
|
return true;
|
|
} else if (this.intersectsLeft(element)) {
|
|
element.vel.x = Math.abs(element.vel.x) * -1;
|
|
return true;
|
|
} else if (this.intersectsRight(element)) {
|
|
element.vel.x = Math.abs(element.vel.x);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
update(): this {
|
|
this.elements.forEach(element => {
|
|
if (element.isDestroyed) {
|
|
this.removeElement(element);
|
|
} else {
|
|
element.update();
|
|
}
|
|
});
|
|
return this;
|
|
}
|
|
|
|
reset(): this {
|
|
this.elements.forEach(element => element.reset());
|
|
return this;
|
|
}
|
|
}
|