#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 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 CreatePolygonVertices(WallPolygon poly, TexturePlane tp, SectorData sd, int lightvalue, bool lightabsolute) { List polylist = new List(1); polylist.Add(poly); return CreatePolygonVertices(polylist, tp, sd, lightvalue, lightabsolute); }*/ // This creates vertices from a wall polygon and applies lighting protected List CreatePolygonVertices(List poly, TexturePlane tp, SectorData sd, int lightvalue, bool lightabsolute) { List polygons = new List(poly); List verts = new List(); //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 texc = new List(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).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 vc = PixelColor.FromInt(v.c); v.c = InterpolationTools.InterpolateColor(vc, GetGlowColor(data.FloorGlow, vc), delta).WithAlpha(255).ToInt(); } } 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 polygons, List 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 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; float linelength = (float)Math.Round(Sidedef.Line.Length); // Let's use ZDoom-compatible line length here if(options.FitAcrossSurfaces) { scalex = Texture.ScaledWidth / (linelength * (options.GlobalBounds.Width / linelength)) * options.HorizontalRepeat; offsetx = (float)Math.Round((options.Bounds.X * scalex - Sidedef.OffsetX - options.ControlSideOffsetX), General.Map.FormatInterface.VertexDecimals); if(Texture.IsImageLoaded) offsetx %= Texture.Width; } else { scalex = Texture.ScaledWidth / linelength * 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); if(Texture.IsImageLoaded) offsety %= Texture.Height; } } else { scaley = Texture.ScaledHeight / options.Bounds.Height * options.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 // 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 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 texturehashes = new HashSet { 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 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 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 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 { Sidedef.Line }, usecopysettings, false); //mxd if(pastesideprops) BuilderPlug.Me.CopiedSidedefProps.Apply(new List { 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 linedefs = mode.GetSelectedLinedefs(); updatelist = new List(); //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 && 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) + ")."); } #endregion } }