UltimateZoneBuilder/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs
MaxED 3d2d9e21ef Changed, Classic modes: displayed mouse map position coordinates are now snapped to current grid size.
Updated model pitch handling to match current GZDoom implementation.
Fixed imprecise vertex coordinates generated by Draw Ellipse mode.
Fixed a resource loading exception when opened map file wad was located in the root of a Directory resource.
Internal: changed output of all InterpolationTools methods from int to float.
Fixed, Internal: InterpolationTools.InterpolateColor() delta usage was inverted.
2016-06-03 20:22:07 +00:00

1552 lines
48 KiB
C#

#region ================== Copyright (c) 2007 Pascal vd Heiden
/*
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
* This program is released under GNU General Public License
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#endregion
#region ================== Namespaces
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Windows.Forms;
using CodeImp.DoomBuilder.Data;
using CodeImp.DoomBuilder.Geometry;
using CodeImp.DoomBuilder.GZBuilder.Data;
using CodeImp.DoomBuilder.Map;
using CodeImp.DoomBuilder.Rendering;
using CodeImp.DoomBuilder.VisualModes;
#endregion
namespace CodeImp.DoomBuilder.BuilderModes
{
internal abstract class BaseVisualGeometrySidedef : VisualGeometry, IVisualEventReceiver
{
#region ================== Constants
private const float DRAG_ANGLE_TOLERANCE = 0.06f;
#endregion
#region ================== Variables
protected readonly BaseVisualMode mode;
protected Plane top;
protected Plane bottom;
protected long setuponloadedtexture;
// UV dragging
private float dragstartanglexy;
private float dragstartanglez;
private Vector3D dragorigin;
private Vector3D deltaxy;
private Vector3D deltaz;
private int startoffsetx;
private int startoffsety;
protected bool uvdragging;
private int prevoffsetx; // We have to provide delta offsets, but I don't
private int prevoffsety; // want to calculate with delta offsets to prevent
// inaccuracy in the dragging.
private static List<BaseVisualSector> updatelist; //mxd
private bool performautoselection; //mxd
// Undo/redo
protected int undoticket;
#endregion
#region ================== Properties
public bool IsDraggingUV { get { return uvdragging; } }
new public BaseVisualSector Sector { get { return (BaseVisualSector)base.Sector; } }
#endregion
#region ================== Constructor / Destructor
// Constructor for sidedefs
protected BaseVisualGeometrySidedef(BaseVisualMode mode, VisualSector vs, Sidedef sd) : base(vs, sd)
{
this.mode = mode;
this.deltaz = new Vector3D(0.0f, 0.0f, 1.0f);
this.deltaxy = (sd.Line.End.Position - sd.Line.Start.Position) * sd.Line.LengthInv;
if(!sd.IsFront) this.deltaxy = -this.deltaxy;
this.performautoselection = (mode.UseSelectionFromClassicMode && sd.Line.Selected); //mxd
}
#endregion
#region ================== Methods
//mxd
override protected void PerformAutoSelection()
{
if(!performautoselection) return;
if(Triangles > 0)
{
dragstartanglexy = General.Map.VisualCamera.AngleXY;
dragstartanglez = General.Map.VisualCamera.AngleZ;
dragorigin = pickintersect;
Point texoffset = GetTextureOffset();
startoffsetx = texoffset.X;
startoffsety = texoffset.Y;
prevoffsetx = texoffset.X;
prevoffsety = texoffset.Y;
this.selected = true;
mode.AddSelectedObject(this);
}
performautoselection = false;
}
// This sets the renderstyle from linedef information and returns the alpha value or the vertices
protected byte SetLinedefRenderstyle(bool solidasmask)
{
byte alpha;
bool canhavealpha = (this is VisualMiddleDouble || this is VisualMiddle3D || this is VisualMiddleBack); //mxd
// From TranslucentLine action
if(Sidedef.Line.Action == 208)
{
alpha = (byte)General.Clamp(Sidedef.Line.Args[1], 0, 255);
if(canhavealpha && Sidedef.Line.Args[2] == 1)
this.RenderPass = RenderPass.Additive;
else if(canhavealpha && (alpha < 255 || Texture.IsTranslucent))
this.RenderPass = RenderPass.Alpha;
else if(solidasmask)
this.RenderPass = RenderPass.Mask;
else
this.RenderPass = RenderPass.Solid;
}
// From UDMF field
else
{
alpha = (byte)(Sidedef.Line.Fields.GetValue("alpha", 1.0f) * 255.0f);
if(alpha == 255 && Sidedef.Line.IsFlagSet("transparent")) alpha = 64; //mxd
if(canhavealpha && Sidedef.Line.Fields.GetValue("renderstyle", "translucent") == "add")
this.RenderPass = RenderPass.Additive;
else if(canhavealpha && (alpha < 255 || Texture.IsTranslucent))
this.RenderPass = RenderPass.Alpha;
else if(solidasmask)
this.RenderPass = RenderPass.Mask;
else
this.RenderPass = RenderPass.Solid;
}
return alpha;
}
// This performs a fast test in object picking
public override bool PickFastReject(Vector3D from, Vector3D to, Vector3D dir)
{
// Check if intersection point is between top and bottom
return (pickintersect.z >= bottom.GetZ(pickintersect)) && (pickintersect.z <= top.GetZ(pickintersect));
}
// This performs an accurate test for object picking
public override bool PickAccurate(Vector3D from, Vector3D to, Vector3D dir, ref float u_ray)
{
// The fast reject pass is already as accurate as it gets,
// so we just return the intersection distance here
u_ray = pickrayu;
return true;
}
// This creates vertices from a wall polygon and applies lighting
/*protected List<WorldVertex> CreatePolygonVertices(WallPolygon poly, TexturePlane tp, SectorData sd, int lightvalue, bool lightabsolute)
{
List<WallPolygon> polylist = new List<WallPolygon>(1);
polylist.Add(poly);
return CreatePolygonVertices(polylist, tp, sd, lightvalue, lightabsolute);
}*/
// This creates vertices from a wall polygon and applies lighting
protected List<WorldVertex> CreatePolygonVertices(List<WallPolygon> poly, TexturePlane tp, SectorData sd, int lightvalue, bool lightabsolute)
{
List<WallPolygon> polygons = new List<WallPolygon>(poly);
List<WorldVertex> verts = new List<WorldVertex>();
//mxd. Do complicated light level shenanigans only when there are extrafloors
if(sd.LightLevels.Count > 2)
{
SectorLevel prevlight = null; //mxd
// Go for all levels to build geometry
for(int i = sd.LightLevels.Count - 1; i >= 0; i--)
{
SectorLevel l = sd.LightLevels[i];
//mxd. Skip current light level when it's between TYPE1 and TYPE1_BOTTOM
if(prevlight != null
&& prevlight.type == SectorLevelType.Light && l.type == SectorLevelType.Light
&& (prevlight.lighttype == LightLevelType.TYPE1 && l.lighttype != LightLevelType.TYPE1_BOTTOM)) continue;
if((l != sd.Floor) && (l != sd.Ceiling) && (l.type != SectorLevelType.Floor || l.splitsides /*(l.alpha < 255)*/))
{
// Go for all polygons
int num = polygons.Count;
Plane plane = (l.type == SectorLevelType.Floor ? l.plane.GetInverted() : l.plane); //mxd
for(int pi = 0; pi < num; pi++)
{
// Split by plane
WallPolygon p = polygons[pi];
WallPolygon np = SplitPoly(ref p, plane, false);
if(np.Count > 0)
{
if(l.type == SectorLevelType.Glow)
{
//mxd. Glow levels should not affect light level
np.color = p.color;
}
else
{
//mxd. Determine color
int lightlevel;
// Sidedef part is not affected by 3d floor brightness
if(l.type != SectorLevelType.Light && (l.disablelighting || !l.extrafloor))
lightlevel = (lightabsolute ? lightvalue : l.brightnessbelow + lightvalue);
// 3d floors and light transfer effects transfers brightness below them ignoring sidedef's brightness
else
lightlevel = l.brightnessbelow;
PixelColor wallbrightness = PixelColor.FromInt(mode.CalculateBrightness(lightlevel, Sidedef)); //mxd
np.color = PixelColor.Modulate(l.colorbelow, wallbrightness).WithAlpha(255).ToInt();
}
if(p.Count == 0)
{
polygons[pi] = np;
}
else
{
polygons[pi] = p;
polygons.Add(np);
}
}
else
{
polygons[pi] = p;
}
}
}
//mxd
if(l.type == SectorLevelType.Light) prevlight = l;
}
}
// Go for all polygons to make geometry
foreach(WallPolygon p in polygons)
{
// Find texture coordinates for each vertex in the polygon
List<Vector2D> texc = new List<Vector2D>(p.Count);
foreach(Vector3D v in p) texc.Add(tp.GetTextureCoordsAt(v));
// Now we create triangles from the polygon.
// The polygon is convex and clockwise, so this is a piece of cake.
if(p.Count > 2)
{
for(int k = 1; k < (p.Count - 1); k++)
{
verts.Add(new WorldVertex(p[0], p.color, texc[0]));
verts.Add(new WorldVertex(p[k], p.color, texc[k]));
verts.Add(new WorldVertex(p[k + 1], p.color, texc[k + 1]));
}
}
}
//mxd. Interpolate vertex colors?
if(sd.CeilingGlow != null || sd.FloorGlow != null)
{
for(int i = 0; i < verts.Count; i++)
{
if(verts[i].c == PixelColor.INT_WHITE) continue; // Fullbright verts are not affected by glows.
verts[i] = InterpolateVertexColor(verts[i], sd);
}
}
return verts;
}
//mxd
private static WorldVertex InterpolateVertexColor(WorldVertex v, SectorData data)
{
// Apply ceiling glow?
if(data.CeilingGlow != null)
{
float cgz = data.CeilingGlowPlane.GetZ(v.x, v.y);
// Vertex is above ceiling glow plane?
if(v.z > cgz)
{
float cz = data.Ceiling.plane.GetZ(v.x, v.y);
float delta = 1.0f - (((v.z - cgz) / (cz - cgz)) * 0.9f);
PixelColor vc = PixelColor.FromInt(v.c);
v.c = InterpolationTools.InterpolateColor(GetGlowColor(data.CeilingGlow, vc), vc, delta);
}
}
// Apply floor glow?
if(data.FloorGlow != null)
{
float fgz = data.FloorGlowPlane.GetZ(v.x, v.y);
// Vertex is below floor glow plane?
if(v.z < fgz)
{
float fz = data.Floor.plane.GetZ(v.x, v.y);
float delta = 1.0f - (((v.z - fz) / (fgz - fz)) * 0.9f);
PixelColor vc = PixelColor.FromInt(v.c);
v.c = InterpolationTools.InterpolateColor(vc, GetGlowColor(data.FloorGlow, vc), delta);
}
}
return v;
}
//mxd
private static PixelColor GetGlowColor(GlowingFlatData data, PixelColor vertexcolor)
{
if(data.Subtractive) return PixelColor.Subtract(vertexcolor, data.Color);
return PixelColor.Add(vertexcolor, data.Color);
}
// This splits a polygon with a plane and returns the other part as a new polygon
// The polygon is expected to be convex and clockwise
protected static WallPolygon SplitPoly(ref WallPolygon poly, Plane p, bool keepfront)
{
const float NEAR_ZERO = 0.01f;
WallPolygon front = new WallPolygon(poly.Count);
WallPolygon back = new WallPolygon(poly.Count);
poly.CopyProperties(front);
poly.CopyProperties(back);
if(poly.Count > 0)
{
// Go for all vertices to see which side they have to be on
Vector3D v1 = poly[poly.Count - 1];
float side1 = p.Distance(v1);
for(int i = 0; i < poly.Count; i++)
{
// Fetch vertex and determine side
Vector3D v2 = poly[i];
float side2 = p.Distance(v2);
// Front?
if(side2 > NEAR_ZERO)
{
if(side1 < -NEAR_ZERO)
{
// Split line with plane and insert the vertex
float u = 0.0f;
p.GetIntersection(v1, v2, ref u);
Vector3D v3 = v1 + (v2 - v1) * u;
front.Add(v3);
back.Add(v3);
}
front.Add(v2);
}
// Back?
else if(side2 < -NEAR_ZERO)
{
if(side1 > NEAR_ZERO)
{
// Split line with plane and insert the vertex
float u = 0.0f;
p.GetIntersection(v1, v2, ref u);
Vector3D v3 = v1 + (v2 - v1) * u;
front.Add(v3);
back.Add(v3);
}
back.Add(v2);
}
else
{
// On the plane, add to both polygons
front.Add(v2);
back.Add(v2);
}
// Next
v1 = v2;
side1 = side2;
}
}
if(keepfront)
{
poly = front;
return back;
}
else
{
poly = back;
return front;
}
}
// This crops a polygon with a plane and keeps only a certain part of the polygon
protected static void CropPoly(ref WallPolygon poly, Plane p, bool keepfront)
{
const float NEAR_ZERO = 0.01f;
float sideswitch = keepfront ? 1 : -1;
WallPolygon newp = new WallPolygon(poly.Count);
poly.CopyProperties(newp);
if(poly.Count > 0)
{
// First split lines that cross the plane so that we have vertices on the plane where the lines cross
Vector3D v1 = poly[poly.Count - 1];
float side1 = p.Distance(v1) * sideswitch;
for(int i = 0; i < poly.Count; i++)
{
// Fetch vertex and determine side
Vector3D v2 = poly[i];
float side2 = p.Distance(v2) * sideswitch;
// Front?
if(side2 > NEAR_ZERO)
{
if(side1 < -NEAR_ZERO)
{
// Split line with plane and insert the vertex
float u = 0.0f;
p.GetIntersection(v1, v2, ref u);
Vector3D v3 = v1 + (v2 - v1) * u;
newp.Add(v3);
}
newp.Add(v2);
}
// Back?
else if(side2 < -NEAR_ZERO)
{
if(side1 > NEAR_ZERO)
{
// Split line with plane and insert the vertex
float u = 0.0f;
p.GetIntersection(v1, v2, ref u);
Vector3D v3 = v1 + (v2 - v1) * u;
newp.Add(v3);
}
}
else
{
// On the plane
newp.Add(v2);
}
// Next
v1 = v2;
side1 = side2;
}
}
poly = newp;
}
//mxd. This clips given polys by extrafloors
protected void ClipExtraFloors(List<WallPolygon> polygons, List<Effect3DFloor> extrafloors, bool clipalways)
{
foreach(Effect3DFloor ef in extrafloors)
{
//mxd. Walls should be clipped by 3D floors
if(ef.ClipSidedefs || clipalways)
{
int num = polygons.Count;
for(int pi = 0; pi < num; pi++)
{
// Split by floor plane of 3D floor
WallPolygon p = polygons[pi];
WallPolygon np = SplitPoly(ref p, ef.Ceiling.plane, true);
if(np.Count > 0)
{
// Split part below floor by the ceiling plane of 3D floor
// and keep only the part below the ceiling (front)
SplitPoly(ref np, ef.Floor.plane, true);
if(p.Count == 0)
{
polygons[pi] = np;
}
else
{
polygons[pi] = p;
polygons.Add(np);
}
}
else
{
polygons[pi] = p;
}
}
}
}
}
//mxd
protected void GetLightValue(out int lightvalue, out bool lightabsolute)
{
lightabsolute = Sidedef.Fields.GetValue("lightabsolute", false);
bool affectedbyfog = General.Map.Data.MapInfo.HasFadeColor || (Sector.Sector.CeilTexture == General.Map.Config.SkyFlatName && General.Map.Data.MapInfo.HasOutsideFogColor) || Sector.Sector.Fields.ContainsKey("fadecolor");
bool ignorelight = affectedbyfog && !Sidedef.IsFlagSet("lightfog") && !lightabsolute;
lightvalue = ignorelight ? 0 : Sidedef.Fields.GetValue("light", 0); //mxd
if(ignorelight) lightabsolute = false;
}
//mxd
protected static float GetRoundedTextureOffset(float oldValue, float offset, float scale, float textureSize)
{
if(offset == 0f) return oldValue;
float scaledOffset = offset * scale;
float result = (float)Math.Round(oldValue + scaledOffset);
if(textureSize > 0) result %= textureSize;
if(result == oldValue) result += (scaledOffset < 0 ? -1 : 1);
return result;
}
//mxd
public void SelectNeighbours(bool select, bool matchtexture, bool matchheight)
{
if(Sidedef.Sector == null || Triangles < 1 || (!matchtexture && !matchheight)) return;
Rectangle rect = BuilderModesTools.GetSidedefPartSize(this);
if(rect.Height == 0) return;
if(select && !selected)
{
selected = true;
mode.AddSelectedObject(this);
}
else if(!select && selected)
{
selected = false;
mode.RemoveSelectedObject(this);
}
// Select
SelectNeighbourLines(Sidedef.Line.Start.Linedefs, rect, select, matchtexture, matchheight);
SelectNeighbourLines(Sidedef.Line.End.Linedefs, rect, select, matchtexture, matchheight);
}
//mxd
private void SelectNeighbourLines(IEnumerable<Linedef> lines, Rectangle sourcerect, bool select, bool matchtexture, bool matchheight)
{
foreach(Linedef line in lines)
{
if(line.Index == Sidedef.Line.Index) continue;
if(line.Front != null && line.Front.Sector != null)
SelectNeighbourSideParts(line.Front, sourcerect, select, matchtexture, matchheight);
if(line.Back != null && line.Back.Sector != null)
SelectNeighbourSideParts(line.Back, sourcerect, select, matchtexture, matchheight);
}
}
//mxd
private void SelectNeighbourSideParts(Sidedef side, Rectangle sourcerect, bool select, bool matchtexture, bool matchheight)
{
BaseVisualSector s = (BaseVisualSector)mode.GetVisualSector(side.Sector);
if(s != null)
{
VisualSidedefParts parts = s.GetSidedefParts(side);
SelectNeighbourSidePart(parts.lower, sourcerect, select, matchtexture, matchheight);
SelectNeighbourSidePart(parts.middlesingle, sourcerect, select, matchtexture, matchheight);
SelectNeighbourSidePart(parts.middledouble, sourcerect, select, matchtexture, matchheight);
SelectNeighbourSidePart(parts.upper, sourcerect, select, matchtexture, matchheight);
if(parts.middle3d != null)
{
foreach(VisualMiddle3D middle3D in parts.middle3d)
SelectNeighbourSidePart(middle3D, sourcerect, select, matchtexture, matchheight);
}
}
}
//mxd
private void SelectNeighbourSidePart(BaseVisualGeometrySidedef visualside, Rectangle sourcerect, bool select, bool matchtexture, bool matchheight)
{
if(visualside != null && visualside.Triangles > 0 && visualside.Selected != select)
{
Rectangle r = BuilderModesTools.GetSidedefPartSize(visualside);
if(r.Width == 0 || r.Height == 0) return;
if((!matchtexture || (visualside.Texture == Texture && r.IntersectsWith(sourcerect))) &&
(!matchheight || (sourcerect.Height == r.Height && sourcerect.Y == r.Y)))
{
visualside.SelectNeighbours(select, matchtexture, matchheight);
}
}
}
//mxd
protected void FitTexture(FitTextureOptions options)
{
// Create undo name
string s;
if(options.FitWidth && options.FitHeight) s = "width and height";
else if(options.FitWidth) s = "width";
else s = "height";
// Create undo
mode.CreateUndo("Fit texture (" + s + ")", UndoGroup.TextureOffsetChange, Sector.Sector.FixedIndex);
Sidedef.Fields.BeforeFieldsChange();
// Get proper control side...
Linedef controlline = GetControlLinedef();
Sidedef controlside;
if(controlline != Sidedef.Line)
{
controlside = controlline.Front;
controlside.Fields.BeforeFieldsChange();
}
else
{
controlside = Sidedef;
}
// Fit width
if(options.FitWidth)
{
float scalex, offsetx;
if(options.FitAcrossSurfaces)
{
scalex = Texture.ScaledWidth / (Sidedef.Line.Length * (options.GlobalBounds.Width / Sidedef.Line.Length)) * options.HorizontalRepeat;
offsetx = (float)Math.Round((options.Bounds.X * scalex - Sidedef.OffsetX - options.ControlSideOffsetX) % Texture.Width, General.Map.FormatInterface.VertexDecimals);
}
else
{
scalex = Texture.ScaledWidth / Sidedef.Line.Length * options.HorizontalRepeat;
offsetx = -Sidedef.OffsetX - options.ControlSideOffsetX;
}
UniFields.SetFloat(controlside.Fields, "scalex_" + partname, (float)Math.Round(scalex, General.Map.FormatInterface.VertexDecimals), 1.0f);
UniFields.SetFloat(Sidedef.Fields, "offsetx_" + partname, offsetx, 0.0f);
}
else
{
// Restore initial offsets
UniFields.SetFloat(controlside.Fields, "scalex_" + partname, options.InitialScaleX, 1.0f);
UniFields.SetFloat(Sidedef.Fields, "offsetx_" + partname, options.InitialOffsetX, 0.0f);
}
// Fit height
if(options.FitHeight)
{
if(Sidedef.Sector != null)
{
float scaley, offsety;
if(options.FitAcrossSurfaces)
{
scaley = Texture.ScaledHeight / (options.Bounds.Height * ((float)options.GlobalBounds.Height / options.Bounds.Height)) * options.VerticalRepeat;
if(this is VisualLower) // Special cases, special cases...
{
offsety = GetLowerOffsetY(scaley);
}
else if(this is VisualMiddleDouble)
{
if(Sidedef.Line.IsFlagSet(General.Map.Config.LowerUnpeggedFlag))
offsety = (options.Bounds.Y - Sidedef.GetHighHeight() - Sidedef.GetLowHeight()) * scaley - Sidedef.OffsetY;
else
offsety = options.Bounds.Y * scaley - Sidedef.OffsetY;
}
else
{
offsety = Tools.GetSidedefOffsetY(Sidedef, geometrytype, options.Bounds.Y * scaley - Sidedef.OffsetY - options.ControlSideOffsetY, scaley, true) % Texture.Height;
}
}
else
{
scaley = Texture.ScaledHeight / options.Bounds.Height * options.VerticalRepeat;
if(this is VisualLower) // Special cases, special cases...
offsety = GetLowerOffsetY(scaley);
else
offsety = Tools.GetSidedefOffsetY(Sidedef, geometrytype, -Sidedef.OffsetY - options.ControlSideOffsetY, scaley, true) % Texture.Height;
}
UniFields.SetFloat(controlside.Fields, "scaley_" + partname, (float)Math.Round(scaley, General.Map.FormatInterface.VertexDecimals), 1.0f);
UniFields.SetFloat(Sidedef.Fields, "offsety_" + partname, (float)Math.Round(offsety, General.Map.FormatInterface.VertexDecimals), 0.0f);
}
}
else
{
// Restore initial offsets
UniFields.SetFloat(controlside.Fields, "scaley_" + partname, options.InitialScaleY, 1.0f);
UniFields.SetFloat(Sidedef.Fields, "offsety_" + partname, options.InitialOffsetY, 0.0f);
}
}
//mxd. Oh so special cases...
private float GetLowerOffsetY(float scaley)
{
if(Sidedef.Line.IsFlagSet(General.Map.Config.LowerUnpeggedFlag))
return ((-Sidedef.OffsetY - Sidedef.GetMiddleHeight() - Sidedef.GetHighHeight()) * scaley) % Texture.Height;
return (-Sidedef.OffsetY * scaley) % Texture.Height;
}
#endregion
#region ================== Events
// Unused
public virtual void OnEditBegin() { }
protected virtual void SetTexture(string texturename) { }
public abstract bool Setup();
protected abstract void SetTextureOffsetX(int x);
protected abstract void SetTextureOffsetY(int y);
protected virtual void ResetTextureScale() { } //mxd
protected abstract void MoveTextureOffset(Point xy);
protected abstract Point GetTextureOffset();
public virtual void OnTextureFit(FitTextureOptions options) { } //mxd
// Insert middle texture
public virtual void OnInsert()
{
// No middle texture yet?
if(!Sidedef.MiddleRequired() && (string.IsNullOrEmpty(Sidedef.MiddleTexture) || (Sidedef.MiddleTexture == "-")))
{
// Make it now
mode.CreateUndo("Create middle texture");
mode.SetActionResult("Created middle texture.");
General.Settings.FindDefaultDrawSettings();
Sidedef.SetTextureMid(General.Map.Options.DefaultWallTexture);
// Update
Sector.Changed = true;
// Other side as well
if(string.IsNullOrEmpty(Sidedef.Other.MiddleTexture) || (Sidedef.Other.MiddleTexture == "-"))
{
Sidedef.Other.SetTextureMid(General.Map.Options.DefaultWallTexture);
// Update
VisualSector othersector = mode.GetVisualSector(Sidedef.Other.Sector);
if(othersector is BaseVisualSector) ((BaseVisualSector)othersector).Changed = true;
}
}
}
// Delete texture
public virtual void OnDelete()
{
// Remove texture
mode.CreateUndo("Delete texture");
mode.SetActionResult("Deleted a texture.");
SetTexture("-");
//mxd. Update linked effects
SectorData sd = mode.GetSectorDataEx(Sector.Sector);
if(sd != null) sd.Reset(true);
}
// Processing
public virtual void OnProcess(long deltatime)
{
// If the texture was not loaded, but is loaded now, then re-setup geometry
if(setuponloadedtexture != 0)
{
ImageData t = General.Map.Data.GetTextureImage(setuponloadedtexture);
if(t != null)
{
if(t.IsImageLoaded)
{
setuponloadedtexture = 0;
Setup();
}
}
}
}
// Change target height
public virtual void OnChangeTargetHeight(int amount)
{
switch(BuilderPlug.Me.ChangeHeightBySidedef)
{
// Change ceiling
case 1:
if(!this.Sector.Ceiling.Changed) this.Sector.Ceiling.OnChangeTargetHeight(amount);
break;
// Change floor
case 2:
if(!this.Sector.Floor.Changed) this.Sector.Floor.OnChangeTargetHeight(amount);
break;
// Change both
case 3:
if(!this.Sector.Floor.Changed) this.Sector.Floor.OnChangeTargetHeight(amount);
if(!this.Sector.Ceiling.Changed) this.Sector.Ceiling.OnChangeTargetHeight(amount);
break;
}
}
// Reset texture offsets
public virtual void OnResetTextureOffset()
{
mode.CreateUndo("Reset texture offsets");
mode.SetActionResult("Texture offsets reset.");
// Apply offsets
Sidedef.OffsetX = 0;
Sidedef.OffsetY = 0;
// Update sidedef geometry
VisualSidedefParts parts = Sector.GetSidedefParts(Sidedef);
parts.SetupAllParts();
}
//mxd
public virtual void OnResetLocalTextureOffset()
{
if(!General.Map.UDMF)
{
OnResetTextureOffset();
return;
}
mode.CreateUndo("Reset local texture offsets, scale and brightness");
mode.SetActionResult("Local texture offsets, scale and brightness reset.");
// Reset texture offsets
SetTextureOffsetX(0);
SetTextureOffsetY(0);
// Scale
ResetTextureScale();
// And brightness
bool setupallparts = false;
if(Sidedef.Fields.ContainsKey("light"))
{
Sidedef.Fields.Remove("light");
setupallparts = true;
}
if(Sidedef.Fields.ContainsKey("lightabsolute"))
{
Sidedef.Fields.Remove("lightabsolute");
setupallparts = true;
}
if(setupallparts)
{
// Update all sidedef geometry
VisualSidedefParts parts = Sector.GetSidedefParts(Sidedef);
parts.SetupAllParts();
}
else
{
// Update this part only
this.Setup();
}
}
// Toggle upper-unpegged
public virtual void OnToggleUpperUnpegged()
{
if(this.Sidedef.Line.IsFlagSet(General.Map.Config.UpperUnpeggedFlag))
{
// Remove flag
mode.ApplyUpperUnpegged(false);
}
else
{
// Add flag
mode.ApplyUpperUnpegged(true);
}
}
// Toggle lower-unpegged
public virtual void OnToggleLowerUnpegged()
{
if(this.Sidedef.Line.IsFlagSet(General.Map.Config.LowerUnpeggedFlag))
{
// Remove flag
mode.ApplyLowerUnpegged(false);
}
else
{
// Add flag
mode.ApplyLowerUnpegged(true);
}
}
// This sets the Upper Unpegged flag
public virtual void ApplyUpperUnpegged(bool set)
{
if(!set)
{
// Remove flag
mode.CreateUndo("Remove upper-unpegged setting");
mode.SetActionResult("Removed upper-unpegged setting.");
this.Sidedef.Line.SetFlag(General.Map.Config.UpperUnpeggedFlag, false);
}
else
{
// Add flag
mode.CreateUndo("Set upper-unpegged setting");
mode.SetActionResult("Set upper-unpegged setting.");
this.Sidedef.Line.SetFlag(General.Map.Config.UpperUnpeggedFlag, true);
}
// Update sidedef geometry
VisualSidedefParts parts = Sector.GetSidedefParts(Sidedef);
parts.SetupAllParts();
// Update other sidedef geometry
if(Sidedef.Other != null)
{
BaseVisualSector othersector = (BaseVisualSector)mode.GetVisualSector(Sidedef.Other.Sector);
parts = othersector.GetSidedefParts(Sidedef.Other);
parts.SetupAllParts();
}
}
// This sets the Lower Unpegged flag
public virtual void ApplyLowerUnpegged(bool set)
{
if(!set)
{
// Remove flag
mode.CreateUndo("Remove lower-unpegged setting");
mode.SetActionResult("Removed lower-unpegged setting.");
this.Sidedef.Line.SetFlag(General.Map.Config.LowerUnpeggedFlag, false);
}
else
{
// Add flag
mode.CreateUndo("Set lower-unpegged setting");
mode.SetActionResult("Set lower-unpegged setting.");
this.Sidedef.Line.SetFlag(General.Map.Config.LowerUnpeggedFlag, true);
}
// Update sidedef geometry
VisualSidedefParts parts = Sector.GetSidedefParts(Sidedef);
parts.SetupAllParts();
// Update other sidedef geometry
if(Sidedef.Other != null)
{
BaseVisualSector othersector = (BaseVisualSector)mode.GetVisualSector(Sidedef.Other.Sector);
parts = othersector.GetSidedefParts(Sidedef.Other);
parts.SetupAllParts();
}
}
// Flood-fill textures
public virtual void OnTextureFloodfill()
{
if(BuilderPlug.Me.CopiedTexture != null)
{
string oldtexture = GetTextureName();
string newtexture = BuilderPlug.Me.CopiedTexture;
if(newtexture != oldtexture)
{
mode.CreateUndo("Flood-fill textures with " + newtexture);
mode.SetActionResult("Flood-filled textures with " + newtexture + ".");
mode.Renderer.SetCrosshairBusy(true);
General.Interface.RedrawDisplay();
// Get the texture
ImageData newtextureimage = General.Map.Data.GetTextureImage(newtexture);
if(newtextureimage != null)
{
if(mode.IsSingleSelection)
{
// Clear all marks, this will align everything it can
General.Map.Map.ClearMarkedSidedefs(false);
}
else
{
// Limit the alignment to selection only
General.Map.Map.ClearMarkedSidedefs(true);
List<Sidedef> sides = mode.GetSelectedSidedefs();
foreach(Sidedef sd in sides) sd.Marked = false;
}
//mxd. We potentially need to deal with 2 textures (because of long and short texture names)...
HashSet<long> texturehashes = new HashSet<long> { Texture.LongName };
switch(this.GeometryType)
{
case VisualGeometryType.WALL_LOWER:
texturehashes.Add(Sidedef.LongLowTexture);
break;
case VisualGeometryType.WALL_MIDDLE:
case VisualGeometryType.WALL_MIDDLE_3D:
texturehashes.Add(Sidedef.LongMiddleTexture);
break;
case VisualGeometryType.WALL_UPPER:
texturehashes.Add(Sidedef.LongHighTexture);
break;
}
// Do the alignment
BuilderModesTools.FloodfillTextures(mode, Sidedef, texturehashes, newtexture, false);
// Get the changed sidedefs
List<Sidedef> changes = General.Map.Map.GetMarkedSidedefs(true);
foreach(Sidedef sd in changes)
{
// Update the parts for this sidedef!
if(mode.VisualSectorExists(sd.Sector))
{
BaseVisualSector vs = (BaseVisualSector)mode.GetVisualSector(sd.Sector);
VisualSidedefParts parts = vs.GetSidedefParts(sd);
parts.SetupAllParts();
}
}
General.Map.Data.UpdateUsedTextures();
mode.Renderer.SetCrosshairBusy(false);
mode.ShowTargetInfo();
}
}
}
}
// Auto-align texture offsets
public virtual void OnTextureAlign(bool alignx, bool aligny)
{
//mxd
string rest;
if(alignx && aligny) rest = "(X and Y)";
else if(alignx) rest = "(X)";
else rest = "(Y)";
mode.CreateUndo("Auto-align textures " + rest);
mode.SetActionResult("Auto-aligned textures " + rest + ".");
// Make sure the texture is loaded (we need the texture size)
if(!base.Texture.IsImageLoaded) base.Texture.LoadImage();
if(mode.IsSingleSelection)
{
// Clear all marks, this will align everything it can
General.Map.Map.ClearMarkedSidedefs(false);
}
else
{
// Limit the alignment to selection only
General.Map.Map.ClearMarkedSidedefs(true);
List<Sidedef> sides = mode.GetSelectedSidedefs();
foreach(Sidedef sd in sides) sd.Marked = false;
}
// Do the alignment
mode.AutoAlignTextures(this, base.Texture, alignx, aligny, false, true);
// Get the changed sidedefs
List<Sidedef> changes = General.Map.Map.GetMarkedSidedefs(true);
foreach(Sidedef sd in changes)
{
// Update the parts for this sidedef!
if(mode.VisualSectorExists(sd.Sector))
{
BaseVisualSector vs = (BaseVisualSector)mode.GetVisualSector(sd.Sector);
VisualSidedefParts parts = vs.GetSidedefParts(sd);
parts.SetupAllParts();
}
}
}
// Select texture
public virtual void OnSelectTexture()
{
if(General.Interface.IsActiveWindow)
{
string oldtexture = GetTextureName();
string newtexture = General.Interface.BrowseTexture(General.Interface, oldtexture);
if(newtexture != oldtexture)
{
mode.ApplySelectTexture(newtexture, false);
}
}
}
// Apply Texture
public virtual void ApplyTexture(string texture)
{
mode.CreateUndo("Change texture " + texture);
SetTexture(texture);
//mxd. Update linked effects
SectorData sd = mode.GetSectorDataEx(Sector.Sector);
if(sd != null) sd.Reset(true);
}
// Paste texture
public virtual void OnPasteTexture()
{
if(BuilderPlug.Me.CopiedTexture != null)
{
mode.CreateUndo("Paste texture \"" + BuilderPlug.Me.CopiedTexture + "\"");
mode.SetActionResult("Pasted texture \"" + BuilderPlug.Me.CopiedTexture + "\".");
SetTexture(BuilderPlug.Me.CopiedTexture);
//mxd. Update linked effects
SectorData sd = mode.GetSectorDataEx(Sector.Sector);
if(sd != null) sd.Reset(true);
}
}
// Paste texture offsets
public virtual void OnPasteTextureOffsets()
{
mode.CreateUndo("Paste texture offsets");
if(General.Map.UDMF)
{
SetTextureOffsetX(BuilderPlug.Me.CopiedOffsets.X);
SetTextureOffsetY(BuilderPlug.Me.CopiedOffsets.Y);
// Update sidedef part geometry
this.Setup();
}
else
{
Sidedef.OffsetX = BuilderPlug.Me.CopiedOffsets.X;
Sidedef.OffsetY = BuilderPlug.Me.CopiedOffsets.Y;
// Update sidedef geometry
VisualSidedefParts parts = Sector.GetSidedefParts(Sidedef);
parts.SetupAllParts();
}
//mxd. Update linked effects
SectorData sd = mode.GetSectorDataEx(Sector.Sector);
if(sd != null) sd.Reset(true);
mode.SetActionResult("Pasted texture offsets " + BuilderPlug.Me.CopiedOffsets.X + ", " + BuilderPlug.Me.CopiedOffsets.Y + ".");
}
// Copy texture
public virtual void OnCopyTexture()
{
//mxd. When UseLongTextureNames is enabled and the image filename is longer than 8 chars, use full name,
// otherwise use texture name as stored in Sidedef
string texturename = ((General.Map.Options.UseLongTextureNames && Texture != null && Texture.UsedInMap
&& Path.GetFileNameWithoutExtension(Texture.Name).Length > DataManager.CLASIC_IMAGE_NAME_LENGTH)
? Texture.Name : GetTextureName());
BuilderPlug.Me.CopiedTexture = texturename;
if(General.Map.Config.MixTexturesFlats) BuilderPlug.Me.CopiedFlat = texturename;
mode.SetActionResult("Copied texture \"" + texturename + "\".");
}
// Copy texture offsets
public virtual void OnCopyTextureOffsets()
{
//mxd
BuilderPlug.Me.CopiedOffsets = General.Map.UDMF ? GetTextureOffset() : new Point(Sidedef.OffsetX, Sidedef.OffsetY);
mode.SetActionResult("Copied texture offsets " + BuilderPlug.Me.CopiedOffsets.X + ", " + BuilderPlug.Me.CopiedOffsets.Y + ".");
}
// Copy properties
public virtual void OnCopyProperties()
{
BuilderPlug.Me.CopiedLinedefProps = new LinedefProperties(Sidedef.Line); //mxd
BuilderPlug.Me.CopiedSidedefProps = new SidedefProperties(Sidedef);
mode.SetActionResult("Copied linedef and sidedef properties.");
}
// Paste properties
public virtual void OnPasteProperties(bool usecopysettings)
{
if(BuilderPlug.Me.CopiedLinedefProps != null)
{
bool pastesideprops = (BuilderPlug.Me.CopiedSidedefProps != null); //mxd
string pastetarget = (pastesideprops ? "linedef and sidedef" : "linedef"); //mxd
mode.CreateUndo("Paste " + pastetarget + " properties");
mode.SetActionResult("Pasted " + pastetarget + " properties.");
BuilderPlug.Me.CopiedLinedefProps.Apply(Sidedef.Line, usecopysettings, false); //mxd
if(pastesideprops) BuilderPlug.Me.CopiedSidedefProps.Apply(Sidedef, usecopysettings); //mxd. Added "usecopysettings"
// Update sectors on both sides
BaseVisualSector front = (BaseVisualSector)mode.GetVisualSector(Sidedef.Sector);
if(front != null) front.Changed = true;
if(Sidedef.Other != null)
{
BaseVisualSector back = (BaseVisualSector)mode.GetVisualSector(Sidedef.Other.Sector);
if(back != null) back.Changed = true;
}
mode.ShowTargetInfo();
}
}
// Return texture name
public virtual string GetTextureName() { return ""; }
// Select button pressed
public virtual void OnSelectBegin()
{
mode.LockTarget();
dragstartanglexy = General.Map.VisualCamera.AngleXY;
dragstartanglez = General.Map.VisualCamera.AngleZ;
dragorigin = pickintersect;
Point texoffset = GetTextureOffset(); //mxd
startoffsetx = texoffset.X;
startoffsety = texoffset.Y;
prevoffsetx = texoffset.X;
prevoffsety = texoffset.Y;
}
// Select button released
public virtual void OnSelectEnd()
{
mode.UnlockTarget();
// Was dragging?
if(uvdragging)
{
// Dragging stops now
uvdragging = false;
}
else
{
// Add/remove selection
if(this.selected)
{
this.selected = false;
mode.RemoveSelectedObject(this);
}
else
{
this.selected = true;
mode.AddSelectedObject(this);
}
}
}
// Edit button released
public virtual void OnEditEnd()
{
if(General.Interface.IsActiveWindow)
{
List<Linedef> linedefs = mode.GetSelectedLinedefs();
updatelist = new List<BaseVisualSector>(); //mxd
foreach(Linedef l in linedefs)
{
if(l.Front != null && mode.VisualSectorExists(l.Front.Sector))
updatelist.Add((BaseVisualSector)mode.GetVisualSector(l.Front.Sector));
if(l.Back != null && mode.VisualSectorExists(l.Back.Sector))
updatelist.Add((BaseVisualSector)mode.GetVisualSector(l.Back.Sector));
}
//mxd. Always select front side for extrafloors
Linedef sourceline = GetControlLinedef();
Sidedef target = (sourceline != Sidedef.Line && sourceline.Front != null ? sourceline.Front : Sidedef);
General.Interface.OnEditFormValuesChanged += Interface_OnEditFormValuesChanged;
mode.StartRealtimeInterfaceUpdate(SelectionType.Linedefs);
DialogResult result = General.Interface.ShowEditLinedefs(linedefs, target.IsFront, !target.IsFront);
mode.StopRealtimeInterfaceUpdate(SelectionType.Linedefs);
General.Interface.OnEditFormValuesChanged -= Interface_OnEditFormValuesChanged;
updatelist.Clear();
updatelist = null;
//mxd. Effects may need updating...
if(result == DialogResult.OK) mode.RebuildElementData();
}
}
//mxd
private void Interface_OnEditFormValuesChanged(object sender, EventArgs e)
{
foreach(BaseVisualSector vs in updatelist) vs.UpdateSectorGeometry(false);
}
// Mouse moves
public virtual void OnMouseMove(MouseEventArgs e)
{
// Dragging UV?
if(uvdragging)
{
UpdateDragUV();
}
else
{
// Select button pressed?
if(General.Actions.CheckActionActive(General.ThisAssembly, "visualselect"))
{
// Check if tolerance is exceeded to start UV dragging
float deltaxy = General.Map.VisualCamera.AngleXY - dragstartanglexy;
float deltaz = General.Map.VisualCamera.AngleZ - dragstartanglez;
if((Math.Abs(deltaxy) + Math.Abs(deltaz)) > DRAG_ANGLE_TOLERANCE)
{
mode.PreAction(UndoGroup.TextureOffsetChange);
mode.CreateUndo("Change texture offsets");
// Start drag now
uvdragging = true;
mode.Renderer.ShowSelection = false;
mode.Renderer.ShowHighlight = false;
UpdateDragUV();
}
}
}
}
// This is called to update UV dragging
protected virtual void UpdateDragUV()
{
float u_ray;
// Calculate intersection position
Line2D ray = new Line2D(General.Map.VisualCamera.Position, General.Map.VisualCamera.Target);
Sidedef.Line.Line.GetIntersection(ray, out u_ray);
Vector3D intersect = General.Map.VisualCamera.Position + (General.Map.VisualCamera.Target - General.Map.VisualCamera.Position) * u_ray;
// Calculate offsets
Vector3D dragdelta = intersect - dragorigin;
Vector3D dragdeltaxy = dragdelta * deltaxy;
Vector3D dragdeltaz = dragdelta * deltaz;
float offsetx = dragdeltaxy.GetLength();
float offsety = dragdeltaz.GetLength();
if((Math.Sign(dragdeltaxy.x) < 0) || (Math.Sign(dragdeltaxy.y) < 0) || (Math.Sign(dragdeltaxy.z) < 0)) offsetx = -offsetx;
if((Math.Sign(dragdeltaz.x) < 0) || (Math.Sign(dragdeltaz.y) < 0) || (Math.Sign(dragdeltaz.z) < 0)) offsety = -offsety;
// Apply offsets
if(General.Interface.CtrlState && General.Interface.ShiftState)
{
//mxd. Clamp to grid size?
int newoffsetx = startoffsetx - (int)Math.Round(offsetx);
int newoffsety = startoffsety + (int)Math.Round(offsety);
int dx = prevoffsetx - newoffsetx;
int dy = prevoffsety - newoffsety;
if(Math.Abs(dx) >= General.Map.Grid.GridSize)
{
dx = General.Map.Grid.GridSize * Math.Sign(dx);
prevoffsetx = newoffsetx;
}
else
{
dx = 0;
}
if(Math.Abs(dy) >= General.Map.Grid.GridSize)
{
dy = General.Map.Grid.GridSize * Math.Sign(dy);
prevoffsety = newoffsety;
}
else
{
dy = 0;
}
if(dx != 0 || dy != 0) mode.ApplyTextureOffsetChange(dx, dy);
}
else
{
//mxd. Constraint to axis?
int newoffsetx = (General.Interface.CtrlState ? startoffsetx : startoffsetx - (int)Math.Round(offsetx)); //mxd
int newoffsety = (General.Interface.ShiftState ? startoffsety : startoffsety + (int)Math.Round(offsety)); //mxd
mode.ApplyTextureOffsetChange(prevoffsetx - newoffsetx, prevoffsety - newoffsety);
prevoffsetx = newoffsetx;
prevoffsety = newoffsety;
}
mode.ShowTargetInfo();
}
// Sector brightness change
public virtual void OnChangeTargetBrightness(bool up)
{
//mxd. Change UDMF wall light?
if(General.Map.UDMF)
{
int light = Sidedef.Fields.GetValue("light", 0);
bool absolute = Sidedef.Fields.GetValue("lightabsolute", false);
int newlight;
if(up)
newlight = General.Map.Config.BrightnessLevels.GetNextHigher(light, absolute);
else
newlight = General.Map.Config.BrightnessLevels.GetNextLower(light, absolute);
if(newlight == light) return;
// Create undo
mode.CreateUndo("Change wall brightness", UndoGroup.SurfaceBrightnessChange, Sector.Sector.FixedIndex);
Sidedef.Fields.BeforeFieldsChange();
// Apply changes
UniFields.SetInteger(Sidedef.Fields, "light", newlight, (absolute ? int.MinValue : 0));
Tools.UpdateLightFogFlag(Sidedef);
mode.SetActionResult("Changed wall brightness to " + newlight + ".");
// Update this part only
this.Setup();
}
else if(!Sector.Changed)
{
// Change brightness
mode.CreateUndo("Change sector brightness", UndoGroup.SectorBrightnessChange, Sector.Sector.FixedIndex);
if(up)
Sector.Sector.Brightness = General.Map.Config.BrightnessLevels.GetNextHigher(Sector.Sector.Brightness);
else
Sector.Sector.Brightness = General.Map.Config.BrightnessLevels.GetNextLower(Sector.Sector.Brightness);
mode.SetActionResult("Changed sector brightness to " + Sector.Sector.Brightness + ".");
Sector.Sector.UpdateCache();
// Rebuild sector
Sector.UpdateSectorGeometry(false);
// Go for all things in this sector
foreach(Thing t in General.Map.Map.Things)
{
if(t.Sector == Sector.Sector)
{
if(mode.VisualThingExists(t))
{
// Update thing
BaseVisualThing vt = (BaseVisualThing)mode.GetVisualThing(t);
vt.Changed = true;
}
}
}
}
}
// Texture offset change
public virtual void OnChangeTextureOffset(int horizontal, int vertical, bool doSurfaceAngleCorrection)
{
if((General.Map.UndoRedo.NextUndo == null) || (General.Map.UndoRedo.NextUndo.TicketID != undoticket))
undoticket = mode.CreateUndo("Change texture offsets");
//mxd
if(General.Map.UDMF)
{
// Apply UDMF offsets
MoveTextureOffset(new Point(-horizontal, -vertical));
Point p = GetTextureOffset();
mode.SetActionResult("Changed texture offsets to " + p.X + ", " + p.Y + ".");
// Update this part only
this.Setup();
}
else
{
//mxd. Apply classic offsets
Sidedef.OffsetX = (Sidedef.OffsetX - horizontal);
if(Texture != null) Sidedef.OffsetX %= Texture.Width;
Sidedef.OffsetY = (Sidedef.OffsetY - vertical);
if(geometrytype != VisualGeometryType.WALL_MIDDLE && Texture != null) Sidedef.OffsetY %= Texture.Height;
mode.SetActionResult("Changed texture offsets to " + Sidedef.OffsetX + ", " + Sidedef.OffsetY + ".");
// Update all sidedef geometry
VisualSidedefParts parts = Sector.GetSidedefParts(Sidedef);
parts.SetupAllParts();
}
//mxd. Update linked effects
SectorData sd = mode.GetSectorDataEx(Sector.Sector);
if(sd != null) sd.Reset(true);
}
//mxd
public virtual void OnChangeScale(int incrementX, int incrementY)
{
if(!General.Map.UDMF || !Texture.IsImageLoaded) return;
if((General.Map.UndoRedo.NextUndo == null) || (General.Map.UndoRedo.NextUndo.TicketID != undoticket))
undoticket = mode.CreateUndo("Change wall scale");
string keyX;
string keyY;
switch(GeometryType)
{
case VisualGeometryType.WALL_UPPER:
keyX = "scalex_top";
keyY = "scaley_top";
break;
case VisualGeometryType.WALL_MIDDLE:
keyX = "scalex_mid";
keyY = "scaley_mid";
break;
case VisualGeometryType.WALL_LOWER:
keyX = "scalex_bottom";
keyY = "scaley_bottom";
break;
default:
throw new Exception("OnChangeTextureScale(): Got unknown GeometryType: " + GeometryType);
}
float scaleX = Sidedef.Fields.GetValue(keyX, 1.0f);
float scaleY = Sidedef.Fields.GetValue(keyY, 1.0f);
Sidedef.Fields.BeforeFieldsChange();
if(incrementX != 0)
{
float pix = (int)Math.Round(Texture.Width * scaleX) - incrementX;
float newscaleX = (float)Math.Round(pix / Texture.Width, 3);
scaleX = (newscaleX == 0 ? scaleX * -1 : newscaleX);
UniFields.SetFloat(Sidedef.Fields, keyX, scaleX, 1.0f);
}
if(incrementY != 0)
{
float pix = (int)Math.Round(Texture.Height * scaleY) - incrementY;
float newscaleY = (float)Math.Round(pix / Texture.Height, 3);
scaleY = (newscaleY == 0 ? scaleY * -1 : newscaleY);
UniFields.SetFloat(Sidedef.Fields, keyY, scaleY, 1.0f);
}
// Update geometry
Setup();
//mxd. Update linked effects
SectorData sd = mode.GetSectorDataEx(Sector.Sector);
if(sd != null) sd.Reset(true);
mode.SetActionResult("Wall scale changed to " + scaleX.ToString("F03", CultureInfo.InvariantCulture) + ", " + scaleY.ToString("F03", CultureInfo.InvariantCulture) + " (" + (int)Math.Round(Texture.Width / scaleX) + " x " + (int)Math.Round(Texture.Height / scaleY) + ").");
}
#endregion
}
}