Rewrite marker culling for signage
This commit is contained in:
1 parent
439dec2efd
commit
2298904f2f
2 files changed
+119
-66
No files matched your search
@@ -9,7 +9,7 @@ const bezierGradeResolution = 80;
|
|||||||
const bezierLengthResolution = 80;
|
const bezierLengthResolution = 80;
|
||||||
const bezierCurvatureResolution = 80;
|
const bezierCurvatureResolution = 80;
|
||||||
const padding = 500;
|
const padding = 500;
|
||||||
const zoomFactor = 1.2;
|
const zoomFactor = 1.1;
|
||||||
const trackWidth = {
|
const trackWidth = {
|
||||||
base: 110,
|
base: 110,
|
||||||
min: 3,
|
min: 3,
|
||||||
@@ -18,10 +18,19 @@ const trackWidth = {
|
|||||||
const signSize = {
|
const signSize = {
|
||||||
base: 4,
|
base: 4,
|
||||||
min: 0.05,
|
min: 0.05,
|
||||||
max: 2.4
|
max: 1.8
|
||||||
}
|
}
|
||||||
|
|
||||||
const scaleCullBaseDistance = 500;
|
const scaleCullBaseDistance = 500;
|
||||||
|
|
||||||
|
// Lower values make shorter track sections show their marker further out.
|
||||||
|
const scaleCullBaseSectionLength = 22;
|
||||||
|
const scaleCullBaseSectionLengthSpeedboard = 100;
|
||||||
|
|
||||||
|
// How long a track section can go without changing before another marker is placed. Increases readability
|
||||||
|
const maxSectionLength = 700;
|
||||||
|
|
||||||
|
// Zoom levels at which different markers get swapped in/out.
|
||||||
const scaleCullThresholds = [
|
const scaleCullThresholds = [
|
||||||
{scale:1},
|
{scale:1},
|
||||||
{scale:2},
|
{scale:2},
|
||||||
@@ -48,6 +57,7 @@ map_markers.setAttribute('id', 'map_markers');
|
|||||||
map.appendChild(map_markers);
|
map.appendChild(map_markers);
|
||||||
|
|
||||||
let mapData = [];
|
let mapData = [];
|
||||||
|
let trackNameCounting = {};
|
||||||
let poiData = [];
|
let poiData = [];
|
||||||
let tracks = [];
|
let tracks = [];
|
||||||
let markers = [];
|
let markers = [];
|
||||||
@@ -196,13 +206,17 @@ function fillSvg(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
sortTracks();
|
sortTracks();
|
||||||
cleanMarkers();
|
|
||||||
drawMarkers();
|
drawMarkers();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Insert individual rail paths, as well as creating signage data for each path*/
|
/** Insert individual rail paths, as well as creating signage data for each path*/
|
||||||
function drawTracks(bezierData){
|
function drawTracks(bezierData){
|
||||||
const trackCol = Utils.rgba2hex(Math.random(),Math.random(),Math.random(),1.0);
|
const trackCol = Utils.rgba2hex(Math.random(),Math.random(),Math.random(),1.0);
|
||||||
|
if(trackNameCounting[bezierData.name] == null){
|
||||||
|
trackNameCounting[bezierData.name] = 0;
|
||||||
|
}else{
|
||||||
|
trackNameCounting[bezierData.name]++;
|
||||||
|
}
|
||||||
for(let i=0; i+1<bezierData.points.length; i++){
|
for(let i=0; i+1<bezierData.points.length; i++){
|
||||||
const bezStart = bezierData.points[i][0];
|
const bezStart = bezierData.points[i][0];
|
||||||
const bezHandle1 = bezierData.points[i][2];
|
const bezHandle1 = bezierData.points[i][2];
|
||||||
@@ -212,55 +226,116 @@ function drawTracks(bezierData){
|
|||||||
let genPath = 'M ';
|
let genPath = 'M ';
|
||||||
genPath += `${bezStart.x} ${bezStart.z} `;
|
genPath += `${bezStart.x} ${bezStart.z} `;
|
||||||
genPath += `C ${bezHandle1.x} ${bezHandle1.z} ${bezHandle2.x} ${bezHandle2.z} ${bezEnd.x} ${bezEnd.z} `;
|
genPath += `C ${bezHandle1.x} ${bezHandle1.z} ${bezHandle2.x} ${bezHandle2.z} ${bezEnd.x} ${bezEnd.z} `;
|
||||||
let grade = Bezier.estimateGrade(bezStart, bezHandle1, bezHandle2, bezEnd, bezierGradeResolution);
|
bezierData.points[i].grade = Bezier.estimateGrade(bezStart, bezHandle1, bezHandle2, bezEnd, bezierGradeResolution);
|
||||||
let gradeClass = Utils.gradeToClass(grade);
|
bezierData.points[i].gradeClass = Utils.gradeToClass(bezierData.points[i].grade);
|
||||||
let length = Bezier.estimateLength(bezStart, bezHandle1, bezHandle2, bezEnd, bezierLengthResolution);
|
bezierData.points[i].bezLength = Bezier.estimateLength(bezStart, bezHandle1, bezHandle2, bezEnd, bezierLengthResolution);
|
||||||
let curvature = Bezier.estimateCurvature(bezStart, bezHandle1, bezHandle2, bezEnd, bezierCurvatureResolution);
|
bezierData.points[i].curvature = Bezier.estimateCurvature(bezStart, bezHandle1, bezHandle2, bezEnd, bezierCurvatureResolution);
|
||||||
|
bezierData.points[i].postedSpeed = Utils.radiusToSpeed(1/bezierData.points[i].curvature);
|
||||||
newBezier.setAttribute('d', genPath);
|
newBezier.setAttribute('d', genPath);
|
||||||
|
|
||||||
newBezier.classList.add('rail');
|
newBezier.classList.add('rail');
|
||||||
newBezier.classList.add(gradeClass ?? 'grade_flat');
|
newBezier.classList.add(bezierData.points[i].gradeClass != null ? 'grade_'+bezierData.points[i].gradeClass : 'grade_flat');
|
||||||
|
|
||||||
//newBezier.setAttribute('stroke', Utils.rgba2hex(grade*50*0,(bezStart.y-113)/(252-113),length*0.01*0,1.0));
|
//newBezier.setAttribute('stroke', Utils.rgba2hex(bezierData.points[i].grade*50*0,(bezStart.y-113)/(252-113),bezierData.points[i].bezLength*0.01*0,1.0));
|
||||||
//newBezier.setAttribute('stroke', Utils.rgba2hex((1/curvature) * 100,0,0,1.0));
|
//newBezier.setAttribute('stroke', Utils.rgba2hex((1/bezierData.points[i].curvature) * 100,0,0,1.0));
|
||||||
//newBezier.setAttribute('stroke', trackCol);
|
//newBezier.setAttribute('stroke', trackCol);
|
||||||
//newBezier.setAttribute('data-grade', grade);
|
//newBezier.setAttribute('data-grade', bezierData.points[i].grade);
|
||||||
//newBezier.setAttribute('data-length', length);
|
//newBezier.setAttribute('data-length', bezierData.points[i].bezLength);
|
||||||
//newBezier.setAttribute('data-maxradius', 1/curvature);
|
//newBezier.setAttribute('data-maxradius', 1/bezierData.points[i].curvature);
|
||||||
tracks.push([(bezStart.y+bezEnd.y)*0.5, newBezier]);
|
tracks.push([(bezStart.y+bezEnd.y)*0.5, newBezier]);
|
||||||
|
|
||||||
// Curve tooltip
|
// Curve tooltip
|
||||||
let title = document.createElementNS(svgns, 'title');
|
let title = document.createElementNS(svgns, 'title');
|
||||||
title.innerHTML = [
|
title.innerHTML = [
|
||||||
`"${bezierData.name}"`,
|
`"${bezierData.name}"${trackNameCounting[bezierData.name] > 0 ? ' #'+trackNameCounting[bezierData.name] : ''} Sec${i}`,
|
||||||
`Max Grade: ${Math.round(grade*1000)/10}%`,
|
`Max Grade: ${Math.round(bezierData.points[i].grade*1000)/10}%`,
|
||||||
`Min Radius: ${Math.round(1/curvature)} meters`,
|
`Min Radius: ${Math.round(1/bezierData.points[i].curvature)} meters`,
|
||||||
`Top Speed: ${Utils.radiusToSpeed(1/curvature)} km/h`,
|
`Top Speed: ${Utils.radiusToSpeed(1/bezierData.points[i].curvature)} km/h`,
|
||||||
`Length: ${Math.round(length*10)/10} meters`,
|
`Length: ${Math.round(bezierData.points[i].bezLength*10)/10} meters`,
|
||||||
`Altitude: ${Math.round((bezStart.y+bezEnd.y)*0.5)} meters`,
|
`Altitude: ${Math.round((bezStart.y+bezEnd.y)*0.5)} meters`,
|
||||||
].join('\n');
|
].join('\n');
|
||||||
newBezier.appendChild(title);
|
newBezier.appendChild(title);
|
||||||
|
}
|
||||||
// Add Signage
|
|
||||||
if(grade > 0.001){
|
// Add Signage
|
||||||
markers.push({
|
let trackZoneData = {};
|
||||||
type: 'grade',
|
for(let i=0; i+1<bezierData.points.length; i++){
|
||||||
value: grade,
|
for(let zI=0; zI < ((i == bezierData.points.length-2) ? 2 : 1); zI++){
|
||||||
class: gradeClass,
|
let resetTzdGrade = () => {
|
||||||
position: Bezier.evaluatePoint(bezStart, bezHandle1, bezHandle2, bezEnd, 0.5),
|
trackZoneData.gradeClass = bezierData.points[i].gradeClass;
|
||||||
tangent: grade > 0
|
trackZoneData.gradeDirection = Math.sign(bezierData.points[i].grade);
|
||||||
?
|
trackZoneData.gradeCount = 0;
|
||||||
Bezier.evaluateVelocity(bezStart, bezHandle1, bezHandle2, bezEnd, 0.5)
|
trackZoneData.gradeSectionLength = bezierData.points[i].bezLength;
|
||||||
:
|
};
|
||||||
Bezier.evaluateVelocity(bezEnd, bezHandle2, bezHandle1,bezStart, 0.5)
|
let resetTzdSpeed = () => {
|
||||||
});
|
trackZoneData.prevSpeed = trackZoneData.speed;
|
||||||
|
trackZoneData.speed = bezierData.points[i].postedSpeed;
|
||||||
|
trackZoneData.speedCount = 0;
|
||||||
|
trackZoneData.speedSectionLength = bezierData.points[i].bezLength;
|
||||||
|
}
|
||||||
|
if(i <= 0){
|
||||||
|
resetTzdGrade();
|
||||||
|
resetTzdSpeed();
|
||||||
|
}else{
|
||||||
|
//Grade Signs
|
||||||
|
if(trackZoneData.gradeClass != bezierData.points[i].gradeClass || trackZoneData.gradeDirection != Math.sign(bezierData.points[i].grade) || trackZoneData.gradeSectionLength > maxSectionLength || zI > 0){
|
||||||
|
if(trackZoneData.gradeClass != null){
|
||||||
|
let offset = Math.floor((trackZoneData.gradeCount+1)/2)+1;
|
||||||
|
let center = trackZoneData.gradeCount % 2 ? 1 : 0.5;
|
||||||
|
let b1 = bezierData.points[(i+zI)-offset][0];
|
||||||
|
let b2 = bezierData.points[(i+zI)-offset][2];
|
||||||
|
let b3 = bezierData.points[(i+zI)+1-offset][1];
|
||||||
|
let b4 = bezierData.points[(i+zI)+1-offset][0];
|
||||||
|
markers.push({
|
||||||
|
type: 'grade',
|
||||||
|
value: bezierData.points[(i+zI)-offset].grade,
|
||||||
|
class: 'grade_'+trackZoneData.gradeClass,
|
||||||
|
position: Bezier.evaluatePoint(b1, b2, b3, b4, center),
|
||||||
|
sectionLength: trackZoneData.gradeSectionLength,
|
||||||
|
cullLevel: Math.round(scaleCullThresholds.length-1-trackZoneData.gradeSectionLength/scaleCullBaseSectionLength),
|
||||||
|
tangent: bezierData.points[(i+zI)].grade > 0
|
||||||
|
? Bezier.evaluateVelocity(b1, b2, b3, b4, center)
|
||||||
|
: Bezier.evaluateVelocity(b4, b3, b2, b1, center)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
resetTzdGrade();
|
||||||
|
}else{
|
||||||
|
trackZoneData.gradeCount++;
|
||||||
|
trackZoneData.gradeSectionLength += bezierData.points[i].bezLength;
|
||||||
|
}
|
||||||
|
//Speed Signs
|
||||||
|
if(trackZoneData.speed != bezierData.points[i].postedSpeed || trackZoneData.speedSectionLength > maxSectionLength || zI > 0){
|
||||||
|
let offset = Math.floor((trackZoneData.speedCount+1)/2)+1;
|
||||||
|
let center = trackZoneData.speedCount % 2 ? 0.75 : 0.25;
|
||||||
|
let b1 = bezierData.points[(i+zI)-offset][0];
|
||||||
|
let b2 = bezierData.points[(i+zI)-offset][2];
|
||||||
|
let b3 = bezierData.points[(i+zI)+1-offset][1];
|
||||||
|
let b4 = bezierData.points[(i+zI)+1-offset][0];
|
||||||
|
let cullLevel = Math.round(scaleCullThresholds.length-1-trackZoneData.gradeSectionLength/scaleCullBaseSectionLengthSpeedboard);
|
||||||
|
let speedDelta = Math.max(0,
|
||||||
|
(bezierData.points[i].postedSpeed != null && bezierData.points[i].postedSpeed < 100) ? bezierData.points[i].postedSpeed-trackZoneData.speed : 0,
|
||||||
|
(trackZoneData.prevSpeed != null && trackZoneData.prevSpeed < 100) ? trackZoneData.prevSpeed-trackZoneData.speed : 0
|
||||||
|
);
|
||||||
|
if(speedDelta >= 10 && trackZoneData.speed <= 30){
|
||||||
|
cullLevel = 0;
|
||||||
|
}else if(speedDelta >= 20){
|
||||||
|
cullLevel = 1;
|
||||||
|
}
|
||||||
|
markers.push({
|
||||||
|
type: 'speed',
|
||||||
|
value: trackZoneData.speed,
|
||||||
|
position: Bezier.evaluatePoint(b1, b2, b3, b4, center),
|
||||||
|
sectionLength: trackZoneData.speedSectionLength,
|
||||||
|
cullLevel: cullLevel,
|
||||||
|
tangent: Bezier.evaluateVelocity(b1, b2, b3, b4, center)
|
||||||
|
});
|
||||||
|
resetTzdSpeed();
|
||||||
|
}else{
|
||||||
|
trackZoneData.speedCount++;
|
||||||
|
trackZoneData.speedSectionLength += bezierData.points[i].bezLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
markers.push({
|
|
||||||
type: 'speed',
|
|
||||||
value: Utils.radiusToSpeed(1/curvature),
|
|
||||||
position: Bezier.evaluatePoint(bezStart, bezHandle1, bezHandle2, bezEnd, 0.5),
|
|
||||||
tangent: Bezier.evaluateVelocity(bezStart, bezHandle1, bezHandle2, bezEnd, 0.5)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,27 +349,6 @@ function sortTracks(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Assign signage to cull at certain zoom levels, reducing clutter and lag. */
|
|
||||||
function cleanMarkers(){
|
|
||||||
let roughDist = 0;
|
|
||||||
for(let i=0; i<markers.length; i++){
|
|
||||||
markers[i].nearestDistance = Infinity;
|
|
||||||
for(let j=0; j<markers.length; j++){
|
|
||||||
if(i != j && markers[i].type == markers[j].type){
|
|
||||||
roughDist = Vector.roughDistance(markers[i].position, markers[j].position);
|
|
||||||
if(markers[i].nearestDistance > roughDist){
|
|
||||||
markers[i].nearestDistance = roughDist;
|
|
||||||
markers[i].nearestMarker = markers[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
markers[i].cullLevel = 3;
|
|
||||||
for(let cullIndex=0; cullIndex<scaleCullThresholds.length; cullIndex++){
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create the signage */
|
/** Create the signage */
|
||||||
function drawMarkers(){
|
function drawMarkers(){
|
||||||
const gradeSigns = document.createElementNS(svgns, 'g');
|
const gradeSigns = document.createElementNS(svgns, 'g');
|
||||||
@@ -319,19 +373,21 @@ function drawMarkers(){
|
|||||||
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})`);
|
||||||
title.innerHTML = `${Math.round(markerData.value*1000)/10}%`;
|
title.innerHTML = `${Math.round(markerData.value*1000)/10}% Grade\nSection Length: ${markerData.sectionLength.toFixed(1)} meters`;
|
||||||
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');
|
||||||
title.innerHTML = `${Math.round(markerData.value)} km/h`;
|
title.innerHTML = `${Math.round(markerData.value)} km/h\nSection Length: ${markerData.sectionLength.toFixed(1)} meters`;
|
||||||
/*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})`);*/
|
||||||
marker.setAttribute('transform', `translate(${markerData.position.x} ${markerData.position.z})`);
|
marker.setAttribute('transform', `translate(${markerData.position.x} ${markerData.position.z})`);
|
||||||
speedSigns.appendChild(marker);
|
speedSigns.appendChild(marker);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(markerData.cullLevel) marker.classList.add(`cullThreshold_${markerData.cullLevel}`);
|
if(markerData.cullLevel){
|
||||||
|
marker.classList.add(`cullThreshold_${Math.min(scaleCullThresholds.length-1, markerData.cullLevel)}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-4
@@ -34,14 +34,11 @@ export function rgba2hex(r,g,b,a=1.0){
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function gradeToClass(grade){
|
export function gradeToClass(grade){
|
||||||
let result = '';
|
|
||||||
for(let j=0; j<gradeIncrements.length; j++){
|
for(let j=0; j<gradeIncrements.length; j++){
|
||||||
if(grade > gradeIncrements[j]){
|
if(grade > gradeIncrements[j]){
|
||||||
result = `grade_${gradeIncrements.length-(j+1)}`;
|
return gradeIncrements.length-(j+1);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(result) return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function radiusToSpeed(radius){
|
export function radiusToSpeed(radius){
|
||||||
|
|||||||
Reference in new issue
Block a user