This commit is contained in:
Andras Schmelczer 2026-09-13 20:53:11 +01:00
commit 36a53f343e
6 changed files with 300431 additions and 0 deletions

View file

@ -0,0 +1,40 @@
:root {
--bg-color: #303030;
--text-color: #EEEEEE;
}
html {
height: 100%;
}
body {
margin: 0;
background-color: var(--bg-color);
height: 100%;
}
h1, h2, h3, h4, h5, h6, p {
font-family: "Roboto", sans-serif;
font-weight: 100;
color: var(--text-color);
}
h1 {
font-size: 5em;
text-align: center;
margin-top: 0;
padding-top: 1em;
}
.flexContainer {
display: flex;
align-content: center;
align-items: center;
justify-content: center;
height: 100%;
}
@media (max-width: 800px) {
}

View file

@ -0,0 +1,66 @@
:root {
--bg-color: #303030; /*Ez a pulse háttérszíne, a canvasét jsben kell megváltoztatni */
--pulse-color: #AAAAAA;
}
.simulationContainer {
transform: matrix(0.87,-0.50,0.93,0.62,0,0);
-ms-transform: matrix(0.87,-0.50,0.93,0.62,0,0);
-webkit-transform: matrix(0.87,-0.50,0.93,0.62,0,0);
margin: 2em calc((50% - 350px) / 2);
}
.simulationContainer > .passedCarCount {
font-size: 2.5em;
width: 90%;
margin-top: 0.33em;
text-align: right;
text-shadow: -10px 10px 5px rgba(0, 0, 0, 0.35);
}
.simulationContainer > canvas {
background: linear-gradient(270deg, var(--bg-color) 0%, var(--bg-color) 40%, var(--pulse-color) 50%, var(--bg-color) 60%, var(--bg-color) 100%);
background-size: 600% 600%;
-webkit-animation: pulsing 10s ease infinite;
-moz-animation: pulsing 10s ease infinite;
animation: pulsing 10s ease infinite;
box-shadow: -15px 10px 5px rgba(0, 0, 0, 0.1);
}
@-webkit-keyframes pulsing {
0% {
background-position: 0% 51%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 51%;
}
}
@-moz-keyframes pulsing {
0% {
background-position: 0% 51%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 51%;
}
}
@keyframes pulsing {
0% {
background-position: 0% 51%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 51%;
}
}

BIN
Visualisation/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

23
Visualisation/index.html Normal file
View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>Simulation</title>
<link rel="stylesheet" type="text/css" href="css/visualiser.css">
<link rel="stylesheet" type="text/css" href="css/main.css">
<link href="https://fonts.googleapis.com/css?family=Roboto:100&amp;subset=latin-ext" rel="stylesheet">
<link rel="icon" type="image/png" href="favicon.png">
<meta name="theme-color" content="#303030">
</head>
<body>
<div class="flexContainer">
<div class="simulationContainer">
<canvas id="canvas_0"></canvas>
<h3 class="passedCarCount" id="canvas_0_text">0000</h3>
</div>
</div>
</body>
<script type="text/javascript" src="js/visualiser.js"></script>
<script type="text/javascript" src="js/main.js"></script>
</html>

5
Visualisation/js/main.js Normal file
View file

@ -0,0 +1,5 @@
//Visualiser.constructor(int width, int height, string canvasID, string textID, string serverAddress, bool shouldPulse, bool shouldDrawGrid);
const visualiser_0 = new Visualiser(300, 300, "canvas_0", "canvas_0_text", "http://localhost:8080/", true, false);
visualiser_0.start();

View file

@ -0,0 +1,521 @@
/*
2D vizualizáció a szimuláció állapotáról
Az utakon lévő autók számából találja ki, hogy milyen szélesek legyenek az utak a grafikán.
(Ezeket a határértékeket az intToVisual methodban lehet állítani.)
A normálisan müködő dolgok kék színűek, a nem jó dolgok piros színűek.
Ha egy úton túl sok autó van, azaz dugó alakult ki, akkor az út kék helyett piros színű lesz.
A pulzáló körök a jelzőlámpákat jelképezik, ha valid állapotban van egy lámpa (egyik irányba piros, másikba zöld, vagy mindegyik sárga),
akkor kék színű, ha invalid (minden más, pl összes piros, összes zöld), akkor piros színű lesz.
Minél pirosabb a megjelenítés annál rosszabb a helyzet.
A RestAPI-t használja, azon belül is a GET /all-t.
Szépen animált, nem túl erőforrásigényes. Ha mégis az lenne, akkor a shouldPulse = false értékkel célszerű inicializálni, ez nagy mértékben felgyorsítja.
A hibákat tűri, ha elveszti a kapcsolatot, amint lehet helyreállítja.
A resize-t szépen kezeli, animált átmenettel mérteződik újra.
Használható metódusok:
//Célszerű width = height-tal dolgozni, különben széthúzza, és az nem szép.
constructor(int width, int height, string canvasID, string textID, string serverAddress, bool shouldPulse, bool shouldDrawGrid);
//Egyszer kell meghívni az indításhoz, 2 setInterval van benne. Stop nincs.
void start();
//A canvas méretét kizárólag ezen keresztül változtassuk.
void resize(int width, int height);
Részletek:
Kifejezetten izgalmas része nincsen, az olvashatóságra törekedtem írás közben. Szóval előbbiek következménye, hogy szükség esetén
könnyű továbbfejleszteni.
A beállításokat a konstruktor kódjában lehet változtatni.
*/
class Visualiser {
constructor(width, height, canvasName, canvasTextName, serverAddress, shouldPulse, shouldDrawGrid) {
/* Külső referenciák */
this.canvas = document.getElementById(canvasName);
this.text = document.getElementById(canvasTextName);
this.serverAddress = serverAddress;
this.ctx = this.canvas.getContext("2d");
/**/
/* Beállítások, amiket könnyen lehet változtatni */
this.goodColor = "#4242FF";
this.badColor = "#9E1414";
this.bgColor = "rgb(55, 55, 55)";
this.strokeColor = "#EEEEEE";
//Egy négyzetrács hányad részét foglalja el egy adott szélességű útelem?
this.smallWidthQ = 0.25;
this.mediumWidthQ = 0.75;
this.largeWidthQ = 1.33; //lógjon túl rajta
//A lámpa pulzálásának méretei, szintén a cellák oldalhosszúságához mérve.
this.trafficlightRadiusMinQ = 0.75;
this.trafficlightRadiusMaxQ = 0.85;
this.trafficlightSpeedQ = 0.025;
//A lámpa körvonalának (és a gridnek) a vastagsága.
this.lineWidthQ = 0.073;
//s-ben, célszerű kissebnek lennie, mint this.ajaxUpdateInterval
this.roadGrowSpeed = 0.7;
/**/
/* Beállítások, amiket nem nagyon kell változtatni */
//Az utak topológiája ettől függ, velük együtt lehet csak megváltoztatni.
this.horizontalLineCount = 11;
this.verticalLineCount = 11;
this.FPS = 30;
//s-ben, az egész projektben 1s-es intervallumok vannak updatekre, szóval ez ahhoz illeszkedik.
this.ajaxUpdateInterval = 1;
this.shouldPulse = shouldPulse;
this.shouldDrawGrid = shouldDrawGrid;
/**/
/* Belső */
this.resize(width, height);
this.trafficlightRadiusCurrent = this.trafficlightRadiusMin;
if(!shouldPulse) this.canvas.style["animation"] = "none";
//Az útelemek animációjához kell, kezdő és végállapotokat tárol.
//startWidth: az útelem először rajzolt oldala, endWith: az utoljára rajzolt
this.roadElemWidths = [];
for(let x = 0; x < this.verticalLineCount; x++) {
this.roadElemWidths[x] = [];
for(let y = 0; y < this.horizontalLineCount; y++) {
this.roadElemWidths[x][y] = [];
for(let layer = 0; layer < 2; layer++)
this.roadElemWidths[x][y][layer] = {
//Lehetne külön class ehhez, de nem szépítene annyit, hogy megérje
startWidth: {
start: 0,
end: 0
},
endWith: {
start: 0,
end: 0
}
};
}
}
this.deltaTimeSinceAjax = 0;
/**/
}
//Az úton lévő autók számából kitalálja, hogy milyen széles legyen az út a grafikán
//A visual egy nem túl intuitív nevű struct, ami tartalmazza, hogy egy út milyen fajta elemekből áll, visual(szélesElemekSzáma, közepes, vékony);
intToVisual(int) {
if(int >= 5) this.ctx.fillStyle = this.badColor;
if(int <= 0)
return new visual(0, 0, 3);
if(int <= 1)
return new visual(0, 1, 2);
if(int <= 2)
return new visual(0, 2, 1);
if(int <= 3)
return new visual(0, 3, 0);
if(int <= 5)
return new visual(1, 2, 0);
if(int <= 7)
return new visual(2, 1, 0);
else
return new visual(3, 0, 0);
}
resize(width, height) {
this.width = width;
this.height = height;
this.canvas.width = this.width
this.canvas.height = this.height
this.deltaX = this.width / this.verticalLineCount;
this.deltaY = this.height / this.horizontalLineCount;
this.smallWidth = 0.5 * this.deltaX * this.smallWidthQ;
this.mediumWidth = 0.5 * this.deltaX * this.mediumWidthQ;
this.largeWidth = 0.5 * this.deltaX * this.largeWidthQ;
this.trafficlightRadiusMin = this.deltaX * this.trafficlightRadiusMinQ;
this.trafficlightRadiusMax = this.deltaX * this.trafficlightRadiusMaxQ;
this.trafficlightSpeed = this.deltaX * this.trafficlightSpeedQ;
this.ctx.lineWidth = this.deltaX * this.lineWidthQ;
this.ctx.strokeStyle = this.strokeColor;
}
start() {
this.getRoadStates();
let that = this;
setInterval(function() {
that.getRoadStates();
}, this.ajaxUpdateInterval * 1000);
setInterval(function() {
that.drawTraffic(false);
}, 1000 / this.FPS);
}
//Index vektor által jelölt cella bal felső sarkának koordinátáit adja vissza.
getOriginOfVec(indexVec) {
return new Vec2 (
this.deltaX * indexVec.x,
this.deltaY * indexVec.y
);
}
drawGrid() {
this.ctx.beginPath();
for(let i = 1; i < this.horizontalLineCount; i++) {
this.ctx.moveTo(0, i * this.deltaY);
this.ctx.lineTo(this.width, i * this.deltaY);
}
for(let i = 1; i < this.verticalLineCount; i++) {
this.ctx.moveTo(i * this.deltaX, 0);
this.ctx.lineTo(i * this.deltaX, this.height);
}
this.ctx.stroke();
}
//Egy cellát tölt fel
drawRoadElem(newStartWidth, newEndWith, isHorizontal, indexVec, isKey, isJamAtStart) {
//A kereszteződések közti utak duplán vannak rajzolva (egyszer egyik irány, aztán a másik), ezért külön rétegen tárolom az állapotukat
const layerIndex = isJamAtStart ? 0 : 1;
//Nincs külön tárolva, mert egységes mindegyik útra nézve
const currentStartWidth = this.deltaTimeSinceAjax < this.roadGrowSpeed ?
this.roadElemWidths[indexVec.x][indexVec.y][layerIndex].startWidth.start +
(this.roadElemWidths[indexVec.x][indexVec.y][layerIndex].startWidth.end -
this.roadElemWidths[indexVec.x][indexVec.y][layerIndex].startWidth.start)
/ this.roadGrowSpeed * this.deltaTimeSinceAjax :
this.roadElemWidths[indexVec.x][indexVec.y][layerIndex].startWidth.end;
const currentEndWith = this.deltaTimeSinceAjax < this.roadGrowSpeed ?
this.roadElemWidths[indexVec.x][indexVec.y][layerIndex].endWith.start +
(this.roadElemWidths[indexVec.x][indexVec.y][layerIndex].endWith.end -
this.roadElemWidths[indexVec.x][indexVec.y][layerIndex].endWith.start)
/ this.roadGrowSpeed * this.deltaTimeSinceAjax :
this.roadElemWidths[indexVec.x][indexVec.y][layerIndex].endWith.end;
//isKey"frame", csak ha igaz, akkor veszi figyelembe a start/endWidth-et, onnan folytatja, ahol most tart
if(isKey) {
this.roadElemWidths[indexVec.x][indexVec.y][layerIndex] = {
startWidth: {
start: currentStartWidth,
end: newStartWidth
},
endWith: {
start: currentEndWith,
end: newEndWith
}
};
}
let origin = this.getOriginOfVec(indexVec);
//szebb anti-aliasing, picit nagyobbra rajzolja, mint kéne, így nem lesznek indokolatlan vonalak
let d = 0.5;
this.ctx.beginPath();
if(isHorizontal) {
this.ctx.moveTo(origin.x - d, origin.y + this.deltaY / 2 - currentStartWidth);
this.ctx.lineTo(origin.x + this.deltaX + d, origin.y + this.deltaY / 2 - currentEndWith);
this.ctx.lineTo(origin.x + this.deltaX + d, origin.y + this.deltaY / 2 + currentEndWith);
this.ctx.lineTo(origin.x - d, origin.y + this.deltaY / 2 + currentStartWidth);
}
else {
this.ctx.moveTo(origin.x + this.deltaX / 2 + currentStartWidth, origin.y - d);
this.ctx.lineTo(origin.x + this.deltaX / 2 + currentEndWith, origin.y + this.deltaY + d);
this.ctx.lineTo(origin.x + this.deltaX / 2 - currentEndWith, origin.y + this.deltaY + d);
this.ctx.lineTo(origin.x + this.deltaX / 2 - currentStartWidth, origin.y - d);
}
this.ctx.fill();
}
//Egy egész utat lerajzol
drawRoad(road, visual, isKey) {
const startVec = road.startVec;
const isHorizontal = road.isHorizontal;
const isJamAtStart = road.isJamAtStart;
const smallLength = visual.small;
const mediumLength = visual.medium;
const largeLength = visual.large;
const length = largeLength + mediumLength + smallLength;
if(isJamAtStart) {
for(let i = 0; i < length; i++) {
if(i < largeLength)
this.drawRoadElem(this.largeWidth, this.largeWidth, isHorizontal, startVec, isKey, isJamAtStart);
else if(i == largeLength && i > 0)
this.drawRoadElem(this.largeWidth, this.mediumWidth, isHorizontal, startVec, isKey, isJamAtStart);
else if(i < largeLength + mediumLength)
this.drawRoadElem(this.mediumWidth, this.mediumWidth, isHorizontal, startVec, isKey, isJamAtStart);
else if(i == largeLength + mediumLength && i > 0)
this.drawRoadElem(this.mediumWidth, this.smallWidth, isHorizontal, startVec, isKey, isJamAtStart);
else
this.drawRoadElem(this.smallWidth, this.smallWidth, isHorizontal, startVec, isKey, isJamAtStart);
if(isHorizontal) startVec.x++;
else startVec.y++;
}
}
else {
if(isHorizontal) startVec.x += length - 1;
else startVec.y += length - 1;
for(let i = 0; i < length; i++) {
if(i < largeLength)
this.drawRoadElem(this.largeWidth, this.largeWidth, isHorizontal, startVec, isKey, isJamAtStart);
else if(i == largeLength && i > 0)
this.drawRoadElem(this.mediumWidth, this.largeWidth, isHorizontal, startVec, isKey, isJamAtStart);
else if(i < largeLength + mediumLength)
this.drawRoadElem(this.mediumWidth, this.mediumWidth, isHorizontal, startVec, isKey, isJamAtStart);
else if(i == largeLength + mediumLength && i > 0)
this.drawRoadElem(this.smallWidth, this.mediumWidth, isHorizontal, startVec, isKey, isJamAtStart);
else
this.drawRoadElem(this.smallWidth, this.smallWidth, isHorizontal, startVec, isKey, isJamAtStart);
if(isHorizontal) startVec.x--;
else startVec.y--;
}
}
}
drawTrafficlight(indexVec, isValid) {
const origin = this.getOriginOfVec(indexVec);
origin.x += this.deltaX / 2;
origin.y += this.deltaY / 2;
this.ctx.fillStyle = isValid ? this.goodColor : this.badColor;
this.trafficlightRadiusCurrent += this.trafficlightSpeed / this.FPS;
if(this.trafficlightRadiusCurrent > this.trafficlightRadiusMax) {
this.trafficlightRadiusCurrent = this.trafficlightRadiusMax;
this.trafficlightSpeed *= -1;
}
else if(this.trafficlightRadiusCurrent < this.trafficlightRadiusMin) {
this.trafficlightRadiusCurrent = this.trafficlightRadiusMin;
this.trafficlightSpeed *= -1;
}
this.ctx.beginPath();
this.ctx.arc(origin.x, origin.y, this.trafficlightRadiusCurrent, 0, Math.PI * 2);
this.ctx.fill();
this.ctx.stroke();
}
drawTraffic(isKey) {
this.deltaTimeSinceAjax += 1 / this.FPS;
this.ctx.fillStyle = this.bgColor;
this.ctx.fillRect(0, 0, this.width, this.height);
this.ctx.fillStyle = this.goodColor;
if(this.shouldDrawGrid)
this.drawGrid();
if(this.roads) {
/* Út topológia:
* (megjelenítésben ez balra van "forgatva" 45°-kal)
*
* 1 5
* V V
* 0 > < 2 4 > < 6
* ^ ^
* 3 7
*
* 13 9
* V V
* 12 > < 14 8 > < 10
* ^ ^
* 15 11
*/
//Az indexek nem az id-k
const roads = [
new road(new Vec2(0, 3), true, false, "NW", "W"),
new road(new Vec2(3, 0), false, false, "NW", "N"),
new road(new Vec2(4, 3), true, true, "NW", "E", true),
new road(new Vec2(4, 3), true, false, "NE", "W"),
new road(new Vec2(7, 0), false, false, "NE", "N"),
new road(new Vec2(8, 3), true, true, "NE", "E"),
new road(new Vec2(7, 4), false, true, "NE", "S", true),
new road(new Vec2(7, 4), false, false, "SE", "N"),
new road(new Vec2(8, 7), true, true, "SE", "E"),
new road(new Vec2(7, 8), false, true, "SE", "S"),
new road(new Vec2(0, 7), true, false, "SW", "W"),
new road(new Vec2(3, 4), false, true, "NW", "S", true),
new road(new Vec2(3, 4), false, false, "SW", "N"),
new road(new Vec2(4, 7), true, false, "SE", "W", true),
new road(new Vec2(4, 7), true, true, "SW", "E"),
new road(new Vec2(3, 8), false, true, "SW", "S"),
];
//Mivel a kereszteződések közt 2 utat rajzol egymásra, fontos, hogy azok azonos színűek legyenek
let shouldNotResetColor = false;
for(let i = 0; i < roads.length; i++) {
if(!shouldNotResetColor)
this.ctx.fillStyle = this.goodColor;
this.drawRoad(roads[i], this.intToVisual(this.roads[roads[i].secondaryDirection][roads[i].primaryDirection]), isKey);
shouldNotResetColor = roads[i].shouldNotResetColor;
}
//Lámpák rajzolása
const tlVecs = [new Vec2(3, 3), new Vec2(7, 3), new Vec2(7, 7), new Vec2(3, 7)];
const directions = ["NW", "NE", "SE", "SW"];
for(let i = 0; i < 4; i++) {
//Ezeket tartom valid jelzőlámpa helyzeteknek
this.drawTrafficlight(
tlVecs[i],
(
this.trafficLights[directions[i]]["W"] == "red" &&
this.trafficLights[directions[i]]["E"] == "red" &&
this.trafficLights[directions[i]]["N"] == "green" &&
this.trafficLights[directions[i]]["S"] == "green"
) ||
(
this.trafficLights[directions[i]]["W"] == "green" &&
this.trafficLights[directions[i]]["E"] == "green" &&
this.trafficLights[directions[i]]["N"] == "red" &&
this.trafficLights[directions[i]]["S"] == "red"
) ||
(
this.trafficLights[directions[i]]["W"] == "yellow" &&
this.trafficLights[directions[i]]["E"] == "yellow" &&
this.trafficLights[directions[i]]["N"] == "yellow" &&
this.trafficLights[directions[i]]["S"] == "yellow"
)
);
}
}
//Minimálisan CPU igényes, inkább GPU-val kéne ilyeneket csinálni, de ez még belefér-
if(this.shouldPulse) {
let pixels = this.ctx.getImageData(0, 0, this.width, this.height);
for(let y = 0; y < this.height; y++) {
for(let x = 0; x < this.width; x++) {
if(
!( pixels.data[(this.width * y + x) * 4 + 0] == 55 &&
pixels.data[(this.width * y + x) * 4 + 1] == 55 &&
pixels.data[(this.width * y + x) * 4 + 2] == 55 )
) {
pixels.data[(this.width * y + x) * 4 + 3] = 200;
}
}
}
this.ctx.putImageData(pixels, 0, 0);
}
//Itt nullázom le a számlálót, az animáció és amiatt van erre szükség, hogy a jelenlegi szélességét nem tárolom az utaknak, ezért amikor update érkezik,
//tudnia kell, hogy jelenleg hol tart, azt eltárolja kezdetinek, és csak utána lehet lenullázni.
if(isKey)
this.deltaTimeSinceAjax = 0;
}
//a restApi /all-ját használja
//Átírja a szöveget is, ezt értelemszerűen lehet továbbbővíteni, más infókkal
getRoadStates() {
let xhttp = new XMLHttpRequest();
let that = this;
xhttp.onreadystatechange = function() {
//4-szer hívódik meg egy kérés alatt
if(this.readyState == 4) {
if(this.status == 200) {
const parsed = JSON.parse(this.responseText);
that.text.innerHTML = parsed.passedCarCount;
that.roads = parsed.roads;
that.trafficLights = parsed.trafficLights;
that.drawTraffic(true);
}
//Megfelelő mértékű hibakezelés
else if(this.status)
that.text.innerHTML = "Error: " + this.status;
else
that.text.innerHTML = "Timeout";
}
};
xhttp.open("GET", this.serverAddress + "all", true);
xhttp.send();
}
}; //Nem kell pontosvessző, de azért így jobban néz ki.
//Így gyorsabb létrehozni új vektorokat.
class Vec2 {
constructor(x, y) {
this.x = x;
this.y = y;
}
};
//A visual egy nem túl intuitív nevű struct, ami tartalmazza, hogy egy út milyen fajta elemekből áll.
class visual {
constructor(large, medium, small) {
this.large = large;
this.medium = medium;
this.small = small;
}
};
//Struct az utak tárolására, hossz nincs benne, az egységesen három, ez az intToVisualban van "megadva".
//Csak az utak tárolásában van igazi szerepe, később nem lesz nagyon kihasználva
class road {
constructor(startVec, isHorizontal, isJamAtStart, secondaryDirection, primaryDirection, shouldNotResetColor) {
this.startVec = startVec;
this.isHorizontal = isHorizontal;
this.isJamAtStart = isJamAtStart;
this.secondaryDirection = secondaryDirection;
this.primaryDirection = primaryDirection;
this.shouldNotResetColor = shouldNotResetColor;
}
};