diff --git a/src/Direction.ts b/src/Direction.ts new file mode 100644 index 0000000..f2ccf9a --- /dev/null +++ b/src/Direction.ts @@ -0,0 +1,41 @@ +import * as PIXI from 'pixi.js'; + +import { angleDegrees } from './utils'; + +enum Direction { + North, // 0 + Northeast, // 1 + East, // 2 + Southeast, // 3 + South, // 4 + Southwest, // 5 + West, // 6 + Northwest, // 7 +} + +export const getPointDirection = (pointA: PIXI.Point, pointB: PIXI.Point): Direction => { + const angle = angleDegrees(pointA, pointB); + let direction = null; + if (angle >= 337.5 || angle < 22.5) { + direction = Direction.North; + } else if (angle >= 22.5 && angle < 67.5) { + direction = Direction.Northeast; + } else if (angle >= 67.5 && angle < 112.5) { + direction = Direction.East; + } else if (angle >= 112.5 && angle < 157.5) { + direction = Direction.Southeast; + } else if (angle >= 157.5 && angle < 202.5) { + direction = Direction.South; + } else if (angle >= 202.5 && angle < 247.5) { + direction = Direction.Southwest; + } else if (angle >= 247.5 && angle < 292.5) { + direction = Direction.West; + } else if (angle >= 292.5 && angle < 337.5) { + direction = Direction.Northwest; + } else { + throw Error('Angle between points is not a valid degree'); + } + return direction; +}; + +export default Direction; diff --git a/src/Line.ts b/src/Line.ts index b37a548..480c369 100644 --- a/src/Line.ts +++ b/src/Line.ts @@ -1,25 +1,67 @@ +import * as tinycolor from 'tinycolor2'; + +import Direction, { getPointDirection } from './Direction'; 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), +) / 4); + export default class Line { + public static getLinesWithStation(lines: Line[], station: Station): Line[] { + return lines.filter(line => line.stations.indexOf(station) >= 0); + } + public stations: Station[]; + public color: tinycolorInstance; - constructor(stations: Station[], numStations: number) { + constructor(stations: Station[], start: PIXI.Point, startDirection: Direction, + numStations: number, color: tinycolorInstance) { + this.color = color; this.stations = []; - let stationsLeft = stations; - let largest = Station.largestStation(stationsLeft); - stationsLeft = stationsLeft.filter(s => s !== largest); - this.stations.push(largest); + let stationsLeft = stations.slice(); + let currentStation = Station.randomCloseLargeStation(stationsLeft, start, CONNECTION_RADIUS); + stationsLeft = stationsLeft.filter(s => s !== currentStation); + this.stations.push(currentStation); + let direction = startDirection; while (this.stations.length < numStations) { - largest = Station.largestStation( - Station.stationsWithinRadius(stationsLeft, largest.location, 500), + const previousStation = this.stations[this.stations.length - 1]; + let candidateStations = Station.stationsWithinRadius( + stationsLeft, + currentStation.location, + CONNECTION_RADIUS, ); - if (largest === null) { + + if (this.stations.length > 1) { + const secondPreviousStation = this.stations[this.stations.length - 2]; + direction = getPointDirection(secondPreviousStation.location, + previousStation.location); + } + + const straightStations = Station.stationsInDirection( + candidateStations, previousStation.location, direction, + ); + const leftStations = Station.stationsInDirection( + candidateStations, previousStation.location, (direction - 1) % 7, + ); + const rightStations = Station.stationsInDirection( + candidateStations, previousStation.location, (direction + 1) % 7, + ); + candidateStations = [ + ...straightStations, + ...leftStations, + ...rightStations, + ]; + + currentStation = Station.randomCloseLargeStation(candidateStations, previousStation.location, + CONNECTION_RADIUS); + if (currentStation === null || currentStation === undefined) { break; } - stationsLeft = stationsLeft.filter(s => s !== largest); - this.stations.push(largest); + stationsLeft = stationsLeft.filter(s => s !== currentStation); + this.stations.push(currentStation); } } } diff --git a/src/Station.ts b/src/Station.ts index 26c7aa2..6b829c5 100644 --- a/src/Station.ts +++ b/src/Station.ts @@ -1,6 +1,7 @@ import * as tinycolor from 'tinycolor2'; -import { distance, randomPoint } from './utils'; +import Direction, { getPointDirection } from './Direction'; +import { distance, randomPoint, weightedRandom } from './utils'; let stationCount = 0; @@ -21,12 +22,25 @@ export default class Station { return stations.filter(station => distance(point, station.location) <= radius); } - public static closestStation(stations: Station[], point: PIXI.Point, num: number): Station { + public static stationsInDirection(stations: Station[], point: PIXI.Point, + direction: Direction): Station[] { + return stations.filter(station => getPointDirection(point, station.location) === direction); + } + + public static closestStation(stations: Station[], point: PIXI.Point): Station { 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: PIXI.Point, + radius: number): Station { + const closeStations = Station.stationsWithinRadius(stations, point, + radius); + const closeStationWeights = closeStations.map(station => station.population); + return weightedRandom(closeStations, closeStationWeights); + } + public static isPointDistant(point: PIXI.Point, stations: Station[], minDistance: number): boolean { for (const station of stations) { diff --git a/src/transport.ts b/src/transport.ts index b906816..24c7f63 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -1,6 +1,7 @@ import * as PIXI from 'pixi.js'; import * as tinycolor from 'tinycolor2'; +import Direction from './Direction'; import Line from './Line'; import Station from './Station'; import Train from './Train'; @@ -119,11 +120,14 @@ const drawTrains = (trains: Train[], graphics: PIXI.Graphics) => { } }; -const drawLine = (line: Line, graphics: PIXI.Graphics) => { - const start = line.stations[0].location; - graphics.moveTo(start.x, start.y); - for (const station of line.stations.slice(1)) { - graphics.lineTo(station.location.x, station.location.y); +const drawLines = (lines: Line[], graphics: PIXI.Graphics) => { + for (const line of lines) { + graphics.lineStyle(1, parseInt(line.color.toHex(), 16), 1); + const start = line.stations[0].location; + graphics.moveTo(start.x, start.y); + for (const station of line.stations.slice(1)) { + graphics.lineTo(station.location.x, station.location.y); + } } }; @@ -143,7 +147,24 @@ const run = () => { const stations = initStations(30); const trains = initTrains(50, stations); - // const line = new Line(stations, 10); + const lines = [ + new Line( + stations, new PIXI.Point(0, 0), + Direction.Southeast, 12, tinycolor('red'), + ), + new Line( + stations, new PIXI.Point(window.innerWidth, 0), + Direction.Southwest, 12, tinycolor('darkcyan'), + ), + new Line( + stations, new PIXI.Point(window.innerWidth, window.innerHeight), + Direction.Northwest, 12, tinycolor('yellow'), + ), + new Line( + stations, new PIXI.Point(0, window.innerHeight), + Direction.Northeast, 12, tinycolor('green'), + ), + ]; ticker.stop(); ticker.add((deltaTime) => { @@ -158,8 +179,7 @@ const run = () => { graphics.lineStyle(1, 0xAEAEAE, 1); drawTrains(trains, graphics); - // TODO: ? - // drawLine(line, graphics); + drawLines(lines, graphics); }); ticker.start(); diff --git a/src/utils.ts b/src/utils.ts index f48e332..08be619 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -42,3 +42,11 @@ export const rangeMap = (num: number, inMin: number, inMax: number, outMin: number, outMax: number): number => ( (num - inMin) * (outMax - outMin) / (inMax - inMin) + outMin ); + +export const angleRadians = (pointA: PIXI.Point, pointB: PIXI.Point): number => ( + Math.atan2(-(pointB.x - pointA.x), pointB.y - pointA.y) +); + +export const angleDegrees = (pointA: PIXI.Point, pointB: PIXI.Point): number => ( + 180 + angleRadians(pointA, pointB) * (180 / Math.PI) +);