Merge branch 'refs/heads/main' into align-track

# Conflicts:
#	src/components/map/object-renderers/TrackRenderer.js
This commit is contained in:
dominik-korsa 2025-07-23 00:23:25 +02:00
commit a653a5ac2e
No known key found for this signature in database
GPG Key ID: 5A24D76EE0A30974
20 changed files with 385 additions and 92 deletions

View File

@ -8,20 +8,22 @@ import SettingsManager from './SettingsManager';
import LocList from './loclist/LocList';
import DialogManager from './dialog/DialogManager';
import DistanceMeterContext from '../contexts/DistanceMeterContext';
import {GradientsManager} from "./GradientsManager";
function App() {
const [isLoading, setIsLoading] = useState(false);
const [scenery, setScenery] = useState(null);
const [sideMenuOpen, setSideMenuOpen] = useState(false);
const [distancePoints, setDistancePoints] = useState(null);
return (
<SettingsManager>
<GradientsManager scenery={scenery}>
<DistanceMeterContext.Provider value={{distancePoints, setDistancePoints}}>
<MainContext.Provider value={{
<MainContext.Provider value={{
scenery, setScenery,
isLoading, setIsLoading,
sideMenuOpen, setSideMenuOpen
sideMenuOpen, setSideMenuOpen,
}}>
<div className="App">
<Toolbar />
@ -34,9 +36,9 @@ function App() {
</div>
</MainContext.Provider>
</DistanceMeterContext.Provider>
</GradientsManager>
</SettingsManager>
);
}
export default App;

View File

@ -0,0 +1,68 @@
import Constants from "../helpers/constants";
import {useMemo} from "react";
import GradientsContext from "../contexts/GradientsContext";
function getMinMax(trackColorMode, scenery) {
if (trackColorMode === 'elevation') {
if (!scenery) return null;
if (scenery.trackElevationBounds.minY > scenery.trackElevationBounds.maxY) return null;
const min = scenery.trackElevationBounds.minY;
// Make sure diff > 0 even on flat sceneries
let diff = Math.max(scenery.trackElevationBounds.maxY - scenery.trackElevationBounds.minY, 1);
// Ensure `diff` is even so that the min, max and middle legend values are integers
diff = Math.ceil(diff / 2)*2;
const max = min + diff;
return [min, max];
}
return null;
}
function createGradientSettings(trackColorMode, scenery, def) {
if ('gradient' in def) return def.gradient;
if ('dynamicGradient' in def) {
const gradient = def.dynamicGradient;
const [minValue, maxValue] = getMinMax(trackColorMode, scenery) ?? [gradient.defaultMin, gradient.defaultMax];
const diff = gradient.min.map((min, i) => {
const max = gradient.max[i];
return (max - min) / (maxValue - minValue);
});
const base = gradient.min.map((min, i) => {
return min - diff[i] * minValue;
});
return {
base,
diff,
legendMin: minValue,
legendMax: maxValue,
unit: gradient.unit,
startLegendAt0: gradient.startLegendAt0 ?? false,
};
}
return null;
}
function createGradientDefs(scenery) {
return Object.fromEntries(
Object.entries(Constants.trackColorModes).map(
([mode, def]) => [mode, createGradientSettings(mode, scenery, def)],
),
);
}
export function GradientsManager(props) {
const { children, scenery } = props;
const gradientDefs = useMemo(() => createGradientDefs(scenery), [scenery]);
return (
<GradientsContext.Provider value={{
gradientDefs,
}}>
{ children }
</GradientsContext.Provider>
);
}

View File

@ -128,3 +128,7 @@
.map svg g.spawn-point path.background {
fill: #111;
}
.map svg g.platform rect {
fill: #444;
}

View File

@ -11,8 +11,17 @@ import DerailerRenderer from './object-renderers/DerailerRenderer';
import ElectrificationStatusPopup from './additional-layer-components/ElectrificationStatusPopup';
import SpawnPointRenderer from './object-renderers/SpawnPointRenderer';
import IsolationEndRenderer from './object-renderers/IsolationEndRenderer';
import PlatformRenderer from './object-renderers/PlatformRenderer';
import Constants from '../../helpers/constants';
const ObjectRendererQueue = [
{
'name': 'platforms',
'category': 'misc',
'type': 'Platform',
'renderer': PlatformRenderer,
'cond': (layers) => layers['platforms'] && !Constants.parser.skipPlatforms
},
{
'name': 'tracks',
'category': 'tracks',

View File

@ -0,0 +1,23 @@
import Constants from "../../../helpers/constants";
export default function PlatformRenderer(props) {
const { object } = props;
const [x, y] = object.pos.toSVGCoords();
const tiltNotOk = object.yawData.tilt > Constants.map.platformMaxTilt;
if(!object.def || tiltNotOk) return null;
const [centerX, centerY] = object.def.center.toSVGCoords();
const sizeX = object.def.size.x;
const sizeY = object.def.size.z;
return (
<g className="platform" transform={`translate(${x}, ${y}) rotate(${object.yawData.yaw})`}>
<rect
x={-sizeX / 2 + centerX}
y={-sizeY / 2 + centerY}
width={sizeX}
height={sizeY}
/>
</g>
);
}

View File

@ -6,8 +6,9 @@ export default function SignalBoxRenderer(props) {
const [x, y] = object.pos.toSVGCoords();
const text = object.getPrintableSignalBoxName();
const rotDiff = object.def.undef ? Math.round(object.rot.y / 90) * (-90) : object.def.rot * 90;
const rot = object.rot.y + rotDiff;
const yaw = object.yawData?.yaw || object.rot.y;
const rotDiff = object.def.undef ? Math.round(yaw / 90) * (-90) : object.def.rot * 90;
const rot = yaw + rotDiff;
return (
<g className="signalbox" transform={`translate(${x}, ${y}) rotate(${rot})`}>

View File

@ -2,31 +2,35 @@ 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";
import GradientsContext from "../../../contexts/GradientsContext";
import MiscHelper from "../../../helpers/miscHelper";
export default function TrackRenderer(props) {
const { object } = props;
const { trackColorMode } = useContext(SettingsContext);
const {alignView} = useContext(ZoomPanContext);
const { gradientDefs } = useContext(GradientsContext);
const {alignView} = useContext(ZoomPanContext);
const onAlign = (event) => {
alignView(object.rot.y, event);
};
const onAlign = (event) => {
alignView(object.rot.y, event);
};
return (
<MemoizedTrackRenderer
object={object}
trackColorMode={trackColorMode}
onAlign={onAlign}/>
gradientDef={gradientDefs[trackColorMode] ?? null}
onAlign={onAlign}
/>
);
}
const MemoizedTrackRenderer = React.memo(StatelessTrackRenderer);
function StatelessTrackRenderer(props) {
const { object, trackColorMode , onAlign} = props;
const { object, trackColorMode, gradientDef, onAlign } = props;
if (object.points.start.distanceSq(object.points.end) < 0.001) {
return null;
@ -41,16 +45,16 @@ function StatelessTrackRenderer(props) {
};
const onClick = (event) => {
if (object.type !== 'StandardTrack' || object.r !== 0) return;
if (event.detail === 2) {
event.preventDefault();
onAlign(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);
const path = getTrackPath(object);
const color = getTrackColor(object, trackColorMode, gradientDef);
const defs = getTrackDefs(object, trackColorMode, gradientDef);
return (
<g id={`track-${object.id}`}>
@ -97,7 +101,18 @@ function getTrackPath(object) {
}
}
function getTrackColor(object, trackColorMode) {
function getGradientValues(object, trackColorMode) {
switch (trackColorMode) {
case 'slope':
return [Math.abs(object.start_slope), Math.abs(object.end_slope)];
case 'elevation':
return [object.points.start.y, object.points.end.y];
default:
return null;
}
}
function getTrackColor(object, trackColorMode, gradientDef) {
const modeDef = Constants.trackColorModes[trackColorMode];
switch (trackColorMode) {
@ -135,46 +150,50 @@ function getTrackColor(object, trackColorMode) {
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);
return MiscHelper.getTrackGradientColor(gradientDef, object.maxspeed);
case "elevation":
case "slope":
const [startValue, endValue] = getGradientValues(object, trackColorMode);
if (startValue === endValue) return MiscHelper.getTrackGradientColor(gradientDef, startValue);
return `url(#track-${trackColorMode}-${object.id})`;
}
}
function getTrackDefs(object, trackColorMode) {
if (trackColorMode !== "slope") return null;
if (object.start_slope === object.end_slope) return null;
function getGradientDefs(object, trackColorMode, gradientDef, startValue, endValue) {
if (startValue === endValue) 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));
const gradId = `track-${trackColorMode}-${object.id}`;
const startColor = MiscHelper.getTrackGradientColor(gradientDef, startValue);
const endColor = MiscHelper.getTrackGradientColor(gradientDef, endValue);
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>
<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>
);
}
function getTrackDefs(object, trackColorMode, gradientDef) {
const gradientVals = getGradientValues(object, trackColorMode);
if (gradientVals === null) return null;
const [startValue, endValue] = gradientVals;
return getGradientDefs(object, trackColorMode, gradientDef, startValue, endValue);
}

View File

@ -36,6 +36,8 @@ export default function LayersMenu(props) {
function LayersMenuItem(props) {
const { layer, isChecked, onToggle } = props;
if(layer.cond && !layer.cond()) return null;
return (
<li>
<label>

View File

@ -1,4 +1,6 @@
import Constants from "../../../helpers/constants";
import {useContext} from "react";
import GradientsContext from "../../../contexts/GradientsContext";
import MiscHelper from "../../../helpers/miscHelper";
export default function TrackColorModeLegend(props) {
@ -7,7 +9,7 @@ export default function TrackColorModeLegend(props) {
return (
<div className="track-color-mode-legend">
<LegendGradient colorModeDef={colorModeDef} />
<LegendGradient trackColorMode={trackColorMode} />
<LegendOptions colorModeDef={colorModeDef} />
</div>
);
@ -22,7 +24,7 @@ function LegendOptions(props) {
<ul>
{ Object.entries(colorModeDef.options).map(([key, optionDef]) => (
<LegendOptionsItem key={key} optionDef={optionDef} />
))}
))}
</ul>
</div>
);
@ -39,23 +41,30 @@ function LegendOptionsItem(props) {
}
function LegendGradient(props) {
const { colorModeDef } = props;
const gradientDef = colorModeDef?.gradient || null;
const { gradientDefs } = useContext(GradientsContext);
const { trackColorMode } = props;
const gradientDef = gradientDefs[trackColorMode];
if (!gradientDef) return null;
const from = MiscHelper.getTrackGradient(gradientDef, gradientDef.legendMin);
const to = MiscHelper.getTrackGradient(gradientDef, gradientDef.legendMax);
const from = MiscHelper.getTrackGradientColor(gradientDef, gradientDef.legendMin);
const to = MiscHelper.getTrackGradientColor(gradientDef, gradientDef.legendMax);
const gradientStyle = `linear-gradient(to right, ${from}, ${to})`;
const midVal = (gradientDef.legendMin + gradientDef.legendMax) / 2;
let { legendMin, legendMax } = gradientDef;
if (gradientDef.startLegendAt0) {
legendMax -= legendMin;
legendMin = 0;
}
const midVal = (legendMin + legendMax) / 2;
return (
<div className="gradient">
<div className="gradient-bar" style={{ background: gradientStyle}}></div>
<div className="gradient-labels">
<span className="gradient-label">{gradientDef.legendMin} {gradientDef.unit}</span>
<span className="gradient-label">{legendMin} {gradientDef.unit}</span>
<span className="gradient-label">{midVal} {gradientDef.unit}</span>
<span className="gradient-label">{gradientDef.legendMax} {gradientDef.unit}</span>
<span className="gradient-label">{legendMax} {gradientDef.unit}</span>
</div>
</div>
);

View File

@ -0,0 +1,4 @@
import { createContext } from 'react';
const GradientsContext = createContext();
export default GradientsContext;

View File

@ -5,7 +5,8 @@ const Constants = {
zoomMin: 0.03,
zoomMax: 200.0,
rotationSensitivity: 0.2,
forcePointerEvents: false
forcePointerEvents: false,
platformMaxTilt: 0.5
},
parser: {
logSceneryAfterFinished: true,
@ -20,7 +21,8 @@ const Constants = {
attachSignsMaxDistanceX: 0.3,
attachSignsGridSize: 10,
logAttachedSigns: false,
skipBaseMisc: true
skipBaseMisc: true,
skipPlatforms: false
},
warnings: {
all: false, // enable all warnings
@ -63,6 +65,12 @@ const Constants = {
fetchListUrl: `${process.env.PUBLIC_URL}/sceneries/sceneries.json`,
},
layers: [
{
id: 'platforms',
name: 'Platforms',
default: true,
cond: () => !Constants.parser.skipPlatforms,
},
{
id: 'tracks',
name: 'Tracks',
@ -151,12 +159,14 @@ const Constants = {
},
'slope': {
name: 'Slope',
type: 'gradient',
gradient: {
base: [128, 0, 128],
diff: [0, 25.6, 0],
legendMin: 0,
legendMax: 10,
unit: '‰',
startLegendAt0: false,
}
},
'max-speed': {
@ -168,12 +178,25 @@ const Constants = {
legendMin: 0,
legendMax: 170,
unit: 'km/h',
startLegendAt0: false,
},
options: {
'derail': ['#f22', 'Derail track'],
'unknown': ['#aaa', 'Unknown speed'],
}
}
},
'elevation': {
name: 'Elevation',
type: 'gradient',
dynamicGradient: {
min: [0, 190, 0],
max: [255, 22, 22],
defaultMin: 0,
defaultMax: 2,
unit: 'm',
startLegendAt0: true,
},
},
}
};

View File

@ -1,10 +1,10 @@
export default class MiscHelper {
static getTrackGradient(gradientDef, value) {
static getTrackGradientColor(gradientDef, value) {
const { base, diff } = gradientDef;
const out = base.map((b, i) =>
Math.min(255, Math.max(0, b + diff[i] * value))
const out = base.map((b, i) =>
Math.min(255, Math.max(0, b + diff[i] * value))
);
return `rgb(${out[0]}, ${out[1]}, ${out[2]})`;
}
}

View File

@ -1,8 +1,10 @@
import Vector3 from "./vector3";
import Quaternion from "./quaternion";
export default class MiscGroup {
pos;
rot;
quaternion;
constructor(pos, rot) {
this.pos = pos;
@ -18,4 +20,13 @@ export default class MiscGroup {
return object;
}
getQuaternion() {
if (!this.quaternion) {
const rotRad = this.rot.multiply(Math.PI / 180);
this.quaternion = Quaternion.fromEulerAngles(rotRad).normalize();
}
return this.quaternion;
}
}

View File

@ -1,19 +1,22 @@
import AngleHelper from "../../helpers/angleHelper";
import Quaternion from "../quaternion";
import SceneryObject from "../scenery-object";
import Vector3 from "../vector3";
export default class Misc extends SceneryObject {
prefab_name;
name;
yawData;
category = "misc";
type = "Misc";
constructor(id, misc_id, prefab_name, pos, rot, name) {
constructor(id, misc_id, prefab_name, pos, rot, yawData, name) {
super(id, pos, rot);
Object.assign(this, {
misc_id,
prefab_name,
name,
yawData
});
}
@ -35,19 +38,20 @@ export default class Misc extends SceneryObject {
return object;
}
static applyGroupTransforms(pos, rot, miscGroups) {
if(!miscGroups) return [ pos, rot ];
static applyGroupTransforms(localPos, localRot, miscGroups) {
const localRotRad = localRot.multiply(Math.PI / 180);
let worldQuat = Quaternion.fromEulerAngles(localRotRad).normalize();
let worldPos = localPos.clone();
for (const group of miscGroups) {
const rotRad = AngleHelper.degToRad(group.rot.y);
const rotRadY = new Vector3(0, rotRad, 0);
pos = pos.rotate(rotRadY).add(group.pos);
rot = rot.add(group.rot);
// TODO: Replace this with some fancy quaternion math maybe
const groupQuat = group.getQuaternion();
worldPos = groupQuat.rotateVector(worldPos).add(group.pos);
worldQuat = groupQuat.multiply(worldQuat).normalize();
}
rot = AngleHelper.normalizeDegVector(rot);
return [ pos, rot ];
const worldRot = worldQuat.toEulerAngles().multiply(180 / Math.PI);
const yawData = worldQuat.getMiscYawData();
return [ worldPos, worldRot, yawData ];
}
}

View File

@ -7,8 +7,8 @@ export default class Platform extends Misc {
type = "Platform";
def = null;
constructor(id, misc_id, prefab_name, pos, rot, name) {
super(id, misc_id, prefab_name, pos, rot, name);
constructor(id, misc_id, prefab_name, pos, rot, yawData, name) {
super(id, misc_id, prefab_name, pos, rot, yawData, name);
this.def = Platform.getDef(name, prefab_name);
}

View File

@ -8,8 +8,8 @@ export default class SignalBox extends Misc {
applied = false;
def = null;
constructor(id, misc_id, prefab_name, pos, rot, name) {
super(id, misc_id, prefab_name, pos, rot, name);
constructor(id, misc_id, prefab_name, pos, rot, yawData, name) {
super(id, misc_id, prefab_name, pos, rot, yawData, name);
this.def = SignalBox.getDef(name, prefab_name);
}

103
src/model/quaternion.js Normal file
View File

@ -0,0 +1,103 @@
import Vector3 from "./vector3";
export default class Quaternion {
w;
x;
y;
z;
constructor(w, x, y, z) {
this.w = w;
this.x = x;
this.y = y;
this.z = z;
}
static fromEulerAngles(vector) {
const halfZ = vector.z * 0.5;
const halfX = vector.x * 0.5;
const halfY = vector.y * 0.5;
const sz = Math.sin(halfZ);
const cz = Math.cos(halfZ);
const sx = Math.sin(halfX);
const cx = Math.cos(halfX);
const sy = Math.sin(halfY);
const cy = Math.cos(halfY);
return new Quaternion(
sx * sy * sz + cx * cy * cz,
sx * cy * cz + cx * sy * sz,
cx * sy * cz - sx * cy * sz,
cx * cy * sz - sx * sy * cz
)
}
toEulerAngles() {
const sinr_cosp = 2 * (this.w * this.x + this.y * this.z);
const cosr_cosp = 1 - 2 * (this.x * this.x + this.y * this.y);
const roll = Math.atan2(sinr_cosp, cosr_cosp);
const sinp = 2 * (this.w * this.y - this.z * this.x);
let pitch;
if (Math.abs(sinp) >= 1) {
pitch = Math.sign(sinp) * Math.PI / 2; // use 90 degrees if out of range
} else {
pitch = -Math.asin(sinp);
}
const siny_cosp = 2 * (this.w * this.z + this.x * this.y);
const cosy_cosp = 1 - 2 * (this.y * this.y + this.z * this.z);
const yaw = Math.atan2(siny_cosp, cosy_cosp);
return new Vector3(roll, pitch, yaw);
}
rotateVector(v) {
const qv = new Quaternion(0, v.x, v.y, v.z);
const conjugate = this.conjugate();
const rotated = this.multiply(qv).multiply(conjugate);
return new Vector3(rotated.x, rotated.y, rotated.z);
}
conjugate() {
return new Quaternion(this.w, -this.x, -this.y, -this.z);
}
normalize() {
const length = Math.sqrt(this.w * this.w + this.x * this.x + this.y * this.y + this.z * this.z);
if (length === 0) return new Quaternion(1, 0, 0, 0); // return identity quaternion if length is zero
return new Quaternion(this.w / length, this.x / length, this.y / length, this.z / length);
}
multiply(q) {
return new Quaternion(
this.w * q.w - this.x * q.x - this.y * q.y - this.z * q.z,
this.w * q.x + this.x * q.w + this.y * q.z - this.z * q.y,
this.w * q.y - this.x * q.z + this.y * q.w + this.z * q.x,
this.w * q.z + this.x * q.y - this.y * q.x + this.z * q.w
);
}
multiplyScalar(scalar) {
return new Quaternion(
this.w * scalar,
this.x * scalar,
this.y * scalar,
this.z * scalar
);
}
getMiscYawData() {
const forward = new Vector3(0, 0, -1);
const rotatedForward = this.rotateVector(forward);
const up = new Vector3(0, 1, 0);
const rotatedUp = this.rotateVector(up);
return {
yaw: Math.atan2(rotatedForward.x, rotatedForward.z) * 180 / Math.PI,
tilt: rotatedUp.x * rotatedUp.x + rotatedUp.z * rotatedUp.z
}
}
};

View File

@ -115,11 +115,11 @@ export default class SceneryParser {
return MainCamera.fromText(text);
case 'MiscGroup':
const group = MiscGroup.fromText(text);
currentMiscGroups.push(group);
currentMiscGroups.unshift(group);
return null;
case 'EndMiscGroup':
if(!currentMiscGroups.pop()) {
if(!currentMiscGroups.shift()) {
SceneryParserLog.warn('endMiscGroupWithoutStart', 'Unexpected EndMiscGroup found without a preceding MiscGroup');
}
@ -176,12 +176,12 @@ export default class SceneryParser {
const id = SceneryParser.nextMiscId++;
if (Platform.isPlatform(prefabName)) {
if(Constants.parser.skipPlatforms) return null;
return Platform.fromText(id, text, miscGroups);
} else if(SignalBox.isSignalBox(prefabName)) {
return SignalBox.fromText(id, text, miscGroups);
} else if(Constants.parser.skipBaseMisc) {
return null;
} else {
if(Constants.parser.skipBaseMisc) return null;
return Misc.fromText(id, text, miscGroups);
}
}

View File

@ -6,6 +6,7 @@ export default class Scenery
signalBoxes = [];
spawnPoints = [];
bounds = { minX: Infinity, minZ: Infinity, maxX: -Infinity, maxZ: -Infinity };
trackElevationBounds = { minY: Infinity, maxY: -Infinity };
electrificationResolved = ElectrificationResolutionStatus.NOT_RESOLVED;
getBounds () {
@ -32,6 +33,9 @@ export default class Scenery
if(object.getRenderBounds) {
this._updateBounds(object.getRenderBounds());
}
if (object.category === 'tracks') {
this._updateTrackElevationBounds(object);
}
}
applyObjects() {
@ -69,10 +73,17 @@ export default class Scenery
if(!objBounds || !Array.isArray(objBounds)) return;
objBounds.forEach(point => {
if(point.x < this.bounds.minX) this.bounds.minX = point.x;
if(point.z < this.bounds.minZ) this.bounds.minZ = point.z;
if(point.x > this.bounds.maxX) this.bounds.maxX = point.x;
if(point.z > this.bounds.maxZ) this.bounds.maxZ = point.z;
if (point.x < this.bounds.minX) this.bounds.minX = point.x;
if (point.z < this.bounds.minZ) this.bounds.minZ = point.z;
if (point.x > this.bounds.maxX) this.bounds.maxX = point.x;
if (point.z > this.bounds.maxZ) this.bounds.maxZ = point.z;
});
}
_updateTrackElevationBounds(object) {
[object.points.start, object.points.end].forEach(point => {
if (point.y < this.trackElevationBounds.minY) this.trackElevationBounds.minY = point.y;
if (point.y > this.trackElevationBounds.maxY) this.trackElevationBounds.maxY = point.y;
})
}
}

View File

@ -5,7 +5,7 @@ import sys
#
# [ TD2 SCENERY PROCESSOR ]
# by masuo
# v1.6
# v1.7
#
BAD_WORDS = ['Fence', 'TerrainPoint', 'Wires']
@ -16,7 +16,7 @@ def should_exclude(line: str) -> bool:
if line.startswith(word):
return True
if line.startswith("Misc"):
if line.startswith("Misc") and not line.startswith("MiscGroup"):
if re.match(MISC_REGEX, line):
return True