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;