// This sets the Lower Unpegged flag public virtual void ApplyLowerUnpegged(bool set) { if (!set) { // Remove flag mode.CreateUndo("Remove lower-unpegged setting"); mode.SetActionResult("Removed lower-unpegged setting."); this.Sidedef.Line.SetFlag(General.Map.Config.LowerUnpeggedFlag, false); } else { // Add flag mode.CreateUndo("Set lower-unpegged setting"); mode.SetActionResult("Set lower-unpegged setting."); this.Sidedef.Line.SetFlag(General.Map.Config.LowerUnpeggedFlag, true); } // Update sidedef geometry VisualSidedefParts parts = Sector.GetSidedefParts(Sidedef); parts.SetupAllParts(); // Update other sidedef geometry if (Sidedef.Other != null) { BaseVisualSector othersector = (BaseVisualSector)mode.GetVisualSector(Sidedef.Other.Sector); parts = othersector.GetSidedefParts(Sidedef.Other); parts.SetupAllParts(); } }
// Flood-fill textures public virtual void OnTextureFloodfill() { if (BuilderPlug.Me.CopiedTexture != null) { string oldtexture = GetTextureName(); long oldtexturelong = Lump.MakeLongName(oldtexture); string newtexture = BuilderPlug.Me.CopiedTexture; if (newtexture != oldtexture) { mode.CreateUndo("Flood-fill textures with " + newtexture); mode.SetActionResult("Flood-filled textures with " + newtexture + "."); mode.Renderer.SetCrosshairBusy(true); General.Interface.RedrawDisplay(); // Get the texture ImageData newtextureimage = General.Map.Data.GetTextureImage(newtexture); if (newtextureimage != null) { if (mode.IsSingleSelection) { // Clear all marks, this will align everything it can General.Map.Map.ClearMarkedSidedefs(false); } else { // Limit the alignment to selection only General.Map.Map.ClearMarkedSidedefs(true); List <Sidedef> sides = mode.GetSelectedSidedefs(); foreach (Sidedef sd in sides) { sd.Marked = false; } } // Do the alignment Tools.FloodfillTextures(this.Sidedef, oldtexturelong, newtextureimage, false); // Get the changed sidedefs List <Sidedef> changes = General.Map.Map.GetMarkedSidedefs(true); foreach (Sidedef sd in changes) { // Update the parts for this sidedef! if (mode.VisualSectorExists(sd.Sector)) { BaseVisualSector vs = (mode.GetVisualSector(sd.Sector) as BaseVisualSector); VisualSidedefParts parts = vs.GetSidedefParts(sd); parts.SetupAllParts(); } } General.Map.Data.UpdateUsedTextures(); mode.Renderer.SetCrosshairBusy(false); mode.ShowTargetInfo(); } } } }
// Paste texture offsets public virtual void OnPasteTextureOffsets() { mode.CreateUndo("Paste texture offsets"); Sidedef.OffsetX = BuilderPlug.Me.CopiedOffsets.X; Sidedef.OffsetY = BuilderPlug.Me.CopiedOffsets.Y; mode.SetActionResult("Pasted texture offsets " + Sidedef.OffsetX + ", " + Sidedef.OffsetY + "."); // Update sidedef geometry VisualSidedefParts parts = Sector.GetSidedefParts(Sidedef); parts.SetupAllParts(); }
//mxd. This checks if any of the sidedef texture match the given textures public static bool SidedefTextureMatch(BaseVisualMode mode, Sidedef sd, HashSet <long> texturelongnames) { if (!mode.VisualSectorExists(sd.Sector)) { return(false); } VisualSidedefParts parts = ((BaseVisualSector)mode.GetVisualSector(sd.Sector)).GetSidedefParts(sd); return((texturelongnames.Contains(sd.LongHighTexture) && (parts.upper != null && parts.upper.Triangles > 0)) || (texturelongnames.Contains(sd.LongLowTexture) && (parts.lower != null && parts.lower.Triangles > 0)) || (texturelongnames.Contains(sd.LongMiddleTexture) && ((parts.middledouble != null && parts.middledouble.Triangles > 0) || (parts.middlesingle != null && parts.middlesingle.Triangles > 0)))); }
// Reset texture offsets public virtual void OnResetTextureOffset() { mode.CreateUndo("Reset texture offsets"); mode.SetActionResult("Texture offsets reset."); // Apply offsets Sidedef.OffsetX = 0; Sidedef.OffsetY = 0; // Update sidedef geometry VisualSidedefParts parts = Sector.GetSidedefParts(Sidedef); parts.SetupAllParts(); }
//mxd protected override void UpdateSkyRenderFlag() { bool isrenderedassky = renderassky; renderassky = (level.sector.FloorTexture == General.Map.Config.SkyFlatName || level.sector.LongFloorTexture == MapSet.EmptyLongName); if (isrenderedassky != renderassky && Sector.Sides != null) { // Middle geometry may need updating... foreach (Sidedef side in level.sector.Sidedefs) { VisualSidedefParts parts = Sector.GetSidedefParts(side); if (parts.middlesingle != null) { parts.middlesingle.UpdateSkyRenderFlag(); } } } }
// Texture offset change public virtual void OnChangeTextureOffset(int horizontal, int vertical) { if ((General.Map.UndoRedo.NextUndo == null) || (General.Map.UndoRedo.NextUndo.TicketID != undoticket)) { undoticket = mode.CreateUndo("Change texture offsets"); } // Apply offsets Sidedef.OffsetX -= horizontal; Sidedef.OffsetY -= vertical; mode.SetActionResult("Changed texture offsets to " + Sidedef.OffsetX + ", " + Sidedef.OffsetY + "."); // Update sidedef geometry VisualSidedefParts parts = Sector.GetSidedefParts(Sidedef); parts.SetupAllParts(); }
// Auto-align texture X offsets public virtual void OnTextureAlign(bool alignx, bool aligny) { mode.CreateUndo("Auto-align textures"); mode.SetActionResult("Auto-aligned textures."); // Make sure the texture is loaded (we need the texture size) if (!base.Texture.IsImageLoaded) { base.Texture.LoadImage(); } if (mode.IsSingleSelection) { // Clear all marks, this will align everything it can General.Map.Map.ClearMarkedSidedefs(false); } else { // Limit the alignment to selection only General.Map.Map.ClearMarkedSidedefs(true); List <Sidedef> sides = mode.GetSelectedSidedefs(); foreach (Sidedef sd in sides) { sd.Marked = false; } } // Do the alignment Tools.AutoAlignTextures(this.Sidedef, base.Texture, alignx, aligny, false); // Get the changed sidedefs List <Sidedef> changes = General.Map.Map.GetMarkedSidedefs(true); foreach (Sidedef sd in changes) { // Update the parts for this sidedef! if (mode.VisualSectorExists(sd.Sector)) { BaseVisualSector vs = (mode.GetVisualSector(sd.Sector) as BaseVisualSector); VisualSidedefParts parts = vs.GetSidedefParts(sd); parts.SetupAllParts(); } } }
//mxd protected override void UpdateSkyRenderFlag() { bool isrenderedassky = renderassky; renderassky = (level.sector.CeilTexture == General.Map.Config.SkyFlatName); if (isrenderedassky != renderassky && Sector.Sides != null) { // Upper/middle geometry may need updating... foreach (Sidedef side in level.sector.Sidedefs) { VisualSidedefParts parts = Sector.GetSidedefParts(side); if (parts.upper != null) { parts.upper.UpdateSkyRenderFlag(); } else if (parts.middlesingle != null) { parts.middlesingle.UpdateSkyRenderFlag(); } // On the other side as well... if (side.Other != null && side.Other.Sector != null && side.Other.Sector.CeilTexture == General.Map.Config.SkyFlatName) { BaseVisualSector other = (BaseVisualSector)mode.GetVisualSector(side.Other.Sector); if (other != null && other.Sides != null) { parts = other.GetSidedefParts(side.Other); if (parts.upper != null) { parts.upper.UpdateSkyRenderFlag(); } } } } } }
// This performs texture floodfill along all walls that match with the same texture // NOTE: This method uses the sidedefs marking to indicate which sides have been filled // When resetsidemarks is set to true, all sidedefs will first be marked false (not aligned). // Setting resetsidemarks to false is usefull to fill only within a specific selection // (set the marked property to true for the sidedefs outside the selection) public static void FloodfillTextures(BaseVisualMode mode, Sidedef start, HashSet <long> originaltextures, string filltexture, bool resetsidemarks) { Stack <Tools.SidedefFillJob> todo = new Stack <Tools.SidedefFillJob>(50); // Mark all sidedefs false (they will be marked true when the texture is aligned) if (resetsidemarks) { General.Map.Map.ClearMarkedSidedefs(false); } // Begin with first sidedef if (SidedefTextureMatch(mode, start, originaltextures)) { todo.Push(new Tools.SidedefFillJob { sidedef = start, forward = true }); } // Continue until nothing more to align while (todo.Count > 0) { // Get the align job to do Tools.SidedefFillJob j = todo.Pop(); //mxd. Get visual parts if (mode.VisualSectorExists(j.sidedef.Sector)) { VisualSidedefParts parts = ((BaseVisualSector)mode.GetVisualSector(j.sidedef.Sector)).GetSidedefParts(j.sidedef); // Apply texturing if ((parts.upper != null && parts.upper.Triangles > 0) && originaltextures.Contains(j.sidedef.LongHighTexture)) { j.sidedef.SetTextureHigh(filltexture); } if (((parts.middledouble != null && parts.middledouble.Triangles > 0) || (parts.middlesingle != null && parts.middlesingle.Triangles > 0)) && originaltextures.Contains(j.sidedef.LongMiddleTexture)) { j.sidedef.SetTextureMid(filltexture); } if ((parts.lower != null && parts.lower.Triangles > 0) && originaltextures.Contains(j.sidedef.LongLowTexture)) { j.sidedef.SetTextureLow(filltexture); } } j.sidedef.Marked = true; if (j.forward) { // Add sidedefs forward (connected to the right vertex) Vertex v = j.sidedef.IsFront ? j.sidedef.Line.End : j.sidedef.Line.Start; AddSidedefsForFloodfill(mode, todo, v, true, originaltextures); // Add sidedefs backward (connected to the left vertex) v = j.sidedef.IsFront ? j.sidedef.Line.Start : j.sidedef.Line.End; AddSidedefsForFloodfill(mode, todo, v, false, originaltextures); } else { // Add sidedefs backward (connected to the left vertex) Vertex v = j.sidedef.IsFront ? j.sidedef.Line.Start : j.sidedef.Line.End; AddSidedefsForFloodfill(mode, todo, v, false, originaltextures); // Add sidedefs forward (connected to the right vertex) v = j.sidedef.IsFront ? j.sidedef.Line.End : j.sidedef.Line.Start; AddSidedefsForFloodfill(mode, todo, v, true, originaltextures); } } }
//mxd public bool Setup(SectorLevel level, Effect3DFloor extrafloor, bool innerside) { Sector s = level.sector; Vector2D texscale; this.innerside = innerside; //mxd base.Setup(level, extrafloor); // Fetch ZDoom fields float rotate = Angle2D.DegToRad(s.Fields.GetValue("rotationceiling", 0.0f)); Vector2D offset = new Vector2D(s.Fields.GetValue("xpanningceiling", 0.0f), s.Fields.GetValue("ypanningceiling", 0.0f)); Vector2D scale = new Vector2D(s.Fields.GetValue("xscaleceiling", 1.0f), s.Fields.GetValue("yscaleceiling", 1.0f)); //Load ceiling texture if (s.LongCeilTexture != MapSet.EmptyLongName) { base.Texture = General.Map.Data.GetFlatImage(s.LongCeilTexture); if (base.Texture == null || base.Texture is UnknownImage) { base.Texture = General.Map.Data.UnknownTexture3D; setuponloadedtexture = s.LongCeilTexture; } else if (!base.Texture.IsImageLoaded) { setuponloadedtexture = s.LongCeilTexture; } } else { // Use missing texture base.Texture = General.Map.Data.MissingTexture3D; setuponloadedtexture = 0; } // Determine texture scale if (base.Texture.IsImageLoaded) { texscale = new Vector2D(1.0f / base.Texture.ScaledWidth, 1.0f / base.Texture.ScaledHeight); } else { texscale = new Vector2D(1.0f / 64.0f, 1.0f / 64.0f); } // Determine brightness byte alpha = (byte)General.Clamp(level.alpha, 0, 255); int color = PixelColor.FromInt(level.color).WithAlpha(alpha).ToInt(); int targetbrightness; if (extrafloor != null && !extrafloor.VavoomType && !level.disablelighting) { //mxd. Top extrafloor level should calculate fogdensity from the brightness of the level above it if (!innerside) { targetbrightness = 0; SectorData sd = mode.GetSectorData(this.Sector.Sector); for (int i = 0; i < sd.LightLevels.Count - 1; i++) { if (sd.LightLevels[i] == level) { targetbrightness = sd.LightLevels[i + 1].brightnessbelow; break; } } } //mxd. Inner extrafloor ceilings must be colored using control sector's color and brightness else { targetbrightness = level.brightnessbelow; SectorData sd = mode.GetSectorData(this.Sector.Sector); for (int i = 0; i < sd.LightLevels.Count; i++) { if (sd.LightLevels[i] == level) { if (i > 0) { color = PixelColor.FromInt(sd.LightLevels[i - 1].color).WithAlpha(alpha).ToInt(); } break; } } } } else { targetbrightness = level.brightnessbelow; } //mxd. Determine fog density fogfactor = CalculateFogFactor(targetbrightness); // Make vertices ReadOnlyCollection <Vector2D> triverts = Sector.Sector.Triangles.Vertices; WorldVertex[] verts = new WorldVertex[triverts.Count]; for (int i = 0; i < triverts.Count; i++) { // Color shading verts[i].c = color; //mxd // Vertex coordinates verts[i].x = triverts[i].x; verts[i].y = triverts[i].y; verts[i].z = level.plane.GetZ(triverts[i]); // Texture coordinates Vector2D pos = triverts[i]; pos = pos.GetRotated(rotate); pos.y = -pos.y; pos = (pos + offset) * scale * texscale; verts[i].u = pos.x; verts[i].v = pos.y; } // The sector triangulation created clockwise triangles that // are right up for the floor. For the ceiling we must flip // the triangles upside down. if (extrafloor == null || extrafloor.VavoomType || innerside) { SwapTriangleVertices(verts); } // Determine render pass if (extrafloor != null) { if (extrafloor.Sloped3dFloor) //mxd { this.RenderPass = RenderPass.Mask; } else if (extrafloor.RenderAdditive) //mxd { this.RenderPass = RenderPass.Additive; } else if (level.alpha < 255) { this.RenderPass = RenderPass.Alpha; } else { this.RenderPass = RenderPass.Mask; } } else { this.RenderPass = RenderPass.Solid; } //mxd. Update sky render flag bool isrenderedassky = renderassky; renderassky = (level.sector.CeilTexture == General.Map.Config.SkyFlatName); if (isrenderedassky != renderassky && Sector.Sides != null) { // Upper sidedef geometry may need updating... foreach (Sidedef side in level.sector.Sidedefs) { VisualSidedefParts parts = Sector.GetSidedefParts(side); // Upper side can exist in either our, or the neightbouring sector, right? if (parts.upper != null && parts.upper.Triangles > 0) { parts.upper.UpdateSkyRenderFlag(); } else if (side.Other != null && side.Other.Sector != null && side.Other.Sector.CeilTexture == General.Map.Config.SkyFlatName) { // Update upper side of the neightbouring sector BaseVisualSector other = (BaseVisualSector)mode.GetVisualSector(side.Other.Sector); if (other != null && other.Sides != null) { parts = other.GetSidedefParts(side.Other); if (parts.upper != null && parts.upper.Triangles > 0) { parts.upper.UpdateSkyRenderFlag(); } } } } } // Apply vertices base.SetVertices(verts); return(verts.Length > 0); }
// This (re)builds the visual sector, calculating all geometry from scratch public void Rebuild() { // Forget old geometry base.ClearGeometry(); // Get sector data SectorData data = GetSectorData(); if (!data.Updated) { data.Update(); } // Create floor floor = (floor ?? new VisualFloor(mode, this)); if (floor.Setup(data.Floor, null)) { AddGeometry(floor); } // Create ceiling ceiling = (ceiling ?? new VisualCeiling(mode, this)); if (ceiling.Setup(data.Ceiling, null)) { AddGeometry(ceiling); } // Create 3D floors for (int i = 0; i < data.ExtraFloors.Count; i++) { Effect3DFloor ef = data.ExtraFloors[i]; bool floorRequired = ef.VavoomType; //mxd bool ceilingRequired = ef.VavoomType; //mxd if (ef.VavoomType || !ef.IgnoreBottomHeight) { //mxd. check if 3d floor is between real floor and ceiling if (!ef.VavoomType) { if (ef.Ceiling.plane.GetInverted().Normal != floor.Level.plane.Normal || ef.Ceiling.plane.Normal != ceiling.Level.plane.Normal) { //mxd. check if at least one vertex of 3d floor is between floor and ceiling floorRequired = Check3dFloorPlane(floor.Vertices, ceiling.Vertices, ef.Ceiling.plane); } //if floor, ceiling and 3d floor are not sloped, compare offsets else if (-floor.Level.plane.Offset < ef.Ceiling.plane.Offset && ceiling.Level.plane.Offset > ef.Ceiling.plane.Offset) { floorRequired = true; } } //mxd. Create a floor if (floorRequired && ef.Ceiling.sector.IsDisposed == false) { VisualFloor vf = (i < extrafloors.Count) ? extrafloors[i] : new VisualFloor(mode, this); if (vf.Setup(ef.Ceiling, ef)) { base.AddGeometry(vf); //mxd. add backside as well if (!ef.VavoomType && ef.RenderInside) { VisualFloor vfb = (i < extrabackfloors.Count) ? extrabackfloors[i] : new VisualFloor(mode, this); if (vfb.Setup(ef.Ceiling, ef, true)) { base.AddGeometry(vfb); } if (i >= extrabackfloors.Count) { extrabackfloors.Add(vfb); } } } if (i >= extrafloors.Count) { extrafloors.Add(vf); } } } //mxd. check if 3d ceiling is between real floor and ceiling if (!ef.VavoomType) { if (ef.Floor.plane.GetInverted().Normal != ceiling.Level.plane.Normal || ef.Floor.plane.Normal != floor.Level.plane.Normal) { //mxd. check if at least one vertex of 3d ceiling is between floor and ceiling ceilingRequired = Check3dFloorPlane(floor.Vertices, ceiling.Vertices, ef.Floor.plane); } //if floor, ceiling and 3d ceiling are not sloped, compare offsets else if (ceiling.Level.plane.Offset > -ef.Floor.plane.Offset && floor.Level.plane.Offset > ef.Floor.plane.Offset) { ceilingRequired = true; } } //mxd. Create a ceiling if (ceilingRequired && ef.Floor.sector.IsDisposed == false) { VisualCeiling vc = (i < extraceilings.Count) ? extraceilings[i] : new VisualCeiling(mode, this); if (vc.Setup(ef.Floor, ef)) { base.AddGeometry(vc); //mxd. add backside as well if (!ef.VavoomType && (ef.RenderInside || ef.IgnoreBottomHeight)) { VisualCeiling vcb = (i < extrabackceilings.Count) ? extrabackceilings[i] : new VisualCeiling(mode, this); if (vcb.Setup(ef.Floor, ef, true)) { base.AddGeometry(vcb); } if (i >= extrabackceilings.Count) { extrabackceilings.Add(vcb); } } } if (i >= extraceilings.Count) { extraceilings.Add(vc); } } } // Go for all sidedefs Dictionary <Sidedef, VisualSidedefParts> oldsides = sides ?? new Dictionary <Sidedef, VisualSidedefParts>(1); sides = new Dictionary <Sidedef, VisualSidedefParts>(base.Sector.Sidedefs.Count); foreach (Sidedef sd in base.Sector.Sidedefs) { // VisualSidedef already exists? VisualSidedefParts parts = oldsides.ContainsKey(sd) ? oldsides[sd] : new VisualSidedefParts(); // Doublesided or singlesided? if (sd.Other != null && sd.Line.IsFlagSet(General.Map.Config.DoubleSidedFlag)) { // Create upper part VisualUpper vu = parts.upper ?? new VisualUpper(mode, this, sd); if (vu.Setup()) { base.AddGeometry(vu); } // Create lower part VisualLower vl = parts.lower ?? new VisualLower(mode, this, sd); if (vl.Setup()) { base.AddGeometry(vl); } // Create middle part VisualMiddleDouble vm = parts.middledouble ?? new VisualMiddleDouble(mode, this, sd); if (vm.Setup()) { base.AddGeometry(vm); } //mxd. Create fog boundary VisualFogBoundary vb = parts.fogboundary ?? new VisualFogBoundary(mode, this, sd); if (vb.Setup()) { vm.FogFactor = 0; // Avoid double-fogging the middle part base.AddGeometry(vb); } // Create 3D wall parts SectorData osd = mode.GetSectorData(sd.Other.Sector); if (!osd.Updated) { osd.Update(); } List <VisualMiddle3D> middles = parts.middle3d ?? new List <VisualMiddle3D>(osd.ExtraFloors.Count); for (int i = 0; i < osd.ExtraFloors.Count; i++) { Effect3DFloor ef = osd.ExtraFloors[i]; if (!ef.VavoomType && ef.IgnoreBottomHeight) { continue; //mxd } VisualMiddle3D vm3 = (i < middles.Count) ? middles[i] : new VisualMiddle3D(mode, this, sd); if (vm3.Setup(ef)) { base.AddGeometry(vm3); } if (i >= middles.Count) { middles.Add(vm3); } } //mxd. Create backsides List <VisualMiddleBack> middlebacks = new List <VisualMiddleBack>(); for (int i = 0; i < data.ExtraFloors.Count; i++) { Effect3DFloor ef = data.ExtraFloors[i]; if (!ef.VavoomType && ef.RenderInside && !ef.IgnoreBottomHeight) { VisualMiddleBack vms = new VisualMiddleBack(mode, this, sd); if (vms.Setup(ef)) { base.AddGeometry(vms); } middlebacks.Add(vms); } } // Store sides.Add(sd, new VisualSidedefParts(vu, vl, vm, vb, middles, middlebacks)); } else { // Create middle part VisualMiddleSingle vm = parts.middlesingle ?? new VisualMiddleSingle(mode, this, sd); if (vm.Setup()) { base.AddGeometry(vm); } // Store sides.Add(sd, new VisualSidedefParts(vm)); } } // Done changed = false; }
// This usually happens when geometry is changed by undo, redo, cut or paste actions // and uses the marks to check what needs to be reloaded. protected override void ResourcesReloadedPartial() { bool sectorsmarked = false; if (General.Map.UndoRedo.GeometryChanged) { // Let the core do this (it will just dispose the sectors that were changed) base.ResourcesReloadedPartial(); } else { // Neighbour sectors must be updated as well foreach (Sector s in General.Map.Map.Sectors) { if (s.Marked) { sectorsmarked = true; foreach (Sidedef sd in s.Sidedefs) { sd.Marked = true; if (sd.Other != null) { sd.Other.Marked = true; } } } } // Go for all sidedefs to update foreach (Sidedef sd in General.Map.Map.Sidedefs) { if (sd.Marked && VisualSectorExists(sd.Sector)) { BaseVisualSector vs = (BaseVisualSector)GetVisualSector(sd.Sector); VisualSidedefParts parts = vs.GetSidedefParts(sd); parts.SetupAllParts(); } } // Go for all sectors to update foreach (Sector s in General.Map.Map.Sectors) { if (s.Marked && VisualSectorExists(s)) { BaseVisualSector vs = (BaseVisualSector)GetVisualSector(s); vs.Floor.Setup(); vs.Ceiling.Setup(); } } if (!sectorsmarked) { // No sectors or geometry changed. So we only have // to update things when they have changed. foreach (KeyValuePair <Thing, VisualThing> vt in allthings) { if (vt.Key.Marked) { (vt.Value as BaseVisualThing).Rebuild(); } } } else { // Things depend on the sector they are in and because we can't // easily determine which ones changed, we dispose all things foreach (KeyValuePair <Thing, VisualThing> vt in allthings) { vt.Value.Dispose(); } // Apply new lists allthings = new Dictionary <Thing, VisualThing>(allthings.Count); } // Clear visibility collections visiblesectors.Clear(); visibleblocks.Clear(); visiblegeometry.Clear(); visiblethings.Clear(); // Make new blockmap if (sectorsmarked || General.Map.UndoRedo.PopulationChanged) { FillBlockMap(); } // Visibility culling (this re-creates the needed resources) DoCulling(); } // Determine what we're aiming at now PickTarget(); }
// This (re)builds the visual sector, calculating all geometry from scratch public void Rebuild() { // Forget old geometry base.ClearGeometry(); // Create floor if (floor == null) { floor = new VisualFloor(mode, this); } floor.Setup(); base.AddGeometry(floor); // Create ceiling if (ceiling == null) { ceiling = new VisualCeiling(mode, this); } ceiling.Setup(); base.AddGeometry(ceiling); // Go for all sidedefs Dictionary <Sidedef, VisualSidedefParts> oldsides = sides ?? new Dictionary <Sidedef, VisualSidedefParts>(1); sides = new Dictionary <Sidedef, VisualSidedefParts>(base.Sector.Sidedefs.Count); foreach (Sidedef sd in base.Sector.Sidedefs) { // VisualSidedef already exists? VisualSidedefParts parts = oldsides.ContainsKey(sd) ? oldsides[sd] : new VisualSidedefParts(); // Doublesided or singlesided? if (sd.Other != null) { // Create upper part VisualUpper vu = parts.upper ?? new VisualUpper(mode, this, sd); vu.Setup(); base.AddGeometry(vu); // Create lower part VisualLower vl = parts.lower ?? new VisualLower(mode, this, sd); vl.Setup(); base.AddGeometry(vl); // Create middle part VisualMiddleDouble vm = parts.middledouble ?? new VisualMiddleDouble(mode, this, sd); vm.Setup(); base.AddGeometry(vm); // Store sides.Add(sd, new VisualSidedefParts(vu, vl, vm)); } else { // Create middle part VisualMiddleSingle vm = parts.middlesingle ?? new VisualMiddleSingle(mode, this, sd); vm.Setup(); base.AddGeometry(vm); // Store sides.Add(sd, new VisualSidedefParts(vm)); } } // Done changed = false; }