From b5a62e5a88a0ff67595b5cd61428d8a99244d0fe Mon Sep 17 00:00:00 2001 From: ElementalAlchemist Date: Tue, 12 Nov 2024 22:04:23 -0600 Subject: [PATCH] Implement canvas-based bus system --- thrimbletrimmer/driveclock/db_dawn.png | Bin 196 -> 0 bytes thrimbletrimmer/driveclock/db_day.png | Bin 196 -> 0 bytes thrimbletrimmer/driveclock/db_dusk.png | Bin 196 -> 0 bytes thrimbletrimmer/driveclock/db_night.png | Bin 196 -> 0 bytes thrimbletrimmer/driveclock/db_stop.png | Bin 0 -> 173 bytes thrimbletrimmer/driveclock/drive.css | 0 thrimbletrimmer/driveclock/drive.html | 62 +----- thrimbletrimmer/driveclock/drive.js | 276 +++++++++++++++--------- thrimbletrimmer/driveclock/point.png | Bin 0 -> 397 bytes thrimbletrimmer/driveclock/stops.png | Bin 7397 -> 0 bytes 10 files changed, 186 insertions(+), 152 deletions(-) delete mode 100644 thrimbletrimmer/driveclock/db_dawn.png delete mode 100644 thrimbletrimmer/driveclock/db_day.png delete mode 100644 thrimbletrimmer/driveclock/db_dusk.png delete mode 100644 thrimbletrimmer/driveclock/db_night.png create mode 100644 thrimbletrimmer/driveclock/db_stop.png create mode 100644 thrimbletrimmer/driveclock/drive.css create mode 100644 thrimbletrimmer/driveclock/point.png delete mode 100644 thrimbletrimmer/driveclock/stops.png diff --git a/thrimbletrimmer/driveclock/db_dawn.png b/thrimbletrimmer/driveclock/db_dawn.png deleted file mode 100644 index 552dc35c887af27018c364e409409d5b5969def0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196 zcmeAS@N?(olHy`uVBq!ia0y~yU~d4jQ<#{6q}0Bn{y>T+z$e62Tfe=?*HJs~ShStV z?TH5rf#SuUE{-7;bKYKcJ(kE=; z_rRj@F^G}ZZXzzDUNGlCVI7E2vjWNp%R&Y2@P9AQZ^#Jq^8^{~>FVdQ&MBb@01;v| AssI20 diff --git a/thrimbletrimmer/driveclock/db_day.png b/thrimbletrimmer/driveclock/db_day.png deleted file mode 100644 index 5cf7d3a0c9b2b0b5faf2712cabc50091342dca78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196 zcmeAS@N?(olHy`uVBq!ia0y~yU~d4jQ<#{6q}0Bn{y>T+z$e7j@!X@Qlclxuj%_a! z+h4z&4Jcmh>EaktG3V`7M_vX)9+r)&l`7tw+!wx|=$km@EJy)CaA30cExSuBeZm%g z4=frVgBW@3CgL*c1#=D*)`18$E1-<9EL7kQ|M&9zhKw*jPms}`u6{1-oD!M<5P>#P diff --git a/thrimbletrimmer/driveclock/db_dusk.png b/thrimbletrimmer/driveclock/db_dusk.png deleted file mode 100644 index b58fb45917fc6ebf2dcf6ab60119e5728be819ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196 zcmeAS@N?(olHy`uVBq!ia0y~yU~d4jQ<#{6q}0Bn{y>T+z$e7@{-k|(CuM2p9otwM zYGD$&5hz~l>EaktG3V`7M_vX)9+r)&l`7tw+!wx|=$km@EE`Y(L2zKQ_bt0iEPcWj zeh(}fAA=Zq?Iz+f>IHKS6xM+VH7lTuuq;&I4*&P^{DzD$KTnX+p00i_>zopr06c&; Am;e9( diff --git a/thrimbletrimmer/driveclock/db_night.png b/thrimbletrimmer/driveclock/db_night.png deleted file mode 100644 index c444a53276f8f252ce869ed69771edd918e70bdc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196 zcmeAS@N?(olHy`uVBq!ia0y~yU~d4jQ<#{6q}0Bn{y>T+z$e62NZ8CkK}tLCn3|Mm z*3vtwK=EQv7srr_Id88z@-i6muxwPVRPo;AzVQ7--^3|r*?_!@p - - - - - -
-
-
-
-
-
- - - - - + + + Bus Progress + + + + + + + diff --git a/thrimbletrimmer/driveclock/drive.js b/thrimbletrimmer/driveclock/drive.js index 788bde4..ab571cc 100644 --- a/thrimbletrimmer/driveclock/drive.js +++ b/thrimbletrimmer/driveclock/drive.js @@ -1,122 +1,198 @@ - -const PAGE_WIDTH = 1920; -const MINUTES_PER_PAGE = 60; -const POINT_WIDTH = PAGE_WIDTH * 8 * 60 / MINUTES_PER_PAGE; -const MILES_PER_PAGE = 45; -const BUS_POSITION_X = 93; -const BASE_ODO = 109.3; -const UPDATE_INTERVAL_MS = 5000 -const WUBLOADER_URL = ""; -const SKY_URLS = { - day: "db_day.png", - dawn: "db_dawn.png", - dusk: "db_dusk.png", - night: "db_night.png", -}; -const BUS_URLS = { - day: "bus_day.png", - dawn: "bus_day.png", - dusk: "bus_day.png", - night: "bus_night.png", +const COLORS = { + day: { + sky: "#41cee2", + ground: "#e5931b", + surface: "#b77616", + }, + dusk: { + sky: "#db92be", + ground: "#dd926a", + surface: "#b17555", + }, + night: { + sky: "#121336", + ground: "#30201a", + surface: "#261a15", + }, + dawn: { + sky: "#2b2f87", + ground: "#724d41", + surface: "#5b3e34", + }, }; -function setSkyElements(left, right, timeToTransition) { - const leftElement = document.getElementById("timeofday-left"); - const rightElement = document.getElementById("timeofday-right"); - const busElement = document.getElementById("bus"); +// The width from the left side of the bus image to the front of the bus +const BUS_FRONT_OFFSET = 73; - leftElement.style.backgroundImage = `url(${SKY_URLS[left]})`; - rightElement.style.backgroundImage = `url(${SKY_URLS[right]})`; +// Start time of each day phase +const DAY_START_MINUTES = 450; +const DUSK_START_MINUTES = 1140; +const NIGHT_START_MINUTES = 1200; +const DAWN_START_MINUTES = 400; - if (left === right) { - leftElement.style.width = "100%"; - } else { - const transitionPercent = timeToTransition / MINUTES_PER_PAGE; - leftElement.style.width = `${transitionPercent * 100}%` - } +const BUS_STOP_OFFSET = 6; +const POINT_OFFSET = 17; - bus.style.backgroundImage = `url(${BUS_URLS[left]})`; -} +// Bus stop positions are recorded in miles with the 0 position +// at route start. This array can be looped every point. +const BUS_STOP_POSITIONS = [1, 55.2, 125.4, 166.3, 233.9, 295.2]; -function nextSkyTransition(timeofday, clock) { - switch (timeofday) { - case "dawn": +// The default scaling factor is 20 seconds per pixel at max speed. +// This gives us +// - 3px per minute +// - 4px per mile +let scaleFactor = 1; + +function nextPhase(timeOfDay) { + switch (timeOfDay) { case "day": - return [19 * 60, "dusk"]; // 7pm + case "dawn": + return "dusk"; case "dusk": - return [20 * 60, "night"]; // 8pm + return "night"; case "night": - return [6 * 60 + 40, "dawn"]; // 6:40am + return "dawn"; } } -function setSky(timeofday, clock) { - const [transition, newSky] = nextSkyTransition(timeofday, clock); - // 1440 minutes in 24h, this code will return time remaining even if - // the transition is in the morning and we're currently in the evening. - const timeToTransition = (1440 + transition - clock) % 1440; - if (timeToTransition < MINUTES_PER_PAGE) { - // Transition on screen - setSkyElements(timeofday, newSky, timeToTransition); - } else { - // No transition on screen - setSkyElements(timeofday, timeofday, undefined); +function phaseStartTime(timeOfDay) { + switch (timeOfDay) { + case "day": + return DAY_START_MINUTES; + case "dusk": + return DUSK_START_MINUTES; + case "night": + return NIGHT_START_MINUTES; + case "dawn": + return DAWN_START_MINUTES; } } -function setOdo(odo) { - const distancePixels = PAGE_WIDTH * (odo - BASE_ODO) / MILES_PER_PAGE; - const offset = (BUS_POSITION_X - distancePixels) % POINT_WIDTH; +function drawBackground(context, timeOfDay, leftX, width) { + const skyColor = COLORS[timeOfDay].sky; + const groundColor = COLORS[timeOfDay].ground; + const surfaceColor = COLORS[timeOfDay].surface; - const stopsElement = document.getElementById("stops"); - stopsElement.style.backgroundPosition = `${offset}px 0px`; + context.fillStyle = COLORS[timeOfDay].sky; + context.fillRect(leftX, 0, width, 100); + context.fillStyle = COLORS[timeOfDay].surface; + context.fillRect(leftX, 80, width, 1); + context.fillStyle = COLORS[timeOfDay].ground; + context.fillRect(leftX, 81, width, 7); + context.fillRect(leftX, 89, width, 3); + context.fillRect(leftX, 94, width, 2); + context.fillRect(leftX, 99, width, 1); } -async function update() { - const busDataResponse = await fetch(`${WUBLOADER_URL}/thrimshim/bus/buscam`); - if (!busDataResponse.ok) { - return; - } - const busData = await busDataResponse.json(); - console.log("Got data:", busData); - setOdo(busData.odometer); - setSky(busData.timeofday, busData.clock_minutes); -} +async function drawRoad() { + const busDataResponse = await fetch("/thrimshim/bus/buscam"); + if (!busDataResponse.ok) { + return; + } + const busData = await busDataResponse.json(); + + const canvas = document.getElementById("road"); + if (!canvas.getContext) { + return; + } + const context = canvas.getContext("2d"); + + // Clear the previous canvas before starting + context.clearRect(0, 0, 1920, 100); + + const currentTime = busData.clock_minutes; + const distance = busData.odometer; + const timeOfDay = busData.timeofday; + + drawBackground(context, timeOfDay, 0, BUS_FRONT_OFFSET); -// Initial conditions, before the first refresh finishes -setSky("day", 7 * 60); -setOdo(BASE_ODO); - -// Testing mode. Set true to enable. -const test = false; -if (test) { - let h = 0; - // Set to how long 1h of in-game time should take in real time - const hourTimeMs = 1 * 1000; - // Set to how often to update the screen - const interval = 30; - setInterval(() => { - h += interval / hourTimeMs; - setOdo(BASE_ODO + 45 * h); - if (h < 19) { - setSky("day", 60 * h); - } else { - m = (h % 24) * 60; - let tod; - if (m < 6 * 60 + 40) { - tod = "night"; - } else if (m < 19 * 60) { - tod = "dawn"; - } else if (m < 20 * 60) { - tod = "dusk"; - } else { - tod = "night"; + const maxWidth = 1920 - BUS_FRONT_OFFSET; + + // TODO Figure out scaling factor + const startMinute = busData.clock_minutes; + const timeDuration = maxWidth / 3; + + let previousTime = startMinute; + let previousTimeOfDay = timeOfDay; + let remainingDuration = timeDuration; + let x = BUS_FRONT_OFFSET; + while (remainingDuration > 0) { + const nextTimeOfDay = nextPhase(previousTimeOfDay); + const nextStartTime = phaseStartTime(nextTimeOfDay); + + let thisDuration = nextStartTime - previousTime; + if (thisDuration < 0) { + thisDuration += 1440; + } + + // TODO Figure out scaling factor + const pixelWidth = thisDuration * 3; + drawBackground(context, previousTimeOfDay, x, pixelWidth); + + remainingDuration -= thisDuration; + previousTime = nextStartTime; + previousTimeOfDay = nextTimeOfDay; + x += pixelWidth; + } + + x = BUS_FRONT_OFFSET; + const currentPointProgress = distance % 360; + let distanceToNextPoint; + if (currentPointProgress <= 109.3) { + distanceToNextPoint = 109.3 - currentPointProgress; + } else { + distanceToNextPoint = 469.3 - currentPointProgress; + } + + // TODO Figure out scaling factor + x += distanceToNextPoint * 4; + const pointImage = new Image(); + pointImage.src = "point.png"; + context.drawImage(pointImage, x - POINT_OFFSET, 0); + while (x < maxWidth) { + // TODO Figure out scaling factor + x += 360 * 4; + context.drawImage(pointImage, x - POINT_OFFSET, 0); + } + + const distanceOnRoute = (distance - 109.3) % 360; + const busStopImage = new Image(); + busStopImage.src = "db_stop.png"; + // TODO Figure out scaling factor + let distanceTracked = distanceOnRoute - BUS_FRONT_OFFSET / 4; + if (distanceTracked < 0) { + distanceTracked += 720; + } + x = 0; + while (x < 1920) { + const distanceTrackedOnRoute = distanceTracked % 360; + let nextBusStopPosition = null; + for (const busStopPosition of BUS_STOP_POSITIONS) { + if (busStopPosition >= distanceTrackedOnRoute + 1) { + nextBusStopPosition = busStopPosition; + break; } - setSky(tod, m); } - }, interval); -} else { - // Do first update immediately, then every UPDATE_INTERVAL_MS - setInterval(update, UPDATE_INTERVAL_MS); - update(); + if (nextBusStopPosition === null) { + nextBusStopPosition = 360 + BUS_STOP_POSITIONS[0]; + } + const nextBusStopDistance = nextBusStopPosition - distanceTrackedOnRoute; + distanceTracked += nextBusStopDistance; + // TODO Figure out scaling factor + x += nextBusStopDistance * 4; + context.drawImage(busStopImage, x - BUS_STOP_OFFSET, 0); + } + + const busImage = new Image(); + if (timeOfDay === "night") { + busImage.src = "bus_night.png"; + } else { + busImage.src = "bus_day.png"; + } + context.drawImage(busImage, 0, 0); } + +window.addEventListener("DOMContentLoaded", (event) => { + drawRoad(); + setInterval(drawRoad, 10000); +}); diff --git a/thrimbletrimmer/driveclock/point.png b/thrimbletrimmer/driveclock/point.png new file mode 100644 index 0000000000000000000000000000000000000000..601c57ab0bdcb430f12d4cf6358c7fcf7e57c927 GIT binary patch literal 397 zcmeAS@N?(olHy`uVBq!ia0vp^N1P;jIbW(++GCit4OaVeV;+Jy2Y-@A-4dhqi|l zTqanV*5ymvmU*8rlMJpsVZ8d-^|lVBsH8gY$zf_5T}_`0=e61XemsrWB;<>~O7U~W zM@!8wyG%BGGV^=Jd7n3X)iUP)cmJ}s$zqYa%%WQzjp=@2uCG4+*;jw)(w>rdna=Ot zLZ950S8O)hJ4^0ko9To3$!(f8LsJnRn7_(&^6^ iCf=3;1|ceF=;(O&`&pHFaLbyNAQ?|rKbLh*2~7ZGXR9~> literal 0 HcmV?d00001 diff --git a/thrimbletrimmer/driveclock/stops.png b/thrimbletrimmer/driveclock/stops.png deleted file mode 100644 index 552266e2c402b7f9b1b2b802dda9b577d1ada7f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7397 zcmeAS@N?(olHy`uVBq!ia0y~yumLg|QaIRvWY@eGtqcq@Ydl>XLn`LHy|uCQ_6e!B zhxN}kFfZ@(VAo^OX^cL!ZGqUjs2f6Ogbp~#%;=f3t8M2&y;7FWNqeHWn&YeH9WiQ{ zXn*|kw1^4UZmm8)Eoj{i7LWxXP{A#w2w{QAXUkIA!HfpY@(hHyLs%7pmyr3T7a>)X zvh_QHM>?x0!qsK>hdpO(;Z7pmjE19`-~Vrl;y=7t->==dyTj4?4NHL6KDFGb?x78Q{25OCwsz_9T3fIfw6(~jM>v3tm79!am`4YivSbEW% z39jcu-H!l z!ynf3&*f{^c%7KB{-?Nq`{AF*Kh63)eap91n)Uq};j`g^x<{~A0Lh->%bS?N+yhgr zO%UP=+O`PZ29sZI2&qZgf`YFTE3Q5)`p5eJ;MG;s3~80`zZA@r!a&9!y!=o=aN!D8 zgvF$?PAqnDiRM4Nns0sIV~ffSH}lqS;3U-yg!+c>4=no1?bm<%eY;!V{Px>5ixF}p zvm$^la&tLSS6}@-=j(^XpHDxwIF`11Zl)B;CLuI<7s?#J@jhSPcD=tWkbZ2@mv2Q$ zqDr;wOMdfxyZHU9`~O+lR2J;t-}~5N-&H+?jU==s4yno;B=e~10D2UjGBd@&W zfldQ@*m4`mjzDO*aI#P)dH?>}Z?ml~iy?$bW=&eX7g8gUtP`wZg7aHj@^pfAHORc0 zU(dJKoB|^zI8f+E?)osV zO#b;>uX*+B!kl#AE2WiA;F@{WJ8$1T0?ez~r;k{^`up|g>B;Y1_Q7nJFx`y<#%5q> z*qM0~5-tg+WRbG=gsE}}UW8PwGD6GyvuV45+3XmwWcc{^)yn4GcYSlV-8Ps0HYdny zx5(FdbMH?Lnsq=Nrs2~?4y1rLFxRz)$oTAZhLq}H?xssfJnzN%h-S=#y|;2e*$7l_ z?2Wdq*>L;r9obh8mu~pJFMj@hjvAQZ3X&978uxaeEt5W8*wfMR_usd7KeM*)uKc11 z^932S{B2-3b#y$oxMv$ZS63$(3a;Jy`Ll)n{{R2}_-Zb7b=mpF8s>+F zrxz#;ti{0eRc=4MBjf+W@>uH?x%|rap8wk;{jvOw_B&W{*6{Q)Wwt2n{`N_I`r4$0 zfo~2se82R&qvD%1EPxVrnmEDOK>CdDX+8)y_}vABc*B}62;PB9FQj21z`)?KYR|WX zz0bO`v)0~a-821OXBA9N!JNVvwwo3Dp3z@(sSne=-wWRdUxYWmCbUzSC*;pA(z@Ki=*X@&!Lw!DGJ7(C_U zg{fg+IN`;Q;OX@46NF1O^k-bHJ`jBF;XBJud%r-F6i|aVrN*WKjrESoe%f#UFC%_` zUGMw*`;TwWzu&SKX7z`@3<|pefmiM!m2@z3iKb61{3b`VVoZe&d=&O9fhh;mRH8*F zZaGj|Z&&jZ?N&^M3hlS5F-3_Nu^f$h;sXm!WrNIU)T0Fn@!Zi|PkdNms2t7p7(qh3 zz-WO_d{|+q9N~4n*L%kATd&2{ZkyuzHyjo?4crv=D1Mw6tw>;jNiscJk&ql@2o0kZ i2_h^=W{vQQ