/** * Draw Road Network */ 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, // pink 0xb7ebe4, // cyan 0xdbebb7, // blue 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 = ""; } /** * Upload files */ 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); }); // replay callback }); // roadnet callback } 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; } /** * Draw Map */ 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); /** * Settings for Cars */ 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) { //var car = Sprite.fromImage("images/car.png") 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 () { // selectedDOM.innerText = ""; 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); // Draw Traffic Lights 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; } // Draw Roads 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]; // graphics.lineStyle(LANE_BORDER_WIDTH, LANE_BORDER_COLOR); // graphics.drawLine(pointA.moveAlong(pointAOffset, offset), pointB.moveAlong(pointBOffset, offset)); } 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); } } /* Chart */ 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 };