td2-visualizer/src/components/map/object-renderers/TrackRenderer.js
dominik-korsa 2b94c0efaa
Align the view to a straight track on double click
This feature will be useful when exporting the visualization to an SVG file
2025-07-19 16:33:43 +02:00

181 lines
5.8 KiB
JavaScript

import React, { useContext } from "react";
import SettingsContext from "../../../contexts/SettingsContext";
import Constants from "../../../helpers/constants";
import { ElectrificationStatus } from "../../../model/electrification-status";
import MiscHelper from "../../../helpers/miscHelper";
import { setHoveredTrack, unsetHoveredTrack } from "../../../services/trackHoverInfoService";
import ZoomPanContext from "../../../contexts/ZoomPanContext";
export default function TrackRenderer(props) {
const { object } = props;
const { trackColorMode } = useContext(SettingsContext);
const {alignView} = useContext(ZoomPanContext);
const onAlign = (event) => {
alignView(object.rot.y, event);
};
return (
<MemoizedTrackRenderer
object={object}
trackColorMode={trackColorMode}
onAlign={onAlign}/>
);
}
const MemoizedTrackRenderer = React.memo(StatelessTrackRenderer);
function StatelessTrackRenderer(props) {
const { object, trackColorMode , onAlign} = props;
if (object.points.start.distanceSq(object.points.end) < 0.001) {
return null;
}
const onMouseEnter = () => {
setHoveredTrack(object);
};
const onMouseLeave = () => {
unsetHoveredTrack(null);
};
const onClick = (event) => {
if (object.type !== 'StandardTrack' || object.r !== 0) return;
if (event.detail === 2) {
event.preventDefault();
onAlign(event);
}
};
const path = getTrackPath(object);
const color = getTrackColor(object, trackColorMode);
const defs = getTrackDefs(object, trackColorMode);
return (
<g id={`track-${object.id}`}>
{defs}
<path
d={path}
stroke={color}
className="track-unscaled"
/>
<path
d={path}
stroke={color}
className="track"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={onClick}/>
</g>
);
}
function getTrackPath(object) {
switch (object.type) {
case "StandardTrack":
case "PointTrack":
case "RouteTrack":
const [x1, y1] = object.points.start.toSVGCoords();
const [x2, y2] = object.points.end.toSVGCoords();
const ra = Math.abs(object.r);
if (object.r < 0) {
return `M${x1},${y1} A${ra} ${ra} 0 0 1 ${x2},${y2}`;
} else if (object.r > 0) {
return `M${x2},${y2} A${ra} ${ra} 0 0 1 ${x1},${y1}`;
} else {
return `M${x1},${y1} L${x2},${y2}`;
}
case "BezierTrack":
const [bx1, by1] = object.points.start.toSVGCoords();
const [bcx1, bcy1] = object.points.control1.toSVGCoords();
const [bx2, by2] = object.points.end.toSVGCoords();
const [bcx2, bcy2] = object.points.control2.toSVGCoords();
return `M${bx1},${by1} C${bcx1},${bcy1} ${bcx2},${bcy2} ${bx2},${by2}`;
default:
console.warn(`TrackRenderer: Unsupported track type: ${object.type}`);
}
}
function getTrackColor(object, trackColorMode) {
const modeDef = Constants.trackColorModes[trackColorMode];
switch (trackColorMode) {
case "standard":
default:
if(object.prefab_name && object.prefab_name.includes('trans-mat'))
return modeDef.options['invisible'][0];
return modeDef.options[modeDef.optionDefault][0];
case "electrification":
switch(object.electrificationStatus) {
case ElectrificationStatus.NOT_CHECKED:
return modeDef.options['not-checked'][0];
case ElectrificationStatus.NON_ELECTRIFIED:
return modeDef.options['non-electrified'][0];
case ElectrificationStatus.ELECTRIFIED:
return modeDef.options['electrified'][0];
case ElectrificationStatus.CONFLICT:
return modeDef.options['conflict'][0];
default:
return modeDef.options[modeDef.optionDefault][0];
}
case "type":
switch (object.type) {
case "StandardTrack":
return modeDef.options['standard-track'][0];
case "PointTrack":
return modeDef.options['point-track'][0];
case "RouteTrack":
return modeDef.options['route-track'][0];
case "BezierTrack":
return modeDef.options['bezier-track'][0];
default:
return modeDef.options[modeDef.optionDefault][0];
}
case "slope":
// do not use track gradient unless the slope is different on both ends
if (object.start_slope === object.end_slope) {
return MiscHelper.getTrackGradient(modeDef.gradient, Math.abs(object.start_slope));
}
return `url(#track-slope-${object.id})`;
case "max-speed":
if (!object.maxspeed) {
if (object.type === 'RouteTrack') return modeDef.options['unknown'][0];
return modeDef.options['derail'][0];
}
return MiscHelper.getTrackGradient(modeDef.gradient, object.maxspeed);
}
}
function getTrackDefs(object, trackColorMode) {
if (trackColorMode !== "slope") return null;
if (object.start_slope === object.end_slope) return null;
const [x1, y1] = object.points.start.toSVGCoords();
const [x2, y2] = object.points.end.toSVGCoords();
const gradId = `track-slope-${object.id}`;
const startColor = MiscHelper.getTrackGradient(Constants.trackColorModes['slope'].gradient, Math.abs(object.start_slope));
const endColor = MiscHelper.getTrackGradient(Constants.trackColorModes['slope'].gradient, Math.abs(object.end_slope));
return (
<defs>
<linearGradient
id={gradId}
gradientUnits="userSpaceOnUse"
x1={x1}
y1={y1}
x2={x2}
y2={y2}
>
<stop offset="0%" stopColor={startColor} />
<stop offset="100%" stopColor={endColor} />
</linearGradient>
</defs>
);
}