Overhead extended signals support

+Added overhead extended signals parsing and rendering
*Cleaned up SignalExtendedRenderer
*Dwarf signals now use SignalElementEnums.HeadPosition.DWARF instead of NO_POLE
*Minor bugfixes and tweaks related to extended signals
This commit is contained in:
izawartka 2025-06-29 21:29:58 +02:00
parent fd1f3107ee
commit 6bae0a31ea
8 changed files with 96 additions and 51 deletions

View File

@ -5,17 +5,19 @@ export default function SignalExtendedRenderer(props) {
const { object } = props;
const [x, y] = object.pos.toSVGCoords();
const rot = AngleHelper.normalizeDegAngle(object.rot.y);
const overheadRot = object.signal_elements.isOverhead() ? 180 : 0;
const baseRot = object.rot.y;
const rot = AngleHelper.normalizeDegAngle(overheadRot + baseRot);
const upsideDown = rot >= 180;
const upsideDownRot = upsideDown ? 90 : -90;
const anchor = upsideDown ? "start" : "end";
return (
<g className="extended-signal" transform={`translate(${x}, ${y}) rotate(${object.rot.y})`}>
<g className="extended-signal-icon">
<g className="extended-signal" transform={`translate(${x}, ${y}) rotate(${rot})`}>
<g className="extended-signal-icon" transform={`rotate(${-overheadRot})`}>
<SignalIcon object={object} />
</g>
<g transform={`translate(0, 1.5) rotate(${upsideDownRot})`}>
<g transform={`translate(0, 1) rotate(${upsideDownRot})`}>
<text x="0" y="0" textAnchor={anchor} dominantBaseline="middle">
{object.getPrintableSignalName()}
</text>
@ -28,25 +30,34 @@ function SignalIcon(props) {
const { object } = props;
const halfBaseWidth = getHalfBaseWidth(object);
const headOff = getHeadOffset(object);
const poleLength = getPoleLength(object, headOff);
const headOffsetX = getHeadOffsetX(object);
const poleLength = getPoleLength(object, headOffsetX);
const headOffsetY = getHeadOffsetY(object, poleLength);
return (
<g strokeWidth={0.2} strokeLinecap="round" strokeLinejoin="round">
{ getBase(halfBaseWidth) }
{ getPole(poleLength, headOff) }
{ getBars(object, poleLength, headOff) }
{ getHead(object, poleLength, headOff) }
{ getOverheadFrame(object) }
{ getPole(poleLength, headOffsetY, headOffsetX) }
{ getBars(object, headOffsetY, headOffsetX) }
{ getHead(object, headOffsetY, headOffsetX) }
</g>
);
}
function getHeadOffsetY(object, poleLength) {
if(object.signal_elements.isOverhead()) {
return object.signal_elements.units.length * 1.2 + 0.1;
}
return -poleLength;
}
function getHalfBaseWidth(object) {
const isDwarf = object.signal_elements.type === SignalElementsEnums.Type.DWARF;
const isDoubleDwarf = object.signal_elements.type === SignalElementsEnums.Type.DWARF_DOUBLE;
const isOverhead = object.signal_elements.headPosition === SignalElementsEnums.HeadPosition.OVERHEAD;
if(isOverhead) return 0;
if(object.signal_elements.isOverhead()) return 0;
if(isDwarf) return 0.6;
if(isDoubleDwarf) return 1.2;
@ -58,7 +69,13 @@ function getBase(halfBaseWidth) {
return <path d={`M ${-halfBaseWidth} 0 L ${halfBaseWidth} 0`} />;
}
function getHeadOffset(object) {
function getOverheadFrame(object) {
if(!object.signal_elements.isOverhead()) return null;
return <path d={`M -1 0 L 1 0 M -1 -.5 L -1 .5 M 1 -.5 L 1 .5`} />;
}
function getHeadOffsetX(object) {
const isTOP = object.signal_elements.type === SignalElementsEnums.Type.TOP;
if(isTOP) return 0;
@ -66,8 +83,8 @@ function getHeadOffset(object) {
switch (headPos) {
case SignalElementsEnums.HeadPosition.STANDARD:
case SignalElementsEnums.HeadPosition.NO_POLE:
case SignalElementsEnums.HeadPosition.OVERHEAD:
case SignalElementsEnums.HeadPosition.UNKNOWN:
case SignalElementsEnums.HeadPosition.DWARF:
default:
return 0;
case SignalElementsEnums.HeadPosition.LEFT:
@ -77,39 +94,39 @@ function getHeadOffset(object) {
}
}
function getPoleLength(object, headOff) {
const headPos = object.signal_elements.headPosition;
if(headPos === SignalElementsEnums.HeadPosition.NO_POLE) return 0;
function getPoleLength(object, headOffsetX) {
const isOverhead = object.signal_elements.isOverhead();
let length = 0.8;
let length = isOverhead ? 0 : 0.6;
if(headOff !== 0) {
length += 0.4;
if(headOffsetX !== 0) {
length += 0.4; // zigzag offset
}
switch(object.signal_elements.bar) {
case SignalElementsEnums.BarType.YELLOW:
case SignalElementsEnums.BarType.GREEN:
length += 1.0;
length += 0.7;
break;
case SignalElementsEnums.BarType.YELLOW_GREEN:
length += 1.8;
length += 1.5;
break;
default:
break;
}
return Math.max(length, 2);
if(isOverhead) return length;
return Math.max(length + 0.2, 2);
}
function getPole(poleLength, headOff) {
function getPole(poleLength, headOffsetY, headOffsetX) {
if(!poleLength) return null;
if(headOff) {
return <path d={`M 0 0 L 0 -${poleLength - .4} L ${headOff} -${poleLength -.4} L ${headOff} -${poleLength}`} />;
if(headOffsetX) {
return <path d={`M 0 ${headOffsetY+poleLength} L 0 ${headOffsetY + .4} L ${headOffsetX} ${headOffsetY + .4} L ${headOffsetX} ${headOffsetY}`} />;
}
return <path d={`M 0 0 L 0 -${poleLength}`} />;
return <path d={`M 0 ${headOffsetY+poleLength} L 0 ${headOffsetY}`} />;
}
function getUnit(key, unitType, x, y) {
@ -161,12 +178,11 @@ function getUnit(key, unitType, x, y) {
}
}
function getHead(object, poleLength, headOff) {
function getHead(object, headOffsetY, headOffsetX) {
if(object.signal_elements.type === SignalElementsEnums.Type.TOP) {
return getHeadTOP(object, poleLength);
return getHeadTOP(object, headOffsetY);
}
// TODO: overhead signals
const isDoubleDwarf = object.signal_elements.type === SignalElementsEnums.Type.DWARF_DOUBLE;
const unitsCount = object.signal_elements.units.length;
const units = [];
@ -174,41 +190,41 @@ function getHead(object, poleLength, headOff) {
for(let i = 0; i < unitsCount; i++) {
const unitType = object.signal_elements.units[i];
const cx = isDoubleDwarf ? (i > 2 ? r : -r) : headOff;
const cy = -poleLength - r - i * r * 2 + (isDoubleDwarf && i > 2 ? 6 * r : 0);
const cx = isDoubleDwarf ? (i > 2 ? r : -r) : headOffsetX;
const cy = headOffsetY - r - i * r * 2 + (isDoubleDwarf && i > 2 ? 6 * r : 0);
units.push(getUnit(i, unitType, cx, cy));
}
return units;
}
function getHeadTOP(object, poleLength) {
function getHeadTOP(object, headOffsetY) {
const isLeft = object.signal_elements.headPosition === SignalElementsEnums.HeadPosition.LEFT;
const r = 0.6;
const whiteCX = isLeft ? r*2 : -r*2;
return <>
<path d={`M ${whiteCX} -${poleLength} L 0 -${poleLength}`} />
{ getUnit(0, SignalElementsEnums.UnitType.YELLOW_LOWER, 0, -poleLength-r) }
{ getUnit(1, SignalElementsEnums.UnitType.WHITE, 0, -poleLength-3*r) }
{ getUnit(2, SignalElementsEnums.UnitType.WHITE, 0, -poleLength-5*r) }
{ getUnit(3, SignalElementsEnums.UnitType.YELLOW_LOWER, whiteCX, -poleLength-r) }
<path d={`M ${whiteCX} ${headOffsetY} L 0 ${headOffsetY}`} />
{ getUnit(0, SignalElementsEnums.UnitType.YELLOW_LOWER, 0, headOffsetY-r) }
{ getUnit(1, SignalElementsEnums.UnitType.WHITE, 0, headOffsetY-3*r) }
{ getUnit(2, SignalElementsEnums.UnitType.WHITE, 0, headOffsetY-5*r) }
{ getUnit(3, SignalElementsEnums.UnitType.YELLOW_LOWER, whiteCX, headOffsetY-r) }
</>
}
function getBar(object, headOff, y, isYellow, key = 0) {
function getBar(object, headOffsetX, y, isYellow, key = 0) {
const yellowOff = isYellow ? 0.2 : 0;
return <g key={key}>
<rect x={headOff-0.6} y={y-0.2} width={1.2} height={0.4} className='background' />;
<line x1={headOff - yellowOff} y1={y-0.2} x2={headOff + yellowOff} y2={y+0.2} />;
<rect x={headOffsetX-0.6} y={y-0.2} width={1.2} height={0.4} className='background' />;
<line x1={headOffsetX - yellowOff} y1={y-0.2} x2={headOffsetX + yellowOff} y2={y+0.2} />;
</g>
}
function getBars(object, poleLength, headOff) {
function getBars(object, headOffsetY, headOffsetX) {
const barType = object.signal_elements.bar;
const headOffOffset = headOff !== 0 ? 0.4 : 0;
const y = -poleLength + headOffOffset + 0.6;
const zigzagOffset = headOffsetX !== 0 ? 0.4 : 0;
const y = headOffsetY + zigzagOffset + 0.6;
if(barType === SignalElementsEnums.BarType.NONE) return null;

View File

@ -280,6 +280,33 @@ const DefinedSwitches = {
new Vector3(0, 0, 25.221),
new Vector3(-1.667, 0, 25.111)
],
"Rz 60E1-2500-1_26.5 R": [
0,
-2500,
[0, 0, 3, 4],
new Vector3(0, 0, 0),
new Vector3(0, 0, 0),
new Vector3(0, 0, 94.306),
new Vector3(1.778, 0, 94.273)
],
"Rz 60E1-2500-1_26.5 L": [
0,
2500,
[0, 0, 3, 4],
new Vector3(0, 0, 0),
new Vector3(0, 0, 0),
new Vector3(0, 0, 94.306),
new Vector3(-1.778, 0, 94.273)
],
"Rlds 60E1-600-600-1_18.5": [
-600,
600,
[0, 0, 3, 4],
new Vector3(0, 0, 0),
new Vector3(0, 0, 0),
new Vector3(0.92, 0, 33.206),
new Vector3(-0.92, 0, 33.206)
],
"Rlds 60E1-190-190-1_9": [
-190,
190,

View File

@ -11,8 +11,7 @@ const SignalElementsEnums = {
STANDARD: 1,
RIGHT: 2,
LEFT: 3,
NO_POLE: 4,
OVERHEAD: 5,
DWARF: 4,
},
UnitType: {
UNKNOWN: 0,

View File

@ -10,7 +10,6 @@ export default class SignalElementsParserCommon {
static getHeadPosition(poleString) {
const headPosition = this.getAfterLastUnderscoreNoVersion(poleString)?.[0];
if (!headPosition) return SignalElementsEnums.HeadPosition.UNKNOWN;
switch (headPosition) {
case 'p':

View File

@ -18,7 +18,7 @@ export default class SignalElementsParserEhaEriDwarfPs {
return new SignalElements(
isDouble ? SignalElementsEnums.Type.DWARF_DOUBLE : SignalElementsEnums.Type.DWARF,
SignalElementsEnums.HeadPosition.NO_POLE,
SignalElementsEnums.HeadPosition.DWARF,
[mainUnits, secondaryUnits].flat(),
SignalElementsEnums.BarType.NONE,
SignalElementsParserCommon.getSigns(entries, 15, 1, 1)

View File

@ -15,7 +15,7 @@ export default class SignalElementsParserEhaEriDwarf {
return new SignalElements(
SignalElementsEnums.Type.DWARF,
SignalElementsEnums.HeadPosition.NO_POLE,
SignalElementsEnums.HeadPosition.DWARF,
SignalElementsParserCommon.getUnits(entries, 2, 3, 2),
SignalElementsEnums.BarType.NONE,
[]

View File

@ -14,7 +14,7 @@ export default class SignalElementsParserSkDwarf {
return new SignalElements(
SignalElementsEnums.Type.DWARF,
SignalElementsEnums.HeadPosition.NO_POLE,
SignalElementsEnums.HeadPosition.DWARF,
SignalElementsParserCommon.getUnits(entries, 2, 2, 1),
SignalElementsEnums.BarType.NONE,
[]

View File

@ -19,6 +19,10 @@ export default class SignalElements {
});
}
isOverhead() {
return this.headPosition === SignalElementsEnums.HeadPosition.UNKNOWN;
}
static fromPrefabText(id, text) {
const prefabName = text.split(",", 2)[0].trim();