#region ================== Namespaces using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; using CodeImp.DoomBuilder.Actions; using CodeImp.DoomBuilder.Controls; using CodeImp.DoomBuilder.Editing; using CodeImp.DoomBuilder.Geometry; using CodeImp.DoomBuilder.Map; using CodeImp.DoomBuilder.Rendering; using CodeImp.DoomBuilder.Windows; #endregion //JBR Draw Shape Mode namespace CodeImp.DoomBuilder.BuilderModes { [EditMode(DisplayName = "Draw Shape Mode", SwitchAction = "drawshapemode", ButtonImage = "DrawShapeMode.png", ButtonOrder = int.MinValue + 6, ButtonGroup = "000_drawing", AllowCopyPaste = false, Volatile = true, Optional = false)] public class DrawShapeMode : DrawGeometryMode { #region ================== Variables public const int CLOSESHAPE_ORIGIN = 0; public const int CLOSESHAPE_VERTICES = 1; public const int SHAPE = 2; public const int LINEDEFS = 3; public const int VERTICES = 4; public const int THINGS = 5; protected static int thingtype = 1; // Changed when adding things protected static bool previewreference = true; protected static bool ellipse = true; protected static int firstpointtype = 0; protected static int sides = 8; protected static int spikiness = 0; protected static int spikingmode = 0; protected static int createas = 0; protected static bool frontoutside = false; protected static float startangle = 0f; protected static float sweepangle = Angle2D.PI2; protected static bool limitonquad = true; protected static int transformonquad = 0; // Leave as always 0 for now protected Vector2D start; protected Vector2D end; protected Vector2D origin; //interface private DrawShapeOptionsPanel panel; private Docker docker; #endregion #region ================== Constructor public DrawShapeMode() { } #endregion #region ================== Methods // This generates the shape from a vertex protected static List GenerateShape(Vector2D origin, int sides, int spikiness, int spikingmode, float radiusX, float radiusY, float start, float end) { if (sides <= 0) return new List(); float spikeX = (float)spikiness / 100f * radiusX; float spikeY = (float)spikiness / 100f * radiusY; float spikeDef = (float)spikiness; // Make list List points = new List(); // Spiking fun! int spiketype = spikingmode >> 2; int spikematch = spikingmode & 1; if ((spikingmode & 2) == 2) { spikeX = -spikeX; spikeY = -spikeY; } // FEATURE: In some spikingmodes, if spikiness is over 100% the spikes can cross between center for interesting results! // Plot each side for (int i = 0; i < sides + 1; i++) { float offX = 0f; float offY = 0f; if (spiketype == 0) { // Spike outside and inside if ((i & 1) == spikematch) { offX = spikeX; offY = spikeY; } } else if (spiketype == 1) { // Spike zig-zag and gear offX = spikeX; offY = spikeY; if ((spikematch == 0) || (i & 1) == spikematch) { spikeX = -spikeX; spikeY = -spikeY; } } else if (spiketype == 2) { // Simulate the DrawEllipse spikiness if ((i & 1) == spikematch) { offX = spikeDef; offY = spikeDef; } } float delta = (float)i / sides; float angle = start + (end - start) * delta; float x = (float)Math.Cos(angle) * (radiusX + offX); float y = (float)Math.Sin(angle) * (radiusY + offY); Vector2D vertex = new Vector2D(x, y); points.Add(origin + vertex); } // Done return points; } // Calculate origin and radius from 2 vectors protected static void CalculateOrigin(int firstpointtype, bool ellipse, Vector2D start, Vector2D end, out Vector2D origin, out Vector2D endpoint, out float radiusX, out float radiusY) { origin = start; endpoint = end; if (firstpointtype == 1) { // 1st point in corner like DrawEllipse origin = (start + end) / 2f; float centerX = (end.x - start.x) / 2f; float centerY = (end.y - start.y) / 2f; radiusX = Math.Abs(centerX); radiusY = Math.Abs(centerY); if (!ellipse) { if (radiusX > radiusY) { radiusY = radiusX; centerY = (centerY >= 0f) ? Math.Abs(centerX) : -Math.Abs(centerX); } else { radiusX = radiusY; centerX = (centerX >= 0f) ? Math.Abs(centerY) : -Math.Abs(centerY); } origin.x = start.x + centerX; origin.y = start.y + centerY; } } else if (firstpointtype == 2) { // 1st point in side/corner towards origin radiusX = Math.Abs(end.x - start.x); radiusY = Math.Abs(end.y - start.y); origin = end; endpoint = start; if (!ellipse) { if (radiusX > radiusY) radiusY = radiusX; else radiusX = radiusY; } } else { // 1st point in center towards radius if (ellipse) { radiusX = Math.Abs(end.x - start.x); radiusY = Math.Abs(end.y - start.y); } else { float radius = (end - start).GetLength(); radiusX = radius; radiusY = radius; } } } // Recalculate angle from quadrant protected static float ReCalculateAngleFromQuadrant(int transformonquad, float angle, bool inverse, Vector2D start, Vector2D end) { Vector2D delta = end - start; if (transformonquad == 0) // top-right quadrant (1) { if (delta.x < 0) { if (delta.y < 0) // quadrant 3 angle += inverse ? -Angle2D.PI : Angle2D.PI; else // quadrant 2 angle += inverse ? -Angle2D.PIHALF : Angle2D.PIHALF; } else { if (delta.y < 0) // quadrant 4 angle += inverse ? -Angle2D.PIANDHALF : Angle2D.PIANDHALF; else // quadrant 1 angle += inverse ? -0 : 0; } } else if (transformonquad == 1) // top-left quadrant (2) { if (delta.x < 0) { if (delta.y < 0) // quadrant 3 angle += inverse ? -Angle2D.PIHALF : Angle2D.PIHALF; else // quadrant 2 angle += inverse ? -0 : 0; } else { if (delta.y < 0) // quadrant 4 angle += inverse ? -Angle2D.PI : Angle2D.PI; else // quadrant 1 angle += inverse ? Angle2D.PIHALF : -Angle2D.PIHALF; } } else if (transformonquad == 2) // bottom-left quadrant (3) { if (delta.x < 0) { if (delta.y < 0) // quadrant 3 angle += inverse ? -0 : 0; else // quadrant 2 angle += inverse ? Angle2D.PIHALF : -Angle2D.PIHALF; } else { if (delta.y < 0) // quadrant 4 angle += inverse ? -Angle2D.PIHALF : Angle2D.PIHALF; else // quadrant 1 angle += inverse ? Angle2D.PI : -Angle2D.PI; } } else if (transformonquad == 3) // bottom-right quadrant (4) { if (delta.x < 0) { if (delta.y < 0) // quadrant 3 angle += inverse ? Angle2D.PIHALF : -Angle2D.PIHALF; else // quadrant 2 angle += inverse ? Angle2D.PI : -Angle2D.PI; } else { if (delta.y < 0) // quadrant 4 angle += inverse ? -0 : 0; else // quadrant 1 angle += inverse ? Angle2D.PIANDHALF : -Angle2D.PIANDHALF; } } else if (transformonquad == 4) // Flip around (disabled!) { if (delta.x < 0) { // Flip horizontally if (angle < Angle2D.PI) angle = Angle2D.PI - angle; else angle = Angle2D.PI2 + Angle2D.PI - angle; } if (delta.y < 0) { // Flip vertically angle = Angle2D.PI2 - angle; } } if (angle < 0f) angle += Angle2D.PI2; if (angle >= Angle2D.PI2) angle -= Angle2D.PI2; return angle; } // Allow user to modify angles by using the mouse protected void EndpointDrawShapeAngle(bool doSweepAngle) { Vector2D delta = end - start; float angle = -(float)Math.Atan2(-delta.y, delta.x); if (angle < 0f) angle = Angle2D.PI2 + angle; if (doSweepAngle) { float restartang = startangle; if (limitonquad) restartang = ReCalculateAngleFromQuadrant(transformonquad, restartang, false, start, end); sweepangle = angle - restartang; if (sweepangle < 0f) sweepangle += Angle2D.PI2; if (sweepangle == 0f || sweepangle > Angle2D.PI2) sweepangle = Angle2D.PI2; panel.SweepAngle = Angle2D.RadToDeg(sweepangle); } else { startangle = angle; if (limitonquad) startangle = ReCalculateAngleFromQuadrant(transformonquad, startangle, true, start, end); panel.StartAngle = Angle2D.RadToDeg(startangle); } } // This draws a point at a specific location protected virtual bool AddPointAt(List shapepts, Vector2D pos, bool stitch, bool stitchline) { if (pos.x < General.Map.Config.LeftBoundary || pos.x > General.Map.Config.RightBoundary || pos.y > General.Map.Config.TopBoundary || pos.y < General.Map.Config.BottomBoundary) return false; DrawnVertex newpoint = new DrawnVertex(); newpoint.pos = pos; newpoint.stitch = stitch; newpoint.stitchline = stitchline; shapepts.Add(newpoint); return true; } override protected void Update() { PixelColor stitchcolor = General.Colors.Highlight; PixelColor losecolor = General.Colors.Selection; snaptocardinaldirection = General.Interface.ShiftState && General.Interface.AltState; //mxd snaptogrid = (snaptocardinaldirection || General.Interface.ShiftState ^ General.Interface.SnapToGrid); snaptonearest = General.Interface.CtrlState ^ General.Interface.AutoMerge; DrawnVertex curp = GetCurrentPosition(); float vsize = (renderer.VertexSize + 1.0f) / renderer.Scale; // Render drawing lines if (renderer.StartOverlay(true)) { PixelColor color = snaptonearest ? stitchcolor : losecolor; if (points.Count == 1) { // Update ending point if (curp.pos.IsFinite()) end = curp.pos; // Generate shape Vector2D endpoint; float radiusX, radiusY; CalculateOrigin(firstpointtype, ellipse, start, end, out origin, out endpoint, out radiusX, out radiusY); float angle = startangle; if (limitonquad) angle = ReCalculateAngleFromQuadrant(transformonquad, startangle, false, start, end); List shapevecs = GenerateShape(origin, sides, spikiness, spikingmode, radiusX, radiusY, angle, angle + sweepangle); // Check if the shape is closed bool isShapeClosed = shapevecs[0].CloseTo(shapevecs[shapevecs.Count - 1], 0.1f); // render reference if (previewreference) { List refcircle = GenerateShape(origin, 32, 0, 0, radiusX, radiusY, angle, angle + sweepangle); for (int i = 1; i < refcircle.Count; i++) renderer.RenderLine(refcircle[i - 1], refcircle[i], LINE_THICKNESS, General.Colors.Grid, true); renderer.RenderLine(origin, shapevecs[0], LINE_THICKNESS, General.Colors.Grid, true); renderer.RenderLine(origin, shapevecs[shapevecs.Count - 1], LINE_THICKNESS, General.Colors.Grid, true); } renderer.RenderRectangleFilled(new RectangleF(start.x - vsize, start.y - vsize, vsize * 2.0f, vsize * 2.0f), General.Colors.InfoLine, true); renderer.RenderRectangleFilled(new RectangleF(endpoint.x - vsize, endpoint.y - vsize, vsize * 2.0f, vsize * 2.0f), General.Colors.InfoLine, true); // render labels Vector2D[] labelCoords = new[] { new Vector2D(origin.x - radiusX, origin.y - radiusY), new Vector2D(origin.x + radiusX, origin.y - radiusY), new Vector2D(origin.x + radiusX, origin.y + radiusY), new Vector2D(origin.x - radiusX, origin.y + radiusY), new Vector2D(origin.x - radiusX, origin.y - radiusY) }; for (int i = 1; i < 5; i++) { labels[i - 1].Move(labelCoords[i], labelCoords[i - 1]); renderer.RenderText(labels[i - 1].TextLabel); } labels[4].Move(origin, shapevecs[0]); renderer.RenderText(labels[4].TextLabel); labels[5].Move(shapevecs[0], shapevecs[1]); renderer.RenderText(labels[5].TextLabel); if (sweepangle < Angle2D.PI2) { labels[5].Move(origin, shapevecs[shapevecs.Count - 1]); renderer.RenderText(labels[5].TextLabel); } // render shape if (createas <= LINEDEFS) { for (int i = 1; i < shapevecs.Count; i++) renderer.RenderLine(shapevecs[i - 1], shapevecs[i], LINE_THICKNESS, color, true); if (!isShapeClosed && createas == CLOSESHAPE_ORIGIN) { renderer.RenderLine(shapevecs[shapevecs.Count - 1], origin, LINE_THICKNESS, color, true); renderer.RenderLine(origin, shapevecs[0], LINE_THICKNESS, color, true); } if (!isShapeClosed && createas == CLOSESHAPE_VERTICES) { renderer.RenderLine(shapevecs[shapevecs.Count - 1], shapevecs[0], LINE_THICKNESS, color, true); } } // render vertices for (int i = 0; i < shapevecs.Count; i++) renderer.RenderRectangleFilled(new RectangleF(shapevecs[i].x - vsize, shapevecs[i].y - vsize, vsize * 2.0f, vsize * 2.0f), color, true); } else { // Render vertex at cursor renderer.RenderRectangleFilled(new RectangleF(curp.pos.x - vsize, curp.pos.y - vsize, vsize * 2.0f, vsize * 2.0f), color, true); } // Done renderer.Finish(); } // Done renderer.Present(); } // This draws a point at a specific location override public bool DrawPointAt(Vector2D pos, bool stitch, bool stitchline) { if (pos.x < General.Map.Config.LeftBoundary || pos.x > General.Map.Config.RightBoundary || pos.y > General.Map.Config.TopBoundary || pos.y < General.Map.Config.BottomBoundary) return false; DrawnVertex newpoint = new DrawnVertex(); newpoint.pos = pos; newpoint.stitch = true; //stitch newpoint.stitchline = stitchline; points.Add(newpoint); if (points.Count == 1) //add point and labels { start = newpoint.pos; labels.AddRange(new[] { new LineLengthLabel(false, true), new LineLengthLabel(false, true), new LineLengthLabel(false, true), new LineLengthLabel(false, true), new LineLengthLabel(true, true), new LineLengthLabel(true, true) }); Update(); } else if (points[0].pos == points[1].pos) //nothing is drawn { points = new List(); FinishDraw(); } else { end = newpoint.pos; FinishDraw(); } return true; } override public void RemovePoint() { if (points.Count > 0) points.RemoveAt(points.Count - 1); if (labels.Count > 0) labels = new List(); Update(); } #endregion #region ================== Events public override void OnEngage() { base.OnEngage(); // Create and setup settings panel panel = new DrawShapeOptionsPanel(); panel.PreviewReference = previewreference; panel.Ellipse = ellipse; panel.FirstPointType = firstpointtype; panel.CreateAs = createas; panel.FrontOutside = frontoutside; panel.Sides = sides; panel.Spikiness = spikiness; panel.SpikingMode = spikingmode; panel.StartAngle = Angle2D.RadToDeg(startangle); panel.SweepAngle = Angle2D.RadToDeg(sweepangle); panel.LimitAngleQuad = limitonquad; panel.OnValueChanged += OptionsPanelOnValueChanged; // Add docker docker = new Docker("drawshape", "Draw Shape", panel); General.Interface.AddDocker(docker); General.Interface.SelectDocker(docker); } public override void OnDisengage() { base.OnDisengage(); // Remove docker General.Interface.RemoveDocker(docker); panel.Dispose(); panel = null; } override public void OnAccept() { Cursor.Current = Cursors.AppStarting; General.Settings.FindDefaultDrawSettings(); if (points.Count == 2) { // Make undo for the draw General.Map.UndoRedo.CreateUndo(String.Format("{0} sides shape draw", sides)); // Generate shape Vector2D endpoint; float radiusX, radiusY; CalculateOrigin(firstpointtype, ellipse, start, end, out origin, out endpoint, out radiusX, out radiusY); float angle = startangle; if (limitonquad) angle = ReCalculateAngleFromQuadrant(transformonquad, startangle, false, start, end); List shapevecs = GenerateShape(origin, sides, spikiness, spikingmode, radiusX, radiusY, angle, angle + sweepangle); if (points.Count > 0) { // Delete last vertex if match with first bool wasClosedShape = shapevecs[0].CloseTo(shapevecs[shapevecs.Count - 1], 0.001f); if (wasClosedShape) { shapevecs.RemoveAt(shapevecs.Count - 1); } // Flip normals by reversing the list if (!frontoutside) shapevecs.Reverse(); // Make the drawing if (createas < LINEDEFS) { List shapepts = new List(); foreach (Vector2D t in shapevecs) AddPointAt(shapepts, t, true, true); if (!wasClosedShape && createas == CLOSESHAPE_ORIGIN) { AddPointAt(shapepts, origin, true, true); // Go toward origin before closing } if (wasClosedShape || createas <= CLOSESHAPE_VERTICES) { AddPointAt(shapepts, shapevecs[0], true, true); // Close the shape } if (!Tools.DrawLines(shapepts, true, BuilderPlug.Me.AutoAlignTextureOffsetsOnCreate)) { // 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; } } else { List vertices = new List(); // Create each point if (createas != THINGS) { for (int i = 0; i < shapevecs.Count; i++) { Vertex vp = General.Map.Map.CreateVertex(shapevecs[i]); if (vp == null) { General.Map.UndoRedo.WithdrawUndo(); return; } vertices.Add(vp); } } // of for things create things.... if (createas == THINGS) { // Pick the thing to add... ThingBrowserForm f = new ThingBrowserForm(thingtype); if (f.ShowDialog(BuilderPlug.Me.MenusForm) != DialogResult.OK) { f.Dispose(); General.Map.UndoRedo.WithdrawUndo(); return; } thingtype = f.SelectedType; f.Dispose(); // ... and place them for (int i = 0; i < shapevecs.Count; i++) { Thing tp = General.Map.Map.CreateThing(); if (tp == null) { General.Map.UndoRedo.WithdrawUndo(); return; } General.Settings.ApplyDefaultThingSettings(tp); tp.SRB2Type = thingtype; tp.Parameter = 0; tp.Move(shapevecs[i]); tp.UpdateConfiguration(); } //Update Things filter General.Map.ThingsFilter.Update(); } // Create linedefs if (createas == LINEDEFS && shapevecs.Count > 1) { for (int i = 1; i < shapevecs.Count; i++) { // Join points together General.Map.Map.CreateLinedef(vertices[i - 1], vertices[i]); } if (wasClosedShape) { // Join first with last vertex for closed shape General.Map.Map.CreateLinedef(vertices[shapevecs.Count - 1], vertices[0]); } } } } // 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 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(); //mxd General.Map.Renderer2D.UpdateExtraFloorFlag(); // Map is changed General.Map.IsChanged = true; // Show info General.Interface.DisplayStatus(StatusType.Action, String.Format("Created {0} sides shape.", sides)); } // Done Cursor.Current = Cursors.Default; // Return to original mode General.Editing.ChangeMode(General.Editing.PreviousStableMode.Name); } private void OptionsPanelOnValueChanged(object sender, EventArgs eventArgs) { previewreference = panel.PreviewReference; ellipse = panel.Ellipse; firstpointtype = panel.FirstPointType; createas = panel.CreateAs; frontoutside = panel.FrontOutside; sides = panel.Sides; spikiness = panel.Spikiness; spikingmode = panel.SpikingMode; startangle = Angle2D.DegToRad(panel.StartAngle); sweepangle = Angle2D.DegToRad(panel.SweepAngle); limitonquad = panel.LimitAngleQuad; Update(); } // Mouse moves public override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (e.Button == MouseButtons.Middle) { EndpointDrawShapeAngle(General.Interface.CtrlState); } } public override void OnHelp() { General.ShowHelp("mode_drawshape.html"); } #endregion #region ================== Actions [BeginAction("increasebevel")] protected void IncreaseBevel() { if (spikiness < 4096) { spikiness++; panel.Spikiness = spikiness; Update(); } } [BeginAction("decreasebevel")] protected void DecreaseBevel() { if (spikiness > 0) { spikiness--; panel.Spikiness = spikiness; Update(); } } [BeginAction("increasesubdivlevel")] protected void IncreaseSubdivLevel() { if (sides < 1024) { sides++; panel.Sides = sides; Update(); } } [BeginAction("decreasesubdivlevel")] protected void DecreaseSubdivLevel() { if (sides > 2) { sides--; panel.Sides = sides; Update(); } } #endregion } }