Try initializing and move trains in Web Workers

Unfortunately, all of the train data has to be copied from the train web worker
to the main thread on every tick so it knows the new positions of the trains to
render. So every few ticks, the GC causes a little lag as it collects ~90+MB of
obsolete train arrays. There's no way for the two threads to share the same data
thanks to Spectre.
This commit is contained in:
Tyler Hallada 2018-04-22 18:40:20 -04:00
parent ad9167528a
commit 4d1ef84568
11 changed files with 340 additions and 226 deletions

10
package-lock.json generated
View File

@ -12769,6 +12769,16 @@
"errno": "0.1.7"
}
},
"worker-loader": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-1.1.1.tgz",
"integrity": "sha512-qJZLVS/jMCBITDzPo/RuweYSIG8VJP5P67mP/71alGyTZRe1LYJFdwLjLalY3T5ifx0bMDRD3OB6P2p1escvlg==",
"dev": true,
"requires": {
"loader-utils": "1.1.0",
"schema-utils": "0.4.5"
}
},
"wrap-ansi": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",

View File

@ -41,7 +41,8 @@
"uglifyjs-webpack-plugin": "^1.2.4",
"webpack": "^4.4.1",
"webpack-cli": "^2.0.13",
"webpack-dev-server": "^3.1.1"
"webpack-dev-server": "^3.1.1",
"worker-loader": "^1.1.1"
},
"dependencies": {
"@types/pixi.js": "^4.7.2",

View File

@ -5,17 +5,13 @@ import LineConnection from './LineConnection';
import Station from './Station';
import { distance, randomInt, randomPoint } from './utils';
const CONNECTION_RADIUS = Math.floor(Math.sqrt(
Math.pow(window.innerWidth, 2) + Math.pow(window.innerHeight, 2),
) / 8);
export default class Line {
public name: string;
public color: tinycolorInstance;
public color: ColorFormats.RGBA;
constructor(
name: string,
color: tinycolorInstance,
color: ColorFormats.RGBA,
) {
this.name = name;
this.color = color;
@ -26,13 +22,14 @@ export default class Line {
stations: Station[],
visitedStations: Station[],
connectionLimit: number,
connectionRadius: number,
) {
visitedStations.push(currentStation);
const otherStations = stations.filter(station => station !== currentStation);
const closeStations = Station.stationsWithinRadius(
otherStations,
currentStation.location,
CONNECTION_RADIUS,
connectionRadius,
);
for (let i = 0; i < connectionLimit; i += 1) {
if (closeStations.length < 1) {
@ -51,6 +48,7 @@ export default class Line {
stations,
visitedStations,
connectionLimit,
connectionRadius,
);
}
}

View File

@ -1,9 +1,11 @@
import Line from './Line';
import Signal from './Signal';
import Station from './Station';
export default class LineConnection {
public station: Station;
public line: Line;
public signals: Signal[];
public station: Station;
constructor(
station: Station,

View File

@ -1,3 +1,4 @@
import Point from 'pixi.js/lib/core/math/Point';
import * as tinycolor from 'tinycolor2';
import Direction, { getPointDirection } from './Direction';
@ -18,23 +19,23 @@ export default class Station {
return largest;
}
public static stationsWithinRadius(stations: Station[], point: PIXI.Point,
public static stationsWithinRadius(stations: Station[], point: Point,
radius: number): Station[] {
return stations.filter(station => distance(point, station.location) <= radius);
}
public static stationsInDirection(stations: Station[], point: PIXI.Point,
public static stationsInDirection(stations: Station[], point: Point,
direction: Direction): Station[] {
return stations.filter(station => getPointDirection(point, station.location) === direction);
}
public static closestStation(stations: Station[], point: PIXI.Point): Station {
public static closestStation(stations: Station[], point: Point): Station {
return stations.reduce(
(prev, curr) => distance(point, prev.location) < distance(point, curr.location) ? prev : curr,
);
}
public static randomCloseLargeStation(stations: Station[], point: PIXI.Point,
public static randomCloseLargeStation(stations: Station[], point: Point,
radius: number): Station {
const closeStations = Station.stationsWithinRadius(stations, point,
radius);
@ -42,7 +43,7 @@ export default class Station {
return weightedRandom(closeStations, closeStationWeights);
}
public static isPointDistant(point: PIXI.Point, stations: Station[],
public static isPointDistant(point: Point, stations: Station[],
minDistance: number): boolean {
for (const station of stations) {
if (distance(point, station.location) < minDistance) {
@ -52,10 +53,15 @@ export default class Station {
return true;
}
public static randomDistantPoint(stations: Station[], minDistance: number): PIXI.Point | null {
public static randomDistantPoint(
stations: Station[],
minDistance: number,
height: number,
width: number,
): Point | null {
let tries = 100;
while (tries > 0) {
const point = randomPoint();
const point = randomPoint(height, width);
if (Station.isPointDistant(point, stations, minDistance)) {
return point;
}
@ -64,19 +70,16 @@ export default class Station {
return null;
}
public location: PIXI.Point;
public location: Point;
public population: number;
public connections: LineConnection[];
public id: number;
public label: PIXI.Text;
public color: tinycolorInstance;
private textStyle: object;
public color: ColorFormats.RGBA;
constructor(
location: PIXI.Point,
location: Point,
population: number,
color: tinycolorInstance,
color: ColorFormats.RGBA,
connections?: LineConnection[],
) {
this.location = location;
@ -87,15 +90,5 @@ export default class Station {
// for debugging
stationCount += 1;
this.id = stationCount;
this.textStyle = {
fill: '#FFA500',
fontFamily: 'monospace',
fontSize: '12px',
};
this.renderLabel();
}
public renderLabel() {
this.label = new PIXI.Text(`${this.id}`, this.textStyle);
}
}

60
src/Station.worker.ts Normal file
View File

@ -0,0 +1,60 @@
import * as tinycolor from 'tinycolor2';
import Line from './Line';
import Station from './Station';
import { randomInt } from './utils';
const LINE_CONNECTION_LIMIT = 5;
const ctx: Worker = self as any;
const initStations = (numStations: number, height: number, width: number): Station[] => {
const stations: Station[] = [];
for (let i = 0; i < numStations; i += 1) {
stations.push(new Station(
Station.randomDistantPoint(stations, 30, height, width),
randomInt(300, 2000),
tinycolor.random().toRgb()));
}
return stations;
};
const initLines = (numLines: number, stations: Station[], connectionRadius: number): Line[] => {
const lines = [];
for (let i = 0; i < numLines; i += 1) {
let color = tinycolor.random();
while (color.isDark()) {
color = tinycolor.random();
}
const lineColor = color.toRgb();
const stationsWithoutConnections = stations.filter(station =>
station.connections.length === 0,
);
let centralHub: Station;
if (stationsWithoutConnections.length > 0) {
centralHub = Station.largestStation(stationsWithoutConnections);
} else {
centralHub = stations[randomInt(0, stations.length - 1)];
}
const line = new Line(`line-${i}`, lineColor);
const stationsLeft = stations.slice(0);
line.connectStations(centralHub, stationsLeft, [], LINE_CONNECTION_LIMIT, connectionRadius);
lines.push(line);
}
return lines;
};
ctx.addEventListener('message', (event: MessageEvent) => {
if ('initStations' in event.data) {
const { connectionRadius, height, numLines, numStations, width } = event.data.initStations;
let stations: Station[] = [];
let lines: Line[] = [];
let stationsWithConnections: Station[] = [];
while (stationsWithConnections.length === 0) {
// If all stations are too far away to connect, try generating again
stations = initStations(numStations, height, width);
lines = initLines(numLines, stations, connectionRadius);
stationsWithConnections = stations.filter(station => station.connections.length > 0);
}
ctx.postMessage({ stations, lines });
}
});

View File

@ -1,3 +1,4 @@
import Point from 'pixi.js/lib/core/math/Point';
import * as tinycolor from 'tinycolor2';
import Station from './Station';
@ -5,25 +6,23 @@ import Station from './Station';
let trainCount = 0;
export default class Train {
public location: PIXI.Point;
public location: Point;
public speed: number;
public origin: Station;
public destination: Station;
public passengers: number;
public id: number;
public label: PIXI.Text;
public color: tinycolorInstance;
public sprite: PIXI.Sprite;
public color: ColorFormats.RGBA;
private textStyle: object;
constructor(
location: PIXI.Point,
location: Point,
speed: number,
passengers: number,
origin: Station,
destination: Station,
color: tinycolorInstance,
color: ColorFormats.RGBA,
) {
this.location = location;
this.speed = speed;
@ -32,25 +31,8 @@ export default class Train {
this.passengers = passengers;
this.color = color;
this.sprite = new PIXI.Sprite(PIXI.loader.resources.nodeImg.texture);
// for debugging
trainCount += 1;
this.id = trainCount;
this.textStyle = {
fill: '#AEAEAE',
fontFamily: 'monospace',
fontSize: '12px',
};
this.renderLabel();
}
public boardPassengers() {
if (this.location === this.origin.location) { // about to leave a station
}
}
public renderLabel() {
this.label = new PIXI.Text(`${this.id}`, this.textStyle);
}
}

116
src/Train.worker.ts Normal file
View File

@ -0,0 +1,116 @@
import Point from 'pixi.js/lib/core/math/Point';
import * as tinycolor from 'tinycolor2';
import Station from './Station';
import Train from './Train';
import { distance, pointsAlmostEqual, randomInt, weightedRandom } from './utils';
// TODO: define these in a common constants file
const MAX_SPEED = 10.0;
const ACCELERATION = 0.025;
const APPROACH_DISTANCE = 3.0;
const TRAIN_CAPACITY = 50;
const ctx: Worker = self as any;
let stations: Station[] = [];
let trains: Train[] = [];
const initTrains = (numTrains: number, stations: Station[]): Train[] => {
const trains = [];
const stationsWithConnections = stations.filter(station => station.connections.length > 0);
for (let i = 0; i < numTrains; i += 1) {
const originStation = stationsWithConnections[
Math.floor(Math.random() * stationsWithConnections.length)
];
trains.push(new Train(
new Point(originStation.location.x, originStation.location.y),
0, 0, originStation, undefined, tinycolor('grey').toRgb()),
);
}
return trains;
};
const moveTrains = (trains: Train[], stations: Station[]) => {
for (const train of trains) {
if (train.origin.connections.length === 0) {
// train is stuck at an orphaned station
continue;
}
// choose a destination randomly with a bias towards larger stations
if (train.destination === undefined) {
const otherStations = train.origin.connections.map(conn => conn.station);
const closeStationWeights = otherStations.map(station => station.population);
train.destination = weightedRandom(otherStations, closeStationWeights);
// board passengers
const boardingPassengers = randomInt(0, Math.min(TRAIN_CAPACITY - train.passengers,
train.origin.population));
// set or mix train color with the color of new passenger origin
if (train.passengers === 0) {
train.color = train.origin.color;
} else {
train.color = tinycolor.mix(
train.color,
train.origin.color,
Math.round((boardingPassengers / train.passengers) * 100),
).toRgb();
}
train.passengers += boardingPassengers;
train.origin.population -= boardingPassengers;
}
// train reached destination, stop moving and let passengers off
if (pointsAlmostEqual(train.location, train.destination.location)) {
train.speed = 0;
// average destination color with passenger color weighted by ratio
// (a simulation of culture mixing)
train.destination.color = tinycolor.mix(
train.destination.color,
train.origin.color,
Math.round((train.passengers / train.destination.population) * 100),
).toRgb();
// transfer passengers to destination
const disembarkingPassengers = randomInt(0, train.passengers);
train.destination.population += disembarkingPassengers;
train.passengers -= disembarkingPassengers;
// prepare for next journey
train.origin = train.destination;
train.destination = undefined;
continue;
}
const journeyLeft = distance(train.location, train.destination.location);
if ((train.speed / ACCELERATION) >= ((journeyLeft / train.speed) - APPROACH_DISTANCE) &&
train.speed !== ACCELERATION) {
// slowing down
train.speed -= ACCELERATION;
} else if (train.speed < MAX_SPEED) {
// speeding up
train.speed += ACCELERATION;
}
// advance train
const progress = train.speed / journeyLeft;
train.location.x += ((train.destination.location.x - train.location.x) * progress);
train.location.y += ((train.destination.location.y - train.location.y) * progress);
}
};
ctx.addEventListener('message', (event: MessageEvent) => {
if ('initTrains' in event.data) {
const { numTrains } = event.data.initTrains;
stations = event.data.initTrains.stations;
trains = initTrains(numTrains, stations);
ctx.postMessage({ initTrains: trains });
} else if ('moveTrains' in event.data) {
// trains = event.data.moveTrains.trains;
// stations = event.data.moveTrains.stations;
moveTrains(trains, stations);
ctx.postMessage({ moveTrains: trains });
}
});

View File

@ -6,18 +6,20 @@ import * as tinycolor from 'tinycolor2';
import Direction from './Direction';
import Line from './Line';
import Station from './Station';
import StationWorker from 'worker-loader!./Station.worker';
import Train from './Train';
import { distance, pointsAlmostEqual, pointsEqual, randomInt, randomPoint,
rangeMap, weightedRandom } from './utils';
import TrainWorker from 'worker-loader!./Train.worker';
import * as imgNode from './node.png';
import './style.css';
const NODE_RES = 100;
const MAX_SPEED = 10.0;
const ACCELERATION = 0.025;
const APPROACH_DISTANCE = 3.0;
const CONNECTION_RADIUS = Math.floor(Math.sqrt(
Math.pow(window.innerWidth, 2) + Math.pow(window.innerHeight, 2),
) / 8);
const TRAIN_CAPACITY = 50;
const LINE_CONNECTION_LIMIT = 5;
const WORLD_WIDTH = 1000;
@ -29,161 +31,49 @@ const ZOOM_MAX_HEIGHT = 4000;
const trainTexts: PIXI.Text[] = [];
const initStations = (numStations: number): Station[] => {
const stations: Station[] = [];
for (let i = 0; i < numStations; i += 1) {
stations.push(new Station(
Station.randomDistantPoint(stations, 30),
randomInt(300, 2000),
tinycolor.random()));
}
return stations;
};
const initTrains = (numTrains: number, stations: Station[]): Train[] => {
const trains = [];
const stationsWithConnections = stations.filter(station => station.connections.length > 0);
for (let i = 0; i < numTrains; i += 1) {
const originStation = stationsWithConnections[
Math.floor(Math.random() * stationsWithConnections.length)
];
trains.push(new Train(
new PIXI.Point(originStation.location.x, originStation.location.y),
0, 0, originStation, undefined, tinycolor('grey')),
);
}
return trains;
};
const initLines = (numLines: number, stations: Station[]): Line[] => {
const lines = [];
for (let i = 0; i < numLines; i += 1) {
let color = tinycolor.random();
while (color.isDark()) {
color = tinycolor.random();
}
const stationsWithoutConnections = stations.filter(station =>
station.connections.length === 0,
);
let centralHub: Station;
if (stationsWithoutConnections.length > 0) {
centralHub = Station.largestStation(stationsWithoutConnections);
} else {
centralHub = stations[randomInt(0, stations.length - 1)];
}
const line = new Line(`line-${i}`, tinycolor.random());
const stationsLeft = stations.slice(0);
line.connectStations(centralHub, stationsLeft, [], LINE_CONNECTION_LIMIT);
lines.push(line);
}
return lines;
};
const moveTrains = (trains: Train[], stations: Station[]) => {
for (const train of trains) {
if (train.origin.connections.length === 0) {
// train is stuck at an orphaned station
continue;
}
// choose a destination randomly with a bias towards larger stations
if (train.destination === undefined) {
const otherStations = train.origin.connections.map(conn => conn.station);
const closeStationWeights = otherStations.map(station => station.population);
train.destination = weightedRandom(otherStations, closeStationWeights);
// board passengers
const boardingPassengers = randomInt(0, Math.min(TRAIN_CAPACITY - train.passengers,
train.origin.population));
// set or mix train color with the color of new passenger origin
if (train.passengers === 0) {
train.color = train.origin.color;
} else {
train.color = tinycolor.mix(
train.color,
train.origin.color,
Math.round((boardingPassengers / train.passengers) * 100),
);
}
train.passengers += boardingPassengers;
train.origin.population -= boardingPassengers;
}
// train reached destination, stop moving and let passengers off
if (pointsAlmostEqual(train.location, train.destination.location)) {
train.speed = 0;
// average destination color with passenger color weighted by ratio
// (a simulation of culture mixing)
train.destination.color = tinycolor.mix(
train.destination.color,
train.origin.color,
Math.round((train.passengers / train.destination.population) * 100),
);
// transfer passengers to destination
const disembarkingPassengers = randomInt(0, train.passengers);
train.destination.population += disembarkingPassengers;
train.passengers -= disembarkingPassengers;
// prepare for next journey
train.origin = train.destination;
train.destination = undefined;
continue;
}
const journeyLeft = distance(train.location, train.destination.location);
if ((train.speed / ACCELERATION) >= ((journeyLeft / train.speed) - APPROACH_DISTANCE) &&
train.speed !== ACCELERATION) {
// slowing down
train.speed -= ACCELERATION;
} else if (train.speed < MAX_SPEED) {
// speeding up
train.speed += ACCELERATION;
}
// advance train
const progress = train.speed / journeyLeft;
train.location.x += ((train.destination.location.x - train.location.x) * progress);
train.location.y += ((train.destination.location.y - train.location.y) * progress);
}
};
const drawStations = (stations: Station[], graphics: PIXI.Graphics) => {
for (const station of stations) {
const drawStations = (stations: Station[], stationLabels: PIXI.Text[], graphics: PIXI.Graphics) => {
stations.forEach((station, i) => {
const radius = station.population / 150;
graphics.beginFill(parseInt(station.color.toHex(), 16), 0.5);
const color = tinycolor(station.color);
graphics.beginFill(parseInt(color.toHex(), 16), 0.5);
graphics.drawCircle(station.location.x, station.location.y, radius);
graphics.endFill();
station.label.x = station.location.x + radius + 1;
station.label.y = station.location.y + radius + 1;
}
stationLabels[i].x = station.location.x + radius + 1;
stationLabels[i].y = station.location.y + radius + 1;
});
};
const drawTrains = (trains: Train[], graphics: PIXI.Graphics) => {
for (const train of trains) {
const drawTrains = (
trains: Train[],
trainLabels: PIXI.Text[],
trainSprites: PIXI.Sprite[],
graphics: PIXI.Graphics,
) => {
trains.forEach((train, i) => {
const trainSize = rangeMap(train.passengers, 0, TRAIN_CAPACITY, 1, 5);
const scale = trainSize / NODE_RES;
train.sprite.x = train.location.x;
train.sprite.y = train.location.y;
train.sprite.scale.x = scale;
train.sprite.scale.y = scale;
train.sprite.tint = parseInt(train.color.toHex(), 16);
train.label.x = train.location.x + scale + 1;
train.label.y = train.location.y + scale + 1;
}
const color = tinycolor(train.color);
trainSprites[i].x = train.location.x;
trainSprites[i].y = train.location.y;
trainSprites[i].scale.x = scale;
trainSprites[i].scale.y = scale;
trainSprites[i].tint = parseInt(color.toHex(), 16);
trainLabels[i].x = train.location.x + scale + 1;
trainLabels[i].y = train.location.y + scale + 1;
});
};
const drawLines = (stations: Station[], graphics: PIXI.Graphics) => {
for (const station of stations) {
for (const connection of station.connections) {
const color = tinycolor(connection.line.color);
let twoWay = false;
for (const conn of connection.station.connections) {
if (conn.station === station) {
twoWay = true;
}
}
graphics.lineStyle(twoWay ? 2 : 1, parseInt(connection.line.color.toHex(), 16), 1);
graphics.lineStyle(twoWay ? 2 : 1, parseInt(color.toHex(), 16), 1);
graphics.moveTo(station.location.x, station.location.y);
graphics.lineTo(connection.station.location.x, connection.station.location.y);
}
@ -210,28 +100,85 @@ const run = () => {
let stations: Station[] = [];
let lines: Line[] = [];
let stationsWithConnections: Station[] = [];
while (stationsWithConnections.length === 0) {
// If all stations are too far away to connect, try generating again
stations = initStations(30);
lines = initLines(4, stations);
stationsWithConnections = stations.filter(station => station.connections.length > 0);
let trains: Train[] = [];
const stationLabels: PIXI.Text[] = [];
const trainLabels: PIXI.Text[] = [];
const trainSprites: PIXI.Sprite[] = [];
const stationWorker = new StationWorker();
const trainWorker = new TrainWorker();
stationWorker.postMessage({ initStations: {
connectionRadius: CONNECTION_RADIUS,
height: window.innerHeight,
numLines: 4,
numStations: 30,
width: window.innerWidth,
}});
stationWorker.onmessage = (event: MessageEvent) => {
stations = event.data.stations;
lines = event.data.lines;
console.log(stations);
console.log(lines);
trainWorker.postMessage({ initTrains: {
stations,
numTrains: 50,
}});
trainWorker.onmessage = (trainEvent: MessageEvent) => {
if ('initTrains' in trainEvent.data) {
trains = trainEvent.data.initTrains;
// add train sprites
for (const train of trains) {
const sprite = new PIXI.Sprite(PIXI.loader.resources.nodeImg.texture);
sprite.visible = false;
trainSprites.push(sprite);
viewport.addChild(sprite);
}
const trains = initTrains(50, stations);
// Add debug labels
for (const train of trains) {
const label = new PIXI.Text(
`${train.id}`, {
fill: '#AEAEAE',
fontFamily: 'monospace',
fontSize: '12px',
},
);
trainLabels.push(label);
viewport.addChild(label);
}
} else if ('moveTrains' in trainEvent.data) {
trains = trainEvent.data.moveTrains;
}
};
for (const station of stations) {
const label = new PIXI.Text(
`${station.id}`, {
fill: '#FFA500',
fontFamily: 'monospace',
fontSize: '12px',
},
);
stationLabels.push(label);
viewport.addChild(label);
}
};
ticker.stop();
ticker.add((deltaTime) => {
stats.begin();
moveTrains(trains, stations);
trainWorker.postMessage({ moveTrains: {} });
graphics.clear();
graphics.lineStyle(1, 0xFFA500, 1);
drawStations(stations, graphics);
drawStations(stations, stationLabels, graphics);
graphics.lineStyle(1, 0xAEAEAE, 1);
drawTrains(trains, graphics);
drawTrains(trains, trainLabels, trainSprites, graphics);
drawLines(stations, graphics);
@ -240,17 +187,6 @@ const run = () => {
ticker.start();
viewport.addChild(graphics);
// add train sprites
for (const train of trains) {
viewport.addChild(train.sprite);
}
// Add debug labels
for (const train of trains) {
viewport.addChild(train.label);
}
for (const station of stations) {
viewport.addChild(station.label);
}
document.body.appendChild(app.view);
app.stage.addChild(viewport);
viewport.drag().pinch().wheel().clampZoom({

View File

@ -1,4 +1,4 @@
import * as PIXI from 'pixi.js';
import Point from 'pixi.js/lib/core/math/Point';
const EPSILON = 1.0;
@ -19,20 +19,20 @@ export const weightedRandom = (choices: any[], weights: number[]): any => {
}
};
export const randomPoint = () => (
new PIXI.Point(randomInt(0, window.innerWidth), randomInt(0, window.innerHeight))
export const randomPoint = (height: number, width: number): Point => (
new Point(randomInt(0, width), randomInt(0, height))
);
export const pointsEqual = (pointA: PIXI.Point, pointB: PIXI.Point): boolean => (
export const pointsEqual = (pointA: Point, pointB: Point): boolean => (
(pointA.x === pointB.x && pointA.y === pointB.y)
);
export const pointsAlmostEqual = (pointA: PIXI.Point, pointB: PIXI.Point): boolean => (
export const pointsAlmostEqual = (pointA: Point, pointB: Point): boolean => (
Math.abs(pointA.x - pointB.x) < EPSILON &&
Math.abs(pointA.y - pointB.y) < EPSILON
);
export const distance = (pointA: PIXI.Point, pointB: PIXI.Point): number => {
export const distance = (pointA: Point, pointB: Point): number => {
const distX = pointA.x - pointB.x;
const distY = pointA.y - pointB.y;
return Math.sqrt((distX * distX) + (distY * distY));
@ -43,10 +43,10 @@ export const rangeMap = (num: number, inMin: number, inMax: number,
(num - inMin) * (outMax - outMin) / (inMax - inMin) + outMin
);
export const angleRadians = (pointA: PIXI.Point, pointB: PIXI.Point): number => (
export const angleRadians = (pointA: Point, pointB: Point): number => (
Math.atan2(-(pointB.x - pointA.x), pointB.y - pointA.y)
);
export const angleDegrees = (pointA: PIXI.Point, pointB: PIXI.Point): number => (
export const angleDegrees = (pointA: Point, pointB: Point): number => (
180 + angleRadians(pointA, pointB) * (180 / Math.PI)
);

16
typings/custom.d.ts vendored
View File

@ -1 +1,17 @@
declare module '*.png';
declare module 'worker-loader!*' {
class WebpackWorker extends Worker {
constructor();
}
export default WebpackWorker;
}
declare module 'pixi.js/lib/core/math/Point' {
export default PIXI.Point;
}
declare module 'pixi.js/lib/core/text/Text' {
export default PIXI.Text;
}