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) {