diff --git a/TombEditor/Controls/Panel3D/Panel3DDraw.cs b/TombEditor/Controls/Panel3D/Panel3DDraw.cs
index dd6229218d..a650bdbcb8 100644
--- a/TombEditor/Controls/Panel3D/Panel3DDraw.cs
+++ b/TombEditor/Controls/Panel3D/Panel3DDraw.cs
@@ -519,6 +519,10 @@ private void DrawLights(Effect effect, Room[] roomsWhoseObjectsToDraw, List
+
+
diff --git a/TombLib/TombLib/LevelData/Compilers/Rooms.cs b/TombLib/TombLib/LevelData/Compilers/Rooms.cs
index 1224b8a2dc..6507f422a1 100644
--- a/TombLib/TombLib/LevelData/Compilers/Rooms.cs
+++ b/TombLib/TombLib/LevelData/Compilers/Rooms.cs
@@ -1240,6 +1240,8 @@ private void ConvertLights(Room room, tr_room newRoom)
newLight.Length = light.Intensity; // Store float intensity as length
break;
case LightType.Effect:
+ case LightType.Glow:
+ case LightType.Move:
continue;
default:
throw new Exception("Unknown light type '" + light.Type + "' encountered.");
diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/Rooms.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/Rooms.cs
index e05d8b4859..7c5b02aa06 100644
--- a/TombLib/TombLib/LevelData/Compilers/TombEngine/Rooms.cs
+++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/Rooms.cs
@@ -282,6 +282,8 @@ private TombEngineRoom BuildRoom(Room room)
}
}
+ int importedGeometryVertexStart = 0;
+
// Generate geometry
{
// Add room geometry
@@ -555,6 +557,7 @@ private TombEngineRoom BuildRoom(Room room)
// Add geometry imported objects
+ importedGeometryVertexStart = roomVertices.Count;
foreach (var geometry in room.Objects.OfType())
{
if (geometry.Model?.DirectXModel == null)
@@ -731,6 +734,10 @@ private TombEngineRoom BuildRoom(Room room)
// Assign vertex effects
+ var vertexEffectLights = room.Objects.OfType()
+ .Where(l => l.Enabled && (l.Type == LightType.Glow || l.Type == LightType.Move))
+ .ToList();
+
for (int i = 0; i < newRoom.Vertices.Count; ++i)
{
var trVertex = newRoom.Vertices[i];
@@ -739,9 +746,31 @@ private TombEngineRoom BuildRoom(Room room)
var xv = (int)(trVertex.Position.X / Level.SectorSizeUnit);
var zv = (int)(trVertex.Position.Z / Level.SectorSizeUnit);
+ // Apply vertex effects from Glow and Move light objects to all vertices,
+ // including border/wall vertices that are skipped by the sector bounds check below.
+ var vertexPosLocal = new Vector3(trVertex.Position.X, -trVertex.Position.Y - room.WorldPos.Y, trVertex.Position.Z);
+ bool isImportedGeometryVertex = i >= importedGeometryVertexStart;
+ foreach (var effectLight in vertexEffectLights)
+ {
+ if (isImportedGeometryVertex && !effectLight.IsUsedForImportedGeometry)
+ continue;
+
+ float strength = RoomGeometry.CalculateVertexEffectStrength(room, effectLight, vertexPosLocal);
+ if (strength <= 0f)
+ continue;
+
+ if (effectLight.Type == LightType.Glow)
+ trVertex.Glow = Math.Max(trVertex.Glow, strength);
+ else if (effectLight.Type == LightType.Move)
+ trVertex.Move = Math.Max(trVertex.Move, strength);
+ }
+
// Check for vertex out of room bounds
if (xv <= 0 || zv <= 0 || xv >= room.NumXSectors || zv >= room.NumZSectors)
+ {
+ newRoom.Vertices[i] = trVertex;
continue;
+ }
foreach (var portal in room.PortalsCache)
{
@@ -1001,6 +1030,8 @@ private void ConvertLights(Room room, TombEngineRoom newRoom)
newLight.Out = light.OuterRange * Level.SectorSizeUnit;
break;
case LightType.Effect:
+ case LightType.Glow:
+ case LightType.Move:
continue;
default:
throw new Exception("Unknown light type '" + light.Type + "' encountered.");
diff --git a/TombLib/TombLib/LevelData/Instances/LightInstance.cs b/TombLib/TombLib/LevelData/Instances/LightInstance.cs
index 8ff5cdaa40..cd3e731df0 100644
--- a/TombLib/TombLib/LevelData/Instances/LightInstance.cs
+++ b/TombLib/TombLib/LevelData/Instances/LightInstance.cs
@@ -9,7 +9,7 @@ public enum LightQuality : byte
}
public enum LightType : byte
{
- Point, Shadow, Spot, Effect, Sun, FogBulb
+ Point, Shadow, Spot, Effect, Sun, FogBulb, Glow, Move
}
public class LightInstance : PositionBasedObjectInstance, IColorable, IReplaceable, IRotateableYX
@@ -65,6 +65,11 @@ public LightInstance(LightType type)
IsStaticallyUsed = false;
IsUsedForImportedGeometry = false;
break;
+ case LightType.Glow:
+ case LightType.Move:
+ IsDynamicallyUsed = false;
+ CastDynamicShadows = false;
+ break;
}
}
diff --git a/TombLib/TombLib/LevelData/RoomGeometry.cs b/TombLib/TombLib/LevelData/RoomGeometry.cs
index 5276002643..b25959b982 100644
--- a/TombLib/TombLib/LevelData/RoomGeometry.cs
+++ b/TombLib/TombLib/LevelData/RoomGeometry.cs
@@ -1163,6 +1163,7 @@ public static Vector3 CalculateLightForVertex(Room room, LightInstance light, Ve
LightType.Effect => CalculateEffectLight(light, position),
LightType.Sun => CalculateSunLight(room, light, position, normal, highQuality),
LightType.Spot => CalculateSpotLight(room, light, position, normal, highQuality),
+ LightType.Glow or LightType.Move => Vector3.Zero,
_ => Vector3.Zero
};
}
@@ -1229,6 +1230,28 @@ private static Vector3 CalculateEffectLight(LightInstance light, Vector3 positio
return finalIntensity * light.Color * ColorNormalization;
}
+ // Calculates the strength of a Glow or Move vertex effect at a given position.
+ // Returns a value in [0, 1] based on linear falloff from InnerRange to OuterRange,
+ // scaled by Intensity. Respects IsObstructedByRoomGeometry via raytrace.
+ public static float CalculateVertexEffectStrength(Room room, LightInstance light, Vector3 position)
+ {
+ float distance = Vector3.Distance(position, light.Position);
+ float outerRadius = light.OuterRange * Level.SectorSizeUnit;
+
+ if (distance > outerRadius)
+ return 0.0f;
+
+ float innerRadius = light.InnerRange * Level.SectorSizeUnit;
+ float rangeDelta = outerRadius - innerRadius;
+ float attenuation = rangeDelta > 0 ? Math.Clamp((outerRadius - distance) / rangeDelta, 0.0f, 1.0f) : 1.0f;
+
+ if (attenuation <= 0.0f)
+ return 0.0f;
+
+ float raytraceResult = light.IsObstructedByRoomGeometry && LightRayTrace(room, position, light.Position) ? 0.0f : 1.0f;
+ return Math.Clamp(light.Intensity * attenuation * raytraceResult, 0.0f, 1.0f);
+ }
+
private static Vector3 CalculateSunLight(Room room, LightInstance light, Vector3 position,
Vector3 normal, bool highQuality)
{