UNPKG

@dominicstop/utils

Version:

Yet another event emitter written in typescript.

193 lines (150 loc) 4.32 kB
import { Cloneable } from "../types/Cloneable"; import { ValueRepresentable } from "../types/ValueRepresentable"; import { Point } from "./Point"; export type AngleUnit = 'radians' | 'degrees'; export type AngleValue = { angleUnit: AngleUnit; angleValue: number; }; export class Angle implements Cloneable<Angle>, ValueRepresentable<AngleValue> { angleUnit: AngleUnit; angleRawValue: number; constructor(args: AngleValue){ this.angleUnit = args.angleUnit; this.angleRawValue = args.angleValue; }; get asValue(): AngleValue { return { angleUnit: this.angleUnit, angleValue: this.angleRawValue, }; }; get radians(): number { switch(this.angleUnit){ case 'degrees': return this.angleRawValue * (Math.PI / 180); case 'radians': return this.angleRawValue }; }; get degrees(): number { switch(this.angleUnit){ case 'degrees': return this.angleRawValue; case 'radians': return this.angleRawValue * (180 / Math.PI); }; }; get normalized(): Angle { const normalizedDegrees = this.degrees % 360; const adj = (() => { if(normalizedDegrees < 0) { return 360; }; if(normalizedDegrees > 360) { return -360; }; return 0; })(); const normalizedDegreesAdj = normalizedDegrees + adj; return new Angle({ angleUnit: 'degrees', angleValue: normalizedDegreesAdj, }); }; get isZero(): boolean { return this.angleRawValue == 0; }; clone(): Angle { return new Angle({ ...this.asValue, }); }; getPointAlongCircle(args: { radius: number; centerPoint: Point; isClockwise: boolean; }): Point { const angleRadians = this.radians; const adjustedAngle = args.isClockwise ? -angleRadians : angleRadians; /// cw: `x = r * cos(angle)`, `y = r * sin(angle)` const x = args.centerPoint.x + args.radius * Math.cos(adjustedAngle); const y = args.centerPoint.y + args.radius * Math.sin(adjustedAngle); return new Point({x, y}); }; computeMidAngle(args: { otherAngle: Angle; isClockwise: boolean; }): Angle { const isClockwise = args.isClockwise ?? true; const angleLeading = this.normalized.degrees; const angleTrailing = args.otherAngle.normalized.degrees; const delta = angleLeading - angleTrailing; const needsAdj = isClockwise ? delta > 0 : delta < 0; // amount to shift ccw direction const adj: number = (() => { if(needsAdj){ return 0; }; return Math.abs(delta) / 2; })(); const angleMidDeg: number = (() => { if (adj == 0){ return (angleLeading + angleTrailing) / 2; }; // adjust by shifting counter clockwise const angleLeadingShifted = new Angle({ angleUnit: 'degrees', angleValue: angleLeading + adj, }); const angleTrailingShifted = new Angle({ angleUnit: 'degrees', angleValue: angleTrailing + adj, }); // normalized to 0...360 let angleLeadingNormalized = angleLeadingShifted.normalized.degrees; let angleTrailingNormalized = angleTrailingShifted.normalized.degrees; let angleMidShifted = (angleLeadingNormalized + angleTrailingNormalized) / 2; // undo shifting return angleMidShifted - adj; })(); return new Angle({ angleUnit: 'degrees', angleValue: angleMidDeg, }); }; addOtherAngle(otherAngle: Angle): Angle{ return Angle.addTwoAngles(this, otherAngle); }; // MARK: - Static Init // ------------------- static initFromDegrees(degreeValue: number): Angle { return new Angle({ angleUnit: 'degrees', angleValue: degreeValue, }); }; static initFromRadians(radianValue: number): Angle { return new Angle({ angleUnit: 'radians', angleValue: radianValue, }); }; static addTwoAngles(angleA: Angle, angleB: Angle): Angle { if(angleA.angleUnit === angleB.angleUnit){ return new Angle({ angleUnit: angleA.angleUnit, angleValue: angleA.angleRawValue + angleB.angleRawValue, }); }; return new Angle({ angleUnit: 'degrees', angleValue: angleA.degrees + angleB.degrees, }); }; };