From 818a485ec28e87077380b65ff9f97435e6aacda6 Mon Sep 17 00:00:00 2001 From: MaxED <j.maxed@gmail.com> Date: Mon, 8 Apr 2013 13:28:04 +0000 Subject: [PATCH] Added Draw Curve mode. --- Source/Core/Builder.csproj | 1 + Source/Core/Geometry/CurveTools.cs | 359 ++++++++++++++++++ .../Plugins/BuilderModes/BuilderModes.csproj | 4 + .../ClassicModes/DrawCurveMode.cs | 246 ++++++++++++ .../BuilderModes/General/BuilderPlug.cs | 13 + .../Properties/Resources.Designer.cs | 31 +- .../BuilderModes/Properties/Resources.resx | 3 + .../BuilderModes/Resources/DrawCurveMode.png | Bin 0 -> 3092 bytes 8 files changed, 645 insertions(+), 12 deletions(-) create mode 100644 Source/Core/Geometry/CurveTools.cs create mode 100644 Source/Plugins/BuilderModes/ClassicModes/DrawCurveMode.cs create mode 100644 Source/Plugins/BuilderModes/Resources/DrawCurveMode.png diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj index e63f973a3..be8fb4f9e 100644 --- a/Source/Core/Builder.csproj +++ b/Source/Core/Builder.csproj @@ -696,6 +696,7 @@ <Compile Include="General\ErrorItem.cs" /> <Compile Include="General\ErrorLogger.cs" /> <Compile Include="General\SavePurpose.cs" /> + <Compile Include="Geometry\CurveTools.cs" /> <Compile Include="GZBuilder\Controls\CustomLinedefColorProperties.cs"> <SubType>UserControl</SubType> </Compile> diff --git a/Source/Core/Geometry/CurveTools.cs b/Source/Core/Geometry/CurveTools.cs new file mode 100644 index 000000000..9279e2ab0 --- /dev/null +++ b/Source/Core/Geometry/CurveTools.cs @@ -0,0 +1,359 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodeImp.DoomBuilder.Geometry +{ + /// <summary> + /// mxd. Tools to work with curves. + /// </summary> + public static class CurveTools + { + //mxd. Ported from Cubic Bezier curve tools by Andy Woodruff (http://cartogrammar.com/source/CubicBezier.as) + //"default" values: z = 0.5, angleFactor = 0.75; if targetSegmentLength <= 0, will return lines + public static Curve CurveThroughPoints(List<Vector2D> points, float z, float angleFactor, int targetSegmentLength) { + Curve result = new Curve(); + + // First calculate all the curve control points + // None of this junk will do any good if there are only two points + if(points.Count > 2 && targetSegmentLength > 0) { + List<List<Vector2D>> controlPts = new List<List<Vector2D>>(); // An array to store the two control points (of a cubic Bézier curve) for each point + + // Make sure z is between 0 and 1 (too messy otherwise) + if(z <= 0) + z = 0.1f; + else if(z > 1) + z = 1; + + // Make sure angleFactor is between 0 and 1 + if(angleFactor < 0) + angleFactor = 0; + else if(angleFactor > 1) + angleFactor = 1; + + // Ordinarily, curve calculations will start with the second point and go through the second-to-last point + int firstPt = 1; + int lastPt = points.Count - 1; + + // Check if this is a closed line (the first and last points are the same) + if(points[0].x == points[points.Count - 1].x && points[0].y == points[points.Count - 1].y) { + // Include first and last points in curve calculations + firstPt = 0; + lastPt = points.Count; + } else { + controlPts.Add(new List<Vector2D>()); //add a dummy entry + } + + // Loop through all the points (except the first and last if not a closed line) to get curve control points for each. + for(int i = firstPt; i < lastPt; i++) { + + // The previous, current, and next points + Vector2D p0 = (i - 1 < 0) ? points[points.Count - 2] : points[i - 1]; // If the first point (of a closed line), use the second-to-last point as the previous point + Vector2D p1 = points[i]; + Vector2D p2 = (i + 1 == points.Count) ? points[1] : points[i + 1]; // If the last point (of a closed line), use the second point as the next point + + float a = Vector2D.Distance(p0, p1); // Distance from previous point to current point + if(a < 0.001) + a = 0.001f; // Correct for near-zero distances, a cheap way to prevent division by zero + float b = Vector2D.Distance(p1, p2); // Distance from current point to next point + if(b < 0.001) + b = 0.001f; + float c = Vector2D.Distance(p0, p2); // Distance from previous point to next point + if(c < 0.001) + c = 0.001f; + + float cos = (b * b + a * a - c * c) / (2 * b * a); + // Make sure above value is between -1 and 1 so that Math.acos will work + if(cos < -1) + cos = -1; + else if(cos > 1) + cos = 1; + + float C = (float)Math.Acos(cos); // Angle formed by the two sides of the triangle (described by the three points above) adjacent to the current point + + // Duplicate set of points. Start by giving previous and next points values RELATIVE to the current point. + Vector2D aPt = new Vector2D(p0.x - p1.x, p0.y - p1.y); + Vector2D bPt = new Vector2D(p1.x, p1.y); + Vector2D cPt = new Vector2D(p2.x - p1.x, p2.y - p1.y); + + /* + We'll be adding adding the vectors from the previous and next points to the current point, + but we don't want differing magnitudes (i.e. line segment lengths) to affect the direction + of the new vector. Therefore we make sure the segments we use, based on the duplicate points + created above, are of equal length. The angle of the new vector will thus bisect angle C + (defined above) and the perpendicular to this is nice for the line tangent to the curve. + The curve control points will be along that tangent line. + */ + if(a > b) + aPt = aPt.GetNormal() * b; // Scale the segment to aPt (bPt to aPt) to the size of b (bPt to cPt) if b is shorter. + else if(b > a) + cPt = cPt.GetNormal() * a; // Scale the segment to cPt (bPt to cPt) to the size of a (aPt to bPt) if a is shorter. + + // Offset aPt and cPt by the current point to get them back to their absolute position. + aPt += p1; + cPt += p1; + + // Get the sum of the two vectors, which is perpendicular to the line along which our curve control points will lie. + float ax = bPt.x - aPt.x; // x component of the segment from previous to current point + float ay = bPt.y - aPt.y; + float bx = bPt.x - cPt.x; // x component of the segment from next to current point + float by = bPt.y - cPt.y; + float rx = ax + bx; // sum of x components + float ry = ay + by; + + // Correct for three points in a line by finding the angle between just two of them + if(rx == 0 && ry == 0) { + rx = -bx; // Really not sure why this seems to have to be negative + ry = by; + } + + // Switch rx and ry when y or x difference is 0. This seems to prevent the angle from being perpendicular to what it should be. + if(ay == 0 && by == 0) { + rx = 0; + ry = 1; + } else if(ax == 0 && bx == 0) { + rx = 1; + ry = 0; + } + + float r = (float)Math.Sqrt(rx * rx + ry * ry); // length of the summed vector - not being used, but there it is anyway + float theta = (float)Math.Atan2(ry, rx); // angle of the new vector + + float controlDist = Math.Min(a, b) * z; // Distance of curve control points from current point: a fraction the length of the shorter adjacent triangle side + float controlScaleFactor = C / (float)Math.PI; // Scale the distance based on the acuteness of the angle. Prevents big loops around long, sharp-angled triangles. + controlDist *= ((1 - angleFactor) + angleFactor * controlScaleFactor); // Mess with this for some fine-tuning + float controlAngle = theta + (float)Math.PI / 2; // The angle from the current point to control points: the new vector angle plus 90 degrees (tangent to the curve). + + Vector2D controlPoint2 = new Vector2D(controlDist, 0); + Vector2D controlPoint1 = new Vector2D(controlDist, 0); + controlPoint2 = controlPoint2.GetRotated(controlAngle); + controlPoint1 = controlPoint1.GetRotated(controlAngle + (float)Math.PI); + + // Offset control points to put them in the correct absolute position + controlPoint1 += p1; + controlPoint2 += p1; + + /* + Haven't quite worked out how this happens, but some control points will be reversed. + In this case controlPoint2 will be farther from the next point than controlPoint1 is. + Check for that and switch them if it's true. + */ + if(Vector2D.Distance(controlPoint2, p2) > Vector2D.Distance(controlPoint1, p2)) + controlPts.Add(new List<Vector2D>() { controlPoint2, controlPoint1 }); + else + controlPts.Add(new List<Vector2D>() { controlPoint1, controlPoint2 }); + } + + // If this isn't a closed line, draw a regular quadratic Bézier curve from the first to second points, using the first control point of the second point + if(firstPt == 1) { + float length = (points[1] - points[0]).GetLength(); + int numSteps = Math.Max(1, (int)Math.Round(length / targetSegmentLength)); + CurveSegment segment = new CurveSegment(); + segment.Start = points[0]; + segment.CPMid = controlPts[1][0]; + segment.End = points[1]; + CreateQuadraticCurve(segment, numSteps); + + result.Segments.Add(segment); + } + + // Loop through points to draw cubic Bézier curves through the penultimate point, or through the last point if the line is closed. + for(int i = firstPt; i < lastPt - 1; i++) { + float length = (points[i + 1] - points[i]).GetLength(); + int numSteps = Math.Max(1, (int)Math.Round(length / targetSegmentLength)); + + CurveSegment segment = new CurveSegment(); + segment.CPStart = controlPts[i][1]; + segment.CPEnd = controlPts[i + 1][0]; + segment.Start = points[i]; + segment.End = points[i + 1]; + CreateCubicCurve(segment, numSteps); + + result.Segments.Add(segment); + } + + // If this isn't a closed line, curve to the last point using the second control point of the penultimate point. + if(lastPt == points.Count - 1) { + float length = (points[lastPt] - points[lastPt - 1]).GetLength(); + int numSteps = Math.Max(1, (int)Math.Round(length / targetSegmentLength)); + + CurveSegment segment = new CurveSegment(); + segment.Start = points[lastPt - 1]; + segment.CPMid = controlPts[lastPt - 1][1]; + segment.End = points[lastPt]; + CreateQuadraticCurve(segment, numSteps); + + result.Segments.Add(segment); + } + + // create lines + } else if(points.Count >= 2) { + for(int i = 0; i < points.Count - 1; i++) { + CurveSegment segment = new CurveSegment(); + segment.Start = points[i]; + segment.End = points[i + 1]; + segment.Points = new Vector2D[] { segment.Start, segment.End }; + segment.UpdateLength(); + result.Segments.Add(segment); + } + } + + result.UpdateShape(); + return result; + } + + public static void CreateQuadraticCurve(CurveSegment segment, int steps) { + segment.CurveType = CurveSegmentType.QUADRATIC; + segment.Points = GetQuadraticCurve(segment.Start, segment.CPMid, segment.End, steps); + segment.UpdateLength(); + } + + //this returns array of Vector2D to draw 3-point bezier curve + public static Vector2D[] GetQuadraticCurve(Vector2D p1, Vector2D p2, Vector2D p3, int steps) { + if(steps < 0) + return null; + + int totalSteps = steps + 1; + Vector2D[] points = new Vector2D[totalSteps]; + float step = 1f / (float)steps; + float curStep = 0f; + + for(int i = 0; i < totalSteps; i++) { + points[i] = GetPointOnQuadraticCurve(p1, p2, p3, curStep); + curStep += step; + } + + return points; + } + + public static void CreateCubicCurve(CurveSegment segment, int steps) { + segment.CurveType = CurveSegmentType.CUBIC; + segment.Points = GetCubicCurve(segment.Start, segment.End, segment.CPStart, segment.CPEnd, steps); + segment.UpdateLength(); + } + + //this returns array of Vector2D to draw 4-point bezier curve + public static Vector2D[] GetCubicCurve(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; + + for(int i = 0; i < totalSteps; i++) { + points[i] = GetPointOnCubicCurve(p1, p2, cp1, cp2, curStep); + curStep += step; + } + return points; + } + + public static Vector2D GetPointOnCurve(CurveSegment segment, float delta) { + if(segment.CurveType == CurveSegmentType.QUADRATIC) + return GetPointOnQuadraticCurve(segment.Start, segment.CPMid, segment.End, delta); + + if(segment.CurveType == CurveSegmentType.CUBIC) + return GetPointOnCubicCurve(segment.Start, segment.End, segment.CPStart, segment.CPEnd, delta); + + if(segment.CurveType == CurveSegmentType.LINE) + return GetPointOnLine(segment.Start, segment.End, delta); + + throw new Exception("GetPointOnCurve: got unknown curve type: " + segment.CurveType); + } + + public static Vector2D GetPointOnQuadraticCurve(Vector2D p1, Vector2D p2, Vector2D p3, float delta) { + float invDelta = 1f - delta; + + float m1 = invDelta * invDelta; + float m2 = 2 * invDelta * delta; + float m3 = delta * delta; + + int px = (int)(m1 * p1.x + m2 * p2.x + m3 * p3.x); + int py = (int)(m1 * p1.y + m2 * p2.y + m3 * p3.y); + + return new Vector2D(px, py); + } + + public static Vector2D GetPointOnCubicCurve(Vector2D p1, Vector2D p2, Vector2D cp1, Vector2D cp2, float delta) { + float invDelta = 1f - delta; + + float m1 = invDelta * invDelta * invDelta; + float m2 = 3 * delta * invDelta * invDelta; + float m3 = 3 * delta * delta * invDelta; + float m4 = delta * delta * delta; + + 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); + + return new Vector2D(px, py); + } + + //it's basically 2-point bezier curve + public static 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)); + } + } + + public class Curve + { + public List<CurveSegment> Segments; + public List<Vector2D> Shape; + public float Length; + + public Curve() { + Segments = new List<CurveSegment>(); + } + + public void UpdateShape() { + Shape = new List<Vector2D>(); + Length = 0; + + foreach(CurveSegment segment in Segments) { + Length += segment.Length; + + foreach(Vector2D point in segment.Points) { + if(Shape.Count == 0 || point != Shape[Shape.Count - 1]) + Shape.Add(point); + } + } + + float curDelta = 0; + for(int i = 0; i < Segments.Count; i++) { + Segments[i].Delta = Segments[i].Length / Length; + curDelta += Segments[i].Delta; + Segments[i].GlobalDelta = curDelta; + } + } + } + + public class CurveSegment + { + public Vector2D[] Points; + public Vector2D Start; + public Vector2D End; + public Vector2D CPStart; + public Vector2D CPMid; + public Vector2D CPEnd; + public float Length; + public float Delta; //length of this segment / total curve length + public float GlobalDelta; //length of this segment / total curve length + deltas of previous segments + public CurveSegmentType CurveType; + + public void UpdateLength() { + if(Points.Length < 2) + return; + + Length = 0; + for(int i = 1; i < Points.Length; i++) + Length += Vector2D.Distance(Points[i], Points[i - 1]); + } + } + + public enum CurveSegmentType + { + LINE, + QUADRATIC, + CUBIC, + } +} diff --git a/Source/Plugins/BuilderModes/BuilderModes.csproj b/Source/Plugins/BuilderModes/BuilderModes.csproj index c421d53aa..a5bd14c1a 100644 --- a/Source/Plugins/BuilderModes/BuilderModes.csproj +++ b/Source/Plugins/BuilderModes/BuilderModes.csproj @@ -226,6 +226,7 @@ <ItemGroup> <Compile Include="ClassicModes\BridgeMode.cs" /> <Compile Include="ClassicModes\CeilingAlignMode.cs" /> + <Compile Include="ClassicModes\DrawCurveMode.cs" /> <Compile Include="ClassicModes\DrawEllipseMode.cs" /> <Compile Include="ClassicModes\DrawRectangleMode.cs" /> <Compile Include="ClassicModes\FlatAlignMode.cs" /> @@ -396,6 +397,9 @@ <ItemGroup> <None Include="Resources\AlignThings.png" /> </ItemGroup> + <ItemGroup> + <None Include="Resources\DrawCurveMode.png" /> + </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. diff --git a/Source/Plugins/BuilderModes/ClassicModes/DrawCurveMode.cs b/Source/Plugins/BuilderModes/ClassicModes/DrawCurveMode.cs new file mode 100644 index 000000000..3576062cd --- /dev/null +++ b/Source/Plugins/BuilderModes/ClassicModes/DrawCurveMode.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; +using System.Windows.Forms; +using CodeImp.DoomBuilder.Actions; +using CodeImp.DoomBuilder.Editing; +using CodeImp.DoomBuilder.Geometry; +using CodeImp.DoomBuilder.Map; +using CodeImp.DoomBuilder.Rendering; +using CodeImp.DoomBuilder.Windows; + +namespace CodeImp.DoomBuilder.BuilderModes.ClassicModes +{ + [EditMode(DisplayName = "Draw Curve Mode", + SwitchAction = "drawcurvemode", + AllowCopyPaste = false, + Volatile = true, + Optional = false)] + + public class DrawCurveMode : DrawGeometryMode + { + private HintLabel hintLabel; + private Curve curve; + private static int segmentLength = 32; + private int minSegmentLength = 16; + private int maxSegmentLength = 4096; //just some arbitrary number + + public DrawCurveMode() : base() { + hintLabel = new HintLabel(); + } + + public override void Dispose() { + if(!isdisposed && hintLabel != null) + hintLabel.Dispose(); + + base.Dispose(); + } + + protected override void Update() { + PixelColor stitchcolor = General.Colors.Highlight; + PixelColor losecolor = General.Colors.Selection; + PixelColor color; + + snaptogrid = General.Interface.ShiftState ^ General.Interface.SnapToGrid; + snaptonearest = General.Interface.CtrlState ^ General.Interface.AutoMerge; + + DrawnVertex curp = GetCurrentPosition(); + float vsize = ((float)renderer.VertexSize + 1.0f) / renderer.Scale; + float vsizeborder = ((float)renderer.VertexSize + 3.0f) / renderer.Scale; + + // The last label's end must go to the mouse cursor + if(labels.Count > 0) + labels[labels.Count - 1].End = curp.pos; + + // Render drawing lines + if(renderer.StartOverlay(true)) { + // Go for all points to draw lines + if(points.Count > 0) { + //update curve + List<Vector2D> verts = new List<Vector2D>(); + + for(int i = 0; i < points.Count; i++) + verts.Add(points[i].pos); + + if(curp.pos != verts[verts.Count-1]) + verts.Add(curp.pos); + + curve = CurveTools.CurveThroughPoints(verts, 0.5f, 0.75f, segmentLength); + + // Render lines + for(int i = 1; i < curve.Shape.Count; i++) { + // Determine line color + color = snaptonearest ? stitchcolor : losecolor; + + // Render line + renderer.RenderLine(curve.Shape[i - 1], curve.Shape[i], LINE_THICKNESS, color, true); + } + + //render "inactive" vertices + for(int i = 1; i < curve.Shape.Count - 1; i++) { + // Determine vertex color + color = !snaptonearest ? stitchcolor : losecolor; + + // Render vertex + renderer.RenderRectangleFilled(new RectangleF(curve.Shape[i].x - vsize, curve.Shape[i].y - vsize, vsize * 2.0f, vsize * 2.0f), color, true); + } + } + + if(points.Count > 0) { + // Render vertices + for(int i = 0; i < points.Count; i++) { + // Determine vertex color + color = points[i].stitch ? stitchcolor : losecolor; + + // Render vertex + renderer.RenderRectangleFilled(new RectangleF(points[i].pos.x - vsize, points[i].pos.y - vsize, vsize * 2.0f, vsize * 2.0f), color, true); + } + } + + // Determine point color + color = snaptonearest ? stitchcolor : losecolor; + + // Render vertex at cursor + renderer.RenderRectangleFilled(new RectangleF(curp.pos.x - vsize, curp.pos.y - vsize, vsize * 2.0f, vsize * 2.0f), color, true); + + // Go for all labels + foreach(LineLengthLabel l in labels) + renderer.RenderText(l.TextLabel); + + //Render info label + hintLabel.Start = new Vector2D(mousemappos.x + (32 / renderer.Scale), mousemappos.y - (16 / renderer.Scale)); + hintLabel.End = new Vector2D(mousemappos.x + (96 / renderer.Scale), mousemappos.y); + hintLabel.Text = "SEG LEN: " + segmentLength; + renderer.RenderText(hintLabel.TextLabel); + + // Done + renderer.Finish(); + } + + // Done + renderer.Present(); + } + + public override void OnAccept() { + Cursor.Current = Cursors.AppStarting; + + General.Settings.FindDefaultDrawSettings(); + + // When points have been drawn + if(points.Count > 0) { + // Make undo for the draw + General.Map.UndoRedo.CreateUndo("Curve draw"); + + // Make an analysis and show info + string[] adjectives = new string[] + { "beautiful", "lovely", "romantic", "stylish", "cheerful", "comical", + "awesome", "accurate", "adorable", "adventurous", "attractive", "cute", + "elegant", "glamorous", "gorgeous", "handsome", "magnificent", "unusual", + "outstanding", "mysterious", "amusing", "charming", "fantastic", "jolly" }; + string word = adjectives[points.Count % adjectives.Length]; + word = (points.Count > adjectives.Length) ? "very " + word : word; + string a = ((word[0] == 'a') || (word[0] == 'e') || (word[0] == 'o')) ? "an " : "a "; + General.Interface.DisplayStatus(StatusType.Action, "Created " + a + word + " drawing."); + + List<DrawnVertex> verts = new List<DrawnVertex>(); + + //if we have a curve... + if(points.Count > 2){ + //is it a closed curve? + int lastPoint = 0; + if(points[0].pos == points[points.Count - 1].pos) { + lastPoint = curve.Segments.Count; + } else { + lastPoint = curve.Segments.Count - 1; + } + + for(int i = 0; i < lastPoint; i++) { + int next = (i == curve.Segments.Count - 1 ? 0 : i + 1); + bool stitch = points[i].stitch && points[next].stitch; + bool stitchline = points[i].stitchline && points[next].stitchline; + + //add segment points except the last one + for(int c = 0; c < curve.Segments[i].Points.Length - 1; c++) { + DrawnVertex dv = new DrawnVertex(); + dv.pos = curve.Segments[i].Points[c]; + dv.stitch = stitch; + dv.stitchline = stitchline; + verts.Add(dv); + } + } + + //add last point + DrawnVertex end = new DrawnVertex(); + end.pos = curve.Segments[lastPoint - 1].End; + end.stitch = verts[verts.Count - 1].stitch; + end.stitchline = verts[verts.Count - 1].stitchline; + verts.Add(end); + }else{ + verts = points; + } + + // Make the drawing + if(!Tools.DrawLines(verts, BuilderPlug.Me.AutoAlignTextureOffsetsOnCreate)) //mxd + { + // Drawing failed + // NOTE: I have to call this twice, because the first time only cancels this volatile mode + General.Map.UndoRedo.WithdrawUndo(); + General.Map.UndoRedo.WithdrawUndo(); + return; + } + + // Snap to map format accuracy + General.Map.Map.SnapAllToAccuracy(); + + // Clear selection + General.Map.Map.ClearAllSelected(); + + // Update cached values + General.Map.Map.Update(); + + // Edit new sectors? + List<Sector> newsectors = General.Map.Map.GetMarkedSectors(true); + if(BuilderPlug.Me.EditNewSector && (newsectors.Count > 0)) + General.Interface.ShowEditSectors(newsectors); + + // Update the used textures + General.Map.Data.UpdateUsedTextures(); + + // Map is changed + General.Map.IsChanged = true; + } + + // Done + Cursor.Current = Cursors.Default; + + // Return to original mode + General.Editing.ChangeMode(General.Editing.PreviousStableMode.Name); + } + + //ACTIONS + [BeginAction("increasesubdivlevel")] + protected virtual void increaseSubdivLevel() { + if(segmentLength < maxSegmentLength) { + int increment = Math.Max(minSegmentLength, segmentLength / 32 * 16); + segmentLength += increment; + + if(segmentLength > maxSegmentLength) + segmentLength = maxSegmentLength; + Update(); + } + } + + [BeginAction("decreasesubdivlevel")] + protected virtual void decreaseSubdivLevel() { + if(segmentLength > minSegmentLength) { + int increment = Math.Max(minSegmentLength, segmentLength / 32 * 16); + segmentLength -= increment; + + if(segmentLength < minSegmentLength) + segmentLength = minSegmentLength; + Update(); + } + } + } +} diff --git a/Source/Plugins/BuilderModes/General/BuilderPlug.cs b/Source/Plugins/BuilderModes/General/BuilderPlug.cs index b444ced00..4c5ad1b47 100644 --- a/Source/Plugins/BuilderModes/General/BuilderPlug.cs +++ b/Source/Plugins/BuilderModes/General/BuilderPlug.cs @@ -85,6 +85,7 @@ namespace CodeImp.DoomBuilder.BuilderModes private ToolStripMenuItem drawLinesModeMenuItem; private ToolStripMenuItem drawRectModeMenuItem; private ToolStripMenuItem drawEllipseModeMenuItem; + private ToolStripMenuItem drawCurveModeMenuItem; // Settings private int showvisualthings; // 0 = none, 1 = sprite only, 2 = sprite caged @@ -249,6 +250,14 @@ namespace CodeImp.DoomBuilder.BuilderModes drawRectModeMenuItem.Enabled = false; General.Interface.AddMenu(drawRectModeMenuItem, MenuSection.ModeDrawModes); + //draw curve + drawCurveModeMenuItem = new ToolStripMenuItem("Draw Curve"); + drawCurveModeMenuItem.Tag = "drawcurvemode"; + drawCurveModeMenuItem.Click += new EventHandler(InvokeTaggedAction); + drawCurveModeMenuItem.Image = CodeImp.DoomBuilder.BuilderModes.Properties.Resources.DrawCurveMode; + drawCurveModeMenuItem.Enabled = false; + General.Interface.AddMenu(drawCurveModeMenuItem, MenuSection.ModeDrawModes); + //draw lines drawLinesModeMenuItem = new ToolStripMenuItem("Draw Lines"); drawLinesModeMenuItem.Tag = "drawlinesmode"; @@ -274,6 +283,7 @@ namespace CodeImp.DoomBuilder.BuilderModes General.Interface.RemoveMenu(exportToObjMenuItem); General.Interface.RemoveMenu(snapModeMenuItem); General.Interface.RemoveMenu(drawLinesModeMenuItem); + General.Interface.RemoveMenu(drawCurveModeMenuItem); General.Interface.RemoveMenu(drawRectModeMenuItem); General.Interface.RemoveMenu(drawEllipseModeMenuItem); @@ -439,6 +449,7 @@ namespace CodeImp.DoomBuilder.BuilderModes exportToObjMenuItem.Enabled = true; snapModeMenuItem.Enabled = true; drawLinesModeMenuItem.Enabled = true; + drawCurveModeMenuItem.Enabled = true; drawRectModeMenuItem.Enabled = true; drawEllipseModeMenuItem.Enabled = true; } @@ -454,6 +465,7 @@ namespace CodeImp.DoomBuilder.BuilderModes exportToObjMenuItem.Enabled = true; snapModeMenuItem.Enabled = true; drawLinesModeMenuItem.Enabled = true; + drawCurveModeMenuItem.Enabled = true; drawRectModeMenuItem.Enabled = true; drawEllipseModeMenuItem.Enabled = true; } @@ -468,6 +480,7 @@ namespace CodeImp.DoomBuilder.BuilderModes exportToObjMenuItem.Enabled = false; snapModeMenuItem.Enabled = false; drawLinesModeMenuItem.Enabled = false; + drawCurveModeMenuItem.Enabled = false; drawRectModeMenuItem.Enabled = false; drawEllipseModeMenuItem.Enabled = false; } diff --git a/Source/Plugins/BuilderModes/Properties/Resources.Designer.cs b/Source/Plugins/BuilderModes/Properties/Resources.Designer.cs index 79a7fc5a9..6d00c21a3 100644 --- a/Source/Plugins/BuilderModes/Properties/Resources.Designer.cs +++ b/Source/Plugins/BuilderModes/Properties/Resources.Designer.cs @@ -1,10 +1,10 @@ //------------------------------------------------------------------------------ // <auto-generated> -// This code was generated by a tool. -// Runtime Version:2.0.50727.5466 +// Ðтот код Ñоздан программой. +// ИÑполнÑÐµÐ¼Ð°Ñ Ð²ÐµÑ€ÑиÑ:2.0.50727.4927 // -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. +// Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² Ñтом файле могут привеÑти к неправильной работе и будут потерÑны в Ñлучае +// повторной генерации кода. // </auto-generated> //------------------------------------------------------------------------------ @@ -13,12 +13,12 @@ namespace CodeImp.DoomBuilder.BuilderModes.Properties { /// <summary> - /// A strongly-typed resource class, for looking up localized strings, etc. + /// КлаÑÑ Ñ€ÐµÑурÑа Ñо Ñтрогой типизацией Ð´Ð»Ñ Ð¿Ð¾Ð¸Ñка локализованных Ñтрок и Ñ‚.д. /// </summary> - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. + // Ðтот клаÑÑ Ñоздан автоматичеÑки клаÑÑом StronglyTypedResourceBuilder + // Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ такого ÑредÑтва, как ResGen или Visual Studio. + // Чтобы добавить или удалить член, измените файл .ResX и Ñнова запуÑтите ResGen + // Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð¾Ð¼ /str или переÑтройте Ñвой проект VS. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] @@ -33,7 +33,7 @@ namespace CodeImp.DoomBuilder.BuilderModes.Properties { } /// <summary> - /// Returns the cached ResourceManager instance used by this class. + /// Возвращает кÑшированный ÑкземплÑÑ€ ResourceManager, иÑпользованный Ñтим клаÑÑом. /// </summary> [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { @@ -47,8 +47,8 @@ namespace CodeImp.DoomBuilder.BuilderModes.Properties { } /// <summary> - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. + /// ПерезапиÑывает ÑвойÑтво CurrentUICulture текущего потока Ð´Ð»Ñ Ð²Ñех + /// обращений к реÑурÑу Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ Ñтого клаÑÑа реÑурÑа Ñо Ñтрогой типизацией. /// </summary> [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { @@ -116,6 +116,13 @@ namespace CodeImp.DoomBuilder.BuilderModes.Properties { } } + internal static System.Drawing.Bitmap DrawCurveMode { + get { + object obj = ResourceManager.GetObject("DrawCurveMode", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + internal static System.Drawing.Bitmap DrawEllipseMode { get { object obj = ResourceManager.GetObject("DrawEllipseMode", resourceCulture); diff --git a/Source/Plugins/BuilderModes/Properties/Resources.resx b/Source/Plugins/BuilderModes/Properties/Resources.resx index 459ae28d8..bc0b49ea4 100644 --- a/Source/Plugins/BuilderModes/Properties/Resources.resx +++ b/Source/Plugins/BuilderModes/Properties/Resources.resx @@ -190,4 +190,7 @@ <data name="AlignThings" type="System.Resources.ResXFileRef, System.Windows.Forms"> <value>..\Resources\AlignThings.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> </data> + <data name="DrawCurveMode" type="System.Resources.ResXFileRef, System.Windows.Forms"> + <value>..\Resources\DrawCurveMode.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> + </data> </root> \ No newline at end of file diff --git a/Source/Plugins/BuilderModes/Resources/DrawCurveMode.png b/Source/Plugins/BuilderModes/Resources/DrawCurveMode.png new file mode 100644 index 0000000000000000000000000000000000000000..ce2cc70348cc269b4809ec2e40e23b77dc76a90f GIT binary patch literal 3092 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 literal 0 HcmV?d00001 -- GitLab