UltimateZoneBuilder/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs
Magnus Norddahl fc7093b959 Load images when they accessed
Load previews when they are accessed
2020-01-12 23:10:57 +01:00

1740 lines
55 KiB
C#
Executable file

#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;
private long lastsetuponloadedtexture;
// 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?
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)
{
// don't process glows if fullbright.
//if (v.c == PixelColor.INT_WHITE)
// return v;
// 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 vertexcolor = PixelColor.FromInt(v.c);
PixelColor glowcolor = PixelColor.Add(vertexcolor, data.CeilingGlow.Color);
v.c = InterpolationTools.InterpolateColor(glowcolor, vertexcolor, delta).WithAlpha(255).ToInt();
}
}
// 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 vertexcolor = PixelColor.FromInt(v.c);
PixelColor glowcolor = PixelColor.Add(vertexcolor, data.FloorGlow.Color);
v.c = InterpolationTools.InterpolateColor(vertexcolor, glowcolor, delta).WithAlpha(255).ToInt();
}
}
// [ZZ] process sector top and bottom colors.
// block
{
float cz = data.Ceiling.plane.GetZ(v.x, v.y);
float cgz = data.Floor.plane.GetZ(v.x, v.y);
float delta = 1.0f - (((v.z - cgz) / (cz - cgz)) * 0.9f);
PixelColor vertexcolor = PixelColor.FromInt(v.c);
PixelColor topcolor = PixelColor.Modulate(vertexcolor, data.ColorWallTop);
PixelColor bottomcolor = PixelColor.Modulate(vertexcolor, data.ColorWallBottom);
v.c = InterpolationTools.InterpolateColor(topcolor, bottomcolor, delta).WithAlpha(255).ToInt();
}
return v;
}
// 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;
}
// biwa
protected static float GetNewTexutreOffset(float oldValue, float offset, float textureSize)
{
return GetRoundedTextureOffset(oldValue, offset, 1.0f, textureSize);
}
//mxd
protected static float GetRoundedTextureOffset(float oldValue, float offset, float scale, float textureSize)
{
if(offset == 0f) return oldValue;
float scaledOffset = offset * Math.Abs(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)
{
SelectNeighbours(select, matchtexture, matchheight, true, true);
}
private void SelectNeighbours(bool select, bool matchtexture, bool matchheight, bool clearlinedefs, bool forward)
{
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);
}
// [ZZ] use the marking system.
if (clearlinedefs) General.Map.Map.ClearMarkedLinedefs(false);
Sidedef.Line.Marked = true;
// Select
Vertex v;
if (forward)
{
v = Sidedef.IsFront ? Sidedef.Line.End : Sidedef.Line.Start;
SelectNeighbourLines(v.Linedefs, v, rect, select, matchtexture, matchheight, true);
v = Sidedef.IsFront ? Sidedef.Line.Start : Sidedef.Line.End;
SelectNeighbourLines(v.Linedefs, v, rect, select, matchtexture, matchheight, false);
}
else
{
v = Sidedef.IsFront ? Sidedef.Line.Start : Sidedef.Line.End;
SelectNeighbourLines(v.Linedefs, v, rect, select, matchtexture, matchheight, false);
v = Sidedef.IsFront ? Sidedef.Line.End : Sidedef.Line.Start;
SelectNeighbourLines(v.Linedefs, v, rect, select, matchtexture, matchheight, true);
}
}
//mxd
private void SelectNeighbourLines(IEnumerable<Linedef> lines, Vertex v, Rectangle sourcerect, bool select, bool matchtexture, bool matchheight, bool forward)
{
foreach(Linedef line in lines)
{
// [ZZ] decide which side of the next linedef to iterate.
// do NOT do both
Sidedef next = null;
Sidedef side1 = forward ? line.Front : line.Back;
Sidedef side2 = forward ? line.Back : line.Front;
if (line.Start == v)
next = side1;
else if (line.End == v)
next = side2;
if (next == null || next.Sector == null)
continue;
SelectNeighbourSideParts(next, sourcerect, select, matchtexture, matchheight, forward);
}
}
//mxd
private void SelectNeighbourSideParts(Sidedef side, Rectangle sourcerect, bool select, bool matchtexture, bool matchheight, bool forward)
{
if (side.Line.Marked)
return;
BaseVisualSector s = (BaseVisualSector)mode.GetVisualSector(side.Sector);
if(s != null)
{
VisualSidedefParts parts = s.GetSidedefParts(side);
SelectNeighbourSidePart(parts.lower, sourcerect, select, matchtexture, matchheight, forward);
SelectNeighbourSidePart(parts.middlesingle, sourcerect, select, matchtexture, matchheight, forward);
SelectNeighbourSidePart(parts.middledouble, sourcerect, select, matchtexture, matchheight, forward);
SelectNeighbourSidePart(parts.upper, sourcerect, select, matchtexture, matchheight, forward);
if(parts.middle3d != null)
{
foreach(VisualMiddle3D middle3D in parts.middle3d)
SelectNeighbourSidePart(middle3D, sourcerect, select, matchtexture, matchheight, forward);
}
}
}
//mxd
private void SelectNeighbourSidePart(BaseVisualGeometrySidedef visualside, Rectangle sourcerect, bool select, bool matchtexture, bool matchheight, bool forward)
{
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, false, forward);
}
}
}
//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;
float linelength = (float)Math.Round(Sidedef.Line.Length); // Let's use ZDoom-compatible line length here
float patternwidth = (options.AutoWidth && options.PatternWidth > 0) ? options.PatternWidth : Texture.Width;
float horizontalrepeat = options.HorizontalRepeat;
if (options.FitAcrossSurfaces)
{
if (options.AutoWidth)
{
horizontalrepeat = (float)Math.Round((float)options.GlobalBounds.Width / patternwidth);
if (horizontalrepeat == 0)
horizontalrepeat = 1.0f;
if (options.PatternWidth > 0)
horizontalrepeat /= Texture.Width / patternwidth;
}
scalex = Texture.ScaledWidth / (linelength * (options.GlobalBounds.Width / linelength)) * horizontalrepeat;
offsetx = (float)Math.Round((options.Bounds.X * scalex - Sidedef.OffsetX - options.ControlSideOffsetX), General.Map.FormatInterface.VertexDecimals);
if(Texture.IsImageLoaded) offsetx %= Texture.Width;
}
else
{
if (options.AutoWidth)
{
horizontalrepeat = (float)Math.Round(linelength / patternwidth);
if (horizontalrepeat == 0)
horizontalrepeat = 1.0f;
if (options.PatternWidth > 0)
horizontalrepeat /= Texture.Width / patternwidth;
}
scalex = Texture.ScaledWidth / linelength * 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;
float patternheight = (options.AutoHeight && options.PatternHeight > 0) ? options.PatternHeight : Texture.Height;
float verticalrepeat = options.VerticalRepeat;
if (options.FitAcrossSurfaces)
{
if (options.AutoHeight)
{
verticalrepeat = (float)Math.Round((float)options.GlobalBounds.Height / patternheight);
if (verticalrepeat == 0)
verticalrepeat = 1.0f;
if (options.PatternHeight > 0)
verticalrepeat /= Texture.Height / patternheight;
}
scaley = Texture.ScaledHeight / (options.Bounds.Height * ((float)options.GlobalBounds.Height / options.Bounds.Height)) * 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);
if(Texture.IsImageLoaded) offsety %= Texture.Height;
}
}
else
{
if (options.AutoHeight)
{
verticalrepeat = (float)Math.Round((float)options.Bounds.Height / patternheight);
if (verticalrepeat == 0)
verticalrepeat = 1.0f;
if (options.PatternHeight > 0)
verticalrepeat /= Texture.Height / patternheight;
}
scaley = Texture.ScaledHeight / options.Bounds.Height * verticalrepeat;
// Special cases, special cases...
if(this is VisualLower)
{
offsety = GetLowerOffsetY(scaley);
}
else
{
offsety = Tools.GetSidedefOffsetY(Sidedef, geometrytype, -Sidedef.OffsetY - options.ControlSideOffsetY, scaley, true);
if(Texture.IsImageLoaded) offsety %= 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)
{
float offsety;
if(Sidedef.Line.IsFlagSet(General.Map.Config.LowerUnpeggedFlag))
offsety = (-Sidedef.OffsetY - Sidedef.GetMiddleHeight() - Sidedef.GetHighHeight()) * scaley;
else
offsety = -Sidedef.OffsetY * scaley;
if(Texture.IsImageLoaded) offsety %= Texture.Height;
return offsety;
}
#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(int offsetx, int offsety);
protected abstract Point GetTextureOffset();
public virtual void OnTextureFit(FitTextureOptions options) { } //mxd
public virtual void OnPaintSelectEnd() { } // biwa
// 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 != lastsetuponloadedtexture)
{
if (setuponloadedtexture != 0)
{
ImageData t = General.Map.Data.GetTextureImage(setuponloadedtexture);
if (t != null && t.IsImageLoaded)
{
lastsetuponloadedtexture = setuponloadedtexture;
Setup();
}
}
else
{
lastsetuponloadedtexture = setuponloadedtexture;
}
}
}
// 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;
if (sd.Other != null)
sd.Other.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.LoadImageNow();
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;
if (sd.Other != null)
sd.Other.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(new List<Linedef> { Sidedef.Line }, usecopysettings, false); //mxd
if(pastesideprops) BuilderPlug.Me.CopiedSidedefProps.Apply(new List<Sidedef> { 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 if (mode.PaintSelectPressed) // biwa. Paint selection going on?
{
if (mode.PaintSelectType == this.GetType().BaseType && mode.Highlighted != this) // using BaseType so that middle, upper, lower, etc can be selecting in one go
{
// toggle selected state
if (General.Interface.ShiftState ^ BuilderPlug.Me.AdditivePaintSelect)
{
this.selected = true;
mode.AddSelectedObject(this);
}
else if (General.Interface.CtrlState)
{
this.selected = false;
mode.RemoveSelectedObject(this);
}
else
{
if (this.selected)
mode.RemoveSelectedObject(this);
else
mode.AddSelectedObject(this);
this.selected = !this.selected;
}
}
return;
}
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();
// [ZZ] why the hell was maxed updating only this part? sidedef change is global per sidedef, not only upper/lower/middle part.
// find this sidedef in sector, update all parts.
VisualSidedefParts parts = Sector.GetSidedefParts(Sidedef);
parts.SetupAllParts();
}
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 && General.Map.Config.UseLocalSidedefTextureOffsets)
{
// Apply per-texture offsets
MoveTextureOffset(-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
bool textureloaded = (Texture != null && Texture.IsImageLoaded);
Sidedef.OffsetX = (Sidedef.OffsetX - horizontal);
if(textureloaded) Sidedef.OffsetX %= Texture.Width;
Sidedef.OffsetY = (Sidedef.OffsetY - vertical);
if(geometrytype != VisualGeometryType.WALL_MIDDLE && textureloaded) 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 == null || !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) + ").");
}
// biwa
public virtual void OnPaintSelectBegin()
{
mode.PaintSelectType = this.GetType().BaseType; // using BaseType so that middle, upper, lower, etc can be selecting in one go
// toggle selected state
if (General.Interface.ShiftState ^ BuilderPlug.Me.AdditivePaintSelect)
{
this.selected = true;
mode.AddSelectedObject(this);
}
else if (General.Interface.CtrlState)
{
this.selected = false;
mode.RemoveSelectedObject(this);
}
else
{
if (this.selected)
mode.RemoveSelectedObject(this);
else
mode.AddSelectedObject(this);
this.selected = !this.selected;
}
}
#endregion
}
}