2014-01-16 09:32:05 +00:00
|
|
|
|
#region ================== Namespaces
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Drawing;
|
|
|
|
|
using System.Windows.Forms;
|
|
|
|
|
using CodeImp.DoomBuilder.Actions;
|
2015-06-25 18:32:29 +00:00
|
|
|
|
using CodeImp.DoomBuilder.Controls;
|
2014-01-16 09:32:05 +00:00
|
|
|
|
using CodeImp.DoomBuilder.Editing;
|
|
|
|
|
using CodeImp.DoomBuilder.Geometry;
|
|
|
|
|
using CodeImp.DoomBuilder.Map;
|
|
|
|
|
using CodeImp.DoomBuilder.Rendering;
|
|
|
|
|
using CodeImp.DoomBuilder.Windows;
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
namespace CodeImp.DoomBuilder.BuilderModes
|
|
|
|
|
{
|
|
|
|
|
[EditMode(DisplayName = "Draw Grid Mode",
|
|
|
|
|
SwitchAction = "drawgridmode",
|
2014-02-26 14:11:06 +00:00
|
|
|
|
ButtonImage = "DrawGridMode.png", //mxd
|
|
|
|
|
ButtonOrder = int.MinValue + 5, //mxd
|
|
|
|
|
ButtonGroup = "000_drawing", //mxd
|
2014-01-16 09:32:05 +00:00
|
|
|
|
AllowCopyPaste = false,
|
|
|
|
|
Volatile = true,
|
|
|
|
|
Optional = false)]
|
|
|
|
|
|
|
|
|
|
public class DrawGridMode : DrawGeometryMode
|
|
|
|
|
{
|
2016-03-04 13:41:55 +00:00
|
|
|
|
#region ================== Enums
|
|
|
|
|
|
|
|
|
|
public enum GridLockMode
|
|
|
|
|
{
|
|
|
|
|
NONE,
|
|
|
|
|
HORIZONTAL,
|
|
|
|
|
VERTICAL,
|
|
|
|
|
BOTH,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2014-01-16 09:32:05 +00:00
|
|
|
|
#region ================== Variables
|
|
|
|
|
|
2016-03-04 13:41:55 +00:00
|
|
|
|
// Settings
|
|
|
|
|
private int horizontalslices;
|
|
|
|
|
private int verticalslices;
|
|
|
|
|
private bool triangulate;
|
2020-06-08 21:00:14 +00:00
|
|
|
|
private bool relativeinterpolation;
|
2016-03-04 13:41:55 +00:00
|
|
|
|
private GridLockMode gridlockmode;
|
|
|
|
|
private InterpolationTools.Mode horizontalinterpolation;
|
|
|
|
|
private InterpolationTools.Mode verticalinterpolation;
|
2014-01-16 09:32:05 +00:00
|
|
|
|
|
2016-03-04 13:41:55 +00:00
|
|
|
|
// Drawing
|
2014-11-04 14:24:47 +00:00
|
|
|
|
private readonly List<DrawnVertex[]> gridpoints;
|
2015-06-25 18:32:29 +00:00
|
|
|
|
private HintLabel hintlabel;
|
2014-01-16 09:32:05 +00:00
|
|
|
|
|
|
|
|
|
private int width;
|
|
|
|
|
private int height;
|
2014-01-17 09:44:08 +00:00
|
|
|
|
private int slicesH;
|
|
|
|
|
private int slicesV;
|
2014-01-16 09:32:05 +00:00
|
|
|
|
private Vector2D start;
|
|
|
|
|
private Vector2D end;
|
|
|
|
|
|
2016-03-04 13:41:55 +00:00
|
|
|
|
// Interface
|
2015-06-25 18:32:29 +00:00
|
|
|
|
private DrawGridOptionsPanel panel;
|
|
|
|
|
private Docker docker;
|
2014-01-16 09:32:05 +00:00
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region ================== Constructor
|
|
|
|
|
|
2014-11-04 14:24:47 +00:00
|
|
|
|
public DrawGridMode()
|
|
|
|
|
{
|
2014-01-16 09:32:05 +00:00
|
|
|
|
snaptogrid = true;
|
2015-07-09 22:32:12 +00:00
|
|
|
|
usefourcardinaldirections = true;
|
2016-03-14 10:25:27 +00:00
|
|
|
|
autoclosedrawing = false;
|
2014-11-04 14:24:47 +00:00
|
|
|
|
gridpoints = new List<DrawnVertex[]>();
|
2014-01-16 09:32:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region ================== Events
|
|
|
|
|
|
2014-11-04 14:24:47 +00:00
|
|
|
|
override public void OnAccept()
|
|
|
|
|
{
|
2014-01-16 09:32:05 +00:00
|
|
|
|
Cursor.Current = Cursors.AppStarting;
|
|
|
|
|
General.Settings.FindDefaultDrawSettings();
|
|
|
|
|
|
|
|
|
|
// When we have a shape...
|
2014-11-04 14:24:47 +00:00
|
|
|
|
if(gridpoints.Count > 0)
|
|
|
|
|
{
|
2014-01-16 09:32:05 +00:00
|
|
|
|
// Make undo for the draw
|
|
|
|
|
General.Map.UndoRedo.CreateUndo("Grid draw");
|
|
|
|
|
|
|
|
|
|
// Make an analysis and show info
|
2020-11-01 17:49:15 +00:00
|
|
|
|
string[] adjectives = new[]
|
|
|
|
|
{ "beautiful", "lovely", "romantic", "stylish", "cheerful", "comical",
|
|
|
|
|
"awesome", "accurate", "adorable", "adventurous", "attractive", "cute",
|
|
|
|
|
"elegant", "glamorous", "gorgeous", "handsome", "magnificent", "unusual",
|
|
|
|
|
"outstanding", "mysterious", "amusing", "charming", "fantastic", "jolly" };
|
2014-01-16 09:32:05 +00:00
|
|
|
|
string word = adjectives[new Random().Next(adjectives.Length - 1)];
|
2020-11-01 17:49:15 +00:00
|
|
|
|
string a = ((word[0] == 'a') || (word[0] == 'e') || (word[0] == 'o') || (word[0] == 'u')) ? "an " : "a ";
|
2014-01-16 09:32:05 +00:00
|
|
|
|
|
|
|
|
|
General.Interface.DisplayStatus(StatusType.Action, "Created " + a + word + " grid.");
|
|
|
|
|
|
|
|
|
|
List<Sector> newsectors = new List<Sector>();
|
2015-12-28 15:01:53 +00:00
|
|
|
|
foreach(DrawnVertex[] shape in gridpoints)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2016-02-17 22:23:18 +00:00
|
|
|
|
if(Tools.DrawLines(shape, true, BuilderPlug.Me.AutoAlignTextureOffsetsOnCreate))
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2016-02-17 22:23:18 +00:00
|
|
|
|
// Update cached values after each step...
|
|
|
|
|
General.Map.Map.Update();
|
2014-01-16 09:32:05 +00:00
|
|
|
|
|
2016-02-17 22:23:18 +00:00
|
|
|
|
newsectors.AddRange(General.Map.Map.GetMarkedSectors(true));
|
2014-01-16 09:32:05 +00:00
|
|
|
|
|
2016-02-17 22:23:18 +00:00
|
|
|
|
// Snap to map format accuracy
|
|
|
|
|
General.Map.Map.SnapAllToAccuracy();
|
2014-01-16 09:32:05 +00:00
|
|
|
|
|
2016-02-17 22:23:18 +00:00
|
|
|
|
// Clear selection
|
|
|
|
|
General.Map.Map.ClearAllSelected();
|
2014-01-16 09:32:05 +00:00
|
|
|
|
|
2016-05-18 20:31:40 +00:00
|
|
|
|
//mxd. Outer sectors may require some splittin...
|
2016-06-15 22:02:51 +00:00
|
|
|
|
if(General.Settings.SplitJoinedSectors) Tools.SplitOuterSectors(General.Map.Map.GetMarkedLinedefs(true));
|
2016-05-18 20:31:40 +00:00
|
|
|
|
|
2016-02-17 22:23:18 +00:00
|
|
|
|
// Edit new sectors?
|
|
|
|
|
if(BuilderPlug.Me.EditNewSector && (newsectors.Count > 0))
|
|
|
|
|
General.Interface.ShowEditSectors(newsectors);
|
2014-01-16 09:32:05 +00:00
|
|
|
|
|
2016-02-17 22:23:18 +00:00
|
|
|
|
// Update the used textures
|
|
|
|
|
General.Map.Data.UpdateUsedTextures();
|
2014-01-16 09:32:05 +00:00
|
|
|
|
|
2016-02-17 22:23:18 +00:00
|
|
|
|
//mxd
|
|
|
|
|
General.Map.Renderer2D.UpdateExtraFloorFlag();
|
2014-01-16 09:32:05 +00:00
|
|
|
|
|
2016-02-17 22:23:18 +00:00
|
|
|
|
// Map is changed
|
|
|
|
|
General.Map.IsChanged = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// 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();
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-01-16 09:32:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Done
|
|
|
|
|
Cursor.Current = Cursors.Default;
|
|
|
|
|
|
2016-02-17 22:23:18 +00:00
|
|
|
|
if(continuousdrawing)
|
|
|
|
|
{
|
|
|
|
|
// Reset settings
|
|
|
|
|
points.Clear();
|
|
|
|
|
labels.Clear();
|
2016-03-14 10:25:27 +00:00
|
|
|
|
drawingautoclosed = false;
|
2016-02-17 22:23:18 +00:00
|
|
|
|
|
|
|
|
|
// Redraw display
|
|
|
|
|
General.Interface.RedrawDisplay();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Return to original mode
|
|
|
|
|
General.Editing.ChangeMode(General.Editing.PreviousStableMode.Name);
|
|
|
|
|
}
|
2014-01-16 09:32:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-18 12:52:12 +00:00
|
|
|
|
public override void OnDisengage()
|
|
|
|
|
{
|
|
|
|
|
if(hintlabel != null) hintlabel.Dispose();
|
|
|
|
|
base.OnDisengage();
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-04 14:24:47 +00:00
|
|
|
|
private void OptionsPanelOnValueChanged(object sender, EventArgs eventArgs)
|
|
|
|
|
{
|
2014-01-16 09:32:05 +00:00
|
|
|
|
triangulate = panel.Triangulate;
|
2016-03-04 13:41:55 +00:00
|
|
|
|
horizontalslices = panel.HorizontalSlices + 1;
|
|
|
|
|
verticalslices = panel.VerticalSlices + 1;
|
2015-01-16 23:37:20 +00:00
|
|
|
|
horizontalinterpolation = panel.HorizontalInterpolationMode;
|
|
|
|
|
verticalinterpolation = panel.VerticalInterpolationMode;
|
2014-01-16 09:32:05 +00:00
|
|
|
|
Update();
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-04 13:41:55 +00:00
|
|
|
|
private void OptionsPanelOnGridLockChanged(object sender, EventArgs eventArgs)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2016-03-04 13:41:55 +00:00
|
|
|
|
gridlockmode = panel.GridLockMode;
|
|
|
|
|
General.Hints.ShowHints(this.GetType(), ((gridlockmode != GridLockMode.NONE) ? "gridlockhelp" : "general"));
|
2014-01-17 09:44:08 +00:00
|
|
|
|
Update();
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-04 14:24:47 +00:00
|
|
|
|
public override void OnHelp()
|
|
|
|
|
{
|
|
|
|
|
General.ShowHelp("/gzdb/features/classic_modes/mode_drawgrid.html");
|
|
|
|
|
}
|
|
|
|
|
|
2014-01-16 09:32:05 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region ================== Methods
|
|
|
|
|
|
2014-11-04 14:24:47 +00:00
|
|
|
|
override protected void Update()
|
|
|
|
|
{
|
2014-01-16 09:32:05 +00:00
|
|
|
|
PixelColor stitchcolor = General.Colors.Highlight;
|
|
|
|
|
PixelColor losecolor = General.Colors.Selection;
|
|
|
|
|
|
2014-11-04 14:24:47 +00:00
|
|
|
|
// We WANT snaptogrid and DON'T WANT snaptonearest when lock to grid is enabled
|
2015-07-09 22:32:12 +00:00
|
|
|
|
snaptocardinaldirection = General.Interface.ShiftState && General.Interface.AltState; //mxd
|
2016-03-04 13:41:55 +00:00
|
|
|
|
snaptogrid = (snaptocardinaldirection || gridlockmode != GridLockMode.NONE || (General.Interface.ShiftState ^ General.Interface.SnapToGrid));
|
|
|
|
|
snaptonearest = (gridlockmode == GridLockMode.NONE && (General.Interface.CtrlState ^ General.Interface.AutoMerge));
|
2014-11-04 14:24:47 +00:00
|
|
|
|
|
2020-06-14 22:05:04 +00:00
|
|
|
|
DrawnVertex curp = GetCurrentPosition();
|
|
|
|
|
|
|
|
|
|
Vector2D curvertexpos = curp.pos;
|
2014-01-16 09:32:05 +00:00
|
|
|
|
float vsize = (renderer.VertexSize + 1.0f) / renderer.Scale;
|
|
|
|
|
|
2020-06-14 22:05:04 +00:00
|
|
|
|
curp.pos = curp.pos.GetRotated(-General.Map.Grid.GridRotate);
|
|
|
|
|
|
2014-01-16 09:32:05 +00:00
|
|
|
|
// Render drawing lines
|
2014-11-04 14:24:47 +00:00
|
|
|
|
if(renderer.StartOverlay(true))
|
|
|
|
|
{
|
2014-01-16 09:32:05 +00:00
|
|
|
|
PixelColor color = snaptonearest ? stitchcolor : losecolor;
|
|
|
|
|
|
2020-06-14 22:05:04 +00:00
|
|
|
|
if (points.Count == 1)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2014-12-03 23:15:26 +00:00
|
|
|
|
UpdateReferencePoints(points[0], curp);
|
|
|
|
|
List<Vector2D[]> shapes = GetShapes(start, end);
|
2014-01-16 09:32:05 +00:00
|
|
|
|
|
2020-06-15 20:13:54 +00:00
|
|
|
|
Vector2D startrotated = start.GetRotated(General.Map.Grid.GridRotate);
|
|
|
|
|
Vector2D endrotated = end.GetRotated(General.Map.Grid.GridRotate);
|
|
|
|
|
|
2020-06-14 22:05:04 +00:00
|
|
|
|
// Rotate the shape to fit the grid rotation
|
2020-06-15 20:13:54 +00:00
|
|
|
|
foreach (Vector2D[] shape in shapes)
|
2020-06-14 22:05:04 +00:00
|
|
|
|
{
|
|
|
|
|
for(int i=0; i < shape.Length; i++)
|
|
|
|
|
shape[i] = shape[i].GetRotated(General.Map.Grid.GridRotate);
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-26 12:53:50 +00:00
|
|
|
|
// Render guidelines
|
|
|
|
|
if(showguidelines)
|
2020-06-15 20:03:28 +00:00
|
|
|
|
RenderGuidelines(startrotated, endrotated, General.Colors.Guideline.WithAlpha(80), -General.Map.Grid.GridRotate);
|
2016-09-26 12:53:50 +00:00
|
|
|
|
|
2014-01-16 09:32:05 +00:00
|
|
|
|
//render shape
|
2015-12-28 15:01:53 +00:00
|
|
|
|
foreach(Vector2D[] shape in shapes)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2014-01-16 09:32:05 +00:00
|
|
|
|
for(int i = 1; i < shape.Length; i++)
|
|
|
|
|
renderer.RenderLine(shape[i - 1], shape[i], LINE_THICKNESS, color, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//vertices
|
2015-12-28 15:01:53 +00:00
|
|
|
|
foreach(Vector2D[] shape in shapes)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2015-12-28 15:01:53 +00:00
|
|
|
|
for(int i = 0; i < shape.Length; i++)
|
2020-05-21 12:20:02 +00:00
|
|
|
|
renderer.RenderRectangleFilled(new RectangleF((float)(shape[i].x - vsize), (float)(shape[i].y - vsize), vsize * 2.0f, vsize * 2.0f), color, true);
|
2014-01-16 09:32:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//and labels
|
2016-09-26 12:53:50 +00:00
|
|
|
|
if(width == 0 || height == 0)
|
|
|
|
|
{
|
|
|
|
|
// Render label for line
|
2020-06-14 22:05:04 +00:00
|
|
|
|
labels[0].Move(startrotated, endrotated);
|
2016-09-26 12:53:50 +00:00
|
|
|
|
renderer.RenderText(labels[0].TextLabel);
|
|
|
|
|
}
|
|
|
|
|
else
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2016-09-26 12:53:50 +00:00
|
|
|
|
// Render labels for grid
|
2020-06-14 22:05:04 +00:00
|
|
|
|
Vector2D[] labelCoords = { startrotated, new Vector2D(end.x, start.y).GetRotated(General.Map.Grid.GridRotate), endrotated, new Vector2D(start.x, end.y).GetRotated(General.Map.Grid.GridRotate), startrotated };
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i < 5; i++)
|
2016-09-26 12:53:50 +00:00
|
|
|
|
{
|
|
|
|
|
labels[i - 1].Move(labelCoords[i], labelCoords[i - 1]);
|
|
|
|
|
renderer.RenderText(labels[i - 1].TextLabel);
|
|
|
|
|
}
|
2014-01-16 09:32:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//render hint
|
2016-03-04 13:41:55 +00:00
|
|
|
|
if(horizontalslices > 1 || verticalslices > 1)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2016-04-29 13:42:52 +00:00
|
|
|
|
string text = "H: " + (slicesH - 1) + "; V: " + (slicesV - 1);
|
2020-06-14 22:05:04 +00:00
|
|
|
|
if(Math.Abs(width) > text.Length * vsize && Math.Abs(height) > 16 * vsize)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2016-04-29 13:42:52 +00:00
|
|
|
|
hintlabel.Text = text;
|
2020-06-14 22:05:04 +00:00
|
|
|
|
hintlabel.Move(startrotated, endrotated);
|
2015-06-25 18:32:29 +00:00
|
|
|
|
renderer.RenderText(hintlabel.TextLabel);
|
2014-01-16 09:32:05 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2014-11-04 14:24:47 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2014-01-16 09:32:05 +00:00
|
|
|
|
// Render vertex at cursor
|
2020-06-14 22:05:04 +00:00
|
|
|
|
renderer.RenderRectangleFilled(new RectangleF((float)(curvertexpos.x - vsize), (float)(curvertexpos.y - vsize), vsize * 2.0f, vsize * 2.0f), color, true);
|
2014-01-16 09:32:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Done
|
|
|
|
|
renderer.Finish();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Done
|
|
|
|
|
renderer.Present();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This draws a point at a specific location
|
2014-11-04 14:24:47 +00:00
|
|
|
|
override public bool DrawPointAt(Vector2D pos, bool stitch, bool stitchline)
|
|
|
|
|
{
|
2014-01-16 09:32:05 +00:00
|
|
|
|
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;
|
|
|
|
|
|
2020-06-14 22:05:04 +00:00
|
|
|
|
DrawnVertex newpoint = new DrawnVertex();
|
|
|
|
|
newpoint.pos = pos.GetRotated(-General.Map.Grid.GridRotate);
|
|
|
|
|
newpoint.stitch = true; //stitch
|
|
|
|
|
newpoint.stitchline = stitchline;
|
2014-01-16 09:32:05 +00:00
|
|
|
|
points.Add(newpoint);
|
|
|
|
|
|
2014-11-04 14:24:47 +00:00
|
|
|
|
if(points.Count == 1)
|
|
|
|
|
{
|
|
|
|
|
// Add labels
|
Fixed, Draw Lines/Rectangle/Circle/Curve modes: line length labels displayed incorrect length.
Changed, Drag Linedefs/Vertices/Sectors/Things modes: line length labels are now displayed the same way as in Draw modes.
Changed, Drag Linedefs/Vertices/Sectors/Things modes: "lock movement to cardinal directions" mode (Alt-Shift-Drag) now locks movement in 4 directions instead of 8 and doesn't snap map elements to nearest grid intersections when they are not aligned to it.
Added, Visual mode, GZDoom, DECORATE: FORCEXYBILLBOARD flag is now supported.
Added, Visual mode, GLOOME, DECORATE: FLOORSPRITE, CEILSPRITE, WALLSPRITE, ROLLSPRITE and STICKTOPLANE flags are now supported (implementation is somewhat broken ATM and probably doesn't work the same way as in GLOOME, because Windows build with most these features is nowhere to be found...).
Fixed, Visual mode: in some cases Thing brightness was calculated incorrectly.
Updated ZDoom_DECORATE.cfg.
2015-08-25 22:05:14 +00:00
|
|
|
|
labels.AddRange(new[] { new LineLengthLabel(false, true), new LineLengthLabel(false, true), new LineLengthLabel(false, true), new LineLengthLabel(false, true) });
|
|
|
|
|
hintlabel = new HintLabel(General.Colors.InfoLine);
|
2014-01-16 09:32:05 +00:00
|
|
|
|
Update();
|
2014-11-04 14:24:47 +00:00
|
|
|
|
}
|
|
|
|
|
else if(points[0].pos == points[1].pos)
|
|
|
|
|
{
|
|
|
|
|
// Nothing is drawn
|
2014-01-16 09:32:05 +00:00
|
|
|
|
FinishDraw();
|
2014-11-04 14:24:47 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Create vertices for final shape.
|
2014-12-03 23:15:26 +00:00
|
|
|
|
UpdateReferencePoints(points[0], newpoint);
|
|
|
|
|
List<Vector2D[]> shapes = GetShapes(start, end);
|
2014-01-16 09:32:05 +00:00
|
|
|
|
|
2015-12-28 15:01:53 +00:00
|
|
|
|
foreach(Vector2D[] shape in shapes)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2014-01-16 09:32:05 +00:00
|
|
|
|
DrawnVertex[] verts = new DrawnVertex[shape.Length];
|
2020-06-14 22:05:04 +00:00
|
|
|
|
for(int i = 0; i < shape.Length; i++)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2020-06-14 22:05:04 +00:00
|
|
|
|
newpoint = new DrawnVertex {
|
|
|
|
|
pos = shape[i].GetRotated(General.Map.Grid.GridRotate), // Take grid rotation into account
|
|
|
|
|
stitch = true,
|
|
|
|
|
stitchline = stitchline
|
|
|
|
|
};
|
2014-01-16 09:32:05 +00:00
|
|
|
|
verts[i] = newpoint;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gridpoints.Add(verts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FinishDraw();
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-03 23:15:26 +00:00
|
|
|
|
private List<Vector2D[]> GetShapes(Vector2D s, Vector2D e)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2016-03-04 13:41:55 +00:00
|
|
|
|
// No shape
|
2014-01-16 09:32:05 +00:00
|
|
|
|
if(s == e) return new List<Vector2D[]>();
|
|
|
|
|
|
2020-10-16 12:45:59 +00:00
|
|
|
|
// Setup slices. Need to do some extra mathy stuff the make sure the rotated grid is taken into account
|
2016-03-04 13:41:55 +00:00
|
|
|
|
switch(gridlockmode)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2016-03-04 13:41:55 +00:00
|
|
|
|
case GridLockMode.NONE:
|
|
|
|
|
slicesH = horizontalslices;
|
|
|
|
|
slicesV = verticalslices;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case GridLockMode.HORIZONTAL:
|
2020-10-16 12:45:59 +00:00
|
|
|
|
slicesH = Convert.ToInt32(Math.Ceiling(Math.Abs(width) / (double)General.Map.Grid.GridSize));
|
2016-03-04 13:41:55 +00:00
|
|
|
|
slicesV = verticalslices;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case GridLockMode.VERTICAL:
|
|
|
|
|
slicesH = horizontalslices;
|
2020-10-16 12:45:59 +00:00
|
|
|
|
slicesV = Convert.ToInt32(Math.Ceiling(Math.Abs(height) / (double)General.Map.Grid.GridSize));
|
2016-03-04 13:41:55 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case GridLockMode.BOTH:
|
2020-10-16 12:45:59 +00:00
|
|
|
|
slicesH = Convert.ToInt32(Math.Ceiling(Math.Abs(width) / (double)General.Map.Grid.GridSize));
|
|
|
|
|
slicesV = Convert.ToInt32(Math.Ceiling(Math.Abs(height) / (double)General.Map.Grid.GridSize));
|
2016-03-04 13:41:55 +00:00
|
|
|
|
break;
|
2014-11-04 14:24:47 +00:00
|
|
|
|
}
|
2014-01-16 09:32:05 +00:00
|
|
|
|
|
2016-03-04 13:41:55 +00:00
|
|
|
|
// Create a segmented line
|
2014-11-04 14:24:47 +00:00
|
|
|
|
List<Vector2D[]> shapes;
|
|
|
|
|
if(width == 0 || height == 0)
|
|
|
|
|
{
|
2015-12-28 15:01:53 +00:00
|
|
|
|
if(slicesH > 0 && width > 0)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
|
|
|
|
shapes = new List<Vector2D[]>();
|
|
|
|
|
int step = width / slicesH;
|
2015-12-28 15:01:53 +00:00
|
|
|
|
for(int w = 0; w < slicesH; w++)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2017-02-12 16:35:11 +00:00
|
|
|
|
shapes.Add(new[] { new Vector2D(s.x + step * w, s.y),
|
|
|
|
|
new Vector2D(s.x + step * w + step, s.y) });
|
2014-11-04 14:24:47 +00:00
|
|
|
|
}
|
|
|
|
|
return shapes;
|
2014-01-17 09:44:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-28 15:01:53 +00:00
|
|
|
|
if(slicesV > 0 && height > 0)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
|
|
|
|
shapes = new List<Vector2D[]>();
|
|
|
|
|
int step = height / slicesV;
|
2015-12-28 15:01:53 +00:00
|
|
|
|
for(int h = 0; h < slicesV; h++)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2017-02-12 16:35:11 +00:00
|
|
|
|
shapes.Add(new[] { new Vector2D(s.x, s.y + step * h),
|
|
|
|
|
new Vector2D(s.x, s.y + step * h + step) });
|
2014-11-04 14:24:47 +00:00
|
|
|
|
}
|
|
|
|
|
return shapes;
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-04 13:41:55 +00:00
|
|
|
|
// Create a line
|
2014-11-04 14:24:47 +00:00
|
|
|
|
return new List<Vector2D[]> {new[] {s, e}};
|
2014-01-17 09:44:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-06-17 20:44:14 +00:00
|
|
|
|
// Create grid shape
|
2017-02-12 16:35:11 +00:00
|
|
|
|
List<Vector2D> rect = new List<Vector2D> { s, new Vector2D(s.x, e.y), e, new Vector2D(e.x, s.y), s };
|
2016-03-04 13:41:55 +00:00
|
|
|
|
if(slicesH == 1 && slicesV == 1)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2014-01-16 09:32:05 +00:00
|
|
|
|
if(triangulate) rect.AddRange(new[] { s, e });
|
2014-11-04 14:24:47 +00:00
|
|
|
|
return new List<Vector2D[]> { rect.ToArray() };
|
2014-01-16 09:32:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-04 13:41:55 +00:00
|
|
|
|
// Create blocks
|
2014-11-04 14:24:47 +00:00
|
|
|
|
shapes = new List<Vector2D[]> { rect.ToArray() };
|
2014-01-17 09:44:08 +00:00
|
|
|
|
RectangleF[,] blocks = new RectangleF[slicesH, slicesV];
|
2014-11-04 14:24:47 +00:00
|
|
|
|
for(int w = 0; w < slicesH; w++)
|
|
|
|
|
{
|
2016-06-17 20:44:14 +00:00
|
|
|
|
for(int h = 0; h < slicesV; h++)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2020-05-21 12:20:02 +00:00
|
|
|
|
double left = (InterpolationTools.Interpolate(s.x, e.x, (double)w / slicesH, horizontalinterpolation));
|
|
|
|
|
double top = (InterpolationTools.Interpolate(s.y, e.y, (double)h / slicesV, verticalinterpolation));
|
|
|
|
|
double right = (InterpolationTools.Interpolate(s.x, e.x, (w + 1.0f) / slicesH, horizontalinterpolation));
|
|
|
|
|
double bottom = (InterpolationTools.Interpolate(s.y, e.y, (h + 1.0f) / slicesV, verticalinterpolation));
|
|
|
|
|
blocks[w, h] = RectangleF.FromLTRB((float)left, (float)top, (float)right, (float)bottom);
|
2014-01-16 09:32:05 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-04 13:41:55 +00:00
|
|
|
|
// Add subdivisions
|
2014-11-04 14:24:47 +00:00
|
|
|
|
if(slicesH > 1)
|
|
|
|
|
{
|
|
|
|
|
for(int w = 1; w < slicesH; w++)
|
|
|
|
|
{
|
2017-02-12 16:35:11 +00:00
|
|
|
|
float px = blocks[w, 0].X;
|
2016-06-17 20:44:14 +00:00
|
|
|
|
shapes.Add(new[] { new Vector2D(px, s.y), new Vector2D(px, e.y) });
|
2014-01-16 09:32:05 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2014-11-04 14:24:47 +00:00
|
|
|
|
if(slicesV > 1)
|
|
|
|
|
{
|
|
|
|
|
for(int h = 1; h < slicesV; h++)
|
|
|
|
|
{
|
2017-02-12 16:35:11 +00:00
|
|
|
|
float py = blocks[0, h].Y;
|
2014-01-16 09:32:05 +00:00
|
|
|
|
shapes.Add(new[] { new Vector2D(s.x, py), new Vector2D(e.x, py) });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-04 13:41:55 +00:00
|
|
|
|
// Triangulate?
|
2015-12-28 15:01:53 +00:00
|
|
|
|
if(triangulate)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2017-01-06 10:01:59 +00:00
|
|
|
|
bool startflip = ((int)Math.Round(((s.x + e.y) / General.Map.Grid.GridSizeF) % 2) == 0);
|
2014-11-04 14:24:47 +00:00
|
|
|
|
bool flip = startflip;
|
|
|
|
|
|
|
|
|
|
for(int w = 0; w < slicesH; w++)
|
|
|
|
|
{
|
|
|
|
|
for(int h = slicesV - 1; h > -1; h--)
|
|
|
|
|
{
|
2015-12-28 15:01:53 +00:00
|
|
|
|
if(flip)
|
2014-01-16 09:32:05 +00:00
|
|
|
|
shapes.Add(new[] { new Vector2D(blocks[w, h].X, blocks[w, h].Y), new Vector2D(blocks[w, h].Right, blocks[w, h].Bottom) });
|
2014-11-04 14:24:47 +00:00
|
|
|
|
else
|
2014-01-16 09:32:05 +00:00
|
|
|
|
shapes.Add(new[] { new Vector2D(blocks[w, h].Right, blocks[w, h].Y), new Vector2D(blocks[w, h].X, blocks[w, h].Bottom) });
|
|
|
|
|
|
|
|
|
|
flip = !flip;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
startflip = !startflip;
|
|
|
|
|
flip = startflip;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return shapes;
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-04 13:41:55 +00:00
|
|
|
|
// Update bottom-left and top-right points, which define drawing shape
|
2014-12-03 23:15:26 +00:00
|
|
|
|
private void UpdateReferencePoints(DrawnVertex p1, DrawnVertex p2)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2014-01-17 09:44:08 +00:00
|
|
|
|
if(!p1.pos.IsFinite() || !p2.pos.IsFinite()) return;
|
2014-01-16 09:32:05 +00:00
|
|
|
|
|
2020-06-08 21:00:14 +00:00
|
|
|
|
// If relative interpolation is enabled the interpolation will use the point where the drawing
|
|
|
|
|
// started as the origin, not always the top left corner
|
|
|
|
|
if (relativeinterpolation)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2020-06-08 21:00:14 +00:00
|
|
|
|
start.x = p1.pos.x;
|
2014-01-16 09:32:05 +00:00
|
|
|
|
start.y = p1.pos.y;
|
2020-06-08 21:00:14 +00:00
|
|
|
|
end.x = p2.pos.x;
|
2014-01-16 09:32:05 +00:00
|
|
|
|
end.y = p2.pos.y;
|
2020-06-08 21:00:14 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2020-06-08 21:00:14 +00:00
|
|
|
|
if (p1.pos.x < p2.pos.x)
|
|
|
|
|
{
|
|
|
|
|
start.x = p1.pos.x;
|
|
|
|
|
end.x = p2.pos.x;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
start.x = p2.pos.x;
|
|
|
|
|
end.x = p1.pos.x;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (p1.pos.y < p2.pos.y)
|
|
|
|
|
{
|
|
|
|
|
start.y = p1.pos.y;
|
|
|
|
|
end.y = p2.pos.y;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
start.y = p2.pos.y;
|
|
|
|
|
end.y = p1.pos.y;
|
|
|
|
|
}
|
2014-01-16 09:32:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-08 21:00:14 +00:00
|
|
|
|
|
2014-01-16 09:32:05 +00:00
|
|
|
|
width = (int)(end.x - start.x);
|
|
|
|
|
height = (int)(end.y - start.y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2016-02-17 22:23:18 +00:00
|
|
|
|
#region ================== Settings panel
|
|
|
|
|
|
|
|
|
|
protected override void SetupInterface()
|
|
|
|
|
{
|
2016-03-04 13:41:55 +00:00
|
|
|
|
// Load stored settings
|
|
|
|
|
triangulate = General.Settings.ReadPluginSetting("drawgridmode.triangulate", false);
|
|
|
|
|
gridlockmode = (GridLockMode)General.Settings.ReadPluginSetting("drawgridmode.gridlockmode", 0);
|
2020-06-08 21:00:14 +00:00
|
|
|
|
horizontalslices = Math.Max(General.Settings.ReadPluginSetting("drawgridmode.horizontalslices", 3), 1);
|
|
|
|
|
verticalslices = Math.Max(General.Settings.ReadPluginSetting("drawgridmode.verticalslices", 3), 1);
|
|
|
|
|
relativeinterpolation = General.Settings.ReadPluginSetting("drawgridmode.relativeinterpolation", true);
|
2016-03-04 13:41:55 +00:00
|
|
|
|
horizontalinterpolation = (InterpolationTools.Mode)General.Settings.ReadPluginSetting("drawgridmode.horizontalinterpolation", 0);
|
|
|
|
|
verticalinterpolation = (InterpolationTools.Mode)General.Settings.ReadPluginSetting("drawgridmode.verticalinterpolation", 0);
|
|
|
|
|
|
2016-02-17 22:23:18 +00:00
|
|
|
|
// Create and setup settings panel
|
|
|
|
|
panel = new DrawGridOptionsPanel();
|
|
|
|
|
panel.MaxHorizontalSlices = (int)General.Map.FormatInterface.MaxCoordinate;
|
|
|
|
|
panel.MaxVerticalSlices = (int)General.Map.FormatInterface.MaxCoordinate;
|
|
|
|
|
panel.Triangulate = triangulate;
|
2016-03-04 13:41:55 +00:00
|
|
|
|
panel.GridLockMode = gridlockmode;
|
|
|
|
|
panel.HorizontalSlices = horizontalslices - 1;
|
|
|
|
|
panel.VerticalSlices = verticalslices - 1;
|
2016-02-17 22:23:18 +00:00
|
|
|
|
panel.HorizontalInterpolationMode = horizontalinterpolation;
|
|
|
|
|
panel.VerticalInterpolationMode = verticalinterpolation;
|
|
|
|
|
|
|
|
|
|
panel.OnValueChanged += OptionsPanelOnValueChanged;
|
2016-03-04 13:41:55 +00:00
|
|
|
|
panel.OnGridLockModeChanged += OptionsPanelOnGridLockChanged;
|
2016-02-17 22:23:18 +00:00
|
|
|
|
panel.OnContinuousDrawingChanged += OnContinuousDrawingChanged;
|
2016-09-26 12:53:50 +00:00
|
|
|
|
panel.OnShowGuidelinesChanged += OnShowGuidelinesChanged;
|
2020-06-08 21:00:14 +00:00
|
|
|
|
panel.OnRelativeInterpolationChanged += OnRelativeInterpolationChanged;
|
2016-02-17 22:23:18 +00:00
|
|
|
|
|
2016-03-04 13:41:55 +00:00
|
|
|
|
// Needs to be set after adding the OnContinuousDrawingChanged event...
|
|
|
|
|
panel.ContinuousDrawing = General.Settings.ReadPluginSetting("drawgridmode.continuousdrawing", false);
|
2016-09-26 12:53:50 +00:00
|
|
|
|
panel.ShowGuidelines = General.Settings.ReadPluginSetting("drawgridmode.showguidelines", false);
|
2020-06-08 21:00:14 +00:00
|
|
|
|
panel.RelativeInterpolation = relativeinterpolation;
|
2016-02-17 22:23:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void AddInterface()
|
|
|
|
|
{
|
|
|
|
|
// Add docker
|
|
|
|
|
docker = new Docker("drawgrid", "Draw Grid", panel);
|
2016-04-15 14:24:18 +00:00
|
|
|
|
General.Interface.AddDocker(docker, true);
|
2016-02-17 22:23:18 +00:00
|
|
|
|
General.Interface.SelectDocker(docker);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void RemoveInterface()
|
|
|
|
|
{
|
2016-03-04 13:41:55 +00:00
|
|
|
|
// Store settings
|
|
|
|
|
General.Settings.WritePluginSetting("drawgridmode.triangulate", triangulate);
|
|
|
|
|
General.Settings.WritePluginSetting("drawgridmode.gridlockmode", (int)gridlockmode);
|
|
|
|
|
General.Settings.WritePluginSetting("drawgridmode.horizontalslices", horizontalslices);
|
|
|
|
|
General.Settings.WritePluginSetting("drawgridmode.verticalslices", verticalslices);
|
2020-06-08 21:00:14 +00:00
|
|
|
|
General.Settings.WritePluginSetting("drawgridmode.relativeinterpolation", relativeinterpolation);
|
2016-03-04 13:41:55 +00:00
|
|
|
|
General.Settings.WritePluginSetting("drawgridmode.horizontalinterpolation", (int)horizontalinterpolation);
|
|
|
|
|
General.Settings.WritePluginSetting("drawgridmode.verticalinterpolation", (int)verticalinterpolation);
|
|
|
|
|
General.Settings.WritePluginSetting("drawgridmode.continuousdrawing", panel.ContinuousDrawing);
|
2016-09-26 12:53:50 +00:00
|
|
|
|
General.Settings.WritePluginSetting("drawgridmode.showguidelines", panel.ShowGuidelines);
|
2016-02-17 22:23:18 +00:00
|
|
|
|
|
|
|
|
|
// Remove docker
|
|
|
|
|
General.Interface.RemoveDocker(docker);
|
|
|
|
|
panel.Dispose();
|
|
|
|
|
panel = null;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-08 21:00:14 +00:00
|
|
|
|
protected void OnRelativeInterpolationChanged(object value, EventArgs e)
|
|
|
|
|
{
|
|
|
|
|
relativeinterpolation = (bool)value;
|
|
|
|
|
Update();
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-17 22:23:18 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
2014-01-16 09:32:05 +00:00
|
|
|
|
#region ================== Actions
|
|
|
|
|
|
|
|
|
|
[BeginAction("increasebevel")]
|
2014-12-03 23:15:26 +00:00
|
|
|
|
protected void IncreaseBevel()
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2016-03-04 13:41:55 +00:00
|
|
|
|
if((gridlockmode == GridLockMode.NONE || gridlockmode == GridLockMode.VERTICAL)
|
|
|
|
|
&& (points.Count < 2 || horizontalslices < width - 2)
|
|
|
|
|
&& horizontalslices - 1 < panel.MaxHorizontalSlices)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2016-03-04 13:41:55 +00:00
|
|
|
|
horizontalslices++;
|
|
|
|
|
panel.HorizontalSlices = horizontalslices - 1;
|
2014-01-16 09:32:05 +00:00
|
|
|
|
Update();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[BeginAction("decreasebevel")]
|
2014-12-03 23:15:26 +00:00
|
|
|
|
protected void DecreaseBevel()
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2016-03-04 13:41:55 +00:00
|
|
|
|
if((gridlockmode == GridLockMode.NONE || gridlockmode == GridLockMode.VERTICAL) && horizontalslices > 1)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2016-03-04 13:41:55 +00:00
|
|
|
|
horizontalslices--;
|
|
|
|
|
panel.HorizontalSlices = horizontalslices - 1;
|
2014-01-16 09:32:05 +00:00
|
|
|
|
Update();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[BeginAction("increasesubdivlevel")]
|
2014-12-03 23:15:26 +00:00
|
|
|
|
protected void IncreaseSubdivLevel()
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2016-03-04 13:41:55 +00:00
|
|
|
|
if((gridlockmode == GridLockMode.NONE || gridlockmode == GridLockMode.HORIZONTAL)
|
|
|
|
|
&& (points.Count < 2 || verticalslices < height - 2)
|
|
|
|
|
&& verticalslices - 1 < panel.MaxVerticalSlices)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2016-03-04 13:41:55 +00:00
|
|
|
|
verticalslices++;
|
|
|
|
|
panel.VerticalSlices = verticalslices - 1;
|
2014-01-16 09:32:05 +00:00
|
|
|
|
Update();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[BeginAction("decreasesubdivlevel")]
|
2014-12-03 23:15:26 +00:00
|
|
|
|
protected void DecreaseSubdivLevel()
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2016-03-04 13:41:55 +00:00
|
|
|
|
if((gridlockmode == GridLockMode.NONE || gridlockmode == GridLockMode.HORIZONTAL) && verticalslices > 1)
|
2014-11-04 14:24:47 +00:00
|
|
|
|
{
|
2016-03-04 13:41:55 +00:00
|
|
|
|
verticalslices--;
|
|
|
|
|
panel.VerticalSlices = verticalslices - 1;
|
2014-01-16 09:32:05 +00:00
|
|
|
|
Update();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|