/**
 * Code to generate traffic light objects from a SUMO network.
 *
 * These are static in the sense that they don't move, but dynamic in the sense that they do change.
 */ import * as _ from 'lodash' ; import * as three from 'three' ; import { Network , TlLogic } from './api' ; import { Transform } from './coords' ; import { InitResources } from './initialization' ; import { TRAFFIC_LIGHTS } from './materials' ; import { parseShape } from './sumo-utils' ; import { setMaterial } from './three-utils' ; import { forceArray } from './utils' ; enum Directions { LEFT = 'l' , RIGHT = 'r' , STRAIGHT = 's' , UTURN = 't' , PARTIAL_LEFT = 'L' , PARTIAL_RIGHT = 'R' } export default class TrafficLights { private lightObjects : { [ lightId ] : three . Object3D [ ] ; } = { } ; private lightCycles : { [ programId ] : { [ lightId ] : TlLogic ; } ; } = { } ; private currentPrograms : { [ lightId ] : string ; } = { } ; private arrows : InitResources [ 'arrows' ] ; constructor ( init ) { this . arrows = init . arrows ; } loadNetwork ( network , t ) { const { net } = network ; const group = new three . Group ( ) ; const tlConnections = _ . filter ( net . connection , c => _ . has ( c , 'tl' ) ) ; const lightCounts = { } ; const edgeIdToEdge = _ . keyBy ( net . edge , 'id' ) ; _ . forEach ( tlConnections , connection => { const lightId = connection . tl ; const { dir } = connection ; const lanes = forceArray ( edgeIdToEdge [ connection . from ] . lane ) ; const lane = lanes [ Number ( connection . fromLane ) ] ; const laneId = lane . id ; lightCounts [ laneId ] = ( lightCounts [ laneId ] || 0 ) + 1 ; const light = this . getObjectForDirection ( dir ) . clone ( ) ; const name = `Light ${ lightId } ${ laneId } ${ dir }` ; light . traverse ( obj => { obj . name = name ; obj . userData = { type : 'light' , } ; } ) ; const shape = parseShape ( lane . shape ) ; const [ x , y = 0 , z ] = t . sumoXyzToXyz ( shape [ shape . length - 1 ] ) ; const [ px , , pz ] = t . sumoXyzToXyz ( shape [ shape . length - 2 ] ) ; const yRotation = Math . atan2 ( z - pz , px - x ) ; light . position . set ( x , y + lightCounts [ laneId ] , z ) ; light . rotation . set ( 0 , yRotation , 0 ) ; group . add ( light ) ; if ( ! this . lightObjects [ lightId ] ) { this . lightObjects [ lightId ] = [ ] ; } this . lightObjects [ lightId ] [ Number ( connection . linkIndex ) ] = light ; } ) ; return group ; } /** Add traffic light programs to the simulation. */ addLogic ( tlLogics ) { _ . forEach ( _ . groupBy ( tlLogics , 'programID' ) , ( programLogics , programId ) => { this . lightCycles [ programId ] = _ . keyBy ( programLogics , 'id' ) ; } ) ; } getRandomLight ( ) { const randomConnection = _ . sample ( this . lightObjects ) ; return randomConnection ? randomConnection [ 0 ] : null ; } setLightProgram ( lightId , programId ) { this . currentPrograms [ lightId ] = programId ; } /** Change a light object to reflect its new phase. */ setPhase ( lightId , phaseIndex ) { const programId = this . currentPrograms [ lightId ] ; const lightCycle = this . lightCycles [ programId ] [ lightId ] ; if ( ! lightCycle ) { console . warn ( 'Cannot set phase for light' , lightId ) ; return ; } const lightState = lightCycle . phase [ phaseIndex ] . state ; const lights = this . lightObjects [ lightId ] ; lights . forEach ( ( light , i ) => { const material = TRAFFIC_LIGHTS [ lightState . charAt ( i ) . toLowerCase ( ) ] || TRAFFIC_LIGHTS . x ; setMaterial ( light , material ) ; } ) ; } private getObjectForDirection ( dir ) { if ( dir === Directions . LEFT || dir === Directions . PARTIAL_LEFT ) { return this . arrows . left ; } else if ( dir === Directions . RIGHT || dir === Directions . PARTIAL_RIGHT ) { return this . arrows . right ; } else if ( dir === Directions . STRAIGHT ) { return this . arrows . straight ; } else if ( dir === Directions . UTURN ) { return this . arrows . uturn ; } else { throw new Error ( `Unknown turn direction: ${ dir }.` ) ; } } }