#region ================== Copyright (c) 2007 Pascal vd Heiden /* * Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com * This program is released under GNU General Public License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #endregion #region ================== Namespaces using System; using System.Collections.Generic; using CodeImp.DoomBuilder.IO; using CodeImp.DoomBuilder.Geometry; using CodeImp.DoomBuilder.Data; #endregion namespace CodeImp.DoomBuilder.Map { public sealed class Sidedef : MapElement { #region ================== Variables // Map private MapSet map; // List items private LinkedListNode sectorlistitem; // Owner private Linedef linedef; // Sector private Sector sector; // Properties private int offsetx; private int offsety; private string texnamehigh; private string texnamemid; private string texnamelow; private long longtexnamehigh; private long longtexnamemid; private long longtexnamelow; //mxd. UDMF properties private Dictionary flags; // Clone private int serializedindex; // Rendering private int lastProcessed; #endregion #region ================== Properties public MapSet Map { get { return map; } } public bool IsFront { get { return (linedef != null) && (this == linedef.Front); } } public Linedef Line { get { return linedef; } } public Sidedef Other { get { return (this == linedef.Front ? linedef.Back : linedef.Front); } } public Sector Sector { get { return sector; } } internal Dictionary Flags { get { return flags; } } //mxd public double Angle { get { return (IsFront ? linedef.Angle : Angle2D.Normalized(linedef.Angle + Angle2D.PI)); } } public int OffsetX { get { return offsetx; } set { BeforePropsChange(); offsetx = value; } } public int OffsetY { get { return offsety; } set { BeforePropsChange(); offsety = value; } } public string HighTexture { get { return texnamehigh; } } public string MiddleTexture { get { return texnamemid; } } public string LowTexture { get { return texnamelow; } } public long LongHighTexture { get { return longtexnamehigh; } } public long LongMiddleTexture { get { return longtexnamemid; } } public long LongLowTexture { get { return longtexnamelow; } } internal int SerializedIndex { get { return serializedindex; } set { serializedindex = value; } } internal int LastProcessed { get { return lastProcessed; } set { lastProcessed = value; } } #endregion #region ================== Constructor / Disposer // Constructor internal Sidedef(MapSet map, int listindex, Linedef l, bool front, Sector s) { // Initialize this.elementtype = MapElementType.SIDEDEF; //mxd this.map = map; this.listindex = listindex; this.texnamehigh = "-"; this.texnamemid = "-"; this.texnamelow = "-"; this.longtexnamehigh = MapSet.EmptyLongName; this.longtexnamemid = MapSet.EmptyLongName; this.longtexnamelow = MapSet.EmptyLongName; this.flags = new Dictionary(StringComparer.Ordinal); //mxd // Attach linedef this.linedef = l; if(l != null) { if(front) l.AttachFrontP(this); else l.AttachBackP(this); } // Attach sector SetSectorP(s); if(map == General.Map.Map) General.Map.UndoRedo.RecAddSidedef(this); // We have no destructor GC.SuppressFinalize(this); } // Disposer public override void Dispose() { // Not already disposed? if(!isdisposed) { // Already set isdisposed so that changes can be prohibited isdisposed = true; if(map == General.Map.Map) General.Map.UndoRedo.RecRemSidedef(this); // Remove from main list map.RemoveSidedef(listindex); // Detach from linedef if(linedef != null) linedef.DetachSidedefP(this); // Detach from sector SetSectorP(null); // Clean up sectorlistitem = null; linedef = null; map = null; sector = null; //mxd. Restore isdisposed so base classes can do their disposal job isdisposed = false; // Dispose base base.Dispose(); } } #endregion #region ================== Management // Call this before changing properties protected override void BeforePropsChange() { if(map == General.Map.Map) General.Map.UndoRedo.RecPrpSidedef(this); } // Serialize / deserialize (passive: this doesn't record) new internal void ReadWrite(IReadWriteStream s) { if(!s.IsWriting) BeforePropsChange(); base.ReadWrite(s); //mxd if(s.IsWriting) { s.wInt(flags.Count); foreach(KeyValuePair f in flags) { s.wString(f.Key); s.wBool(f.Value); } } else { int c; s.rInt(out c); flags = new Dictionary(c, StringComparer.Ordinal); for(int i = 0; i < c; i++) { string t; s.rString(out t); bool b; s.rBool(out b); flags.Add(t, b); } } s.rwInt(ref offsetx); s.rwInt(ref offsety); s.rwString(ref texnamehigh); s.rwString(ref texnamemid); s.rwString(ref texnamelow); s.rwLong(ref longtexnamehigh); s.rwLong(ref longtexnamemid); s.rwLong(ref longtexnamelow); } // This copies all properties to another sidedef public void CopyPropertiesTo(Sidedef s) { s.BeforePropsChange(); // Copy properties s.offsetx = offsetx; s.offsety = offsety; s.texnamehigh = texnamehigh; s.texnamemid = texnamemid; s.texnamelow = texnamelow; s.longtexnamehigh = longtexnamehigh; s.longtexnamemid = longtexnamemid; s.longtexnamelow = longtexnamelow; s.flags = new Dictionary(flags); //mxd base.CopyPropertiesTo(s); } // This changes sector public void SetSector(Sector newsector) { if(map == General.Map.Map) General.Map.UndoRedo.RecRefSidedefSector(this); // Change sector SetSectorP(newsector); } internal void SetSectorP(Sector newsector) { // Detach from sector if(sector != null && !sector.IsDisposed) //mxd sector.DetachSidedefP(sectorlistitem); // Change sector sector = newsector; // Attach to sector if(sector != null && !sector.IsDisposed) //mxd sectorlistitem = sector.AttachSidedefP(this); General.Map.IsChanged = true; } // This sets the linedef public void SetLinedef(Linedef ld, bool front) { if(linedef != null) linedef.DetachSidedefP(this); if(ld != null) { if(front) ld.AttachFront(this); else ld.AttachBack(this); } } // This sets the linedef (passive: this doesn't tell the linedef and doesn't record) internal void SetLinedefP(Linedef ld) { linedef = ld; } //mxd. This translates UDMF fields back into the normal flags and activations internal void TranslateFromUDMF() { // Try to translate UDMF texture offsets to regular ones if(longtexnamemid != MapSet.EmptyLongName && MiddleRequired()) { offsetx += (int)UniFields.GetFloat(this.Fields, "offsetx_mid"); offsety += (int)UniFields.GetFloat(this.Fields, "offsety_mid"); } else if(longtexnamehigh != MapSet.EmptyLongName && HighRequired()) { offsetx += (int)UniFields.GetFloat(this.Fields, "offsetx_top"); offsety += (int)UniFields.GetFloat(this.Fields, "offsety_top"); } else if(longtexnamelow != MapSet.EmptyLongName && LowRequired()) { offsetx += (int)UniFields.GetFloat(this.Fields, "offsetx_bottom"); offsety += (int)UniFields.GetFloat(this.Fields, "offsety_bottom"); } // Clear UDMF-related properties this.Fields.Clear(); this.Flags.Clear(); } #endregion #region ================== Methods // This checks and returns a flag without creating it public bool IsFlagSet(string flagname) { return (flags.ContainsKey(flagname) && flags[flagname]); } // This sets a flag public void SetFlag(string flagname, bool value) { if(!flags.ContainsKey(flagname) || (IsFlagSet(flagname) != value)) { BeforePropsChange(); flags[flagname] = value; } } // This returns a copy of the flags dictionary public Dictionary GetFlags() { return new Dictionary(flags); } //mxd. This returns enabled flags public HashSet GetEnabledFlags() { HashSet result = new HashSet(); foreach(KeyValuePair group in flags) if(group.Value) result.Add(group.Key); return result; } // This clears all flags public void ClearFlags() { BeforePropsChange(); flags.Clear(); } // This removes textures that are not required public void RemoveUnneededTextures(bool removemiddle) { RemoveUnneededTextures(removemiddle, false, false); } public void RemoveUnneededTextures(bool removemiddle, bool force, bool shiftmiddle) { bool changed = false; //mxd // Check if the line or sectors have no action or tags because // if they do, any texture on this side could be needed if(force || ((linedef.Tag == 0) && (linedef.Action == 0) && (sector.Tag == 0) && ((Other == null) || (Other.sector.Tag == 0)))) { if(General.Settings.AutoClearSidedefTextures && !HighRequired()) { BeforePropsChange(); //mxd changed = true; this.texnamehigh = "-"; this.longtexnamehigh = MapSet.EmptyLongName; General.Map.IsChanged = true; } else if(shiftmiddle && this.longtexnamehigh == MapSet.EmptyLongName && HighRequired()) //mxd { SetTextureHigh(this.texnamemid); changed = true; } if(General.Settings.AutoClearSidedefTextures && !LowRequired()) { if(!changed) //mxd { BeforePropsChange(); changed = true; } this.texnamelow = "-"; this.longtexnamelow = MapSet.EmptyLongName; General.Map.IsChanged = true; } else if(shiftmiddle && this.longtexnamelow == MapSet.EmptyLongName && LowRequired()) //mxd { SetTextureLow(this.texnamemid); changed = true; } } // The middle texture can be removed regardless of any sector tag or linedef action if(!MiddleRequired() && removemiddle) { if(!changed) BeforePropsChange(); //mxd this.texnamemid = "-"; this.longtexnamemid = MapSet.EmptyLongName; General.Map.IsChanged = true; } } /// /// This checks if a texture is required /// public bool HighRequired() { // Doublesided? if(Other == null || Other.sector.HasSkyCeiling) return false; //mxd. Check sloped ceilings... if(General.Map.UDMF && this.sector != Other.Sector) { double thisstartz = this.sector.CeilHeight; double thisendz = this.sector.CeilHeight; double otherstartz = Other.sector.CeilHeight; double otherendz = Other.sector.CeilHeight; // Check if this side is affected by UDMF slope (it overrides vertex heights, riiiiiight?..) TODO: check this! if(this.sector.CeilSlope.GetLengthSq() > 0) { Plane ceil = new Plane(this.sector.CeilSlope, this.sector.CeilSlopeOffset); thisstartz = ceil.GetZ(this.Line.Start.Position); thisendz = ceil.GetZ(this.Line.End.Position); } else if(this.sector.Sidedefs.Count == 3) // Check vertex heights on this side { if(!double.IsNaN(this.Line.Start.ZCeiling)) thisstartz = this.Line.Start.ZCeiling; if(!double.IsNaN(this.Line.End.ZCeiling)) thisendz = this.Line.End.ZCeiling; } // Check if other side is affected by UDMF slope (it overrides vertex heights, riiiiiight?..) TODO: check this! if(Other.sector.CeilSlope.GetLengthSq() > 0) { Plane ceil = new Plane(Other.sector.CeilSlope, Other.sector.CeilSlopeOffset); otherstartz = ceil.GetZ(this.Line.Start.Position); otherendz = ceil.GetZ(this.Line.End.Position); } else if(Other.sector.Sidedefs.Count == 3) // Check other line's vertex heights { if(!double.IsNaN(this.Line.Start.ZCeiling)) otherstartz = this.Line.Start.ZCeiling; if(!double.IsNaN(this.Line.End.ZCeiling)) otherendz = this.Line.End.ZCeiling; } // Texture is required when our start or end vertex is higher than on the other side. if(thisstartz > otherstartz || thisendz > otherendz) return true; } // Texture is required when ceiling of other side is lower return Other.sector.CeilHeight < this.sector.CeilHeight; } /// /// This checks if a texture is required /// public bool MiddleRequired() { // Texture is required when the line is singlesided return (Other == null); } /// /// This checks if a texture is required /// public bool LowRequired() { // Doublesided? if (Other == null || Other.sector.HasSkyFloor) return false; //mxd. Check sloped floors... if(General.Map.UDMF && this.sector != Other.Sector) { double thisstartz = this.sector.FloorHeight; double thisendz = this.sector.FloorHeight; double otherstartz = Other.sector.FloorHeight; double otherendz = Other.sector.FloorHeight; // Check if this side is affected by UDMF slope (it overrides vertex heights, riiiiiight?..) TODO: check this! if(this.sector.FloorSlope.GetLengthSq() > 0) { Plane floor = new Plane(this.sector.FloorSlope, this.sector.FloorSlopeOffset); thisstartz = floor.GetZ(this.Line.Start.Position); thisendz = floor.GetZ(this.Line.End.Position); } else if(this.sector.Sidedefs.Count == 3) // Check vertex heights on this side { if(!double.IsNaN(this.Line.Start.ZFloor)) thisstartz = this.Line.Start.ZFloor; if(!double.IsNaN(this.Line.End.ZFloor)) thisendz = this.Line.End.ZFloor; } // Check if other side is affected by UDMF slope (it overrides vertex heights, riiiiiight?..) TODO: check this! if(Other.sector.FloorSlope.GetLengthSq() > 0) { Plane floor = new Plane(Other.sector.FloorSlope, Other.sector.FloorSlopeOffset); otherstartz = floor.GetZ(this.Line.Start.Position); otherendz = floor.GetZ(this.Line.End.Position); } else if(Other.sector.Sidedefs.Count == 3) // Check other line's vertex heights { if(!double.IsNaN(this.Line.Start.ZFloor)) otherstartz = this.Line.Start.ZFloor; if(!double.IsNaN(this.Line.End.ZFloor)) otherendz = this.Line.End.ZFloor; } // Texture is required when our start or end vertex is lower than on the other side. if(thisstartz < otherstartz || thisendz < otherendz) return true; } // Texture is required when floor of other side is higher return Other.sector.FloorHeight > this.sector.FloorHeight; } /// /// This returns the height of the upper wall part. Returns 0 when no upper part exists. /// public int GetHighHeight() { Sidedef other = this.Other; if(other != null) { int top = this.sector.CeilHeight; int bottom = other.sector.CeilHeight; int height = top - bottom; return (height > 0) ? height : 0; } return 0; } /// /// This returns the height of the middle wall part. /// public int GetMiddleHeight() { Sidedef other = this.Other; if(other != null) { int top = Math.Min(this.Sector.CeilHeight, other.Sector.CeilHeight); int bottom = Math.Max(this.Sector.FloorHeight, other.Sector.FloorHeight); int height = top - bottom; return (height > 0) ? height : 0; } else { int top = this.Sector.CeilHeight; int bottom = this.Sector.FloorHeight; int height = top - bottom; return (height > 0) ? height : 0; } } /// /// This returns the height of the lower wall part. Returns 0 when no lower part exists. /// public int GetLowHeight() { Sidedef other = this.Other; if(other != null) { int top = other.sector.FloorHeight; int bottom = this.sector.FloorHeight; int height = top - bottom; return (height > 0) ? height : 0; } return 0; } // This creates a checksum from the sidedef properties // Used for faster sidedefs compression public uint GetChecksum() { CRC crc = new CRC(); crc.Add(sector.FixedIndex); crc.Add(offsetx); crc.Add(offsety); crc.Add(longtexnamehigh); crc.Add(longtexnamelow); crc.Add(longtexnamemid); return (uint)(crc.Value & 0x00000000FFFFFFFF); } // This copies textures to another sidedef // And possibly also the offsets public void AddTexturesTo(Sidedef s) { int copyoffsets = 0; // s cannot be null if(s == null) return; s.BeforePropsChange(); // Upper texture set? if((texnamehigh.Length > 0) && (texnamehigh != "-")) { // Copy upper texture s.texnamehigh = texnamehigh; s.longtexnamehigh = longtexnamehigh; // Counts as a half coice for copying offsets copyoffsets += 1; //mxd. Also copy UDMF offsets and scale if(General.Map.UDMF) { UniFields.SetFloat(s.Fields, "offsetx_top", Fields.GetValue("offsetx_top", 0.0), 0.0); UniFields.SetFloat(s.Fields, "offsety_top", Fields.GetValue("offsety_top", 0.0), 0.0); UniFields.SetFloat(s.Fields, "scalex_top", Fields.GetValue("scalex_top", 1.0), 1.0); UniFields.SetFloat(s.Fields, "scaley_top", Fields.GetValue("scaley_top", 1.0), 1.0); } } // Middle texture set? if((texnamemid.Length > 0) && (texnamemid != "-")) { // Copy middle texture s.texnamemid = texnamemid; s.longtexnamemid = longtexnamemid; // Counts for copying offsets copyoffsets += 2; //mxd. Also copy UDMF offsets and scale if(General.Map.UDMF) { UniFields.SetFloat(s.Fields, "offsetx_mid", Fields.GetValue("offsetx_mid", 0.0), 0.0); UniFields.SetFloat(s.Fields, "offsety_mid", Fields.GetValue("offsety_mid", 0.0), 0.0); UniFields.SetFloat(s.Fields, "scalex_mid", Fields.GetValue("scalex_mid", 1.0), 1.0); UniFields.SetFloat(s.Fields, "scaley_mid", Fields.GetValue("scaley_mid", 1.0), 1.0); } } // Lower texture set? if((texnamelow.Length > 0) && (texnamelow != "-")) { // Copy middle texture s.texnamelow = texnamelow; s.longtexnamelow = longtexnamelow; // Counts as a half coice for copying offsets copyoffsets += 1; //mxd. Also copy UDMF offsets and scale if(General.Map.UDMF) { UniFields.SetFloat(s.Fields, "offsetx_bottom", Fields.GetValue("offsetx_bottom", 0.0), 0.0); UniFields.SetFloat(s.Fields, "offsety_bottom", Fields.GetValue("offsety_bottom", 0.0), 0.0); UniFields.SetFloat(s.Fields, "scalex_bottom", Fields.GetValue("scalex_bottom", 1.0), 1.0); UniFields.SetFloat(s.Fields, "scaley_bottom", Fields.GetValue("scaley_bottom", 1.0), 1.0); } } // Copy offsets also? if(copyoffsets >= 2) { // Copy offsets s.offsetx = offsetx; s.offsety = offsety; } General.Map.IsChanged = true; } //mxd. String representation public override string ToString() { #if DEBUG return "Sidedef " + listindex + (marked ? " (marked)" : "") + " (Line " + linedef.Index + (linedef.Marked ? " (marked)" : "") + ", Sector " + sector.Index + (sector.Marked ? " (marked)" : "") + ")"; #else return "Sidedef " + listindex; #endif } #endregion #region ================== Changes // This updates all properties public void Update(int offsetx, int offsety, string thigh, string tmid, string tlow) { Update(offsetx, offsety, thigh, tmid, tlow, new Dictionary(StringComparer.Ordinal)); } //mxd. This updates all properties (UDMF version) public void Update(int offsetx, int offsety, string thigh, string tmid, string tlow, Dictionary flags) { BeforePropsChange(); // Apply changes this.offsetx = offsetx; this.offsety = offsety; this.flags = new Dictionary(flags); //mxd //SetTextureMid(tmid); //SetTextureLow(tlow); //SetTextureHigh(thigh); //mxd. Set mid texture texnamemid = string.IsNullOrEmpty(tmid) ? "-" : tmid; longtexnamemid = Lump.MakeLongName(tmid); //mxd. Set low texture texnamelow = string.IsNullOrEmpty(tlow) ? "-" : tlow; longtexnamelow = Lump.MakeLongName(tlow); //mxd. Set high texture texnamehigh = string.IsNullOrEmpty(thigh) ? "-" : thigh; longtexnamehigh = Lump.MakeLongName(texnamehigh); //mxd. Map is changed General.Map.IsChanged = true; } // This sets texture public void SetTextureHigh(string name) { BeforePropsChange(); texnamehigh = string.IsNullOrEmpty(name) ? "-" : name; //mxd longtexnamehigh = Lump.MakeLongName(name); General.Map.IsChanged = true; } // This sets texture public void SetTextureMid(string name) { BeforePropsChange(); texnamemid = string.IsNullOrEmpty(name) ? "-" : name; //mxd; longtexnamemid = Lump.MakeLongName(name); General.Map.IsChanged = true; } // This sets texture public void SetTextureLow(string name) { BeforePropsChange(); texnamelow = string.IsNullOrEmpty(name) ? "-" : name; //mxd; longtexnamelow = Lump.MakeLongName(name); General.Map.IsChanged = true; } // This sets udmf texture offset public void SetUdmfTextureOffsetX(int offset) { this.Fields.BeforeFieldsChange(); //top if(longtexnamehigh != MapSet.EmptyLongName && General.Map.Data.GetTextureExists(texnamehigh)) { ImageData texture = General.Map.Data.GetTextureImage(texnamehigh); double scaleTop = Fields.GetValue("scalex_top", 1.0); double value = Fields.GetValue("offsetx_top", 0.0); double result = Math.Round(value + offset * scaleTop); if(texture.IsImageLoaded) result %= texture.Width; UniFields.SetFloat(Fields, "offsetx_top", result); } //middle if(longtexnamemid != MapSet.EmptyLongName && General.Map.Data.GetTextureExists(texnamemid)) { ImageData texture = General.Map.Data.GetTextureImage(texnamemid); double scaleMid = Fields.GetValue("scalex_mid", 1.0); double value = Fields.GetValue("offsetx_mid", 0.0); double result = Math.Round(value + offset * scaleMid); if(texture.IsImageLoaded) result %= texture.Width; UniFields.SetFloat(Fields, "offsetx_mid", result); } //bottom if(longtexnamelow != MapSet.EmptyLongName && General.Map.Data.GetTextureExists(texnamelow)) { ImageData texture = General.Map.Data.GetTextureImage(texnamelow); double scaleLow = Fields.GetValue("scalex_bottom", 1.0); double value = Fields.GetValue("offsetx_bottom", 0.0); double result = Math.Round(value + offset * scaleLow); if(texture.IsImageLoaded) result %= texture.Width; UniFields.SetFloat(Fields, "offsetx_bottom", result); } } #endregion } }