Add route track rendering
This commit is contained in:
parent
c4eac723ba
commit
e78c1ccde5
@ -56,7 +56,8 @@ function StatelessTrackRenderer(props) {
|
||||
function getTrackPath(object) {
|
||||
switch (object.type) {
|
||||
case "StandardTrack":
|
||||
case "PointTrack":
|
||||
case "PointTrack":
|
||||
case "RouteTrack":
|
||||
const [x1, y1] = object.points.start.toSVGCoords();
|
||||
const [x2, y2] = object.points.end.toSVGCoords();
|
||||
const ra = Math.abs(object.r);
|
||||
@ -84,7 +85,7 @@ function getTrackColor(object, trackColorMode) {
|
||||
switch (trackColorMode) {
|
||||
case "standard":
|
||||
default:
|
||||
if(object.prefab_name.includes('trans-mat'))
|
||||
if(object.prefab_name && object.prefab_name.includes('trans-mat'))
|
||||
return modeDef.options['invisible'][0];
|
||||
|
||||
return modeDef.options[modeDef.optionDefault][0];
|
||||
@ -102,13 +103,15 @@ function getTrackColor(object, trackColorMode) {
|
||||
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:
|
||||
@ -133,13 +136,13 @@ function getTrackColor(object, trackColorMode) {
|
||||
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
|
||||
|
||||
@ -11,6 +11,7 @@ const TrackHoverInfoConsts = {
|
||||
'StandardTrack': 'Standard track',
|
||||
'PointTrack': 'Switch track',
|
||||
'BezierTrack': 'Bezier track',
|
||||
'RouteTrack': 'Route track',
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -144,6 +144,7 @@ const Constants = {
|
||||
'standard-track': ['#00a', 'Standard track'],
|
||||
'point-track': ['#0a0', 'Switch track'],
|
||||
'bezier-track': ['#aa8', 'Bezier track'],
|
||||
'route-track': ['#a6d', 'Route track'],
|
||||
}
|
||||
},
|
||||
'slope': {
|
||||
|
||||
24
src/model/connect-routes.js
Normal file
24
src/model/connect-routes.js
Normal file
@ -0,0 +1,24 @@
|
||||
import TrackConnection, {TrackConnectionEnd} from "./track-connection";
|
||||
|
||||
export function connectRoutes(scenery) {
|
||||
const routeTracks = new Map();
|
||||
|
||||
Object.values(scenery.objects['routes'] || {}).forEach((route) => {
|
||||
if (!route.segments[0]) return;
|
||||
route.segments[0].tracks.forEach((track) => {
|
||||
routeTracks.set(track.id, track);
|
||||
});
|
||||
});
|
||||
|
||||
Object.values(scenery.objects['tracks'] || {}).forEach((track) => {
|
||||
track.connections.forEach((connection) => {
|
||||
const routeTrack = routeTracks.get(connection.otherTrackId);
|
||||
if (!routeTrack) return;
|
||||
const alreadyConnected = routeTrack.connections.some(
|
||||
(reverseConnection) => reverseConnection.otherTrackId === track.id,
|
||||
);
|
||||
if (alreadyConnected) return;
|
||||
routeTrack.connections.push(new TrackConnection(track.id, TrackConnectionEnd.START));
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
import SceneryObject from "./scenery-object";
|
||||
import Vector3 from "./vector3";
|
||||
import SceneryParserLog from "./scenery-parser-log";
|
||||
import RouteTrack from "./tracks/route-track";
|
||||
|
||||
export default class Route extends SceneryObject {
|
||||
track_count;
|
||||
@ -8,8 +10,12 @@ export default class Route extends SceneryObject {
|
||||
track_offset;
|
||||
category = "routes";
|
||||
type = "Route";
|
||||
points = [];
|
||||
points;
|
||||
offsets;
|
||||
segments = [];
|
||||
end_center;
|
||||
end_points;
|
||||
end_angle_rad;
|
||||
|
||||
constructor(prefab_name, pos, rot, track_count, route_name, track_offset, electrified) {
|
||||
super(route_name, pos, rot);
|
||||
@ -22,7 +28,11 @@ export default class Route extends SceneryObject {
|
||||
electrified,
|
||||
});
|
||||
|
||||
this.points = this._getConnectionPoints();
|
||||
this.offsets = this._getOffsets();
|
||||
this.end_angle_rad = this.rot.y * Math.PI / 180;
|
||||
this.end_center = this.pos.clone();
|
||||
this.end_points = this._getConnectionPoints();
|
||||
this.points = [...this.end_points];
|
||||
}
|
||||
|
||||
static fromText(text) {
|
||||
@ -33,7 +43,7 @@ export default class Route extends SceneryObject {
|
||||
Vector3.fromValuesArray(values, 6), // rot
|
||||
parseInt(values[9]), // track_count
|
||||
values[10], // route_name
|
||||
parseFloat(values[11]) || 0, // track_offset
|
||||
parseFloat(values[11]) || 0, // center_offset
|
||||
values[13] === "True" || values[12] === "1" // electrified
|
||||
);
|
||||
|
||||
@ -41,21 +51,84 @@ export default class Route extends SceneryObject {
|
||||
}
|
||||
|
||||
addSegment(length, radius, trackIds) {
|
||||
this.segments.push({ length, radius, trackIds });
|
||||
if (trackIds.length !== this.track_count) {
|
||||
SceneryParserLog.warn('routeInvalidSegment', `Route segment has invalid track count: expected ${this.track_count}, got ${trackIds.length}`);
|
||||
return;
|
||||
}
|
||||
const startAngleRad = this.end_angle_rad;
|
||||
const start_rot = new Vector3(0, startAngleRad * 180 / Math.PI, 0);
|
||||
|
||||
const startPoints = this.end_points;
|
||||
this.end_points = [];
|
||||
|
||||
const radii = [];
|
||||
const lengths = [];
|
||||
|
||||
if (radius === 0) {
|
||||
this.end_center = this.end_center.add(Vector3.fromAngleY(startAngleRad, length));
|
||||
this.offsets.forEach(offset => {
|
||||
radii.push(0);
|
||||
lengths.push(length);
|
||||
const end_point = this.end_center
|
||||
.add(Vector3.fromAngleY(this.end_angle_rad + Math.PI / 2, offset));
|
||||
this.end_points.push(end_point);
|
||||
});
|
||||
} else {
|
||||
this.end_angle_rad -= length / radius;
|
||||
const startToCenter = Vector3.fromAngleY(startAngleRad + Math.sign(radius) * Math.PI / 2, Math.abs(radius));
|
||||
const circleCenter = this.end_center.add(startToCenter.negate());
|
||||
const centerToEndUnit = Vector3.fromAngleY(this.end_angle_rad + Math.sign(radius) * Math.PI / 2, 1).multiply(Math.sign(radius));
|
||||
this.end_center = circleCenter.add(centerToEndUnit.multiply(radius));
|
||||
|
||||
this.offsets.forEach(offset => {
|
||||
const trackRadius = radius + offset;
|
||||
const trackLength = length * trackRadius / radius;
|
||||
radii.push(trackRadius);
|
||||
lengths.push(trackLength);
|
||||
const end_point = circleCenter.add(centerToEndUnit.multiply(trackRadius));
|
||||
this.end_points.push(end_point);
|
||||
});
|
||||
}
|
||||
|
||||
this.points.push(...this.end_points);
|
||||
|
||||
const tracks = trackIds.map((trackId, index) => {
|
||||
return new RouteTrack(
|
||||
trackId,
|
||||
startPoints[index],
|
||||
this.end_points[index],
|
||||
start_rot,
|
||||
lengths[index],
|
||||
radii[index],
|
||||
[], // connections
|
||||
this, // route
|
||||
);
|
||||
});
|
||||
this.segments.push({ length, radius, trackIds, tracks });
|
||||
}
|
||||
|
||||
// This method is future-proofed for more than 2 tracks
|
||||
_getOffsets() {
|
||||
const leftmost_offset = -(this.track_count - 1) * 2 + this.track_offset;
|
||||
const result = [];
|
||||
for (let i = 0; i < this.track_count; i++) {
|
||||
result.push(leftmost_offset + i * 4);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
_getConnectionPoints() {
|
||||
if (this.track_count === 1) {
|
||||
return [this.pos.clone()];
|
||||
}
|
||||
return this.offsets.map((offset) => {
|
||||
const offsetVec = Vector3.fromAngleY(this.end_angle_rad + Math.PI / 2, offset);
|
||||
return this.pos.add(offsetVec);
|
||||
});
|
||||
}
|
||||
|
||||
const angle = (this.rot.y + 90) * Math.PI / 180;
|
||||
const rightTrackOffset = Vector3.fromAngleY(angle, this.track_offset - 2);
|
||||
const leftTrackOffset = Vector3.fromAngleY(angle, this.track_offset + 2);
|
||||
|
||||
return [
|
||||
this.pos.add(rightTrackOffset),
|
||||
this.pos.add(leftTrackOffset)
|
||||
];
|
||||
applyObject(scenery) {
|
||||
this.segments.forEach((segment) => {
|
||||
segment.tracks.forEach((track) => {
|
||||
scenery.addObject(track);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ import NEVP from './track-objects/nevp';
|
||||
import Derailer from './track-objects/derailer';
|
||||
import SpawnPoint from './track-objects/spawn-point';
|
||||
import { attachSigns } from './attach-signs';
|
||||
import {connectRoutes} from "./connect-routes";
|
||||
|
||||
/*
|
||||
TODO: add support for WorldRotation and WorldTranslation
|
||||
@ -57,6 +58,7 @@ export default class SceneryParser {
|
||||
});
|
||||
|
||||
scenery.applyObjects();
|
||||
connectRoutes(scenery);
|
||||
|
||||
if(Constants.parser.runTracksConnectionTest) tracksConnectionTest(scenery);
|
||||
if(Constants.parser.resolveElectrification) ElectrificationResolver.resolveScenery(scenery);
|
||||
@ -180,10 +182,6 @@ export default class SceneryParser {
|
||||
SceneryParserLog.warn('routeInvalidSegment', `Invalid route segment track IDs: ${fields[3]}`);
|
||||
return;
|
||||
}
|
||||
if (trackIds.length !== route.track_count) {
|
||||
SceneryParserLog.warn('routeInvalidSegment', `Route segment has invalid track count: expected ${route.track_count}, got ${trackIds.length}`);
|
||||
return;
|
||||
}
|
||||
route.addSegment(
|
||||
parseInt(fields[1]), // length
|
||||
parseInt(fields[2]), // radius
|
||||
|
||||
42
src/model/tracks/route-track.js
Normal file
42
src/model/tracks/route-track.js
Normal file
@ -0,0 +1,42 @@
|
||||
import AngleHelper from "../../helpers/angleHelper";
|
||||
import Track from "./track";
|
||||
import {ElectrificationStatus} from "../electrification-status";
|
||||
|
||||
export default class RouteTrack extends Track {
|
||||
type = "RouteTrack";
|
||||
points;
|
||||
route;
|
||||
|
||||
constructor(id, start, end, rot, len, r, connections, route, electrified) {
|
||||
super(
|
||||
id, start, rot, len, r, connections,
|
||||
null, // id_station,
|
||||
0, // start_slope,
|
||||
0, // end_slope,
|
||||
null, // id_isolation
|
||||
null, // prefab_name
|
||||
null, // maxspeed,
|
||||
null, // derailspeed,
|
||||
);
|
||||
this.electrificationStatus = electrified ? ElectrificationStatus.ELECTRIFIED : ElectrificationStatus.NON_ELECTRIFIED;
|
||||
this.points = {
|
||||
start,
|
||||
end,
|
||||
};
|
||||
this.route = route;
|
||||
}
|
||||
|
||||
getStartAngleXZ() {
|
||||
return AngleHelper.degToRad(this.rot.y);
|
||||
}
|
||||
|
||||
getEndAngleXZ() {
|
||||
const startAngle = AngleHelper.degToRad(this.rot.y);
|
||||
|
||||
if(this.r === 0) {
|
||||
return startAngle
|
||||
}
|
||||
|
||||
return startAngle - this.len / this.r;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user