#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.Windows.Forms; using CodeImp.DoomBuilder.Data; using CodeImp.DoomBuilder.Geometry; using CodeImp.DoomBuilder.GZBuilder.Data; using CodeImp.DoomBuilder.GZBuilder.Tools; using CodeImp.DoomBuilder.IO; 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 // Undo/redo private 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; //mxd if(mode.UseSelectionFromClassicMode && sd.Line.Selected) { this.selected = true; mode.AddSelectedObject(this); } } #endregion #region ================== Methods // This sets the renderstyle from linedef information and returns the alpha value or the vertices protected byte SetLinedefRenderstyle(bool solidasmask) { byte alpha; // From TranslucentLine action if(Sidedef.Line.Action == 208) { alpha = (byte)General.Clamp(Sidedef.Line.Args[1], 0, 255); if(Sidedef.Line.Args[2] == 1) this.RenderPass = RenderPass.Additive; else if(alpha < 255) this.RenderPass = RenderPass.Alpha; else if(solidasmask) this.RenderPass = RenderPass.Mask; else this.RenderPass = RenderPass.Solid; } else { // From UDMF field string field = Sidedef.Line.Fields.GetValue("renderstyle", "translucent"); alpha = (byte)(Sidedef.Line.Fields.GetValue("alpha", 1.0f) * 255.0f); if(alpha == 255 && Sidedef.Line.IsFlagSet("transparent")) alpha = 64; //mxd if(field == "add") this.RenderPass = RenderPass.Additive; else if(alpha < 255) 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(); 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.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) { //mxd. Determine color int lightlevel; if(l.disablelighting) //sidedef part is not affected by 3d floor brightness { lightlevel = lightabsolute ? lightvalue : l.brightnessbelow + lightvalue; } else if(l.restrictlighting || (l.type == SectorLevelType.Floor && l.alpha < 255)) //only happens to a sidedef part inside of a non-opaque 3d floor. { lightlevel = l.sector.Brightness; } else // "Regular" 3d floor transfers brightness below it ignoring sidedef's brightness. { 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 = ((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 = ((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 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 private void OnTextureChanged() { //check for 3d floors if(Sidedef.Line.Action == 160) { int sectortag = ((General.Map.UDMF || (Sidedef.Line.Args[1] & 8) != 0) ? Sidedef.Line.Args[0] : Sidedef.Line.Args[0] + (Sidedef.Line.Args[4] << 8)); if(sectortag == 0) return; foreach(Sector sector in General.Map.Map.Sectors) { if(sector.Tags.Contains(sectortag)) { BaseVisualSector vs = (BaseVisualSector)mode.GetVisualSector(sector); vs.UpdateSectorGeometry(true); } } } } //mxd protected void SelectNeighbours(string texture, bool select, bool withSameTexture, bool withSameHeight) { if(Sidedef.Sector == null || (!withSameTexture && !withSameHeight)) 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 List connectedLines = new List(); foreach(Linedef line in Sidedef.Line.Start.Linedefs) { if(line.Index == Sidedef.Line.Index) continue; connectedLines.Add(line); } foreach(Linedef line in Sidedef.Line.End.Linedefs) { if(line.Index == Sidedef.Line.Index) continue; if(!connectedLines.Contains(line)) connectedLines.Add(line); } // Check connected lines foreach(Linedef line in connectedLines) { bool addFrontTop = false; bool addFrontMiddle = false; bool addFrontBottom = false; bool addBackTop = false; bool addBackMiddle = false; bool addBackBottom = false; bool lineHasFrontSector = (line.Front != null && line.Front.Sector != null); bool lineHasBackSector = (line.Back != null && line.Back.Sector != null); bool doublesided = (lineHasFrontSector && lineHasBackSector); List extrasides = new List(); // Gather 3d floor sides if (doublesided) { BaseVisualSector s = mode.GetVisualSector(line.Front.Sector) as BaseVisualSector; if (s != null) { extrasides.AddRange(s.GetSidedefParts(line.Front).middle3d.ToArray()); } s = mode.GetVisualSector(line.Back.Sector) as BaseVisualSector; if(s != null) { extrasides.AddRange(s.GetSidedefParts(line.Back).middle3d.ToArray()); } } // Add regular sides if(withSameTexture) { if(line.Front != null) { addFrontTop = (line.Front.HighTexture == texture && line.Front.HighRequired() && BuilderModesTools.GetSidedefPartSize(line.Front, VisualGeometryType.WALL_UPPER).IntersectsWith(rect)); addFrontMiddle = (line.Front.MiddleTexture == texture && (line.Front.MiddleRequired() || (line.Back != null && texture != "-")) && line.Front.GetMiddleHeight() > 0 && BuilderModesTools.GetSidedefPartSize(line.Front, VisualGeometryType.WALL_MIDDLE).IntersectsWith(rect)); addFrontBottom = (line.Front.LowTexture == texture && line.Front.LowRequired() && BuilderModesTools.GetSidedefPartSize(line.Front, VisualGeometryType.WALL_LOWER).IntersectsWith(rect)); } if(line.Back != null) { addBackTop = (line.Back.HighTexture == texture && line.Back.HighRequired() && BuilderModesTools.GetSidedefPartSize(line.Back, VisualGeometryType.WALL_UPPER).IntersectsWith(rect)); addBackMiddle = (line.Back.MiddleTexture == texture && (line.Back.MiddleRequired() || (line.Front != null && texture != "-")) && line.Back.GetMiddleHeight() > 0 && BuilderModesTools.GetSidedefPartSize(line.Back, VisualGeometryType.WALL_MIDDLE).IntersectsWith(rect)); addBackBottom = (line.Back.LowTexture == texture && line.Back.LowRequired() && BuilderModesTools.GetSidedefPartSize(line.Back, VisualGeometryType.WALL_LOWER).IntersectsWith(rect)); } // Add 3d floor sides List filtered = new List(); foreach(VisualMiddle3D side3d in extrasides) { Sidedef controlside = side3d.GetControlLinedef().Front; if (controlside.MiddleTexture == texture && BuilderModesTools.GetSidedefPartSize(controlside, VisualGeometryType.WALL_MIDDLE).IntersectsWith(rect)) { filtered.Add(side3d); } } extrasides = filtered; } if(withSameHeight && rect.Height > 0) { // Upper parts match? if((!withSameTexture || addFrontTop) && doublesided && line.Front.HighRequired()) { Rectangle r = BuilderModesTools.GetSidedefPartSize(line.Front, VisualGeometryType.WALL_UPPER); addFrontTop = (rect.Height == r.Height && rect.Y == r.Y); } if((!withSameTexture || addBackTop) && doublesided && line.Back.HighRequired()) { Rectangle r = BuilderModesTools.GetSidedefPartSize(line.Back, VisualGeometryType.WALL_UPPER); addBackTop = (rect.Height == r.Height && rect.Y == r.Y); } // Middle parts match? if((!withSameTexture || addFrontMiddle) && lineHasFrontSector && (line.Front.MiddleRequired() || line.Front.LongMiddleTexture != MapSet.EmptyLongName) ) { Rectangle r = BuilderModesTools.GetSidedefPartSize(line.Front, VisualGeometryType.WALL_MIDDLE); addFrontMiddle = (rect.Height == r.Height && rect.Y == r.Y); } if((!withSameTexture || addBackMiddle) && lineHasBackSector && (line.Back.MiddleRequired() || line.Back.LongMiddleTexture != MapSet.EmptyLongName)) { Rectangle r = BuilderModesTools.GetSidedefPartSize(line.Back, VisualGeometryType.WALL_MIDDLE); addBackMiddle = (rect.Height == r.Height && rect.Y == r.Y); } // Lower parts match? if((!withSameTexture || addFrontBottom) && doublesided && line.Front.LowRequired()) { Rectangle r = BuilderModesTools.GetSidedefPartSize(line.Front, VisualGeometryType.WALL_LOWER); addFrontBottom = (rect.Height == r.Height && rect.Y == r.Y); } if((!withSameTexture || addBackBottom) && doublesided && line.Back.LowRequired()) { Rectangle r = BuilderModesTools.GetSidedefPartSize(line.Back, VisualGeometryType.WALL_LOWER); addBackBottom = (rect.Height == r.Height && rect.Y == r.Y); } // 3d floor parts match? List filtered = new List(); foreach(VisualMiddle3D side3d in extrasides) { Sidedef controlside = side3d.GetControlLinedef().Front; Rectangle r = BuilderModesTools.GetSidedefPartSize(controlside, VisualGeometryType.WALL_MIDDLE); if(rect.Height == r.Height && rect.Y == r.Y) { filtered.Add(side3d); } } extrasides = filtered; } // Select front? if(addFrontTop || addFrontMiddle || addFrontBottom) mode.SelectSideParts(line.Front, addFrontTop, addFrontMiddle, addFrontBottom, select, withSameTexture, withSameHeight); // Select back? if(addBackTop || addBackMiddle || addBackBottom) mode.SelectSideParts(line.Back, addBackTop, addBackMiddle, addBackBottom, select, withSameTexture, withSameHeight); // Select 3d floor sides? foreach (VisualMiddle3D side3d in extrasides) { if( (select && !side3d.Selected) || (!select && side3d.Selected) ) side3d.SelectNeighbours(select, withSameTexture, withSameHeight); } } } //mxd public virtual bool IsSelected() { return selected; } //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; } UDMFTools.SetFloat(controlside.Fields, "scalex_" + partname, (float)Math.Round(scalex, General.Map.FormatInterface.VertexDecimals), 1.0f); UDMFTools.SetFloat(Sidedef.Fields, "offsetx_" + partname, offsetx, 0.0f); } else { // Restore initial offsets UDMFTools.SetFloat(controlside.Fields, "scalex_" + partname, options.InitialScaleX, 1.0f); UDMFTools.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; } UDMFTools.SetFloat(controlside.Fields, "scaley_" + partname, (float)Math.Round(scaley, General.Map.FormatInterface.VertexDecimals), 1.0f); UDMFTools.SetFloat(Sidedef.Fields, "offsety_" + partname, (float)Math.Round(offsety, General.Map.FormatInterface.VertexDecimals), 0.0f); } } else { // Restore initial offsets UDMFTools.SetFloat(controlside.Fields, "scaley_" + partname, options.InitialScaleY, 1.0f); UDMFTools.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 SelectNeighbours(bool select, bool withSameTexture, bool withSameHeight) { } //mxd 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) (othersector as BaseVisualSector).Changed = true; } } } // Delete texture public virtual void OnDelete() { // Remove texture mode.CreateUndo("Delete texture"); mode.SetActionResult("Deleted a texture."); SetTexture("-"); // Update Sector.UpdateSectorGeometry(true); } // Processing public virtual void OnProcess(float 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 if(Sidedef.Fields.ContainsKey("light")) Sidedef.Fields.Remove("light"); if(Sidedef.Fields.ContainsKey("lightabsolute")) Sidedef.Fields.Remove("lightabsolute"); // Update sidedef geometry VisualSidedefParts parts = Sector.GetSidedefParts(Sidedef); parts.SetupAllParts(); } // 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(); long oldtexturelong = Lump.MakeLongName(General.Map.Data.GetFullTextureName(oldtexture)); //mxd 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; } // Do the alignment Tools.FloodfillTextures(this.Sidedef, oldtexturelong, 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 = (mode.GetVisualSector(sd.Sector) as BaseVisualSector); 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 = (mode.GetVisualSector(sd.Sector) as BaseVisualSector); 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); OnTextureChanged();//mxd } // 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); OnTextureChanged();//mxd } } // 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); } else { Sidedef.OffsetX = BuilderPlug.Me.CopiedOffsets.X; Sidedef.OffsetY = BuilderPlug.Me.CopiedOffsets.Y; } mode.SetActionResult("Pasted texture offsets " + BuilderPlug.Me.CopiedOffsets.X + ", " + BuilderPlug.Me.CopiedOffsets.Y + "."); // Update sidedef geometry VisualSidedefParts parts = Sector.GetSidedefParts(Sidedef); parts.SetupAllParts(); } // Copy texture public virtual void OnCopyTexture() { if(Texture == null) return; //mxd string texturename = ((!General.Map.Options.UseLongTextureNames && Texture.UsedInMap) ? Texture.ShortName : GetTextureName()); //mxd 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.CopiedSidedefProps = new SidedefProperties(Sidedef); mode.SetActionResult("Copied sidedef properties."); } // Paste properties public virtual void OnPasteProperties() { if(BuilderPlug.Me.CopiedSidedefProps != null) { mode.CreateUndo("Paste sidedef properties"); mode.SetActionResult("Pasted sidedef properties."); BuilderPlug.Me.CopiedSidedefProps.Apply(Sidedef); // 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; startoffsetx = GetTextureOffset().X; startoffsety = GetTextureOffset().Y; prevoffsetx = GetTextureOffset().X; prevoffsety = GetTextureOffset().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)); } General.Interface.OnEditFormValuesChanged += Interface_OnEditFormValuesChanged; mode.StartRealtimeInterfaceUpdate(SelectionType.Linedefs); DialogResult result = General.Interface.ShowEditLinedefs(linedefs, Sidedef.IsFront, !Sidedef.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 UDMFTools.SetInteger(Sidedef.Fields, "light", newlight, (absolute ? int.MinValue : 0)); Tools.UpdateLightFogFlag(Sidedef); mode.SetActionResult("Changed wall brightness to " + newlight + "."); Sector.Sector.UpdateCache(); //rebuild sector Sector.UpdateSectorGeometry(false); } 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 = (mode.GetVisualThing(t) as BaseVisualThing); 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 + "."); } 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 sidedef geometry VisualSidedefParts parts = Sector.GetSidedefParts(Sidedef); parts.SetupAllParts(); } //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: case VisualGeometryType.WALL_MIDDLE_3D: 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); UDMFTools.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); UDMFTools.SetFloat(Sidedef.Fields, keyY, scaleY, 1.0f); } //update geometry Setup(); 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 } }