UltimateZoneBuilder/Source/Plugins/BuilderModes/ClassicModes/BridgeMode.cs

892 lines
27 KiB
C#
Raw Normal View History

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