#region ================== Namespaces using System; using System.Collections.Generic; using System.Drawing; using System.Threading; using CodeImp.DoomBuilder.Data; using CodeImp.DoomBuilder.Geometry; using CodeImp.DoomBuilder.Map; using CodeImp.DoomBuilder.VisualModes; #endregion namespace CodeImp.DoomBuilder.BuilderModes { [ErrorChecker("Check texture alignment", false, 1000)] public class CheckTextureAlignment : ErrorChecker { #region ================== Constants private const int PROGRESS_STEP = 100; #endregion #region ================== Variables // Now THAT'S what I call a collection! :) private Dictionary>>> donesides; #endregion #region ================== Constructor / Destructor // Constructor public CheckTextureAlignment() { // Total progress is done when all lines are checked SetTotalProgress(General.Map.Map.Linedefs.Count / PROGRESS_STEP); } #endregion #region ================== Methods // This runs the check public override void Run() { donesides = new Dictionary>>>(3); donesides.Add(VisualGeometryType.WALL_UPPER, new Dictionary>>()); donesides.Add(VisualGeometryType.WALL_MIDDLE, new Dictionary>>()); donesides.Add(VisualGeometryType.WALL_LOWER, new Dictionary>>()); int progress = 0; int stepprogress = 0; // Go for all the liendefs foreach(Linedef l in General.Map.Map.Linedefs) { // Check if we need to align any part of the front sidedef if(l.Front != null) { if(!donesides[VisualGeometryType.WALL_UPPER].ContainsKey(l.Front.Index)) CheckTopAlignment(l.Front); if(!donesides[VisualGeometryType.WALL_MIDDLE].ContainsKey(l.Front.Index)) CheckMiddleAlignment(l.Front); if(!donesides[VisualGeometryType.WALL_LOWER].ContainsKey(l.Front.Index)) CheckBottomAlignment(l.Front); } // Check if we need to align any part of the back sidedef if(l.Back != null) { if(!donesides[VisualGeometryType.WALL_UPPER].ContainsKey(l.Back.Index)) CheckTopAlignment(l.Back); if(!donesides[VisualGeometryType.WALL_MIDDLE].ContainsKey(l.Back.Index)) CheckMiddleAlignment(l.Back); if(!donesides[VisualGeometryType.WALL_LOWER].ContainsKey(l.Back.Index)) CheckBottomAlignment(l.Back); } // Handle thread interruption try { Thread.Sleep(0); } catch(ThreadInterruptedException) { // Clear collection donesides.Clear(); return; } // We are making progress! if((++progress / PROGRESS_STEP) > stepprogress) { stepprogress = (progress / PROGRESS_STEP); AddProgress(1); } } // Clear collection donesides.Clear(); } #endregion #region ================== Alignment checks private void CheckTopAlignment(Sidedef sidedef) { if(!sidedef.HighRequired() || sidedef.LongHighTexture == MapSet.EmptyLongName) return; double scaleX = sidedef.Fields.GetValue("scalex_top", 1.0); double scaleY = sidedef.Fields.GetValue("scaley_top", 1.0); int localY = (int)Math.Round(sidedef.Fields.GetValue("offsety_top", 0.0)); int x = sidedef.OffsetX + (int)Math.Round(sidedef.Fields.GetValue("offsetx_top", 0.0)); int y = (int)Tools.GetSidedefTopOffsetY(sidedef, sidedef.OffsetY + localY, scaleY, false); CheckAlignment(sidedef, x, y, scaleX, scaleY, VisualGeometryType.WALL_UPPER, sidedef.HighTexture); } private void CheckMiddleAlignment(Sidedef sidedef) { if(!sidedef.MiddleRequired() || sidedef.LongMiddleTexture == MapSet.EmptyLongName) return; double scaleX = sidedef.Fields.GetValue("scalex_mid", 1.0); double scaleY = sidedef.Fields.GetValue("scaley_mid", 1.0); int localY = (int)Math.Round(sidedef.Fields.GetValue("offsety_mid", 0.0)); int x = sidedef.OffsetX + (int)Math.Round(sidedef.Fields.GetValue("offsetx_mid", 0.0)); int y = (int)Tools.GetSidedefMiddleOffsetY(sidedef, sidedef.OffsetY + localY, scaleY, false); CheckAlignment(sidedef, x, y, scaleX, scaleY, VisualGeometryType.WALL_MIDDLE, sidedef.MiddleTexture); } private void CheckBottomAlignment(Sidedef sidedef) { if(!sidedef.LowRequired() || sidedef.LongLowTexture == MapSet.EmptyLongName) return; double scaleX = sidedef.Fields.GetValue("scalex_bottom", 1.0); double scaleY = sidedef.Fields.GetValue("scaley_bottom", 1.0); int localY = (int)Math.Round(sidedef.Fields.GetValue("offsety_bottom", 0.0)); int x = sidedef.OffsetX + (int)Math.Round(sidedef.Fields.GetValue("offsetx_bottom", 0.0)); int y = (int)Tools.GetSidedefBottomOffsetY(sidedef, sidedef.OffsetY + localY, scaleY, false); CheckAlignment(sidedef, x, y, scaleX, scaleY, VisualGeometryType.WALL_LOWER, sidedef.LowTexture); } #endregion #region ================== Methods private void CheckAlignment(Sidedef sidedef, int offsetx, int offsety, double linescalex, double linescaley, VisualGeometryType parttype, string texturename) { ImageData texture = General.Map.Data.GetTextureImage(texturename); if(!texture.IsImageLoaded) return; Rectangle partsize = BuilderModesTools.GetSidedefPartSize(sidedef, parttype); double scalex = ((General.Map.Config.ScaledTextureOffsets && !texture.WorldPanning) ? texture.Scale.x : 1.0f); double scaley = ((General.Map.Config.ScaledTextureOffsets && !texture.WorldPanning) ? texture.Scale.y : 1.0f); // Move offsets to proper range offsetx %= texture.Width; if(offsetx < 0) offsetx += texture.Width; offsety %= texture.Height; if(offsety < 0) offsety += texture.Height; // Check if current line is aligned to other sides ICollection lines = (sidedef.IsFront ? sidedef.Line.Start.Linedefs : sidedef.Line.End.Linedefs); Vertex v = sidedef.IsFront ? sidedef.Line.Start : sidedef.Line.End; foreach(Linedef line in lines) { if(line.Index == sidedef.Line.Index) continue; Sidedef target = null; if(line.Front != null && line.End == v) target = line.Front; else if(line.Back != null && line.Start == v) target = line.Back; // No target? if(target == null) continue; // Get expected texture offsets VisualGeometryType targetparttype; int alignedY = GetExpectedOffsetY(sidedef, target, texturename, texture.Height, scaley, linescaley, partsize, out targetparttype); if(targetparttype == VisualGeometryType.UNKNOWN) continue; // Already added? if(donesides[parttype].ContainsKey(sidedef.Index) && donesides[parttype][sidedef.Index][targetparttype].ContainsKey(target.Index)) continue; // Not aligned if scaley is not equal double targetscaley = GetSidedefValue(target, targetparttype, "scaley", 1.0); if(targetscaley != linescaley) { SubmitResult(new ResultTexturesMisaligned(sidedef, target, texturename)); } double targetscalex = GetSidedefValue(target, targetparttype, "scalex", 1.0); alignedY %= texture.Height; if(alignedY < 0) alignedY += texture.Height; int alignedX = (target.OffsetX + (int)GetSidedefValue(target, targetparttype, "offsetx", 0f) + (int)Math.Round(target.Line.Length / scalex * targetscalex)) % texture.Width; if(alignedX < 0) alignedX += texture.Width; // Submit result if target offsets don't match expected ones if(offsetx != alignedX || offsety != alignedY) { SubmitResult(new ResultTexturesMisaligned(sidedef, target, texturename)); } // Add to collection AddProcessedSides(sidedef, parttype, target, targetparttype); } // Check if other sides are aligned to current side lines = (sidedef.IsFront ? sidedef.Line.End.Linedefs : sidedef.Line.Start.Linedefs); v = (sidedef.IsFront ? sidedef.Line.End : sidedef.Line.Start); foreach(Linedef line in lines) { if(line.Index == sidedef.Line.Index) continue; Sidedef target = null; if(line.Front != null && line.Start == v) target = line.Front; else if(line.Back != null && line.End == v) target = line.Back; // No target or laready processed? if(target == null) continue; // Get expected texture offsets VisualGeometryType targetparttype; int alignedY = GetExpectedOffsetY(sidedef, target, texturename, texture.Height, scaley, linescaley, partsize, out targetparttype); if(targetparttype == VisualGeometryType.UNKNOWN) continue; // Already added? if(donesides[parttype].ContainsKey(sidedef.Index) && donesides[parttype][sidedef.Index][targetparttype].ContainsKey(target.Index)) continue; // Not aligned if scaley is not equal double targetscaley = GetSidedefValue(target, targetparttype, "scaley", 1.0); if(targetscaley != linescaley) { SubmitResult(new ResultTexturesMisaligned(sidedef, target, texturename)); } alignedY %= texture.Height; if(alignedY < 0) alignedY += texture.Height; int alignedX = (target.OffsetX + (int)GetSidedefValue(target, targetparttype, "offsetx", 0.0) - (int)Math.Round(sidedef.Line.Length / scalex * linescalex)) % texture.Width; if(alignedX < 0) alignedX += texture.Width; // Submit result if target offsets don't match expected ones if(offsetx != alignedX || offsety != alignedY) { SubmitResult(new ResultTexturesMisaligned(sidedef, target, texturename)); } // Add to collection AddProcessedSides(sidedef, parttype, target, targetparttype); } } private static double GetSidedefValue(Sidedef target, VisualGeometryType targetparttype, string key, double defaultvalue) { switch(targetparttype) { case VisualGeometryType.WALL_UPPER: return Math.Round(UniFields.GetFloat(target.Fields, key + "_top", defaultvalue), 3); case VisualGeometryType.WALL_MIDDLE: return Math.Round(UniFields.GetFloat(target.Fields, key + "_mid", defaultvalue), 3); case VisualGeometryType.WALL_LOWER: return Math.Round(UniFields.GetFloat(target.Fields, key + "_bottom", defaultvalue), 3); } return 0; } private void AddProcessedSides(Sidedef s1, VisualGeometryType type1, Sidedef s2, VisualGeometryType type2) { // Add them both ways if(!donesides[type1].ContainsKey(s1.Index)) { donesides[type1].Add(s1.Index, new Dictionary>()); donesides[type1][s1.Index].Add(VisualGeometryType.WALL_UPPER, new Dictionary()); donesides[type1][s1.Index].Add(VisualGeometryType.WALL_MIDDLE, new Dictionary()); donesides[type1][s1.Index].Add(VisualGeometryType.WALL_LOWER, new Dictionary()); } donesides[type1][s1.Index][type2].Add(s2.Index, false); if(!donesides[type2].ContainsKey(s2.Index)) { donesides[type2].Add(s2.Index, new Dictionary>()); donesides[type2][s2.Index].Add(VisualGeometryType.WALL_UPPER, new Dictionary()); donesides[type2][s2.Index].Add(VisualGeometryType.WALL_MIDDLE, new Dictionary()); donesides[type2][s2.Index].Add(VisualGeometryType.WALL_LOWER, new Dictionary()); } donesides[type2][s2.Index][type1].Add(s1.Index, false); } private static int GetExpectedOffsetY(Sidedef source, Sidedef target, string texturename, int textureheight, double texturescaley, double linescaley, Rectangle partsize, out VisualGeometryType matchingparttype) { if(target.MiddleTexture == texturename && partsize.IntersectsWith(BuilderModesTools.GetSidedefPartSize(target, VisualGeometryType.WALL_MIDDLE))) { matchingparttype = VisualGeometryType.WALL_MIDDLE; int partheight = (int)Math.Round(((target.Sector.CeilHeight - source.Sector.CeilHeight) / texturescaley) * linescaley); return ((int)Tools.GetSidedefMiddleOffsetY(target, target.OffsetY + GetSidedefValue(target, matchingparttype, "offsety", 0f), linescaley, false) + partheight) % textureheight; } // Only check upper and lower textures if the sidedef as an other side. See https://github.com/jewalky/UltimateDoomBuilder/issues/533 if (target.Other != null && target.HighTexture == texturename && partsize.IntersectsWith(BuilderModesTools.GetSidedefPartSize(target, VisualGeometryType.WALL_UPPER))) { matchingparttype = VisualGeometryType.WALL_UPPER; int partheight = (int) Math.Round(((target.Sector.CeilHeight - source.Sector.CeilHeight) / texturescaley) * linescaley); return ((int)Tools.GetSidedefTopOffsetY(target, target.OffsetY + GetSidedefValue(target, matchingparttype, "offsety", 0f), linescaley, false) + partheight) % textureheight; } if (target.Other != null && target.LowTexture == texturename && partsize.IntersectsWith(BuilderModesTools.GetSidedefPartSize(target, VisualGeometryType.WALL_LOWER))) { matchingparttype = VisualGeometryType.WALL_LOWER; int partheight = (int)Math.Round(((target.Sector.CeilHeight - source.Sector.CeilHeight) / texturescaley) * linescaley); return ((int)Tools.GetSidedefBottomOffsetY(target, target.OffsetY + GetSidedefValue(target, matchingparttype, "offsety", 0f), linescaley, false) + partheight) % textureheight; } matchingparttype = VisualGeometryType.UNKNOWN; return int.MinValue; } #endregion } }