Skip to content

Commit ad958af

Browse files
authored
Merge pull request #981 from Csantucci/multiple-lightglows-official
Multiple type trainset lightglows
2 parents 01b7af1 + 1c53db2 commit ad958af

File tree

7 files changed

+122
-10
lines changed

7 files changed

+122
-10
lines changed

Source/Documentation/Manual/features-rollingstock.rst

+76
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,82 @@ for the purposes of calculation.
318318
- ``MU ( 3 )`` Locomotive must be in a different group to the lead locomotive
319319

320320

321+
Multiple type locomotive light glows
322+
------------------------------------
323+
324+
Introduction
325+
''''''''''''
326+
327+
As a default all OR (and MSTS) locomotives use the same texture to reproduce
328+
the glow of their lights. This however doesn't allow to easily implement non-round
329+
or LED array lights, neither to provide special glowing effects.
330+
331+
This feature allows to specifiy customized light glow textures for the locomotives.
332+
If nothing is specified, the standard light glow texture is used. Moreover in the ``Content``
333+
folder, two light glow textures are present: the "historical" one, and a new one,
334+
more realistic. As a default the "historical" light glow texture is used, for backwards
335+
compatibility; however adding a line to the Lights block in the .eng file the "new" light
336+
glow texture is taken. Customized light glow textures can be either used for all lights
337+
of a loco, or only for a subset of them. Different lights in the same locomotive
338+
may have different customized light glow textures.
339+
340+
Detailed spec
341+
'''''''''''''
342+
343+
344+
1) In the ``Content`` folder there is the default ``LightGlow.png``, which is displayed if
345+
no changes are done to the .eng file.
346+
2) In such folder there is also an ``ORTSLightGlow.png``, which is maybe more realistic.
347+
3) adding a line within the .eng file it is possible to select either ORTSLightGlow.png or any other picture
348+
with extension ``.png, .jpg, bmp, .gif, .ace, or .dds``.
349+
350+
351+
Here an example for the legacy Acela loco::
352+
353+
Lights ( 17
354+
ORTSGraphic ( "ORTSLightGlow.png" )
355+
Light (
356+
comment( Sphere of light )
357+
Type ( 1 )
358+
Conditions (...
359+
360+
The code first searches for the .png file by building its directory starting from the directory of
361+
the .eng file; in this case the line could be e.g.::
362+
363+
ORTSGraphic ( "ORTSAcelaLightGlow.png" )
364+
365+
4) The ``ORTSGraphic`` line can be added also for one or more ``Light()`` blocks. In that case the
366+
.png file is used only for the related Light block. Here an example::
367+
368+
Light (
369+
comment( Head light outer right bright )
370+
Type ( 0 )
371+
Conditions (
372+
Headlight ( 3 )
373+
Unit ( 2 )
374+
)
375+
FadeIn ( 0.5 )
376+
FadeOut ( 0.5 )
377+
Cycle ( 0 )
378+
States ( 1
379+
State (
380+
Duration ( 0.0 )
381+
LightColour ( ffffffff )
382+
Position ( -0.5922 2.4037 9.63208 )
383+
Azimuth ( 0.0 0.0 0.0 )
384+
Transition ( 0 )
385+
Radius ( 0.60 )
386+
Elevation ( -50 -50 -50 )
387+
)
388+
)
389+
ORTSGraphic (BigLightGlow.png)
390+
)
391+
392+
OR searches for the file as it does for the general file for all lights, as explained above.
393+
If the ``ORTSGraphic`` line is present both at the top of the ``Lights()`` and also in some
394+
``Light()`` subblock, the line present in the subblock prevails. So it is possible to have an
395+
.eng-specific graphic for all the lights, except the ones that have an own ``ORTSGraphic`` line.
396+
321397

322398
Tilting trains
323399
==============

Source/Orts.Formats.Msts/LightCollection.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ public class Light
346346
public float FadeOut;
347347
public List<LightState> States = new List<LightState>();
348348
public List<LightCondition> Conditions = new List<LightCondition>();
349+
public string Graphic;
349350

350351
public Light(int index, STFReader stf)
351352
{
@@ -373,6 +374,7 @@ public Light(int index, STFReader stf)
373374
}),
374375
new STFReader.TokenProcessor("shapeindex", ()=>{ ShapeIndex = stf.ReadIntBlock(null); }),
375376
new STFReader.TokenProcessor("shapehierarchy", ()=>{ ShapeHierarchy = stf.ReadStringBlock(null).ToUpper(); }),
377+
new STFReader.TokenProcessor("ortsgraphic", ()=>{ Graphic = stf.ReadStringBlock(null).ToUpper(); }),
376378
});
377379
}
378380

@@ -401,6 +403,7 @@ public Light(Light light, bool reverse)
401403
public class LightCollection
402404
{
403405
public List<Light> Lights = new List<Light>();
406+
public string GeneralLightGlowGraphic = "LightGlow.png";
404407

405408
// Array of bools, one per type of condition in the same order as presented in the 'LightCondition' class
406409
// A 'true' indicates all lights in this set ignore the corresponding condition, so we don't need to waste time thinking about it
@@ -411,7 +414,8 @@ public LightCollection(STFReader stf)
411414
{
412415
stf.MustMatch("(");
413416
stf.ReadInt(null); // count; ignore this because its not always correct
414-
stf.ParseBlock(new[] {
417+
stf.ParseBlock(new[]{
418+
new STFReader.TokenProcessor("ortsgraphic", ()=>{ GeneralLightGlowGraphic = stf.ReadStringBlock("Lightglow.png").ToUpper(); }),
415419
new STFReader.TokenProcessor("light", ()=>{ Lights.Add(new Light(Lights.Count, stf)); }),
416420
});
417421
if (Lights.Count == 0)
-27.2 KB
Loading
35.3 KB
Loading

Source/RunActivity/RunActivity.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,9 @@
462462
<Content Include="Content\LightGlow.png">
463463
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
464464
</Content>
465+
<Content Include="Content\ORTSLightGlow.png">
466+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
467+
</Content>
465468
<Content Include="Content\Loading.png">
466469
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
467470
</Content>

Source/RunActivity/Viewer3D/Lights.cs

+29-7
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
using System;
3737
using System.Collections.Generic;
3838
using System.Diagnostics;
39+
using System.IO;
3940
using System.Linq;
4041

4142
namespace Orts.Viewer3D
@@ -89,8 +90,8 @@ public LightViewer(Viewer viewer, TrainCar car, TrainCarViewer carViewer)
8990
Viewer = viewer;
9091
Car = car;
9192
CarViewer = carViewer;
92-
93-
LightGlowMaterial = viewer.MaterialManager.Load("LightGlow");
93+
94+
LightGlowMaterial = viewer.MaterialManager.Load("LightGlow", DefineFullTexturePath(Car.Lights.GeneralLightGlowGraphic));
9495
LightConeMaterial = viewer.MaterialManager.Load("LightCone");
9596

9697
UpdateState();
@@ -102,6 +103,10 @@ public LightViewer(Viewer viewer, TrainCar car, TrainCarViewer carViewer)
102103
{
103104
case LightType.Glow:
104105
LightPrimitives.Add(new LightGlowPrimitive(this, Viewer.RenderProcess, light));
106+
if (light.Graphic != null)
107+
(LightPrimitives.Last() as LightGlowPrimitive).SpecificGlowMaterial = viewer.MaterialManager.Load("LightGlow", DefineFullTexturePath(light.Graphic, true));
108+
else
109+
(LightPrimitives.Last() as LightGlowPrimitive).SpecificGlowMaterial = LightGlowMaterial;
105110
break;
106111
case LightType.Cone:
107112
LightPrimitives.Add(new LightConePrimitive(this, Viewer.RenderProcess, light));
@@ -149,6 +154,17 @@ public LightViewer(Viewer viewer, TrainCar car, TrainCarViewer carViewer)
149154
UpdateActiveLightCone();
150155
}
151156

157+
string DefineFullTexturePath(string textureName, bool searchSpecificTexture = false)
158+
{
159+
if (File.Exists(Path.Combine(Path.GetDirectoryName(Car.WagFilePath), textureName)))
160+
return Path.Combine(Path.GetDirectoryName(Car.WagFilePath), textureName);
161+
if (searchSpecificTexture)
162+
Trace.TraceWarning("Could not find light graphic {0} at {1}", textureName, Path.Combine(Path.GetDirectoryName(Car.WagFilePath), textureName));
163+
if (File.Exists(Path.Combine(Viewer.ContentPath, textureName)))
164+
return Path.Combine(Viewer.ContentPath, textureName);
165+
return Path.Combine(Viewer.ContentPath, "LightGlow.png");
166+
}
167+
152168
void UpdateActiveLightCone()
153169
{
154170
var newLightCone = (LightConePrimitive)LightPrimitives.FirstOrDefault(lm => lm is LightConePrimitive && lm.Enabled);
@@ -220,9 +236,9 @@ public void PrepareFrame(RenderFrame frame, ElapsedTime elapsedTime)
220236
if ((lightPrimitive.Enabled || lightPrimitive.FadeOut) && lightPrimitive is LightGlowPrimitive)
221237
{
222238
if (ShapeXNATranslations.TryGetValue(lightPrimitive.Light.ShapeIndex, out Matrix lightMatrix))
223-
frame.AddPrimitive(LightGlowMaterial, lightPrimitive, RenderPrimitiveGroup.Lights, ref lightMatrix);
239+
frame.AddPrimitive((lightPrimitive as LightGlowPrimitive).SpecificGlowMaterial, lightPrimitive, RenderPrimitiveGroup.Lights, ref lightMatrix);
224240
else
225-
frame.AddPrimitive(LightGlowMaterial, lightPrimitive, RenderPrimitiveGroup.Lights, ref xnaDTileTranslation);
241+
frame.AddPrimitive((lightPrimitive as LightGlowPrimitive).SpecificGlowMaterial, lightPrimitive, RenderPrimitiveGroup.Lights, ref xnaDTileTranslation);
226242
}
227243

228244
#if DEBUG_LIGHT_CONE
@@ -252,6 +268,11 @@ public void Mark()
252268
{
253269
LightGlowMaterial.Mark();
254270
LightConeMaterial.Mark();
271+
foreach (var lightPrimitive in LightPrimitives)
272+
if (lightPrimitive is LightGlowPrimitive && lightPrimitive.Light.Graphic != null)
273+
{
274+
(lightPrimitive as LightGlowPrimitive).SpecificGlowMaterial.Mark();
275+
}
255276
}
256277

257278
public static void CalculateLightCone(LightState lightState, out Vector3 position, out Vector3 direction, out float angle, out float radius, out float distance, out Vector4 color)
@@ -680,6 +701,7 @@ public class LightGlowPrimitive : LightPrimitive
680701
static VertexDeclaration VertexDeclaration;
681702
VertexBuffer VertexBuffer;
682703
static IndexBuffer IndexBuffer;
704+
public Material SpecificGlowMaterial;
683705

684706
public LightGlowPrimitive(LightViewer lightViewer, RenderProcess renderProcess, Light light)
685707
: base(light)
@@ -932,11 +954,11 @@ public class LightGlowMaterial : Material
932954
{
933955
readonly Texture2D LightGlowTexture;
934956

935-
public LightGlowMaterial(Viewer viewer)
936-
: base(viewer, null)
957+
public LightGlowMaterial(Viewer viewer, string textureName)
958+
: base(viewer, textureName)
937959
{
938960
// TODO: This should happen on the loader thread.
939-
LightGlowTexture = SharedTextureManager.Get(Viewer.RenderProcess.GraphicsDevice, System.IO.Path.Combine(Viewer.ContentPath, "Lightglow.png"));
961+
LightGlowTexture = SharedTextureManager.Get(Viewer.RenderProcess.GraphicsDevice, textureName);
940962
}
941963

942964
public override void SetState(GraphicsDevice graphicsDevice, Material previousMaterial)

Source/RunActivity/Viewer3D/Materials.cs

+9-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
using System.IO;
2525
using System.Linq;
2626
using System.Threading;
27+
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
2728
using Microsoft.Xna.Framework;
2829
using Microsoft.Xna.Framework.Graphics;
2930
using Orts.Viewer3D.Common;
@@ -179,7 +180,13 @@ public static Texture2D Get(GraphicsDevice graphicsDevice, string path)
179180

180181
if (ext == ".ace")
181182
return Orts.Formats.Msts.AceFile.Texture2DFromFile(graphicsDevice, path);
182-
183+
else if (ext == ".dds" && File.Exists(path))
184+
{
185+
Texture2D ddsTexture;
186+
DDSLib.DDSFromFile(path, graphicsDevice, true, out ddsTexture);
187+
return ddsTexture;
188+
}
189+
183190
using (var stream = File.OpenRead(path))
184191
{
185192
if (ext == ".gif" || ext == ".jpg" || ext == ".png")
@@ -360,7 +367,7 @@ public Material Load(string materialName, string textureName = null, int options
360367
Materials[materialKey] = new LightConeMaterial(Viewer);
361368
break;
362369
case "LightGlow":
363-
Materials[materialKey] = new LightGlowMaterial(Viewer);
370+
Materials[materialKey] = new LightGlowMaterial(Viewer, textureName);
364371
break;
365372
case "PopupWindow":
366373
Materials[materialKey] = new PopupWindowMaterial(Viewer);

0 commit comments

Comments
 (0)