General Improvements
- Added map legend - Added marker toggles - Added links - Improved culling level switching
This commit is contained in:
1 parent
347e6a6068
commit
891fd69ab6
4 files changed
+201
-52
No files matched your search
+61
-4
@@ -5,6 +5,7 @@
|
|||||||
<link href="style.css" rel="stylesheet" type="text/css" media="all">
|
<link href="style.css" rel="stylesheet" type="text/css" media="all">
|
||||||
<title>Derail Valley Community Map</title>
|
<title>Derail Valley Community Map</title>
|
||||||
<script type="module" src="index.js"></script>
|
<script type="module" src="index.js"></script>
|
||||||
|
<script type="module" src="legend.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="mapContainer">
|
<div id="mapContainer">
|
||||||
@@ -22,6 +23,18 @@
|
|||||||
--grade_7: #c33577;
|
--grade_7: #c33577;
|
||||||
--grade_8: #a3008d;
|
--grade_8: #a3008d;
|
||||||
--grade_9: #690097;
|
--grade_9: #690097;
|
||||||
|
|
||||||
|
--signScale: 1;
|
||||||
|
--cullThreshold_state_0: none;
|
||||||
|
--cullThreshold_state_1: none;
|
||||||
|
--cullThreshold_state_2: none;
|
||||||
|
--cullThreshold_state_3: none;
|
||||||
|
--cullThreshold_state_4: none;
|
||||||
|
--cullThreshold_state_5: none;
|
||||||
|
--cullThreshold_state_6: none;
|
||||||
|
}
|
||||||
|
.sign{
|
||||||
|
transform: scale(var(--signScale));
|
||||||
}
|
}
|
||||||
.sign, .rail, .signText{
|
.sign, .rail, .signText{
|
||||||
cursor: default;
|
cursor: default;
|
||||||
@@ -104,6 +117,27 @@
|
|||||||
.gradeSign.grade_9{
|
.gradeSign.grade_9{
|
||||||
fill: var(--grade_9);
|
fill: var(--grade_9);
|
||||||
}
|
}
|
||||||
|
.cullThreshold_0{
|
||||||
|
display: var(--cullThreshold_state_0);
|
||||||
|
}
|
||||||
|
.cullThreshold_1{
|
||||||
|
display: var(--cullThreshold_state_1);
|
||||||
|
}
|
||||||
|
.cullThreshold_2{
|
||||||
|
display: var(--cullThreshold_state_2);
|
||||||
|
}
|
||||||
|
.cullThreshold_3{
|
||||||
|
display: var(--cullThreshold_state_3);
|
||||||
|
}
|
||||||
|
.cullThreshold_4{
|
||||||
|
display: var(--cullThreshold_state_4);
|
||||||
|
}
|
||||||
|
.cullThreshold_5{
|
||||||
|
display: var(--cullThreshold_state_5);
|
||||||
|
}
|
||||||
|
.cullThreshold_6{
|
||||||
|
display: var(--cullThreshold_state_6);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<style id="zoomStyle"></style>
|
<style id="zoomStyle"></style>
|
||||||
<defs>
|
<defs>
|
||||||
@@ -163,15 +197,38 @@
|
|||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div id="headerbar">
|
<div id="topleft">
|
||||||
<div>
|
<div id="headerbar" class="panel orange">
|
||||||
DV Community Map
|
<div>
|
||||||
|
DV Community Map
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="legend" style="display: flex;">
|
||||||
|
<div style="display:flex; flex-direction:column; width: 400px;">
|
||||||
|
<div class="panel">
|
||||||
|
Welcome to the WIP community map of <a href="https://www.derailvalley.com/" style="white-space:nowrap;">Derail Valley</a>, a resource for documenting and displaying all tracks and points of interest.
|
||||||
|
<hr>
|
||||||
|
Hover over tracks and objects for detailed information.
|
||||||
|
</div>
|
||||||
|
<div id="settingsPanel" class="panel">
|
||||||
|
<h3>Map Settings</h3>
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
<div class="panel">
|
||||||
|
<a href="https://github.com/PyroNicampt/DV-Community-Map">Source</a> •
|
||||||
|
<a href="https://github.com/PyroNicampt/DV-Community-Map/issues">Issue Tracker</a> •
|
||||||
|
<a href="https://derailvalley.wiki.gg/wiki/Derail_Valley_Wiki">DV Wiki</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="legendButton" class="panel orange" style="height: fit-content;"><svg height="25" viewBox="0 0 32 32"><path fill="currentColor" d="M31 16 20 31l-16 0L15 16 4 1l16 0Z"/></svg></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
<div id="footer" class="orange">
|
||||||
Map v0.1 alpha
|
Map v0.1 alpha
|
||||||
<br>
|
<br>
|
||||||
Updated for Game Build 99.3
|
Updated for Game Build 99.3
|
||||||
|
<br>
|
||||||
|
This tool is a fan work, Derail Valley owned by Altfuture
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -11,7 +11,7 @@ const bezierCurvatureResolution = 80;
|
|||||||
const padding = 500;
|
const padding = 500;
|
||||||
const zoomFactor = 1.2;
|
const zoomFactor = 1.2;
|
||||||
const trackWidth = {
|
const trackWidth = {
|
||||||
base: 90,
|
base: 110,
|
||||||
min: 3,
|
min: 3,
|
||||||
max: 90,
|
max: 90,
|
||||||
}
|
}
|
||||||
@@ -20,28 +20,20 @@ const signSize = {
|
|||||||
min: 0.05,
|
min: 0.05,
|
||||||
max: 2.4
|
max: 2.4
|
||||||
}
|
}
|
||||||
const zoomThresholds = [
|
const scaleCullThresholds = [
|
||||||
{zoom:1, distance:500},
|
{scale:1, distance:500},
|
||||||
{zoom:1.25, distance:200},
|
{scale:1.25, distance:200},
|
||||||
{zoom:1.5, distance:130},
|
{scale:1.5, distance:130},
|
||||||
{zoom:2.5, distance:80},
|
{scale:2.5, distance:80},
|
||||||
{zoom:5, distance:40},
|
{scale:5, distance:40},
|
||||||
{zoom:20, distance:20},
|
{scale:20, distance:20},
|
||||||
{zoom:60, distance:10},
|
{scale:60, distance:10},
|
||||||
];
|
];
|
||||||
const scaleCullThresholds = {
|
|
||||||
[1.0]: 500,
|
|
||||||
[1.25]: 200,
|
|
||||||
[1.5]: 130,
|
|
||||||
[2.5]: 80,
|
|
||||||
[5]: 40,
|
|
||||||
[20]: 20,
|
|
||||||
[60]: 10
|
|
||||||
}
|
|
||||||
|
|
||||||
// CODE
|
// CODE
|
||||||
const map = document.getElementById('map');
|
const map = document.getElementById('map');
|
||||||
const map_container = document.getElementById('mapContainer');
|
const map_container = document.getElementById('mapContainer');
|
||||||
|
const styleRoot = document.querySelector(':root');
|
||||||
const svgns = map.getAttribute('xmlns');
|
const svgns = map.getAttribute('xmlns');
|
||||||
|
|
||||||
const map_rails = document.createElementNS(svgns, 'g');
|
const map_rails = document.createElementNS(svgns, 'g');
|
||||||
@@ -133,23 +125,18 @@ function updateMapview(navData = {x:0, y:0, scale:1}){
|
|||||||
map.style.transform = `translate(${-navData.x}px, ${-navData.y}px) scale(${navData.scale})`;
|
map.style.transform = `translate(${-navData.x}px, ${-navData.y}px) scale(${navData.scale})`;
|
||||||
if(navData.scale !== previousMapScale){
|
if(navData.scale !== previousMapScale){
|
||||||
let dirtyScale = previousMapScale < 0;
|
let dirtyScale = previousMapScale < 0;
|
||||||
for(const cullThreshold in scaleCullThresholds){
|
for(const cullThreshold of scaleCullThresholds){
|
||||||
if(dirtyScale || (previousMapScale < cullThreshold && navData.scale > cullThreshold) || (previousMapScale > cullThreshold && navData.scale < cullThreshold)){
|
if(dirtyScale || (previousMapScale < cullThreshold.scale && navData.scale > cullThreshold.scale) || (previousMapScale > cullThreshold.scale && navData.scale < cullThreshold.scale)){
|
||||||
dirtyScale = true;
|
dirtyScale = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
map_rails.setAttribute('stroke-width', Utils.clamp(trackWidth.base/navData.scale, trackWidth.min, trackWidth.max));
|
map_rails.setAttribute('stroke-width', Utils.clamp(trackWidth.base/navData.scale, trackWidth.min, trackWidth.max));
|
||||||
|
styleRoot.style.setProperty('--signScale', Utils.clamp(signSize.base/navData.scale, signSize.min, signSize.max));
|
||||||
if(dirtyScale){
|
if(dirtyScale){
|
||||||
let zoomThresholdStyles = '';
|
for(let i=0; i<scaleCullThresholds.length; i++){
|
||||||
for(let i=0; i<zoomThresholds.length; i++){
|
styleRoot.style.setProperty(`--cullThreshold_state_${i}`, navData.scale >= scaleCullThresholds[i].scale ? 'initial' : 'none')
|
||||||
zoomThresholdStyles += `\n.zoomThreshold_${i}{display:${navData.scale > zoomThresholds[i].zoom ? 'unset' : 'none'}}`;
|
|
||||||
}
|
}
|
||||||
map.getElementById('zoomStyle').innerHTML = `
|
|
||||||
.sign{
|
|
||||||
transform: scale(${Utils.clamp(signSize.base/navData.scale, signSize.min, signSize.max)});
|
|
||||||
}${zoomThresholdStyles}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
previousMapScale = navData.scale;
|
previousMapScale = navData.scale;
|
||||||
}
|
}
|
||||||
@@ -322,7 +309,7 @@ function cleanMarkers(){
|
|||||||
/** Create the signage */
|
/** Create the signage */
|
||||||
function drawMarkers(){
|
function drawMarkers(){
|
||||||
const gradeSigns = document.createElementNS(svgns, 'g');
|
const gradeSigns = document.createElementNS(svgns, 'g');
|
||||||
gradeSigns.setAttribute('id', 'grade_Signs');
|
gradeSigns.setAttribute('id', 'grade_signs');
|
||||||
map_markers.appendChild(gradeSigns);
|
map_markers.appendChild(gradeSigns);
|
||||||
const speedSigns = document.createElementNS(svgns, 'g');
|
const speedSigns = document.createElementNS(svgns, 'g');
|
||||||
speedSigns.setAttribute('id', 'speed_signs');
|
speedSigns.setAttribute('id', 'speed_signs');
|
||||||
@@ -332,29 +319,23 @@ function drawMarkers(){
|
|||||||
let marker = document.createElementNS(svgns, 'g');
|
let marker = document.createElementNS(svgns, 'g');
|
||||||
let markerImg = document.createElementNS(svgns, 'use');
|
let markerImg = document.createElementNS(svgns, 'use');
|
||||||
let title = document.createElementNS(svgns, 'title');
|
let title = document.createElementNS(svgns, 'title');
|
||||||
let text = document.createElementNS(svgns, 'text');
|
|
||||||
marker.appendChild(markerImg);
|
marker.appendChild(markerImg);
|
||||||
marker.appendChild(text);
|
|
||||||
marker.appendChild(title);
|
marker.appendChild(title);
|
||||||
marker.setAttribute('transform', `translate(${markerData.position.x} ${markerData.position.z})`);
|
marker.setAttribute('transform', `translate(${markerData.position.x} ${markerData.position.z})`);
|
||||||
// Grade signs
|
// Grade signs
|
||||||
if(markerData.type == 'grade'){
|
if(markerData.type == 'grade'){
|
||||||
markerImg.setAttribute('href', '#gradeArrow');
|
markerImg.setAttribute('href', '#gradeArrow');
|
||||||
markerImg.classList.add('sign', 'gradeSign');
|
markerImg.classList.add('sign', 'gradeSign');
|
||||||
text.classList.add('signText', 'gradeSign');
|
|
||||||
if(markerData.class) markerImg.classList.add(markerData.class);
|
if(markerData.class) markerImg.classList.add(markerData.class);
|
||||||
const tanLen = Math.sqrt(markerData.tangent.x*markerData.tangent.x + markerData.tangent.z*markerData.tangent.z) * Math.sign(markerData.tangent.y);
|
const tanLen = Math.sqrt(markerData.tangent.x*markerData.tangent.x + markerData.tangent.z*markerData.tangent.z) * Math.sign(markerData.tangent.y);
|
||||||
const rot = Math.atan2(markerData.tangent.x/tanLen, -markerData.tangent.z/tanLen) * (180/Math.PI);
|
const rot = Math.atan2(markerData.tangent.x/tanLen, -markerData.tangent.z/tanLen) * (180/Math.PI);
|
||||||
marker.setAttribute('transform', `translate(${markerData.position.x} ${markerData.position.z}) rotate(${rot})`);
|
marker.setAttribute('transform', `translate(${markerData.position.x} ${markerData.position.z}) rotate(${rot})`);
|
||||||
text.setAttribute('transform', `rotate(${-rot})`);
|
|
||||||
title.innerHTML = `${Math.round(markerData.value*1000)/10}%`;
|
title.innerHTML = `${Math.round(markerData.value*1000)/10}%`;
|
||||||
text.innerHTML = `${Math.round(markerData.value*1000)/10}`;
|
|
||||||
gradeSigns.appendChild(marker);
|
gradeSigns.appendChild(marker);
|
||||||
// Speed Signs
|
// Speed Signs
|
||||||
}else if(markerData.type == 'speed'){
|
}else if(markerData.type == 'speed'){
|
||||||
markerImg.setAttribute('href', `#speedSign_${Math.round(markerData.value/10)}`);
|
markerImg.setAttribute('href', `#speedSign_${Math.round(markerData.value/10)}`);
|
||||||
markerImg.classList.add('sign', 'speedSign');
|
markerImg.classList.add('sign', 'speedSign');
|
||||||
text.classList.add('signText', 'speedSign');
|
|
||||||
title.innerHTML = `${Math.round(markerData.value)} km/h`;
|
title.innerHTML = `${Math.round(markerData.value)} km/h`;
|
||||||
/*let offset = Vector.normalize({x:markerData.tangent.z, y:0, z:-markerData.tangent.x});
|
/*let offset = Vector.normalize({x:markerData.tangent.z, y:0, z:-markerData.tangent.x});
|
||||||
marker.setAttribute('transform', `translate(${markerData.position.x + offset.x*100} ${markerData.position.z + offset.z*50})`);*/
|
marker.setAttribute('transform', `translate(${markerData.position.x + offset.x*100} ${markerData.position.z + offset.z*50})`);*/
|
||||||
@@ -362,10 +343,10 @@ function drawMarkers(){
|
|||||||
speedSigns.appendChild(marker);
|
speedSigns.appendChild(marker);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(let i=zoomThresholds.length-1; i>= 0; i--){
|
for(let i=scaleCullThresholds.length-1; i>= 0; i--){
|
||||||
if(markerData.nearestDist < zoomThresholds[i].distance || i==0){
|
if(markerData.nearestDist < scaleCullThresholds[i].distance || i==0){
|
||||||
title.innerHTML += `\n${markerData.nearestDist}`;
|
title.innerHTML += `\n${markerData.nearestDist}`;
|
||||||
marker.classList.add(`zoomThreshold_${i}`);
|
marker.classList.add(`cullThreshold_${i}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
let settingEntries = [
|
||||||
|
{
|
||||||
|
label: 'Signage',
|
||||||
|
id: 'toggle_signage',
|
||||||
|
state: true,
|
||||||
|
func: state =>{
|
||||||
|
document.getElementById('map_markers').style.display = state ? 'unset' : 'none';
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: 'Speed Signs',
|
||||||
|
id: 'toggle_speedSigns',
|
||||||
|
state: true,
|
||||||
|
func: state =>{
|
||||||
|
document.getElementById('speed_signs').style.display = state ? 'unset' : 'none';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Grade Arrows',
|
||||||
|
id: 'toggle_gradeSigns',
|
||||||
|
state: true,
|
||||||
|
func: state =>{
|
||||||
|
document.getElementById('grade_signs').style.display = state ? 'unset' : 'none';
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Track Color',
|
||||||
|
id: 'radio_trackColor',
|
||||||
|
options: [
|
||||||
|
'None',
|
||||||
|
'Grade',
|
||||||
|
],
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
const settingsPanel = document.getElementById('settingsPanel');
|
||||||
|
for(let thisSetting of settingEntries){
|
||||||
|
addSettingEntry(thisSetting, settingsPanel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function addSettingEntry(thisSetting, parent, indent=0){
|
||||||
|
thisSetting.divContainer = document.createElement('div');
|
||||||
|
thisSetting.divContainer.innerHTML = ' '.repeat(indent);
|
||||||
|
thisSetting.inputElement = document.createElement('input');
|
||||||
|
thisSetting.labelElement = document.createElement('label');
|
||||||
|
parent.appendChild(thisSetting.divContainer);
|
||||||
|
thisSetting.divContainer.appendChild(thisSetting.inputElement);
|
||||||
|
thisSetting.divContainer.appendChild(thisSetting.labelElement);
|
||||||
|
|
||||||
|
let settingType = thisSetting.id.split('_')[0];
|
||||||
|
switch(settingType){
|
||||||
|
case 'toggle':
|
||||||
|
thisSetting.inputElement.type = 'checkbox';
|
||||||
|
thisSetting.inputElement.id = thisSetting.id;
|
||||||
|
thisSetting.inputElement.checked = thisSetting.state;
|
||||||
|
thisSetting.labelElement.innerHTML = thisSetting.label;
|
||||||
|
thisSetting.labelElement.htmlFor = thisSetting.id;
|
||||||
|
|
||||||
|
if(thisSetting.func){
|
||||||
|
thisSetting.inputElement.addEventListener('input', e =>{
|
||||||
|
thisSetting.func(e.target.checked);
|
||||||
|
if(thisSetting.children){
|
||||||
|
for(const child of thisSetting.children){
|
||||||
|
document.getElementById(child.id).parentElement.style.display = e.target.checked ? '' : 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
thisSetting.divContainer.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(thisSetting.children){
|
||||||
|
for(let child of thisSetting.children){
|
||||||
|
addSettingEntry(child, parent, indent+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,16 +10,42 @@ html, body{
|
|||||||
width: 100vw;
|
width: 100vw;
|
||||||
font-family: "Noto Sans";
|
font-family: "Noto Sans";
|
||||||
font-variation-settings: "wght" 400;
|
font-variation-settings: "wght" 400;
|
||||||
|
color: #e9e9e9;
|
||||||
|
}
|
||||||
|
|
||||||
|
a{
|
||||||
|
color: #85c4f7;
|
||||||
|
}
|
||||||
|
a:visited{
|
||||||
|
color: #559bd4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orange{
|
||||||
|
color: #e79830;
|
||||||
|
}
|
||||||
|
|
||||||
|
#topleft{
|
||||||
|
position:fixed;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel{
|
||||||
|
width: fit-content;
|
||||||
|
border: 2px solid #b3b3b396;
|
||||||
|
background: linear-gradient(120deg, #2c3f58, #191e2e);
|
||||||
|
text-shadow: 2px 2px 5px #0003;
|
||||||
|
margin: 5px;
|
||||||
|
padding: 8px 13px;
|
||||||
|
border-radius: 10px;
|
||||||
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
#headerbar{
|
#headerbar{
|
||||||
position:fixed;
|
|
||||||
width: fit-content;
|
|
||||||
font-size: 30px;
|
|
||||||
background: linear-gradient(120deg, #3d5779, #121522);
|
background: linear-gradient(120deg, #3d5779, #121522);
|
||||||
border: 2px solid #b3b3b396;
|
font-size: 30px;
|
||||||
border-left: 0px;
|
border-left: 0px;
|
||||||
color: #e79830;
|
|
||||||
font-variation-settings: "wght" 500;
|
font-variation-settings: "wght" 500;
|
||||||
padding: 10px 30px 10px 30px;
|
padding: 10px 30px 10px 30px;
|
||||||
margin: 10px 0px;
|
margin: 10px 0px;
|
||||||
@@ -27,23 +53,22 @@ html, body{
|
|||||||
}
|
}
|
||||||
|
|
||||||
#footer{
|
#footer{
|
||||||
position:fixed;
|
position: fixed;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
bottom: 5px;
|
bottom: 5px;
|
||||||
font-size: 10px;
|
font-size: 12px;
|
||||||
color: #e79830;
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
#map{
|
#map{
|
||||||
transition: transform 100ms ease-out;
|
transition: transform 100ms ease-out;
|
||||||
transform-origin: 0% 0%;
|
transform-origin: 0% 0%;
|
||||||
position:relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mapContainer{
|
#mapContainer{
|
||||||
position:fixed;
|
position: fixed;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|||||||
Reference in new issue
Block a user