using System; using System.Collections.Generic; using System.Drawing; using System.Text; using System.Windows.Forms; 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.Plugins; using CodeImp.DoomBuilder.BuilderModes.Interface; namespace CodeImp.DoomBuilder.BuilderModes.ClassicModes { [EditMode(DisplayName = "Bridge Mode", SwitchAction = "bridgemode", ButtonImage = "BridgeMode.png", ButtonOrder = 51, ButtonGroup = "002_tools", AllowCopyPaste = false, Volatile = true, Optional = false)] public class BridgeMode : BaseClassicMode { private const float GRIP_SIZE = 9.0f; private const float LINE_THICKNESS = 0.8f; internal static int MAX_SUBDIVISIONS = 32; internal static int MIN_SUBDIVISIONS = 0; protected string undoName = "Bridge draw"; protected string shapeName = "mesh"; private Vector2D[] pointGroup1; private Vector2D[] pointGroup2; private SectorProperties[] sectorProps1; private SectorProperties[] sectorProps2; private float[] relLenGroup1; private float[] relLenGroup2; private ControlHandle[] controlHandles; private int curControlHandle = -1; private PixelColor handleColor; private List curves; private int segmentsCount; // Options protected bool snaptogrid; // SHIFT to toggle //tools form private BridgeModeForm form; public BridgeMode() { // We have no destructor GC.SuppressFinalize(this); } // 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 += new EventHandler(form_OnCancelClick); form.OnOkClick += new EventHandler(form_OnOkClick); form.OnFlipClick += new EventHandler(form_OnFlipClick); form.OnSubdivisionChanged += new EventHandler(form_OnSubdivisionChanged); form.Show(Form.ActiveForm); 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 (curControlHandle != -1) { ControlHandle handle = controlHandles[curControlHandle]; if (snaptogrid) { handle.Position = DrawGeometryMode.GetCurrentPosition(mousemappos, false, true, renderer, new List()).pos; } else { handle.Position = mousemappos; } if (form.MirrorMode) { Vector2D pos = handle.RelativePosition; //handle angle float angle = (float)Math.Atan2(-pos.y, -pos.x) + (float)Math.PI / 2f; //angle of line, connecting handles ControlledPoints float dirAngle = -(float)Math.Atan2(handle.Pair.ControlledPoint.y - handle.ControlledPoint.y, handle.Pair.ControlledPoint.x - handle.ControlledPoint.x); float length = (float)Math.Sqrt(Math.Pow(Math.Abs(pos.x), 2.0) + Math.Pow(Math.Abs(pos.y), 2.0)); float mirroredAngle = angle + dirAngle * 2.0f; handle.Pair.RelativePosition = new Vector2D((float)Math.Sin(mirroredAngle) * length, (float)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>>(); List> shapesRow; List points; //set stitch range float stitchrange = BuilderPlug.Me.StitchRange; BuilderPlug.Me.StitchRange = 0.1f; for (int i = 0; i < shapes.Count; i++) { shapesRow = new List>(); for (int c = 0; c < shapes[i].Count; c++) { points = new List(); for (int p = 0; p < shapes[i][c].Length; p++) points.Add(DrawGeometryMode.GetCurrentPosition(shapes[i][c][p], true, 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])) { // 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; } List newsectors = General.Map.Map.GetMarkedSectors(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, 1.0f); renderer.Finish(); } // Normal update update(); } //this checks if initial data is valid private bool setup(List lines) { if (!setupPointGroups(lines)) return false; //setup control handles Vector2D center1 = getPointOnLine(pointGroup1[0], pointGroup1[segmentsCount - 1], 0.5f); Vector2D center2 = 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(); ch1.ControlledPoint = pointGroup1[0]; ch1.RelativePosition = loc1; ControlHandle ch2 = new ControlHandle(); ch2.ControlledPoint = pointGroup2[0]; ch2.RelativePosition = loc2; ControlHandle ch3 = new ControlHandle(); ch3.ControlledPoint = pointGroup1[segmentsCount - 1]; ch3.RelativePosition = loc1; ControlHandle ch4 = new ControlHandle(); ch4.ControlledPoint = pointGroup2[segmentsCount - 1]; ch4.RelativePosition = loc2; ch1.Pair = ch3; ch2.Pair = ch4; ch3.Pair = ch1; ch4.Pair = ch2; controlHandles = new ControlHandle[] {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 Vector2D cp1, cp2; curves = new List(); for (int i = 0; i < segmentsCount; i++) { cp1 = getPointOnLine(controlHandles[0].Position, controlHandles[2].Position, relLenGroup1[i]); cp2 = getPointOnLine(controlHandles[1].Position, controlHandles[3].Position, relLenGroup2[i]); curves.Add(getBezierCurve(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 = ((float)renderer.VertexSize + 1.0f) / renderer.Scale; foreach(Vector2D[] points in curves){ for (int i = 1; i < points.Length - 1; i++ ) { renderer.RenderRectangleFilled(new RectangleF(points[i].x - vsize, 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(controlHandles[i].Position.x - gripsize * 0.5f, 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 = (float)sectorIndex / (float)form.Subdivisions; delta += (1.0f - delta) / (float)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 Vector2D[] { p0, p1, p2, p3, p0 }); } shapes.Add(segShapes); } return shapes; } //POINT OPS //this returns an array of linedef lengths relative to total segment length private float[] getRelativeLengths(Vector2D[] pointGroup) { float[] relLenGroup = new float[pointGroup.Length]; relLenGroup[0] = 0.0f; //get length and angle of line, which defines the shape float length = getLineLength(pointGroup[0], pointGroup[segmentsCount - 1]); float angle = (float)Math.Atan2(pointGroup[0].y - pointGroup[segmentsCount - 1].y, pointGroup[0].x - pointGroup[segmentsCount - 1].x); float curAngle, diff, segLen; Vector2D p0, p1; //get relative length of every line for (int i = 1; i < pointGroup.Length - 1; i++) { p0 = pointGroup[i - 1]; p1 = pointGroup[i]; curAngle = (float)Math.Atan2(p0.y - p1.y, p0.x - p1.x); diff = (angle + (float)Math.PI) - (curAngle + (float)Math.PI); segLen = (int)(getLineLength(p0, p1) * Math.Cos(diff)); relLenGroup[i] = relLenGroup[i - 1] + segLen / length; } relLenGroup[pointGroup.Length - 1] = 1.0f; return relLenGroup; } private float getLineLength(Vector2D p0, Vector2D p1) { float vx = Math.Abs(p0.x - p1.x); float vy = Math.Abs(p0.y - p1.y); return (float)Math.Sqrt(vx * vx + vy * vy); } //this returns relative handle location private Vector2D getHandleLocation(Vector2D start, Vector2D end, Vector2D direction) { float angle = -(float)Math.Atan2(start.y - end.y, start.x - end.x); float dirAngle = -(float)Math.Atan2(direction.y - start.y, direction.x - start.x); float length = (float)Math.Sqrt(Math.Pow(Math.Abs(start.x - end.x), 2.0) + Math.Pow(Math.Abs(start.y - end.y), 2.0)) * 0.3f; float diff = (angle + (float)Math.PI) - (dirAngle + (float)Math.PI); if (diff > Math.PI || (diff < 0 && diff > -Math.PI)) angle += (float)Math.PI; return new Vector2D((float)(Math.Sin(angle) * length), (float)(Math.Cos(angle) * length)); } //LINE DRAWING //this returns array of Vector2D to draw 4-point bezier curve private Vector2D[] getBezierCurve(Vector2D p1, Vector2D p2, Vector2D cp1, Vector2D cp2, int steps) { if (steps < 0) return null; int totalSteps = steps + 1; Vector2D[] points = new Vector2D[totalSteps]; float step = 1f / (float)steps; float curStep = 0f; float curStepInv, m1, m2, m3, m4; for (int i = 0; i < totalSteps; i++) { curStepInv = 1f - curStep; m1 = curStepInv * curStepInv * curStepInv; m2 = 3 * curStep * curStepInv * curStepInv; m3 = 3 * curStep * curStep * curStepInv; m4 = curStep * curStep * curStep; int px = (int)(m1 * p1.x + m2 * cp1.x + m3 * cp2.x + m4 * p2.x); int py = (int)(m1 * p1.y + m2 * cp1.y + m3 * cp2.y + m4 * p2.y); points[i] = new Vector2D(px, py); curStep += step; } return points; } //returns true if 2 lines intersect private bool linesIntersect(Line line1, Line line2) { float 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); float 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); float 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; if ((ch1 / zn <= 1 && ch1 / zn >= 0) && (ch2 / zn <= 1 && ch2 / zn >= 0)) return true; return false; } //it's basically 2-point bezier curve private Vector2D getPointOnLine(Vector2D p1, Vector2D p2, float delta) { return new Vector2D((int)((1f - delta) * p1.x + delta * p2.x), (int)((1f - delta) * p1.y + delta * p2.y)); } //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; } //Easing functions. Based on Robert Penner's original easing equations (http://www.robertpenner.com/easing/) /** * Easing equation function for a sinusoidal (sin(t)) easing in: accelerating from zero velocity. */ private int easeInSine(int val1, int val2, float delta) { float f_val1 = (float)val1; float f_val2 = (float)val2 - f_val1; return (int)(-f_val2 * Math.Cos(delta * (Math.PI / 2.0f)) + f_val2 + f_val1); } /** * Easing equation function for a sinusoidal (sin(t)) easing out: decelerating from zero velocity. */ private int easeOutSine(int val1, int val2, float delta) { float f_val1 = (float)val1; float f_val2 = (float)val2; return (int)((f_val2 - f_val1) * Math.Sin(delta * ((float)Math.PI / 2.0f)) + f_val1); } /** * Easing equation function for a sinusoidal (sin(t)) easing in/out: acceleration until halfway, then deceleration. */ private int easeInOutSine(int val1, int val2, float delta) { float f_val1 = (float)val1; float f_val2 = (float)val2; return (int)(-f_val2 / 2.0f * (Math.Cos(Math.PI * delta) - 1.0f) + f_val1); } private int intepolateValue(int val1, int val2, float delta, string mode) { switch (mode) { case BridgeInterpolationMode.HIGHEST: case BridgeInterpolationMode.BRIGHTNESS_HIGHEST: return Math.Max(val1, val2); break; case BridgeInterpolationMode.LOWEST: case BridgeInterpolationMode.BRIGHTNESS_LOWEST: return Math.Min(val1, val2); break; case BridgeInterpolationMode.LINEAR: return (int)(delta * (float)val2 + (1.0f - delta) * (float)val1); break; case BridgeInterpolationMode.IN_SINE: return easeInSine(val1, val2, delta); break; case BridgeInterpolationMode.OUT_SINE: return easeOutSine(val1, val2, delta); break; case BridgeInterpolationMode.IN_OUT_SINE: return easeInOutSine(val1, val2, delta); break; default: throw new Exception("DrawBezierPathMode.intepolateValue: got unknown mode: '" + mode + "'"); break; } return -1; } //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(); } //ACTIONS // Finish drawing [BeginAction("finishdraw")] public 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--; } } internal struct SectorProperties { public int FloorHeight; public int CeilingHeight; public int Brightness; public float Angle; 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(); SectorProperties.Angle = ld.Angle; 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; SectorProperties.LowTexture = ld.Back.LowTexture; }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; SectorProperties.LowTexture = ld.Front.LowTexture; }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; SectorProperties.Angle *= -1; } } }