Add route track rendering
This commit is contained in:
parent
c4eac723ba
commit
e78c1ccde5
@ -57,6 +57,7 @@ function getTrackPath(object) {
|
|||||||
switch (object.type) {
|
switch (object.type) {
|
||||||
case "StandardTrack":
|
case "StandardTrack":
|
||||||
case "PointTrack":
|
case "PointTrack":
|
||||||
|
case "RouteTrack":
|
||||||
const [x1, y1] = object.points.start.toSVGCoords();
|
const [x1, y1] = object.points.start.toSVGCoords();
|
||||||
const [x2, y2] = object.points.end.toSVGCoords();
|
const [x2, y2] = object.points.end.toSVGCoords();
|
||||||
const ra = Math.abs(object.r);
|
const ra = Math.abs(object.r);
|
||||||
@ -84,7 +85,7 @@ function getTrackColor(object, trackColorMode) {
|
|||||||
switch (trackColorMode) {
|
switch (trackColorMode) {
|
||||||
case "standard":
|
case "standard":
|
||||||
default:
|
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['invisible'][0];
|
||||||
|
|
||||||
return modeDef.options[modeDef.optionDefault][0];
|
return modeDef.options[modeDef.optionDefault][0];
|
||||||
@ -109,6 +110,8 @@ function getTrackColor(object, trackColorMode) {
|
|||||||
return modeDef.options['standard-track'][0];
|
return modeDef.options['standard-track'][0];
|
||||||
case "PointTrack":
|
case "PointTrack":
|
||||||
return modeDef.options['point-track'][0];
|
return modeDef.options['point-track'][0];
|
||||||
|
case "RouteTrack":
|
||||||
|
return modeDef.options['route-track'][0];
|
||||||
case "BezierTrack":
|
case "BezierTrack":
|
||||||
return modeDef.options['bezier-track'][0];
|
return modeDef.options['bezier-track'][0];
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -11,6 +11,7 @@ const TrackHoverInfoConsts = {
|
|||||||
'StandardTrack': 'Standard track',
|
'StandardTrack': 'Standard track',
|
||||||
'PointTrack': 'Switch track',
|
'PointTrack': 'Switch track',
|
||||||
'BezierTrack': 'Bezier track',
|
'BezierTrack': 'Bezier track',
|
||||||
|
'RouteTrack': 'Route track',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -144,6 +144,7 @@ const Constants = {
|
|||||||
'standard-track': ['#00a', 'Standard track'],
|
'standard-track': ['#00a', 'Standard track'],
|
||||||
'point-track': ['#0a0', 'Switch track'],
|
'point-track': ['#0a0', 'Switch track'],
|
||||||
'bezier-track': ['#aa8', 'Bezier track'],
|
'bezier-track': ['#aa8', 'Bezier track'],
|
||||||
|
'route-track': ['#a6d', 'Route track'],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'slope': {
|
'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 SceneryObject from "./scenery-object";
|
||||||
import Vector3 from "./vector3";
|
import Vector3 from "./vector3";
|
||||||
|
import SceneryParserLog from "./scenery-parser-log";
|
||||||
|
import RouteTrack from "./tracks/route-track";
|
||||||
|
|
||||||
export default class Route extends SceneryObject {
|
export default class Route extends SceneryObject {
|
||||||
track_count;
|
track_count;
|
||||||
@ -8,8 +10,12 @@ export default class Route extends SceneryObject {
|
|||||||
track_offset;
|
track_offset;
|
||||||
category = "routes";
|
category = "routes";
|
||||||
type = "Route";
|
type = "Route";
|
||||||
points = [];
|
points;
|
||||||
|
offsets;
|
||||||
segments = [];
|
segments = [];
|
||||||
|
end_center;
|
||||||
|
end_points;
|
||||||
|
end_angle_rad;
|
||||||
|
|
||||||
constructor(prefab_name, pos, rot, track_count, route_name, track_offset, electrified) {
|
constructor(prefab_name, pos, rot, track_count, route_name, track_offset, electrified) {
|
||||||
super(route_name, pos, rot);
|
super(route_name, pos, rot);
|
||||||
@ -22,7 +28,11 @@ export default class Route extends SceneryObject {
|
|||||||
electrified,
|
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) {
|
static fromText(text) {
|
||||||
@ -33,7 +43,7 @@ export default class Route extends SceneryObject {
|
|||||||
Vector3.fromValuesArray(values, 6), // rot
|
Vector3.fromValuesArray(values, 6), // rot
|
||||||
parseInt(values[9]), // track_count
|
parseInt(values[9]), // track_count
|
||||||
values[10], // route_name
|
values[10], // route_name
|
||||||
parseFloat(values[11]) || 0, // track_offset
|
parseFloat(values[11]) || 0, // center_offset
|
||||||
values[13] === "True" || values[12] === "1" // electrified
|
values[13] === "True" || values[12] === "1" // electrified
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -41,21 +51,84 @@ export default class Route extends SceneryObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addSegment(length, radius, trackIds) {
|
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() {
|
_getConnectionPoints() {
|
||||||
if (this.track_count === 1) {
|
return this.offsets.map((offset) => {
|
||||||
return [this.pos.clone()];
|
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;
|
applyObject(scenery) {
|
||||||
const rightTrackOffset = Vector3.fromAngleY(angle, this.track_offset - 2);
|
this.segments.forEach((segment) => {
|
||||||
const leftTrackOffset = Vector3.fromAngleY(angle, this.track_offset + 2);
|
segment.tracks.forEach((track) => {
|
||||||
|
scenery.addObject(track);
|
||||||
return [
|
});
|
||||||
this.pos.add(rightTrackOffset),
|
});
|
||||||
this.pos.add(leftTrackOffset)
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import NEVP from './track-objects/nevp';
|
|||||||
import Derailer from './track-objects/derailer';
|
import Derailer from './track-objects/derailer';
|
||||||
import SpawnPoint from './track-objects/spawn-point';
|
import SpawnPoint from './track-objects/spawn-point';
|
||||||
import { attachSigns } from './attach-signs';
|
import { attachSigns } from './attach-signs';
|
||||||
|
import {connectRoutes} from "./connect-routes";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO: add support for WorldRotation and WorldTranslation
|
TODO: add support for WorldRotation and WorldTranslation
|
||||||
@ -57,6 +58,7 @@ export default class SceneryParser {
|
|||||||
});
|
});
|
||||||
|
|
||||||
scenery.applyObjects();
|
scenery.applyObjects();
|
||||||
|
connectRoutes(scenery);
|
||||||
|
|
||||||
if(Constants.parser.runTracksConnectionTest) tracksConnectionTest(scenery);
|
if(Constants.parser.runTracksConnectionTest) tracksConnectionTest(scenery);
|
||||||
if(Constants.parser.resolveElectrification) ElectrificationResolver.resolveScenery(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]}`);
|
SceneryParserLog.warn('routeInvalidSegment', `Invalid route segment track IDs: ${fields[3]}`);
|
||||||
return;
|
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(
|
route.addSegment(
|
||||||
parseInt(fields[1]), // length
|
parseInt(fields[1]), // length
|
||||||
parseInt(fields[2]), // radius
|
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