| |
| |
| |
| id = Math.random().toString(36).substring(2, 15); |
|
|
| BACKGROUND_COLOR = 0xe8ebed; |
| LANE_COLOR = 0x586970; |
| LANE_BORDER_WIDTH = 1; |
| LANE_BORDER_COLOR = 0x82a8ba; |
| LANE_INNER_COLOR = 0xbed8e8; |
| LANE_DASH = 10; |
| LANE_GAP = 12; |
| TRAFFIC_LIGHT_WIDTH = 3; |
| MAX_TRAFFIC_LIGHT_NUM = 100000; |
| ROTATE = 90; |
|
|
| CAR_LENGTH = 5; |
| CAR_WIDTH = 2; |
| CAR_COLOR = 0xe8bed4; |
|
|
| CAR_COLORS = [0xf2bfd7, |
| 0xb7ebe4, |
| 0xdbebb7, |
| 0xf5ddb5, |
| 0xd4b5f5]; |
| CAR_COLORS_NUM = CAR_COLORS.length; |
|
|
| NUM_CAR_POOL = 150000; |
| DISTRICT_BORDER_COLOR = 0x34495e; |
| GATEWAY_NODE_COLOR = 0xf39c12; |
| GATEWAY_EDGE_COLOR = 0xe67e22; |
| DISTRICT_PALETTE = [ |
| 0x1f77b4, 0xff7f0e, 0x2ca02c, 0xd62728, 0x9467bd, 0x8c564b, |
| 0xe377c2, 0x7f7f7f, 0xbcbd22, 0x17becf, 0x393b79, 0x637939 |
| ]; |
|
|
| LIGHT_RED = 0xdb635e; |
| LIGHT_GREEN = 0x85ee00; |
|
|
| TURN_SIGNAL_COLOR = 0xFFFFFF; |
| TURN_SIGNAL_WIDTH = 1; |
| TURN_SIGNAL_LENGTH = 5; |
|
|
| var simulation, roadnet, steps; |
| var nodes = {}; |
| var edges = {}; |
| var logs; |
| var gettingLog = false; |
|
|
| let Application = PIXI.Application, |
| Sprite = PIXI.Sprite, |
| Graphics = PIXI.Graphics, |
| Container = PIXI.Container, |
| ParticleContainer = PIXI.particles.ParticleContainer, |
| Texture = PIXI.Texture, |
| Rectangle = PIXI.Rectangle |
| ; |
|
|
| var controls = new function () { |
| this.replaySpeedMax = 1; |
| this.replaySpeedMin = 0.01; |
| this.replaySpeed = 0.5; |
| this.paused = false; |
| }; |
|
|
| var trafficLightsG = {}; |
|
|
| var app, viewport, renderer, simulatorContainer, carContainer, trafficLightContainer; |
| var overlayContainer; |
| var turnSignalContainer; |
| var carPool; |
|
|
| var cnt = 0; |
| var frameElapsed = 0; |
| var totalStep; |
|
|
| var nodeCarNum = document.getElementById("car-num"); |
| var nodeProgressPercentage = document.getElementById("progress-percentage"); |
| var nodeTotalStep = document.getElementById("total-step-num"); |
| var nodeCurrentStep = document.getElementById("current-step-num"); |
| var nodeSelectedEntity = document.getElementById("selected-entity"); |
|
|
| var SPEED = 3, SCALE_SPEED = 1.01; |
| var LEFT = 37, UP = 38, RIGHT = 39, DOWN = 40; |
| var MINUS = 189, EQUAL = 187, P = 80; |
| var LEFT_BRACKET = 219, RIGHT_BRACKET = 221; |
| var ONE = 49, TWO = 50; |
| var SPACE = 32; |
|
|
| var keyDown = new Set(); |
|
|
| var turnSignalTextures = []; |
|
|
| let pauseButton = document.getElementById("pause"); |
| let nodeCanvas = document.getElementById("simulator-canvas"); |
| let replayControlDom = document.getElementById("replay-control"); |
| let replaySpeedDom = document.getElementById("replay-speed"); |
|
|
| let loading = false; |
| let infoDOM = document.getElementById("info"); |
| let selectedDOM = document.getElementById("selected-entity"); |
|
|
| function infoAppend(msg) { |
| infoDOM.innerText += "- " + msg + "\n"; |
| } |
|
|
| function infoReset() { |
| infoDOM.innerText = ""; |
| } |
|
|
| |
| |
| |
| let ready = false; |
|
|
| let roadnetData = []; |
| let replayData = []; |
| let chartData = []; |
| let districtDataRaw = []; |
| let districtMap = null; |
| let showDistrictOverlay = true; |
| let showGatewayOverlay = true; |
|
|
| function handleChooseFile(v, label_dom) { |
| return function(evt) { |
| let file = evt.target.files[0]; |
| label_dom.innerText = file.name; |
| } |
| } |
|
|
| function uploadFile(v, file, callback) { |
| let reader = new FileReader(); |
| reader.onloadstart = function () { |
| infoAppend("Loading " + file.name); |
| }; |
| reader.onerror = function() { |
| infoAppend("Loading " + file.name + "failed"); |
| } |
| reader.onload = function (e) { |
| infoAppend(file.name + " loaded"); |
| v[0] = e.target.result; |
| callback(); |
| }; |
| try { |
| reader.readAsText(file); |
| } catch (e) { |
| infoAppend("Loading failed"); |
| console.error(e.message); |
| } |
| } |
|
|
| let debugMode = false; |
| let chartLog; |
| let showChart = false; |
| let chartConainterDOM = document.getElementById("chart-container"); |
| let showDistrictOverlayDom = document.getElementById("show-district-overlay"); |
| let showGatewayOverlayDom = document.getElementById("show-gateway-overlay"); |
|
|
| function parseDistrictMap() { |
| if (!districtDataRaw[0]) { |
| districtMap = null; |
| return; |
| } |
| try { |
| districtMap = JSON.parse(districtDataRaw[0]); |
| } catch (e) { |
| districtMap = null; |
| infoAppend("Parsing district map file failed"); |
| } |
| } |
|
|
| function uploadOptionalDistrictThenChart(done) { |
| if (DistrictFileDom.value) { |
| uploadFile(districtDataRaw, DistrictFileDom.files[0], function() { |
| parseDistrictMap(); |
| if (ChartFileDom.value) { |
| showChart = true; |
| uploadFile(chartData, ChartFileDom.files[0], done); |
| } else { |
| showChart = false; |
| done(); |
| } |
| }); |
| } else { |
| districtMap = null; |
| if (ChartFileDom.value) { |
| showChart = true; |
| uploadFile(chartData, ChartFileDom.files[0], done); |
| } else { |
| showChart = false; |
| done(); |
| } |
| } |
| } |
|
|
| function start() { |
| if (loading) return; |
| loading = true; |
| infoReset(); |
| showDistrictOverlay = showDistrictOverlayDom.checked; |
| showGatewayOverlay = showGatewayOverlayDom.checked; |
| uploadFile(roadnetData, RoadnetFileDom.files[0], function(){ |
| uploadFile(replayData, ReplayFileDom.files[0], function(){ |
| let after_update = function() { |
| infoAppend("drawing roadnet"); |
| ready = false; |
| document.getElementById("guide").classList.add("d-none"); |
| hideCanvas(); |
| try { |
| simulation = JSON.parse(roadnetData[0]); |
| } catch (e) { |
| infoAppend("Parsing roadnet file failed"); |
| loading = false; |
| return; |
| } |
| try { |
| logs = replayData[0].split('\n'); |
| logs.pop(); |
| } catch (e) { |
| infoAppend("Reading replay file failed"); |
| loading = false; |
| return; |
| } |
|
|
| totalStep = logs.length; |
| if (showChart) { |
| chartConainterDOM.classList.remove("d-none"); |
| let chart_lines = chartData[0].split('\n'); |
| if (chart_lines.length == 0) { |
| infoAppend("Chart file is empty"); |
| showChart = false; |
| } |
| chartLog = []; |
| for (let i = 0 ; i < totalStep ; ++i) { |
| step_data = chart_lines[i + 1].split(/[ \t]+/); |
| chartLog.push([]); |
| for (let j = 0; j < step_data.length; ++j) { |
| chartLog[i].push(parseFloat(step_data[j])); |
| } |
| } |
| chart.init(chart_lines[0], chartLog[0].length, totalStep); |
| }else { |
| chartConainterDOM.classList.add("d-none"); |
| } |
|
|
| controls.paused = false; |
| cnt = 0; |
| debugMode = document.getElementById("debug-mode").checked; |
| setTimeout(function () { |
| try { |
| drawRoadnet(); |
| } catch (e) { |
| infoAppend("Drawing roadnet failed"); |
| console.error(e.message); |
| loading = false; |
| return; |
| } |
| ready = true; |
| loading = false; |
| infoAppend("Start replaying"); |
| }, 200); |
| }; |
|
|
| uploadOptionalDistrictThenChart(after_update); |
|
|
| }); |
| }); |
| } |
|
|
| let RoadnetFileDom = document.getElementById("roadnet-file"); |
| let ReplayFileDom = document.getElementById("replay-file"); |
| let ChartFileDom = document.getElementById("chart-file"); |
| let DistrictFileDom = document.getElementById("district-file"); |
|
|
| RoadnetFileDom.addEventListener("change", |
| handleChooseFile(roadnetData, document.getElementById("roadnet-label")), false); |
| ReplayFileDom.addEventListener("change", |
| handleChooseFile(replayData, document.getElementById("replay-label")), false); |
| ChartFileDom.addEventListener("change", |
| handleChooseFile(chartData, document.getElementById("chart-label")), false); |
| DistrictFileDom.addEventListener("change", |
| handleChooseFile(districtDataRaw, document.getElementById("district-label")), false); |
|
|
| showDistrictOverlayDom.addEventListener("change", function(evt) { |
| showDistrictOverlay = evt.target.checked; |
| if (ready) drawRoadnet(); |
| }); |
| showGatewayOverlayDom.addEventListener("change", function(evt) { |
| showGatewayOverlay = evt.target.checked; |
| if (ready) drawRoadnet(); |
| }); |
|
|
| document.getElementById("start-btn").addEventListener("click", start); |
|
|
| document.getElementById("slow-btn").addEventListener("click", function() { |
| updateReplaySpeed(controls.replaySpeed - 0.1); |
| }) |
|
|
| document.getElementById("fast-btn").addEventListener("click", function() { |
| updateReplaySpeed(controls.replaySpeed + 0.1); |
| }) |
|
|
| function updateReplaySpeed(speed){ |
| speed = Math.min(speed, 1); |
| speed = Math.max(speed, 0); |
| controls.replaySpeed = speed; |
| replayControlDom.value = speed * 100; |
| replaySpeedDom.innerHTML = speed.toFixed(2); |
| } |
|
|
| updateReplaySpeed(0.5); |
|
|
| replayControlDom.addEventListener('change', function(e){ |
| updateReplaySpeed(replayControlDom.value / 100); |
| }); |
|
|
| document.addEventListener('keydown', function(e) { |
| if (e.keyCode == P) { |
| controls.paused = !controls.paused; |
| } else if (e.keyCode == ONE) { |
| updateReplaySpeed(Math.max(controls.replaySpeed / 1.5, controls.replaySpeedMin)); |
| } else if (e.keyCode == TWO ) { |
| updateReplaySpeed(Math.min(controls.replaySpeed * 1.5, controls.replaySpeedMax)); |
| } else if (e.keyCode == LEFT_BRACKET) { |
| cnt = (cnt - 1) % totalStep; |
| cnt = (cnt + totalStep) % totalStep; |
| drawStep(cnt); |
| } else if (e.keyCode == RIGHT_BRACKET) { |
| cnt = (cnt + 1) % totalStep; |
| drawStep(cnt); |
| } else { |
| keyDown.add(e.keyCode) |
| } |
| }); |
|
|
| document.addEventListener('keyup', (e) => keyDown.delete(e.keyCode)); |
|
|
| nodeCanvas.addEventListener('dblclick', function(e){ |
| controls.paused = !controls.paused; |
| }); |
|
|
| pauseButton.addEventListener('click', function(e){ |
| controls.paused = !controls.paused; |
| }); |
|
|
| function initCanvas() { |
| app = new Application({ |
| width: nodeCanvas.offsetWidth, |
| height: nodeCanvas.offsetHeight, |
| transparent: false, |
| backgroundColor: BACKGROUND_COLOR |
| }); |
|
|
| nodeCanvas.appendChild(app.view); |
| app.view.classList.add("d-none"); |
|
|
| renderer = app.renderer; |
| renderer.interactive = true; |
| renderer.autoResize = true; |
|
|
| renderer.resize(nodeCanvas.offsetWidth, nodeCanvas.offsetHeight); |
| app.ticker.add(run); |
| } |
|
|
| function showCanvas() { |
| document.getElementById("spinner").classList.add("d-none"); |
| app.view.classList.remove("d-none"); |
| } |
|
|
| function hideCanvas() { |
| document.getElementById("spinner").classList.remove("d-none"); |
| app.view.classList.add("d-none"); |
| } |
|
|
| function drawRoadnet() { |
| if (simulatorContainer) { |
| simulatorContainer.destroy(true); |
| } |
| app.stage.removeChildren(); |
| viewport = new Viewport.Viewport({ |
| screenWidth: window.innerWidth, |
| screenHeight: window.innerHeight, |
| interaction: app.renderer.plugins.interaction |
| }); |
| viewport |
| .drag() |
| .pinch() |
| .wheel() |
| .decelerate(); |
| app.stage.addChild(viewport); |
| simulatorContainer = new Container(); |
| viewport.addChild(simulatorContainer); |
|
|
| roadnet = simulation.static; |
| nodes = []; |
| edges = []; |
| trafficLightsG = {}; |
|
|
| for (let i = 0, len = roadnet.nodes.length;i < len;++i) { |
| node = roadnet.nodes[i]; |
| node.point = new Point(transCoord(node.point)); |
| nodes[node.id] = node; |
| } |
|
|
| for (let i = 0, len = roadnet.edges.length;i < len;++i) { |
| edge = roadnet.edges[i]; |
| edge.from = nodes[edge.from]; |
| edge.to = nodes[edge.to]; |
| for (let j = 0, len = edge.points.length;j < len;++j) { |
| edge.points[j] = new Point(transCoord(edge.points[j])); |
| } |
| edges[edge.id] = edge; |
| } |
|
|
| |
| |
| |
| trafficLightContainer = new ParticleContainer(MAX_TRAFFIC_LIGHT_NUM, {tint: true}); |
| let mapContainer, mapGraphics; |
| if (debugMode) { |
| mapContainer = new Container(); |
| simulatorContainer.addChild(mapContainer); |
| }else { |
| mapGraphics = new Graphics(); |
| simulatorContainer.addChild(mapGraphics); |
| } |
|
|
| for (nodeId in nodes) { |
| if (!nodes[nodeId].virtual) { |
| let nodeGraphics; |
| if (debugMode) { |
| nodeGraphics = new Graphics(); |
| mapContainer.addChild(nodeGraphics); |
| } else { |
| nodeGraphics = mapGraphics; |
| } |
| drawNode(nodes[nodeId], nodeGraphics); |
| } |
| } |
| for (edgeId in edges) { |
| let edgeGraphics; |
| if (debugMode) { |
| edgeGraphics = new Graphics(); |
| mapContainer.addChild(edgeGraphics); |
| } else { |
| edgeGraphics = mapGraphics; |
| } |
| drawEdge(edges[edgeId], edgeGraphics); |
| } |
| drawOverlayLayers(); |
| let bounds = simulatorContainer.getBounds(); |
| simulatorContainer.pivot.set(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2); |
| simulatorContainer.position.set(renderer.width / 2, renderer.height / 2); |
| simulatorContainer.addChild(trafficLightContainer); |
|
|
| |
| |
| |
| TURN_SIGNAL_LENGTH = CAR_LENGTH; |
| TURN_SIGNAL_WIDTH = CAR_WIDTH / 2; |
|
|
| var carG = new Graphics(); |
| carG.lineStyle(0); |
| carG.beginFill(0xFFFFFF, 0.8); |
| carG.drawRect(0, 0, CAR_LENGTH, CAR_WIDTH); |
|
|
| let carTexture = renderer.generateTexture(carG); |
|
|
| let signalG = new Graphics(); |
| signalG.beginFill(TURN_SIGNAL_COLOR, 0.7).drawRect(0,0, TURN_SIGNAL_LENGTH, TURN_SIGNAL_WIDTH) |
| .drawRect(0, 3 * CAR_WIDTH - TURN_SIGNAL_WIDTH, TURN_SIGNAL_LENGTH, TURN_SIGNAL_WIDTH).endFill(); |
| let turnSignalTexture = renderer.generateTexture(signalG); |
|
|
| let signalLeft = new Texture(turnSignalTexture, new Rectangle(0, 0, TURN_SIGNAL_LENGTH, CAR_WIDTH)); |
| let signalStraight = new Texture(turnSignalTexture, new Rectangle(0, CAR_WIDTH, TURN_SIGNAL_LENGTH, CAR_WIDTH)); |
| let signalRight = new Texture(turnSignalTexture, new Rectangle(0, CAR_WIDTH * 2, TURN_SIGNAL_LENGTH, CAR_WIDTH)); |
| turnSignalTextures = [signalLeft, signalStraight, signalRight]; |
|
|
|
|
| carPool = []; |
| if (debugMode) |
| carContainer = new Container(); |
| else |
| carContainer = new ParticleContainer(NUM_CAR_POOL, {rotation: true, tint: true}); |
|
|
|
|
| turnSignalContainer = new ParticleContainer(NUM_CAR_POOL, {rotation: true, tint: true}); |
| simulatorContainer.addChild(carContainer); |
| simulatorContainer.addChild(turnSignalContainer); |
| for (let i = 0, len = NUM_CAR_POOL;i < len;++i) { |
| |
| let car = new Sprite(carTexture); |
| let signal = new Sprite(turnSignalTextures[1]); |
| car.anchor.set(1, 0.5); |
|
|
| if (debugMode) { |
| car.interactive = true; |
| car.on('mouseover', function () { |
| selectedDOM.innerText = car.name; |
| car.alpha = 0.8; |
| }); |
| car.on('mouseout', function () { |
| |
| car.alpha = 1; |
| }); |
| } |
| signal.anchor.set(1, 0.5); |
| carPool.push([car, signal]); |
| } |
| showCanvas(); |
|
|
| return true; |
| } |
|
|
| function appendText(id, text) { |
| let p = document.createElement("span"); |
| p.innerText = text; |
| document.getElementById("info").appendChild(p); |
| document.getElementById("info").appendChild(document.createElement("br")); |
| } |
|
|
| var statsFile = ""; |
| var withRange = false; |
| var nodeStats, nodeRange; |
|
|
| initCanvas(); |
|
|
|
|
| function transCoord(point) { |
| return [point[0], -point[1]]; |
| } |
|
|
| function getDistrictColor(districtId) { |
| return DISTRICT_PALETTE[stringHash(districtId) % DISTRICT_PALETTE.length]; |
| } |
|
|
| function cross2D(o, a, b) { |
| return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x); |
| } |
|
|
| function convexHull(points) { |
| if (!points || points.length <= 2) return points ? points.slice() : []; |
| let sorted = points |
| .slice() |
| .sort((p, q) => (p.x === q.x ? p.y - q.y : p.x - q.x)); |
| let lower = []; |
| for (let i = 0; i < sorted.length; ++i) { |
| while (lower.length >= 2 && cross2D(lower[lower.length - 2], lower[lower.length - 1], sorted[i]) <= 0) { |
| lower.pop(); |
| } |
| lower.push(sorted[i]); |
| } |
| let upper = []; |
| for (let i = sorted.length - 1; i >= 0; --i) { |
| while (upper.length >= 2 && cross2D(upper[upper.length - 2], upper[upper.length - 1], sorted[i]) <= 0) { |
| upper.pop(); |
| } |
| upper.push(sorted[i]); |
| } |
| lower.pop(); |
| upper.pop(); |
| return lower.concat(upper); |
| } |
|
|
| function drawDistrictRegionFills(graphics, districtToPoints) { |
| for (let districtId in districtToPoints) { |
| let pts = districtToPoints[districtId]; |
| if (!pts || pts.length === 0) continue; |
| let color = getDistrictColor(districtId); |
| if (pts.length >= 3) { |
| let hull = convexHull(pts); |
| if (hull.length >= 3) { |
| graphics.lineStyle(1.2, color, 0.35); |
| graphics.beginFill(color, 0.11); |
| graphics.moveTo(hull[0].x, hull[0].y); |
| for (let i = 1; i < hull.length; ++i) { |
| graphics.lineTo(hull[i].x, hull[i].y); |
| } |
| graphics.lineTo(hull[0].x, hull[0].y); |
| graphics.endFill(); |
| continue; |
| } |
| } |
| if (pts.length === 2) { |
| graphics.lineStyle(18, color, 0.10); |
| graphics.drawLine(pts[0], pts[1]); |
| continue; |
| } |
| graphics.lineStyle(0); |
| graphics.beginFill(color, 0.12); |
| graphics.drawCircle(pts[0].x, pts[0].y, 18); |
| graphics.endFill(); |
| } |
| } |
|
|
| function drawEdgePolyline(graphics, edge, width, color, alpha) { |
| graphics.lineStyle(width, color, alpha); |
| for (let i = 1; i < edge.points.length; ++i) { |
| graphics.drawLine(edge.points[i - 1], edge.points[i]); |
| } |
| } |
|
|
| function drawOverlayLayers() { |
| if (overlayContainer) { |
| overlayContainer.destroy(true); |
| overlayContainer = null; |
| } |
| if (!showDistrictOverlay && !showGatewayOverlay) { |
| return; |
| } |
| overlayContainer = new Container(); |
| simulatorContainer.addChild(overlayContainer); |
|
|
| let overlayGraphics = new Graphics(); |
| overlayContainer.addChild(overlayGraphics); |
|
|
| let intersectionToDistrict = {}; |
| if (districtMap && districtMap.intersection_to_district) { |
| intersectionToDistrict = districtMap.intersection_to_district; |
| } |
| let gatewayNodeIds = new Set( |
| districtMap && districtMap.gateway_intersections |
| ? districtMap.gateway_intersections |
| : [] |
| ); |
| let gatewayEdgeIds = new Set( |
| districtMap && districtMap.gateway_roads |
| ? districtMap.gateway_roads |
| : [] |
| ); |
| let nodeDegree = {}; |
| for (let edgeId in edges) { |
| let edge = edges[edgeId]; |
| nodeDegree[edge.from.id] = (nodeDegree[edge.from.id] || 0) + 1; |
| nodeDegree[edge.to.id] = (nodeDegree[edge.to.id] || 0) + 1; |
| } |
| for (let nodeId in nodes) { |
| if ((nodeDegree[nodeId] || 0) <= 1) { |
| gatewayNodeIds.add(nodeId); |
| } |
| } |
|
|
| if (showDistrictOverlay && districtMap && districtMap.intersection_to_district) { |
| let districtToPoints = {}; |
| for (let nodeId in nodes) { |
| let districtId = intersectionToDistrict[nodeId]; |
| if (!districtId) continue; |
| if (!districtToPoints[districtId]) districtToPoints[districtId] = []; |
| districtToPoints[districtId].push(nodes[nodeId].point); |
| } |
| drawDistrictRegionFills(overlayGraphics, districtToPoints); |
|
|
| for (let edgeId in edges) { |
| let edge = edges[edgeId]; |
| let fromDistrict = intersectionToDistrict[edge.from.id]; |
| let toDistrict = intersectionToDistrict[edge.to.id]; |
| if (!fromDistrict || !toDistrict) continue; |
| if (fromDistrict === toDistrict) { |
| drawEdgePolyline( |
| overlayGraphics, |
| edge, |
| 1.2, |
| getDistrictColor(fromDistrict), |
| 0.45 |
| ); |
| } else { |
| drawEdgePolyline( |
| overlayGraphics, |
| edge, |
| 1.8, |
| DISTRICT_BORDER_COLOR, |
| 0.8 |
| ); |
| } |
| } |
| for (let nodeId in nodes) { |
| let districtId = intersectionToDistrict[nodeId]; |
| if (!districtId) continue; |
| let point = nodes[nodeId].point; |
| overlayGraphics.beginFill(getDistrictColor(districtId), 0.55); |
| overlayGraphics.drawCircle(point.x, point.y, 2.0); |
| overlayGraphics.endFill(); |
| } |
| } |
|
|
| if (showGatewayOverlay) { |
| for (let edgeId in edges) { |
| let edge = edges[edgeId]; |
| let fromGateway = gatewayNodeIds.has(edge.from.id) || String(edge.from.id).indexOf("g_") === 0; |
| let toGateway = gatewayNodeIds.has(edge.to.id) || String(edge.to.id).indexOf("g_") === 0; |
| let edgeLooksGateway = edgeId.indexOf("_g_") >= 0 || edgeId.indexOf("r_g_") === 0; |
| if ( |
| gatewayEdgeIds.has(edgeId) |
| || fromGateway |
| || toGateway |
| || edgeLooksGateway |
| ) { |
| drawEdgePolyline( |
| overlayGraphics, |
| edge, |
| 5.6, |
| GATEWAY_EDGE_COLOR, |
| 0.82 |
| ); |
| } |
| } |
| for (let nodeId in nodes) { |
| if (gatewayNodeIds.has(nodeId) || String(nodeId).indexOf("g_") === 0) { |
| let point = nodes[nodeId].point; |
| overlayGraphics.lineStyle(0); |
| overlayGraphics.beginFill(GATEWAY_NODE_COLOR, 0.28); |
| overlayGraphics.drawCircle(point.x, point.y, 34.0); |
| overlayGraphics.endFill(); |
| overlayGraphics.lineStyle(2.2, 0x1f2d3d, 0.98); |
| overlayGraphics.beginFill(GATEWAY_NODE_COLOR, 0.96); |
| overlayGraphics.drawCircle(point.x, point.y, 13.0); |
| overlayGraphics.endFill(); |
| overlayGraphics.lineStyle(0); |
| overlayGraphics.beginFill(0xffffff, 0.92); |
| overlayGraphics.drawCircle(point.x, point.y, 5.2); |
| overlayGraphics.endFill(); |
| } |
| } |
| } |
| } |
|
|
| PIXI.Graphics.prototype.drawLine = function(pointA, pointB) { |
| this.moveTo(pointA.x, pointA.y); |
| this.lineTo(pointB.x, pointB.y); |
| } |
|
|
| PIXI.Graphics.prototype.drawDashLine = function(pointA, pointB, dash = 16, gap = 8) { |
| let direct = pointA.directTo(pointB); |
| let distance = pointA.distanceTo(pointB); |
|
|
| let currentPoint = pointA; |
| let currentDistance = 0; |
| let length; |
| let finish = false; |
| while (true) { |
| this.moveTo(currentPoint.x, currentPoint.y); |
| if (currentDistance + dash >= distance) { |
| length = distance - currentDistance; |
| finish = true; |
| } else { |
| length = dash |
| } |
| currentPoint = currentPoint.moveAlong(direct, length); |
| this.lineTo(currentPoint.x, currentPoint.y); |
| if (finish) break; |
| currentDistance += length; |
|
|
| if (currentDistance + gap >= distance) { |
| break; |
| } else { |
| currentPoint = currentPoint.moveAlong(direct, gap); |
| currentDistance += gap; |
| } |
| } |
| }; |
|
|
| function drawNode(node, graphics) { |
| graphics.beginFill(LANE_COLOR); |
| let outline = node.outline; |
| for (let i = 0 ; i < outline.length ; i+=2) { |
| outline[i+1] = -outline[i+1]; |
| if (i == 0) |
| graphics.moveTo(outline[i], outline[i+1]); |
| else |
| graphics.lineTo(outline[i], outline[i+1]); |
| } |
| graphics.endFill(); |
|
|
| if (debugMode) { |
| graphics.hitArea = new PIXI.Polygon(outline); |
| graphics.interactive = true; |
| graphics.on("mouseover", function () { |
| selectedDOM.innerText = node.id; |
| graphics.alpha = 0.5; |
| }); |
| graphics.on("mouseout", function () { |
| graphics.alpha = 1; |
| }); |
| } |
|
|
| } |
|
|
| function drawEdge(edge, graphics) { |
| let from = edge.from; |
| let to = edge.to; |
| let points = edge.points; |
|
|
| let pointA, pointAOffset, pointB, pointBOffset; |
| let prevPointBOffset = null; |
|
|
| let roadWidth = 0; |
| edge.laneWidths.forEach(function(l){ |
| roadWidth += l; |
| }, 0); |
|
|
| let coords = [], coords1 = []; |
|
|
| for (let i = 1;i < points.length;++i) { |
| if (i == 1){ |
| pointA = points[0].moveAlongDirectTo(points[1], from.virtual ? 0 : from.width); |
| pointAOffset = points[0].directTo(points[1]).rotate(ROTATE); |
| } else { |
| pointA = points[i-1]; |
| pointAOffset = prevPointBOffset; |
| } |
| if (i == points.length - 1) { |
| pointB = points[i].moveAlongDirectTo(points[i-1], to.virtual ? 0 : to.width); |
| pointBOffset = points[i-1].directTo(points[i]).rotate(ROTATE); |
| } else { |
| pointB = points[i]; |
| pointBOffset = points[i-1].directTo(points[i+1]).rotate(ROTATE); |
| } |
| prevPointBOffset = pointBOffset; |
|
|
| lightG = new Graphics(); |
| lightG.lineStyle(TRAFFIC_LIGHT_WIDTH, 0xFFFFFF); |
| lightG.drawLine(new Point(0, 0), new Point(1, 0)); |
| lightTexture = renderer.generateTexture(lightG); |
|
|
| |
| if (i == points.length-1 && !to.virtual) { |
| edgeTrafficLights = []; |
| prevOffset = offset = 0; |
| for (lane = 0;lane < edge.nLane;++lane) { |
| offset += edge.laneWidths[lane]; |
| var light = new Sprite(lightTexture); |
| light.anchor.set(0, 0.5); |
| light.scale.set(offset - prevOffset, 1); |
| point_ = pointB.moveAlong(pointBOffset, prevOffset); |
| light.position.set(point_.x, point_.y); |
| light.rotation = pointBOffset.getAngleInRadians(); |
| edgeTrafficLights.push(light); |
| prevOffset = offset; |
| trafficLightContainer.addChild(light); |
| } |
| trafficLightsG[edge.id] = edgeTrafficLights; |
| } |
|
|
| |
| graphics.lineStyle(LANE_BORDER_WIDTH, LANE_BORDER_COLOR, 1); |
| graphics.drawLine(pointA, pointB); |
|
|
| pointA1 = pointA.moveAlong(pointAOffset, roadWidth); |
| pointB1 = pointB.moveAlong(pointBOffset, roadWidth); |
|
|
| graphics.lineStyle(0); |
| graphics.beginFill(LANE_COLOR); |
|
|
| coords = coords.concat([pointA.x, pointA.y, pointB.x, pointB.y]); |
| coords1 = coords1.concat([pointA1.y, pointA1.x, pointB1.y, pointB1.x]); |
|
|
| graphics.drawPolygon([pointA.x, pointA.y, pointB.x, pointB.y, pointB1.x, pointB1.y, pointA1.x, pointA1.y]); |
| graphics.endFill(); |
|
|
| offset = 0; |
| for (let lane = 0, len = edge.nLane-1;lane < len;++lane) { |
| offset += edge.laneWidths[lane]; |
| graphics.lineStyle(LANE_BORDER_WIDTH, LANE_INNER_COLOR); |
| graphics.drawDashLine(pointA.moveAlong(pointAOffset, offset), pointB.moveAlong(pointBOffset, offset), LANE_DASH, LANE_GAP); |
| } |
|
|
| offset += edge.laneWidths[edge.nLane-1]; |
|
|
| |
| |
| } |
|
|
| if (debugMode) { |
| coords = coords.concat(coords1.reverse()); |
| graphics.interactive = true; |
| graphics.hitArea = new PIXI.Polygon(coords); |
| graphics.on("mouseover", function () { |
| graphics.alpha = 0.5; |
| selectedDOM.innerText = edge.id; |
| }); |
|
|
| graphics.on("mouseout", function () { |
| graphics.alpha = 1; |
| }); |
| } |
| } |
|
|
| function run(delta) { |
| let redraw = false; |
|
|
| if (ready && (!controls.paused || redraw)) { |
| try { |
| drawStep(cnt); |
| }catch (e) { |
| infoAppend("Error occurred when drawing"); |
| ready = false; |
| } |
| if (!controls.paused) { |
| frameElapsed += 1; |
| if (frameElapsed >= 1 / controls.replaySpeed ** 2) { |
| cnt += 1; |
| frameElapsed = 0; |
| if (cnt == totalStep) cnt = 0; |
| } |
| } |
| } |
| } |
|
|
| function _statusToColor(status) { |
| switch (status) { |
| case 'r': |
| return LIGHT_RED; |
| case 'g': |
| return LIGHT_GREEN; |
| default: |
| return 0x808080; |
| } |
| } |
|
|
| function stringHash(str) { |
| let hash = 0; |
| let p = 127, p_pow = 1; |
| let m = 1e9 + 9; |
| for (let i = 0; i < str.length; i++) { |
| hash = (hash + str.charCodeAt(i) * p_pow) % m; |
| p_pow = (p_pow * p) % m; |
| } |
| return hash; |
| } |
|
|
| function drawStep(step) { |
| if (showChart && (step > chart.ptr || step == 0)) { |
| if (step == 0) { |
| chart.clear(); |
| } |
| chart.ptr = step; |
| chart.addData(chartLog[step]); |
| } |
|
|
| let [carLogs, tlLogs] = logs[step].split(';'); |
|
|
| tlLogs = tlLogs.split(','); |
| carLogs = carLogs.split(','); |
| |
| let tlLog, tlEdge, tlStatus; |
| for (let i = 0, len = tlLogs.length;i < len;++i) { |
| tlLog = tlLogs[i].split(' '); |
| tlEdge = tlLog[0]; |
| tlStatus = tlLog.slice(1); |
| for (let j = 0, len = tlStatus.length;j < len;++j) { |
| trafficLightsG[tlEdge][j].tint = _statusToColor(tlStatus[j]); |
| if (tlStatus[j] == 'i' ) { |
| trafficLightsG[tlEdge][j].alpha = 0; |
| }else{ |
| trafficLightsG[tlEdge][j].alpha = 1; |
| } |
| } |
| } |
|
|
| carContainer.removeChildren(); |
| turnSignalContainer.removeChildren(); |
| let carLog, position, length, width; |
| for (let i = 0, len = carLogs.length - 1;i < len;++i) { |
| carLog = carLogs[i].split(' '); |
| position = transCoord([parseFloat(carLog[0]), parseFloat(carLog[1])]); |
| length = parseFloat(carLog[5]); |
| width = parseFloat(carLog[6]); |
| carPool[i][0].position.set(position[0], position[1]); |
| carPool[i][0].rotation = 2*Math.PI - parseFloat(carLog[2]); |
| carPool[i][0].name = carLog[3]; |
| let carColorId = stringHash(carLog[3]) % CAR_COLORS_NUM; |
| carPool[i][0].tint = CAR_COLORS[carColorId]; |
| carPool[i][0].width = length; |
| carPool[i][0].height = width; |
| carContainer.addChild(carPool[i][0]); |
|
|
| let laneChange = parseInt(carLog[4]) + 1; |
| carPool[i][1].position.set(position[0], position[1]); |
| carPool[i][1].rotation = carPool[i][0].rotation; |
| carPool[i][1].texture = turnSignalTextures[laneChange]; |
| carPool[i][1].width = length; |
| carPool[i][1].height = width; |
| turnSignalContainer.addChild(carPool[i][1]); |
| } |
| nodeCarNum.innerText = carLogs.length-1; |
| nodeTotalStep.innerText = totalStep; |
| nodeCurrentStep.innerText = cnt+1; |
| nodeProgressPercentage.innerText = (cnt / totalStep * 100).toFixed(2) + "%"; |
| if (statsFile != "") { |
| if (withRange) nodeRange.value = stats[step][1]; |
| nodeStats.innerText = stats[step][0].toFixed(2); |
| } |
| } |
|
|
| |
| |
| |
| let chart = { |
| max_steps: 3600, |
| data: { |
| labels: [], |
| series: [[]] |
| }, |
| options: { |
| showPoint: false, |
| lineSmooth: false, |
| axisX: { |
| showGrid: false, |
| showLabel: false |
| } |
| }, |
| init : function(title, series_cnt, max_step){ |
| document.getElementById("chart-title").innerText = title; |
| this.max_steps = max_step; |
| this.data.labels = new Array(this.max_steps); |
| this.data.series = []; |
| for (let i = 0 ; i < series_cnt ; ++i) |
| this.data.series.push([]); |
| this.chart = new Chartist.Line('#chart', this.data, this.options); |
| }, |
| addData: function (value) { |
| for (let i = 0 ; i < value.length; ++i) { |
| this.data.series[i].push(value[i]); |
| if (this.data.series[i].length > this.max_steps) { |
| this.data.series[i].shift(); |
| } |
| } |
| this.chart.update(); |
| }, |
| clear: function() { |
| for (let i = 0 ; i < this.data.series.length ; ++i) |
| this.data.series[i] = []; |
| }, |
| ptr: 0 |
| }; |
|
|