1 Commits

Author SHA1 Message Date
46aba476ed Hide train sprites initially to avoid flash 2018-04-22 19:29:30 -04:00
11 changed files with 231 additions and 343 deletions

10
package-lock.json generated
View File

@@ -12769,16 +12769,6 @@
"errno": "0.1.7" "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": { "wrap-ansi": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,60 +0,0 @@
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,4 +1,3 @@
import Point from 'pixi.js/lib/core/math/Point';
import * as tinycolor from 'tinycolor2'; import * as tinycolor from 'tinycolor2';
import Station from './Station'; import Station from './Station';
@@ -6,23 +5,25 @@ import Station from './Station';
let trainCount = 0; let trainCount = 0;
export default class Train { export default class Train {
public location: Point; public location: PIXI.Point;
public speed: number; public speed: number;
public origin: Station; public origin: Station;
public destination: Station; public destination: Station;
public passengers: number; public passengers: number;
public id: number; public id: number;
public color: ColorFormats.RGBA; public label: PIXI.Text;
public color: tinycolorInstance;
public sprite: PIXI.Sprite;
private textStyle: object; private textStyle: object;
constructor( constructor(
location: Point, location: PIXI.Point,
speed: number, speed: number,
passengers: number, passengers: number,
origin: Station, origin: Station,
destination: Station, destination: Station,
color: ColorFormats.RGBA, color: tinycolorInstance,
) { ) {
this.location = location; this.location = location;
this.speed = speed; this.speed = speed;
@@ -31,8 +32,26 @@ export default class Train {
this.passengers = passengers; this.passengers = passengers;
this.color = color; this.color = color;
this.sprite = new PIXI.Sprite(PIXI.loader.resources.nodeImg.texture);
this.sprite.visible = false;
// for debugging // for debugging
trainCount += 1; trainCount += 1;
this.id = trainCount; 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);
} }
} }

View File

@@ -1,116 +0,0 @@
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,20 +6,18 @@ import * as tinycolor from 'tinycolor2';
import Direction from './Direction'; import Direction from './Direction';
import Line from './Line'; import Line from './Line';
import Station from './Station'; import Station from './Station';
import StationWorker from 'worker-loader!./Station.worker';
import Train from './Train'; import Train from './Train';
import { distance, pointsAlmostEqual, pointsEqual, randomInt, randomPoint, import { distance, pointsAlmostEqual, pointsEqual, randomInt, randomPoint,
rangeMap, weightedRandom } from './utils'; rangeMap, weightedRandom } from './utils';
import TrainWorker from 'worker-loader!./Train.worker';
import * as imgNode from './node.png'; import * as imgNode from './node.png';
import './style.css'; import './style.css';
const NODE_RES = 100; const NODE_RES = 100;
const CONNECTION_RADIUS = Math.floor(Math.sqrt( const MAX_SPEED = 10.0;
Math.pow(window.innerWidth, 2) + Math.pow(window.innerHeight, 2), const ACCELERATION = 0.025;
) / 8); const APPROACH_DISTANCE = 3.0;
const TRAIN_CAPACITY = 50; const TRAIN_CAPACITY = 50;
const LINE_CONNECTION_LIMIT = 5; const LINE_CONNECTION_LIMIT = 5;
const WORLD_WIDTH = 1000; const WORLD_WIDTH = 1000;
@@ -31,49 +29,162 @@ const ZOOM_MAX_HEIGHT = 4000;
const trainTexts: PIXI.Text[] = []; const trainTexts: PIXI.Text[] = [];
const drawStations = (stations: Station[], stationLabels: PIXI.Text[], graphics: PIXI.Graphics) => { const initStations = (numStations: number): Station[] => {
stations.forEach((station, i) => { const stations: Station[] = [];
const radius = station.population / 150; for (let i = 0; i < numStations; i += 1) {
const color = tinycolor(station.color); stations.push(new Station(
graphics.beginFill(parseInt(color.toHex(), 16), 0.5); Station.randomDistantPoint(stations, 30),
graphics.drawCircle(station.location.x, station.location.y, radius); randomInt(300, 2000),
graphics.endFill(); tinycolor.random()));
stationLabels[i].x = station.location.x + radius + 1; }
stationLabels[i].y = station.location.y + radius + 1; return stations;
});
}; };
const drawTrains = ( const initTrains = (numTrains: number, stations: Station[]): Train[] => {
trains: Train[], const trains = [];
trainLabels: PIXI.Text[], const stationsWithConnections = stations.filter(station => station.connections.length > 0);
trainSprites: PIXI.Sprite[], for (let i = 0; i < numTrains; i += 1) {
graphics: PIXI.Graphics, const originStation = stationsWithConnections[
) => { Math.floor(Math.random() * stationsWithConnections.length)
trains.forEach((train, i) => { ];
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 radius = station.population / 150;
graphics.beginFill(parseInt(station.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;
}
};
const drawTrains = (trains: Train[], graphics: PIXI.Graphics) => {
for (const train of trains) {
const trainSize = rangeMap(train.passengers, 0, TRAIN_CAPACITY, 1, 5); const trainSize = rangeMap(train.passengers, 0, TRAIN_CAPACITY, 1, 5);
const scale = trainSize / NODE_RES; const scale = trainSize / NODE_RES;
const color = tinycolor(train.color); train.sprite.visible = true;
trainSprites[i].x = train.location.x; train.sprite.x = train.location.x;
trainSprites[i].y = train.location.y; train.sprite.y = train.location.y;
trainSprites[i].scale.x = scale; train.sprite.scale.x = scale;
trainSprites[i].scale.y = scale; train.sprite.scale.y = scale;
trainSprites[i].tint = parseInt(color.toHex(), 16); train.sprite.tint = parseInt(train.color.toHex(), 16);
trainLabels[i].x = train.location.x + scale + 1; train.label.x = train.location.x + scale + 1;
trainLabels[i].y = train.location.y + scale + 1; train.label.y = train.location.y + scale + 1;
}); }
}; };
const drawLines = (stations: Station[], graphics: PIXI.Graphics) => { const drawLines = (stations: Station[], graphics: PIXI.Graphics) => {
for (const station of stations) { for (const station of stations) {
for (const connection of station.connections) { for (const connection of station.connections) {
const color = tinycolor(connection.line.color);
let twoWay = false; let twoWay = false;
for (const conn of connection.station.connections) { for (const conn of connection.station.connections) {
if (conn.station === station) { if (conn.station === station) {
twoWay = true; twoWay = true;
} }
} }
graphics.lineStyle(twoWay ? 2 : 1, parseInt(color.toHex(), 16), 1); graphics.lineStyle(twoWay ? 2 : 1, parseInt(connection.line.color.toHex(), 16), 1);
graphics.moveTo(station.location.x, station.location.y); graphics.moveTo(station.location.x, station.location.y);
graphics.lineTo(connection.station.location.x, connection.station.location.y); graphics.lineTo(connection.station.location.x, connection.station.location.y);
} }
@@ -100,85 +211,28 @@ const run = () => {
let stations: Station[] = []; let stations: Station[] = [];
let lines: Line[] = []; let lines: Line[] = [];
let trains: Train[] = []; let stationsWithConnections: Station[] = [];
const stationLabels: PIXI.Text[] = []; while (stationsWithConnections.length === 0) {
const trainLabels: PIXI.Text[] = []; // If all stations are too far away to connect, try generating again
const trainSprites: PIXI.Sprite[] = []; stations = initStations(30);
const stationWorker = new StationWorker(); lines = initLines(4, stations);
const trainWorker = new TrainWorker(); stationsWithConnections = stations.filter(station => station.connections.length > 0);
}
stationWorker.postMessage({ initStations: { const trains = initTrains(50, stations);
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);
}
// 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.stop();
ticker.add((deltaTime) => { ticker.add((deltaTime) => {
stats.begin(); stats.begin();
trainWorker.postMessage({ moveTrains: {} }); moveTrains(trains, stations);
graphics.clear(); graphics.clear();
graphics.lineStyle(1, 0xFFA500, 1); graphics.lineStyle(1, 0xFFA500, 1);
drawStations(stations, stationLabels, graphics); drawStations(stations, graphics);
graphics.lineStyle(1, 0xAEAEAE, 1); graphics.lineStyle(1, 0xAEAEAE, 1);
drawTrains(trains, trainLabels, trainSprites, graphics); drawTrains(trains, graphics);
drawLines(stations, graphics); drawLines(stations, graphics);
@@ -187,6 +241,17 @@ const run = () => {
ticker.start(); ticker.start();
viewport.addChild(graphics); 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); document.body.appendChild(app.view);
app.stage.addChild(viewport); app.stage.addChild(viewport);
viewport.drag().pinch().wheel().clampZoom({ viewport.drag().pinch().wheel().clampZoom({

View File

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

16
typings/custom.d.ts vendored
View File

@@ -1,17 +1 @@
declare module '*.png'; 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;
}