Merge branch 'main' into prefab-parser
# Conflicts: # src/components/map/object-renderers/TrackRenderer.js # src/helpers/miscHelper.js
This commit is contained in:
commit
b7c71e37ac
@ -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;
|
||||
|
||||
68
src/components/GradientsManager.js
Normal file
68
src/components/GradientsManager.js
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -128,3 +128,7 @@
|
||||
.map svg g.spawn-point path.background {
|
||||
fill: #111;
|
||||
}
|
||||
|
||||
.map svg g.platform rect {
|
||||
fill: #444;
|
||||
}
|
||||
|
||||
@ -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',
|
||||
|
||||
23
src/components/map/object-renderers/PlatformRenderer.js
Normal file
23
src/components/map/object-renderers/PlatformRenderer.js
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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})`}>
|
||||
|
||||
@ -4,16 +4,19 @@ import Constants from "../../../helpers/constants";
|
||||
import { ElectrificationStatus } from "../../../model/electrification-status";
|
||||
import MiscHelper from "../../../helpers/miscHelper";
|
||||
import { setHoveredTrack, unsetHoveredTrack } from "../../../services/trackHoverInfoService";
|
||||
import {TrackSource} from "../../../model/tracks/track";
|
||||
import GradientsContext from "../../../contexts/GradientsContext";
|
||||
import { TrackSource } from "../../../model/tracks/track";
|
||||
|
||||
export default function TrackRenderer(props) {
|
||||
const { object } = props;
|
||||
const { trackColorMode } = useContext(SettingsContext);
|
||||
const { gradientDefs } = useContext(GradientsContext);
|
||||
|
||||
return (
|
||||
<MemoizedTrackRenderer
|
||||
object={object}
|
||||
trackColorMode={trackColorMode}
|
||||
gradientDef={gradientDefs[trackColorMode] ?? null}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -21,7 +24,7 @@ export default function TrackRenderer(props) {
|
||||
const MemoizedTrackRenderer = React.memo(StatelessTrackRenderer);
|
||||
|
||||
function StatelessTrackRenderer(props) {
|
||||
const { object, trackColorMode } = props;
|
||||
const { object, trackColorMode, gradientDef } = props;
|
||||
|
||||
if (object.points.start.distanceSq(object.points.end) < 0.001) {
|
||||
return null;
|
||||
@ -36,8 +39,8 @@ function StatelessTrackRenderer(props) {
|
||||
};
|
||||
|
||||
const path = getTrackPath(object);
|
||||
const color = getTrackColor(object, trackColorMode);
|
||||
const defs = getTrackDefs(object, trackColorMode);
|
||||
const color = getTrackColor(object, trackColorMode, gradientDef);
|
||||
const defs = getTrackDefs(object, trackColorMode, gradientDef);
|
||||
|
||||
return (
|
||||
<g id={`track-${object.id}`}>
|
||||
@ -84,7 +87,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) {
|
||||
@ -122,46 +136,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);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
4
src/contexts/GradientsContext.js
Normal file
4
src/contexts/GradientsContext.js
Normal file
@ -0,0 +1,4 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
const GradientsContext = createContext();
|
||||
export default GradientsContext;
|
||||
@ -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
|
||||
@ -62,6 +64,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',
|
||||
@ -150,12 +158,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': {
|
||||
@ -167,12 +177,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,
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
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))
|
||||
Math.min(255, Math.max(0, b + diff[i] * value))
|
||||
);
|
||||
|
||||
return `rgb(${out[0]}, ${out[1]}, ${out[2]})`;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 ];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
103
src/model/quaternion.js
Normal 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
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user