#region ================== Namespaces using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; using CodeImp.DoomBuilder.Config; using CodeImp.DoomBuilder.Editing; using CodeImp.DoomBuilder.Rendering; using CodeImp.DoomBuilder.Geometry; using CodeImp.DoomBuilder.Actions; using CodeImp.DoomBuilder.Windows; using CodeImp.DoomBuilder.Map; using CodeImp.DoomBuilder.BuilderModes.Interface; #endregion namespace CodeImp.DoomBuilder.BuilderModes.ClassicModes { [EditMode(DisplayName = "Bridge Mode", SwitchAction = "bridgemode", ButtonImage = "BridgeMode.png", ButtonOrder = 2, ButtonGroup = "002_modify", AllowCopyPaste = false, Volatile = true, Optional = false)] public class BridgeMode : BaseClassicMode { #region ================== Constants private const float GRIP_SIZE = 9.0f; private const float LINE_THICKNESS = 0.8f; internal const int MAX_SUBDIVISIONS = 32; internal const int MIN_SUBDIVISIONS = 0; #endregion #region ================== Variables private Vector2D[] pointGroup1; private Vector2D[] pointGroup2; private SectorProperties[] sectorProps1; private SectorProperties[] sectorProps2; private double[] relLenGroup1; private double[] relLenGroup2; private ControlHandle[] controlHandles; private int curControlHandle = -1; private PixelColor handleColor; private List curves; private int segmentsCount; // Options private bool snaptogrid; // SHIFT to toggle //tools form private BridgeModeForm form; #endregion #region ================== Constructor / Disposer public BridgeMode() { // We have no destructor GC.SuppressFinalize(this); } #endregion #region ================== Methods // Engaging public override void OnEngage() { base.OnEngage(); renderer.SetPresentation(Presentation.Standard); //check selection ICollection selection = General.Map.Map.GetSelectedLinedefs(true); List lines = new List(); foreach(Linedef ld in selection) { Line l = new Line(ld); lines.Add(l); } //do we have valid selection? if(!Setup(lines)) { FinishDraw(); return; } //show form form = new BridgeModeForm(); form.OnCancelClick += form_OnCancelClick; form.OnOkClick += form_OnOkClick; form.OnFlipClick += form_OnFlipClick; form.OnSubdivisionChanged += form_OnSubdivisionChanged; form.Show(General.Interface); General.Interface.FocusDisplay(); handleColor = General.Colors.BrightColors[new Random().Next(General.Colors.BrightColors.Length - 1)]; Update(); } // When select button is pressed protected override void OnSelectBegin() { base.OnSelectBegin(); //check if control handle is selected for(int i = 0; i < 4; i++) { if(mousemappos.x <= controlHandles[i].Position.x + GRIP_SIZE && mousemappos.x >= controlHandles[i].Position.x - GRIP_SIZE && mousemappos.y <= controlHandles[i].Position.y + GRIP_SIZE && mousemappos.y >= controlHandles[i].Position.y - GRIP_SIZE) { curControlHandle = i; General.Interface.SetCursor(Cursors.Cross); return; } } curControlHandle = -1; } // When select button is released protected override void OnSelectEnd() { base.OnSelectEnd(); General.Interface.SetCursor(Cursors.Default); curControlHandle = -1; } // Mouse moves public override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if(panning) return; //mxd. Skip all this jass while panning if(curControlHandle != -1) { ControlHandle handle = controlHandles[curControlHandle]; handle.Position = (snaptogrid ? General.Map.Grid.SnappedToGrid(mousemappos) : mousemappos); if(form.MirrorMode) { Vector2D pos = handle.RelativePosition; //handle angle double angle = Math.Atan2(-pos.y, -pos.x) + Angle2D.PIHALF; //angle of line, connecting handles ControlledPoints double dirAngle = -Math.Atan2(handle.Pair.ControlledPoint.y - handle.ControlledPoint.y, handle.Pair.ControlledPoint.x - handle.ControlledPoint.x); double length = Math.Sqrt(Math.Pow(Math.Abs(pos.x), 2.0) + Math.Pow(Math.Abs(pos.y), 2.0)); double mirroredAngle = angle + dirAngle * 2.0; handle.Pair.RelativePosition = new Vector2D(Math.Sin(mirroredAngle) * length, Math.Cos(mirroredAngle) * length); } else if(form.CopyMode) { handle.Pair.RelativePosition = handle.RelativePosition; } Update(); } } // Accepted public override void OnAccept() { Cursor.Current = Cursors.AppStarting; General.Settings.FindDefaultDrawSettings(); //get vertices List> shapes = GetShapes(); List>> drawShapes = new List>>(); //set stitch range float stitchrange = BuilderPlug.Me.StitchRange; BuilderPlug.Me.StitchRange = 0.1f; for(int i = 0; i < shapes.Count; i++) { List> shapesRow = new List>(); for(int c = 0; c < shapes[i].Count; c++) { List points = new List(); for(int p = 0; p < shapes[i][c].Length; p++) points.Add(DrawGeometryMode.GetCurrentPosition(shapes[i][c][p], true, false, false, false, renderer, points)); shapesRow.Add(points); } drawShapes.Add(shapesRow); } //restore stitch range BuilderPlug.Me.StitchRange = stitchrange; //draw lines if(drawShapes.Count > 0) { // Make undo for the draw General.Map.UndoRedo.CreateUndo("Bridge ("+form.Subdivisions+" subdivisions)"); List> sectorProps = new List>(); List>> newSectors = new List>>(); //create sector properties collection //sector row for(int i = 0; i < drawShapes.Count; i++) { sectorProps.Add(new List()); //sector in row for(int c = 0; c < drawShapes[i].Count; c++) sectorProps[i].Add(GetSectorProperties(i, c)); } // Make the drawing //sector row for(int i = 0; i < drawShapes.Count; i++) { newSectors.Add(new List>()); //sector in row for(int c = 0; c < drawShapes[i].Count; c++) { if(!Tools.DrawLines(drawShapes[i][c], false, true)) { // Drawing failed // NOTE: I have to call this twice, because the first time only cancels this volatile mode General.Interface.DisplayStatus(StatusType.Warning, "Failed to create a Bezier Path..."); General.Map.UndoRedo.WithdrawUndo(); General.Map.UndoRedo.WithdrawUndo(); return; } HashSet newsectors = General.Map.Map.GetUnselectedSectorsFromLinedefs(General.Map.Map.GetMarkedLinedefs(true)); newSectors[i].Add(newsectors); //set floor/ceiling heights and brightness foreach(Sector s in newsectors) { SectorProperties sp = sectorProps[i][c]; s.Brightness = sp.Brightness; s.FloorHeight = sp.FloorHeight; s.CeilHeight = (sp.CeilingHeight < sp.FloorHeight ? sp.FloorHeight + 8 : sp.CeilingHeight); } } } //apply textures //sector row for(int i = 0; i < newSectors.Count; i++) { //sector in row for(int c = 0; c < newSectors[i].Count; c++) { foreach(Sector s in newSectors[i][c]) { foreach(Sidedef sd in s.Sidedefs) { if(sd.LowRequired()) sd.SetTextureLow(sectorProps[i][c].LowTexture); if(sd.HighRequired()) sd.SetTextureHigh(sectorProps[i][c].HighTexture); } } } } //apply textures to front/back sides of shape //sector row for(int i = 0; i < newSectors.Count; i++) { //first/last sector in row for(int c = 0; c < newSectors[i].Count; c += newSectors[i].Count-1) { foreach(Sector s in newSectors[i][c]) { foreach(Sidedef sd in s.Sidedefs) { if(sd.Other != null) { if(sd.Other.LowRequired() && sd.Other.LowTexture == "-") sd.Other.SetTextureLow(sectorProps[i][c].LowTexture); if(sd.Other.HighRequired() && sd.Other.HighTexture == "-") sd.Other.SetTextureHigh(sectorProps[i][c].HighTexture); } } } } } General.Interface.DisplayStatus(StatusType.Action, "Created a Bridge with " + form.Subdivisions + " subdivisions."); // Snap to map format accuracy General.Map.Map.SnapAllToAccuracy(); // Clear selection General.Map.Map.ClearAllSelected(); // Update cached values General.Map.Map.Update(); // Update the used textures General.Map.Data.UpdateUsedTextures(); // Map is changed General.Map.IsChanged = true; } //close form if(form != null) form.Close(); // Done Cursor.Current = Cursors.Default; // Return to original mode General.Editing.ChangeMode(General.Editing.PreviousStableMode.Name); } // Cancelled public override void OnCancel() { // Cancel base class base.OnCancel(); //close form if(form != null) form.Dispose(); // Return to original mode General.Editing.ChangeMode(General.Editing.PreviousStableMode.Name); } // When a key is released public override void OnKeyUp(KeyEventArgs e) { base.OnKeyUp(e); if(snaptogrid != (General.Interface.ShiftState ^ General.Interface.SnapToGrid)) Update(); } // When a key is pressed public override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); if(snaptogrid != (General.Interface.ShiftState ^ General.Interface.SnapToGrid)) Update(); } // This redraws the display public override void OnRedrawDisplay() { renderer.RedrawSurface(); // Render lines if(renderer.StartPlotter(true)) { renderer.PlotLinedefSet(General.Map.Map.Linedefs); renderer.PlotVerticesSet(General.Map.Map.Vertices); renderer.Finish(); } // Render things if(renderer.StartThings(true)) { renderer.RenderThingSet(General.Map.Map.Things, General.Settings.ActiveThingsAlpha); renderer.RenderSRB2Extras(); renderer.Finish(); } // Normal update Update(); } public override void OnHelp() { General.ShowHelp("/gzdb/features/classic_modes/mode_drawbridge.html"); } #endregion #region ================== Setup/Update/Utility //this checks if initial data is valid private bool Setup(List lines) { if(!SetupPointGroups(lines)) return false; //setup control handles Vector2D center1 = CurveTools.GetPointOnLine(pointGroup1[0], pointGroup1[segmentsCount - 1], 0.5f); Vector2D center2 = CurveTools.GetPointOnLine(pointGroup2[0], pointGroup2[segmentsCount - 1], 0.5f); Vector2D loc1 = GetHandleLocation(pointGroup1[0], pointGroup1[segmentsCount - 1], center2); Vector2D loc2 = GetHandleLocation(pointGroup2[0], pointGroup2[segmentsCount - 1], center1); ControlHandle ch1 = new ControlHandle {ControlledPoint = pointGroup1[0], RelativePosition = loc1}; ControlHandle ch2 = new ControlHandle {ControlledPoint = pointGroup2[0], RelativePosition = loc2}; ControlHandle ch3 = new ControlHandle {ControlledPoint = pointGroup1[segmentsCount - 1], RelativePosition = loc1}; ControlHandle ch4 = new ControlHandle {ControlledPoint = pointGroup2[segmentsCount - 1], RelativePosition = loc2}; ch1.Pair = ch3; ch2.Pair = ch4; ch3.Pair = ch1; ch4.Pair = ch2; controlHandles = new[] {ch1, ch2, ch3, ch4}; //setup relative segments lengths relLenGroup1 = GetRelativeLengths(pointGroup1); relLenGroup2 = GetRelativeLengths(pointGroup2); return true; } private void Update() { if(renderer.StartOverlay(true)) { snaptogrid = General.Interface.ShiftState ^ General.Interface.SnapToGrid; PixelColor linesColor = snaptogrid ? General.Colors.Selection : General.Colors.Highlight; //draw curves curves = new List(); for(int i = 0; i < segmentsCount; i++) { Vector2D cp1 = CurveTools.GetPointOnLine(controlHandles[0].Position, controlHandles[2].Position, relLenGroup1[i]); Vector2D cp2 = CurveTools.GetPointOnLine(controlHandles[1].Position, controlHandles[3].Position, relLenGroup2[i]); curves.Add(CurveTools.GetCubicCurve(pointGroup1[i], pointGroup2[i], cp1, cp2, form.Subdivisions)); for(int c = 1; c < curves[i].Length; c++) renderer.RenderLine(curves[i][c - 1], curves[i][c], LINE_THICKNESS, linesColor, true); } //draw connecting lines if(form.Subdivisions > 1) { for(int i = 1; i < segmentsCount; i++) { for(int c = 1; c < form.Subdivisions; c++) { renderer.RenderLine(curves[i-1][c], curves[i][c], LINE_THICKNESS, linesColor, true); } } } //draw vertices float vsize = (renderer.VertexSize + 1.0f) / renderer.Scale; foreach(Vector2D[] points in curves) { for(int i = 1; i < points.Length - 1; i++ ) { renderer.RenderRectangleFilled(new RectangleF((float)(points[i].x - vsize), (float)(points[i].y - vsize), vsize * 2.0f, vsize * 2.0f), linesColor, true); } } //draw handle lines renderer.RenderLine(pointGroup1[0], controlHandles[0].Position, LINE_THICKNESS, handleColor, true); renderer.RenderLine(pointGroup2[0], controlHandles[1].Position, LINE_THICKNESS, handleColor, true); renderer.RenderLine(pointGroup1[segmentsCount - 1], controlHandles[2].Position, LINE_THICKNESS, handleColor, true); renderer.RenderLine(pointGroup2[segmentsCount - 1], controlHandles[3].Position, LINE_THICKNESS, handleColor, true); //draw handles float gripsize = GRIP_SIZE / renderer.Scale; for(int i = 0; i < 4; i++) { RectangleF handleRect = new RectangleF((float)(controlHandles[i].Position.x - gripsize * 0.5f), (float)(controlHandles[i].Position.y - gripsize * 0.5f), gripsize, gripsize); renderer.RenderRectangleFilled(handleRect, General.Colors.Background, true); renderer.RenderRectangle(handleRect, 2, General.Colors.Highlight, true); } renderer.Finish(); } renderer.Present(); } private SectorProperties GetSectorProperties(int lineIndex, int sectorIndex) { float delta = sectorIndex / (float)form.Subdivisions; delta += (1.0f - delta) / form.Subdivisions; SectorProperties sp = new SectorProperties(); sp.Brightness = IntepolateValue(sectorProps1[lineIndex].Brightness, sectorProps2[lineIndex].Brightness, delta, form.BrightnessMode); sp.FloorHeight = IntepolateValue(sectorProps1[lineIndex].FloorHeight, sectorProps2[lineIndex].FloorHeight, delta, form.FloorAlignMode); sp.CeilingHeight = IntepolateValue(sectorProps1[lineIndex].CeilingHeight, sectorProps2[lineIndex].CeilingHeight, delta, form.CeilingAlignMode); //textures sp.LowTexture = sectorProps1[lineIndex].LowTexture != "-" ? sectorProps1[lineIndex].LowTexture : sectorProps2[lineIndex].LowTexture; sp.HighTexture = sectorProps1[lineIndex].HighTexture != "-" ? sectorProps1[lineIndex].HighTexture : sectorProps2[lineIndex].HighTexture; return sp; } //this returns a list of shapes to draw private List> GetShapes() { List> shapes = new List>(); for(int i = 1; i < segmentsCount; i++) { List segShapes = new List(); for(int c = 1; c <= form.Subdivisions; c++) { Vector2D p0 = curves[i - 1][c - 1]; Vector2D p1 = curves[i - 1][c]; Vector2D p2 = curves[i][c]; Vector2D p3 = curves[i][c - 1]; segShapes.Add(new[] { p0, p1, p2, p3, p0 }); } shapes.Add(segShapes); } return shapes; } #endregion #region ================== Point ops //this returns an array of linedef lengths relative to total segment length private double[] GetRelativeLengths(Vector2D[] pointGroup) { double[] relLenGroup = new double[pointGroup.Length]; relLenGroup[0] = 0.0f; //get length and angle of line, which defines the shape double length = Vector2D.Distance(pointGroup[0], pointGroup[segmentsCount - 1]); double angle = Math.Atan2(pointGroup[0].y - pointGroup[segmentsCount - 1].y, pointGroup[0].x - pointGroup[segmentsCount - 1].x); //get relative length of every line for(int i = 1; i < pointGroup.Length - 1; i++) { Vector2D p0 = pointGroup[i - 1]; Vector2D p1 = pointGroup[i]; double curAngle = Math.Atan2(p0.y - p1.y, p0.x - p1.x); double diff = (angle + Angle2D.PI) - (curAngle + Angle2D.PI); double segLen = (int)(Vector2D.Distance(p0, p1) * Math.Cos(diff)); relLenGroup[i] = relLenGroup[i - 1] + segLen / length; } relLenGroup[pointGroup.Length - 1] = 1.0f; return relLenGroup; } //this returns relative handle location private static Vector2D GetHandleLocation(Vector2D start, Vector2D end, Vector2D direction) { double angle = -Math.Atan2(start.y - end.y, start.x - end.x); double dirAngle = -Math.Atan2(direction.y - start.y, direction.x - start.x); double length = Math.Sqrt(Math.Pow(Math.Abs(start.x - end.x), 2.0) + Math.Pow(Math.Abs(start.y - end.y), 2.0)) * 0.3f; double diff = (angle + Angle2D.PI) - (dirAngle + Angle2D.PI); if(diff > Angle2D.PI || (diff < 0 && diff > -Angle2D.PI)) angle += Angle2D.PI; return new Vector2D(Math.Sin(angle) * length, Math.Cos(angle) * length); } //LINE DRAWING //returns true if 2 lines intersect private static bool LinesIntersect(Line line1, Line line2) { double zn = (line2.End.y - line2.Start.y) * (line1.End.x - line1.Start.x) - (line2.End.x - line2.Start.x) * (line1.End.y - line1.Start.y); double ch1 = (line2.End.x - line2.Start.x) * (line1.Start.y - line2.Start.y) - (line2.End.y - line2.Start.y) * (line1.Start.x - line2.Start.x); double ch2 = (line1.End.x - line1.Start.x) * (line1.Start.y - line2.Start.y) - (line1.End.y - line1.Start.y) * (line1.Start.x - line2.Start.x); if(zn == 0) return false; return (ch1 / zn <= 1 && ch1 / zn >= 0) && (ch2 / zn <= 1 && ch2 / zn >= 0); } #endregion #region ================== Line sorting //this gets two arrays of connected points from given lines. Returns true if all went well. private bool SetupPointGroups(List linesList) { //find prev/next lines for each line for(int i = 0; i < linesList.Count; i++) { Line curLine = linesList[i]; for(int c = 0; c < linesList.Count; c++) { if(c != i) //don't wanna play with ourselves :) { Line line = linesList[c]; //check start and end points if(curLine.Start == line.Start) { line.Invert(); curLine.Previous = line; } else if(curLine.Start == line.End) { curLine.Previous = line; } else if(curLine.End == line.End) { line.Invert(); curLine.Next = line; } else if(curLine.End == line.Start) { curLine.Next = line; } } } } List> pointGroups = new List>(); List> sortedLines = new List>(); //now find start lines for(int i = 0; i < linesList.Count; i++) { Line curLine = linesList[i]; if(curLine.Previous == null) //found start { //collect points Line l = curLine; List points = new List(); List lines = new List(); points.Add(l.Start); do { points.Add(l.End); lines.Add(l); } while((l = l.Next) != null); pointGroups.Add(points); sortedLines.Add(lines); } } if(pointGroups.Count != 2) { General.Interface.DisplayStatus(StatusType.Warning, "Incorrect number of linedef groups! Expected 2, but got " + pointGroups.Count); return false; } if(pointGroups[0].Count != pointGroups[1].Count) { General.Interface.DisplayStatus(StatusType.Warning, "Linedefs groups must have equal length! Got " + pointGroups[0].Count + " in first group and " + pointGroups[1].Count + " in second."); return false; } //check if lines from first group intersect with lines from second group foreach(Line l1 in sortedLines[0]) { foreach(Line l2 in sortedLines[1]) { if(LinesIntersect(l1, l2)) { General.Interface.DisplayStatus(StatusType.Warning, "One or more lines from first group intersect with one or more lines from second group!"); return false; } } } //both groups count should match at this point segmentsCount = pointGroups[0].Count; //collect sector properties sectorProps1 = new SectorProperties[sortedLines[0].Count]; for(int i = 0; i < sortedLines[0].Count; i++ ) { sectorProps1[i] = sortedLines[0][i].SectorProperties; } sectorProps2 = new SectorProperties[sortedLines[1].Count]; for(int i = 0; i < sortedLines[1].Count; i++) { sectorProps2[i] = sortedLines[1][i].SectorProperties; } //check if we need to reverse one of point groups Line line1 = new Line(pointGroups[0][0], pointGroups[1][0]); Line line2 = new Line(pointGroups[0][segmentsCount - 1], pointGroups[1][segmentsCount - 1]); if(LinesIntersect(line1, line2)) { pointGroups[0].Reverse(); Array.Reverse(sectorProps1); } //fill point groups pointGroup1 = new Vector2D[segmentsCount]; pointGroup2 = new Vector2D[segmentsCount]; pointGroups[0].CopyTo(pointGroup1); pointGroups[1].CopyTo(pointGroup2); return true; } #endregion #region ================== Easing functions private static int IntepolateValue(int val1, int val2, float delta, string mode) { switch(mode) { case BridgeInterpolationMode.HIGHEST: case BridgeInterpolationMode.BRIGHTNESS_HIGHEST: return Math.Max(val1, val2); case BridgeInterpolationMode.LOWEST: case BridgeInterpolationMode.BRIGHTNESS_LOWEST: return Math.Min(val1, val2); case BridgeInterpolationMode.LINEAR: return (int)Math.Round(InterpolationTools.Linear(val1, val2, delta)); case BridgeInterpolationMode.IN_SINE: return (int)Math.Round(InterpolationTools.EaseInSine(val1, val2, delta)); case BridgeInterpolationMode.OUT_SINE: return (int)Math.Round(InterpolationTools.EaseOutSine(val1, val2, delta)); case BridgeInterpolationMode.IN_OUT_SINE: return (int)Math.Round(InterpolationTools.EaseInOutSine(val1, val2, delta)); default: throw new Exception("DrawBezierPathMode.IntepolateValue: \"" + mode + "\" mode is not supported!"); } } #endregion #region ================== Events private void form_OnSubdivisionChanged(object sender, EventArgs e) { Update(); } private void form_OnOkClick(object sender, EventArgs e) { FinishDraw(); } private void form_OnCancelClick(object sender, EventArgs e) { OnCancel(); } private void form_OnFlipClick(object sender, EventArgs e) { Array.Reverse(pointGroup1); Array.Reverse(sectorProps1); //swap handles position Vector2D p = controlHandles[0].Position; controlHandles[0].Position = controlHandles[2].Position; controlHandles[2].Position = p; Update(); } #endregion #region ================== Actions // Finish drawing [BeginAction("finishdraw")] private void FinishDraw() { // Accept the changes General.Editing.AcceptMode(); } [BeginAction("increasesubdivlevel")] private void IncreaseSubdivLevel() { if(form != null && form.Subdivisions < MAX_SUBDIVISIONS) form.Subdivisions++; } [BeginAction("decreasesubdivlevel")] private void DecreaseSubdivLevel() { if(form != null && form.Subdivisions > MIN_SUBDIVISIONS) form.Subdivisions--; } #endregion } #region ================== Helper classes internal struct SectorProperties { public int FloorHeight; public int CeilingHeight; public int Brightness; public string HighTexture; public string LowTexture; } internal class ControlHandle { public Vector2D Position; public Vector2D ControlledPoint; //point, to which this handle is assigned public Vector2D RelativePosition { get { return new Vector2D(Position.x - ControlledPoint.x, Position.y - ControlledPoint.y); } set { Position = new Vector2D(ControlledPoint.x + value.x, ControlledPoint.y + value.y); } } public ControlHandle Pair; //second handle, to which this handle is paired } internal class Line { public Vector2D Start { get { return start; } } private Vector2D start; public Vector2D End { get { return end; } } private Vector2D end; public SectorProperties SectorProperties; public Line Previous; public Line Next; public Line(Linedef ld) { start = new Vector2D((int)ld.Start.Position.x, (int)ld.Start.Position.y); end = new Vector2D((int)ld.End.Position.x, (int)ld.End.Position.y); SectorProperties = new SectorProperties(); if(ld.Back != null) { SectorProperties.CeilingHeight = ld.Back.Sector.CeilHeight; SectorProperties.FloorHeight = ld.Back.Sector.FloorHeight; SectorProperties.Brightness = ld.Back.Sector.Brightness; SectorProperties.HighTexture = ld.Back.HighTexture != "-" ? ld.Back.HighTexture : ld.Back.MiddleTexture; SectorProperties.LowTexture = ld.Back.LowTexture != "-" ? ld.Back.LowTexture : ld.Back.MiddleTexture; } else if(ld.Front != null) { SectorProperties.CeilingHeight = ld.Front.Sector.CeilHeight; SectorProperties.FloorHeight = ld.Front.Sector.FloorHeight; SectorProperties.Brightness = ld.Front.Sector.Brightness; SectorProperties.HighTexture = ld.Front.HighTexture != "-" ? ld.Front.HighTexture : ld.Front.MiddleTexture; SectorProperties.LowTexture = ld.Front.LowTexture != "-" ? ld.Front.LowTexture : ld.Front.MiddleTexture; } else { SectorProperties.CeilingHeight = 128; SectorProperties.FloorHeight = 0; SectorProperties.Brightness = 192; SectorProperties.HighTexture = "-"; SectorProperties.LowTexture = "-"; } } public Line(Vector2D start, Vector2D end) { this.start = start; this.end = end; } public void Invert() { Vector2D s = start; start = end; end = s; } } #endregion }