diff --git a/index.html b/index.html index 9f118d7..c2c4abe 100644 --- a/index.html +++ b/index.html @@ -15,7 +15,8 @@
- + +
diff --git a/index.js b/index.js index b6be92e..700e1a1 100644 --- a/index.js +++ b/index.js @@ -10,9 +10,13 @@ import * as Legend from './legend.js'; const mapContainer = document.getElementById('mapContainer'); const mapCanvas = document.getElementById('mapCanvas'); +const dynCanvas = document.getElementById('dynCanvas'); /** The main map drawing context. * @type {CanvasRenderingContext2D} */ const mapctx = mapCanvas.getContext('2d'); +/** The dynamic map drawing context. + * @type {CanvasRenderingContext2D} */ +const dynctx = dynCanvas.getContext('2d'); const mapSprites = document.getElementById('mapSprites'); const mapTerrain = document.getElementById('mapTerrain'); const zoomLevelDisplay = document.getElementById('zoomLevelDisplay'); @@ -23,7 +27,6 @@ document.addEventListener('DOMContentLoaded', async () => { await loadShopData('shop_items.json'); await loadTrackData('trackdata_dv.json'); await loadPoiData('poi_dv.json'); - MapData.markers.push(MapData.playerMarker); MapData.sortMarkers(); Utils.generateGradeArrows(); @@ -111,6 +114,9 @@ function mapNavigationSetup(){ if(ts && MapData.view.dirty){ redrawMap(); } + if(ts && MapData.view.dynDirty){ + redrawDynamics(); + } requestAnimationFrame(navUpdate); } @@ -298,6 +304,7 @@ function redrawMap(){ let spriteSize; let textMeasure; let tempMeasure = {}; + //Draw Static Markers for(const marker of MapData.markers){ curSprite = null; marker.visible = false; @@ -455,29 +462,6 @@ function redrawMap(){ mapctx.fill(); mapctx.globalAlpha = 1.0; break; - case 'player': - if(!(MapData.layers.poi)) break; - marker.visible = true; - curSprite = Config.spriteBounds.player; - spriteSize = 45; - - mapctx.translate(markerX, markerY); - mapctx.rotate(marker.rotation); - mapctx.translate(-0.5*spriteSize * MapData.view.pixelRatio, -0.5*spriteSize * MapData.view.pixelRatio); - mapctx.drawImage( - mapSprites, - curSprite.x, - curSprite.y, - curSprite.width, - curSprite.height, - 0, - 0, - spriteSize * MapData.view.pixelRatio, - spriteSize * MapData.view.pixelRatio - ); - mapctx.resetTransform(); - curSprite = null; - break; default: if(!MapData.layers.poi) break; marker.visible = true; @@ -499,5 +483,103 @@ function redrawMap(){ ); } } + MapData.view.dirty = false; + + redrawDynamics(); +} + +function redrawDynamics(){ + dynCanvas.width = mapCanvas.width; + dynCanvas.height = mapCanvas.height; + let curSprite; + let spriteSize; + for(const marker of MapData.dynamicMarkers){ + curSprite = null; + marker.visible = false; + if(marker.hidden || (marker.minZoom && MapData.view.scale < marker.minZoom) || (marker.maxZoom && MapData.view.scale > marker.maxZoom)) continue; + let markerX = MapData.view.convertX(marker.position.x); + let markerY = MapData.view.convertY(marker.position.z); + if(markerX > mapCanvas.width + Config.viewCullMargin || markerX < -Config.viewCullMargin || markerY > mapCanvas.height + Config.viewCullMargin || markerY < -Config.viewCullMargin) continue; // View Culling + switch(marker.type){ + case 'player': + if(!(MapData.layers.poi)) break; + marker.visible = true; + curSprite = Config.spriteBounds.player; + spriteSize = 45; + + dynctx.translate(markerX, markerY); + dynctx.rotate(marker.rotation); + dynctx.translate(-0.5 * spriteSize * MapData.view.pixelRatio, -0.5 * spriteSize * MapData.view.pixelRatio); + dynctx.drawImage( + mapSprites, + curSprite.x, + curSprite.y, + curSprite.width, + curSprite.height, + 0, + 0, + spriteSize * MapData.view.pixelRatio, + spriteSize * MapData.view.pixelRatio + ); + dynctx.resetTransform(); + curSprite = null; + break; + case 'car': + if(!(MapData.layers.poi)) break; + let scale = MapData.view.pixelRatio * MapData.view.scale; + let limitScaleRatio = (MapData.view.pixelRatio * Math.max(MapData.view.scale, 3)) / scale; + let width = marker.width * scale; + let length = marker.length * scale; + + marker.visible = true; + dynctx.beginPath(); + dynctx.translate(markerX, markerY); + dynctx.rotate(marker.rotation); + dynctx.translate(-0.5 * width, -0.5 * length); + dynctx.rect(0, 0, width, length); + dynctx.moveTo(0, width); + dynctx.lineTo(0.5 * width, 0); + dynctx.lineTo(width, width); + dynctx.moveTo(0.5 * width, 0); + dynctx.arc(0.5 * width, 0.5 * length, 15, -0.5*Math.PI, 1.5*Math.PI); + dynctx.resetTransform(); + dynctx.strokeStyle = '#fff'; + dynctx.lineWidth = 0.75 * scale * limitScaleRatio; + dynctx.miterLimit = 1.5; + dynctx.stroke(); + + dynctx.fillStyle = marker.isLoco ? '#e79830aa' : '#559bd4aa'; + dynctx.fill(); + dynctx.strokeStyle = '#000'; + dynctx.lineWidth = 0.3 * scale * limitScaleRatio; + dynctx.stroke(); + if(!marker.tooltipHitzone){ + marker.tooltipHitzone = { + radius: 15, + }; + } + break; + default: + if(!MapData.layers.poi) break; + marker.visible = true; + curSprite = Config.spriteBounds.unknown; + spriteSize = 30; + break; + } + if(curSprite != null){ + dynctx.drawImage( + mapSprites, + curSprite.x, + curSprite.y, + curSprite.width, + curSprite.height, + markerX-(0.5*spriteSize)*MapData.view.pixelRatio, + markerY-(0.5*spriteSize)*MapData.view.pixelRatio, + spriteSize*MapData.view.pixelRatio, + spriteSize*MapData.view.pixelRatio + ); + } + } + MapData.view.dynDirty = false; } \ No newline at end of file diff --git a/js/mapdata.js b/js/mapdata.js index 3c1805a..b77dc8e 100644 --- a/js/mapdata.js +++ b/js/mapdata.js @@ -8,6 +8,7 @@ import * as Config from './../config.js'; export let railTracks = []; export let markers = []; +export let dynamicMarkers = []; let visibleMarkers = []; @@ -15,10 +16,9 @@ let uniqueTrackNames = {}; export let testPoint = {x:0,y:0}; -let playerLocation = null; +let locationData = null; export let playerMarker = { type: 'player', - name: 'Player', position: {x:8192, y:200, z:8192}, rotation: 0.0, minZoom: 0.0, @@ -70,6 +70,7 @@ export let view = { view.y = -(mapContainer.clientHeight*0.5 - (matrix.height * 0.5 + matrix.minY)*view.scale); } view.dirty = false; + view.dynDirty = false; view.convertX = x => { return x * view.scale * view.pixelRatio + view.x * view.pixelRatio; @@ -103,7 +104,11 @@ export function sortTracks(){ } export function sortMarkers(){ - markers.sort((a,b) => { + markers.sort(markerSortFunction); +} + +function markerSortFunction(a, b){ + (a,b) => { if(a.type == 'player') return 1; if(b.type == 'player') return -1; if(a.type == b.type){ @@ -126,7 +131,7 @@ export function sortMarkers(){ if(a.type == 'demonstratorSpawnHint') return -1; if(b.type == 'demonstratorSpawnHint') return 1; return 0; - }); + } } export function sortShops(){ @@ -511,6 +516,32 @@ export function testTooltip(cursorX, cursorY){ let radius = 0; let width = 0; let height = 0; + for(const marker of dynamicMarkers){ + if(!marker.visible) continue; + if(marker.boundsCheck != null){ + if(marker.boundsCheck(cursorX, cursorY)) + finalTooltip = marker.tooltip ?? finalTooltip; + }else if(marker.tooltipHitzone && marker.tooltipHitzone.width && marker.tooltipHitzone.height){ + width = marker.tooltipHitzone.width * 0.5/view.scale; + height = marker.tooltipHitzone.height * 0.5/view.scale; + if( marker.position.x - cursorX + (marker.tooltipHitzone.offsetX ?? 0)/view.scale <= width + && marker.position.x - cursorX + (marker.tooltipHitzone.offsetX ?? 0)/view.scale >= -width + && marker.position.z - cursorY + (marker.tooltipHitzone.offsetY ?? 0)/view.scale <= height + && marker.position.z - cursorY + (marker.tooltipHitzone.offsetY ?? 0)/view.scale >= -height){ + finalTooltip = marker.tooltip ?? finalTooltip; + } + }else{ + if(marker.tooltipHitzone) + radius = (marker.tooltipHitzone.radius ?? Config.tooltipHitzone.default.radius) / view.scale; + else + radius = Config.tooltipHitzone.default.radius / view.scale; + if(Math.pow(marker.position.x - cursorX, 2) + Math.pow(marker.position.z - cursorY, 2) <= radius * radius){ + finalTooltip = marker.tooltip ?? finalTooltip; + } + } + } + if(finalTooltip != '') return finalTooltip; + for(const marker of markers){ if(!marker.visible) continue; if(marker.boundsCheck != null){ @@ -662,35 +693,58 @@ export function connectPlayerLocation(address, status){ console.log(`Connecting to game at ${address}`); let started = false; const endConnect = () => { - playerLocation = null; + locationData = null; status.value = 'Connect'; playerMarker.hidden = true; - view.dirty = true; + view.dynDirty = true; } const pingLocation = async () => { - if(playerLocation == null && started){ + if(locationData == null && started){ endConnect(); return; } status.value = 'Disconnect...'; try { const fetchRequest = await fetch(new Request(address), {signal: AbortSignal.timeout(5000)}); - if(!fetchRequest.ok || (playerLocation == null && started)){ + if(!fetchRequest.ok || (locationData == null && started)){ endConnect(); return; } status.value = 'Disconnect '; - playerLocation = await (fetchRequest).json(); + locationData = await (fetchRequest).json(); playerMarker.position = { - x: playerLocation.X, - y: playerLocation.Y, - z: playerLocation.Z, + x: locationData.x, + y: locationData.y, + z: locationData.z, }; - playerMarker.rotation = playerLocation.Rotation; + playerMarker.rotation = locationData.rotation; playerMarker.tooltip = `

Player

X: ${playerMarker.position.x.toFixed(2)}\nY: ${playerMarker.position.y.toFixed(2)}\nZ: ${playerMarker.position.z.toFixed(2)}\nBearing ${(playerMarker.rotation * (180/Math.PI)).toFixed(1)} (${Utils.angleToCardinalDirection(playerMarker.rotation)})`; playerMarker.hidden = false; + + dynamicMarkers = []; + if(!locationData.cars) locationData.cars = []; + for(let car of locationData.cars){ + car.name = car.name.replaceAll('(Clone)', '').replaceAll('_',' ').replaceAll(/(Car|Loco)/g, ''); + dynamicMarkers.push({ + type: 'car', + name: 'car.id', + position: { + x: car.x, + y: car.y, + z: car.z + }, + length: car.length, + width: 2.0, + rotation: car.rotation, + guid: car.carGuid, + isLoco: car.isLoco, + tooltip: `

${car.id}

Type: ${car.name}\nLength: ${car.length.toFixed(1)}m`, + }); + } + dynamicMarkers.push(playerMarker); + started = true; - view.dirty = true; + view.dynDirty = true; setTimeout(pingLocation, locationUpdateRate); }catch(e){ endConnect(); @@ -702,5 +756,5 @@ export function connectPlayerLocation(address, status){ export function disconnectPlayerLocation(){ console.log('Disconnecting'); - playerLocation = null; + locationData = null; } \ No newline at end of file diff --git a/legend.js b/legend.js index a01067e..c7a656c 100644 --- a/legend.js +++ b/legend.js @@ -191,7 +191,7 @@ let settingEntries = [ label: 'Location Update Rate (seconds)', id: 'dropdown_locationFrequency', tooltip: 'Lower update intervals may cause poor performance', - state: '0.5', + state: '0.2', options: [ '0.05', '0.1', diff --git a/style.css b/style.css index dbc6c88..4858a56 100644 --- a/style.css +++ b/style.css @@ -164,6 +164,12 @@ a:visited{ background: linear-gradient(#444, #333); } +#mapCanvas, #dynCanvas{ + position: fixed; + width:100%; + height:100%; +} + .colorKey, .gradientKey{ border:1px #858585 solid; border-radius: 4px;