mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-23 20:32:34 +00:00
1367c37b8d
Visual mode: "Copy Texture", "Copy Texture Offsets" and "Copy Properties" actions now always copy properties from currently highlighted surface (previously they copied properties from the first selected surface when something was selected). Draw Rectangle mode: nothing was drawn in some cases. Drag Things mode: grid was not updated when grid size was changed in this mode.
483 lines
15 KiB
C#
483 lines
15 KiB
C#
|
|
#region ================== Copyright (c) 2007 Pascal vd Heiden
|
|
|
|
/*
|
|
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
|
|
* This program is released under GNU General Public License
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#endregion
|
|
|
|
#region ================== Namespaces
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Drawing;
|
|
using CodeImp.DoomBuilder.Map;
|
|
using CodeImp.DoomBuilder.Geometry;
|
|
using CodeImp.DoomBuilder.Rendering;
|
|
using CodeImp.DoomBuilder.Types;
|
|
using CodeImp.DoomBuilder.VisualModes;
|
|
using CodeImp.DoomBuilder.GZBuilder.Tools;
|
|
using CodeImp.DoomBuilder.Data;
|
|
|
|
#endregion
|
|
|
|
namespace CodeImp.DoomBuilder.BuilderModes
|
|
{
|
|
internal sealed class VisualCeiling : BaseVisualGeometrySector
|
|
{
|
|
#region ================== Constants
|
|
|
|
#endregion
|
|
|
|
#region ================== Variables
|
|
|
|
public bool innerSide; //mxd
|
|
|
|
#endregion
|
|
|
|
#region ================== Properties
|
|
|
|
#endregion
|
|
|
|
#region ================== Constructor / Setup
|
|
|
|
// Constructor
|
|
public VisualCeiling(BaseVisualMode mode, VisualSector vs) : base(mode, vs)
|
|
{
|
|
//mxd
|
|
geoType = VisualGeometryType.CEILING;
|
|
|
|
//mxd
|
|
if(mode.UseSelectionFromClassicMode && vs != null && vs.Sector.Selected && (General.Map.ViewMode == ViewMode.CeilingTextures || General.Map.ViewMode == ViewMode.Normal)) {
|
|
this.selected = true;
|
|
mode.AddSelectedObject(this);
|
|
}
|
|
|
|
// We have no destructor
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
// This builds the geometry. Returns false when no geometry created.
|
|
public override bool Setup(SectorLevel level, Effect3DFloor extrafloor) {
|
|
return Setup(level, extrafloor, innerSide);
|
|
}
|
|
|
|
//mxd
|
|
public bool Setup(SectorLevel level, Effect3DFloor extrafloor, bool innerSide)
|
|
{
|
|
WorldVertex[] verts;
|
|
Sector s = level.sector;
|
|
Vector2D texscale;
|
|
this.innerSide = innerSide; //mxd
|
|
|
|
base.Setup(level, extrafloor);
|
|
|
|
// Fetch ZDoom fields
|
|
float rotate = Angle2D.DegToRad(s.Fields.GetValue("rotationceiling", 0.0f));
|
|
Vector2D offset = new Vector2D(s.Fields.GetValue("xpanningceiling", 0.0f),
|
|
s.Fields.GetValue("ypanningceiling", 0.0f));
|
|
Vector2D scale = new Vector2D(s.Fields.GetValue("xscaleceiling", 1.0f),
|
|
s.Fields.GetValue("yscaleceiling", 1.0f));
|
|
|
|
//Load ceiling texture
|
|
if((s.CeilTexture.Length > 0) && (s.CeilTexture != "-")) {
|
|
base.Texture = General.Map.Data.GetFlatImage(s.LongCeilTexture);
|
|
if(base.Texture == null || base.Texture is UnknownImage) {
|
|
base.Texture = General.Map.Data.UnknownTexture3D;
|
|
setuponloadedtexture = s.LongCeilTexture;
|
|
} else {
|
|
if(!base.Texture.IsImageLoaded) {
|
|
setuponloadedtexture = s.LongCeilTexture;
|
|
}
|
|
}
|
|
} else {
|
|
// Use missing texture
|
|
base.Texture = General.Map.Data.MissingTexture3D;
|
|
setuponloadedtexture = 0;
|
|
}
|
|
|
|
// Determine texture scale
|
|
if(base.Texture.IsImageLoaded)
|
|
texscale = new Vector2D(1.0f / base.Texture.ScaledWidth, 1.0f / base.Texture.ScaledHeight);
|
|
else
|
|
texscale = new Vector2D(1.0f / 64.0f, 1.0f / 64.0f);
|
|
|
|
// Make vertices
|
|
ReadOnlyCollection<Vector2D> triverts = base.Sector.Sector.Triangles.Vertices;
|
|
verts = new WorldVertex[triverts.Count];
|
|
for(int i = 0; i < triverts.Count; i++)
|
|
{
|
|
// Color shading
|
|
PixelColor c = PixelColor.FromInt(level.color);
|
|
verts[i].c = c.WithAlpha((byte)General.Clamp(level.alpha, 0, 255)).ToInt();
|
|
|
|
// Vertex coordinates
|
|
verts[i].x = triverts[i].x;
|
|
verts[i].y = triverts[i].y;
|
|
verts[i].z = level.plane.GetZ(triverts[i]);
|
|
|
|
// Texture coordinates
|
|
Vector2D pos = triverts[i];
|
|
pos = pos.GetRotated(rotate);
|
|
pos.y = -pos.y;
|
|
pos = (pos + offset) * scale * texscale;
|
|
verts[i].u = pos.x;
|
|
verts[i].v = pos.y;
|
|
}
|
|
|
|
// The sector triangulation created clockwise triangles that
|
|
// are right up for the floor. For the ceiling we must flip
|
|
// the triangles upside down.
|
|
if(extrafloor == null || extrafloor.VavoomType || innerSide)
|
|
SwapTriangleVertices(verts);
|
|
|
|
// Determine render pass
|
|
if(extrafloor != null)
|
|
{
|
|
if ((extrafloor.Linedef.Args[2] & (int)Effect3DFloor.Flags.RenderAdditive) != 0) //mxd
|
|
this.RenderPass = RenderPass.Additive;
|
|
else if (level.alpha < 255)
|
|
this.RenderPass = RenderPass.Alpha;
|
|
else
|
|
this.RenderPass = RenderPass.Mask;
|
|
}
|
|
else
|
|
{
|
|
this.RenderPass = RenderPass.Solid;
|
|
}
|
|
|
|
// Apply vertices
|
|
base.SetVertices(verts);
|
|
return (verts.Length > 0);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Methods
|
|
|
|
// Return texture coordinates
|
|
protected override Point GetTextureOffset()
|
|
{
|
|
Point p = new Point();
|
|
p.X = (int)Sector.Sector.Fields.GetValue("xpanningceiling", 0.0f);
|
|
p.Y = (int)Sector.Sector.Fields.GetValue("ypanningceiling", 0.0f);
|
|
return p;
|
|
}
|
|
|
|
// Move texture coordinates
|
|
protected override void MoveTextureOffset(Point xy)
|
|
{
|
|
//mxd
|
|
Sector s = GetControlSector();
|
|
s.Fields.BeforeFieldsChange();
|
|
float nx = (s.Fields.GetValue("xpanningceiling", 0.0f) + xy.X) % (Texture.ScaledWidth / s.Fields.GetValue("xscaleceiling", 1.0f));
|
|
float ny = (s.Fields.GetValue("ypanningceiling", 0.0f) + xy.Y) % (Texture.ScaledHeight / s.Fields.GetValue("yscaleceiling", 1.0f));
|
|
s.Fields["xpanningceiling"] = new UniValue(UniversalType.Float, nx);
|
|
s.Fields["ypanningceiling"] = new UniValue(UniversalType.Float, ny);
|
|
s.UpdateNeeded = true;
|
|
|
|
mode.SetActionResult("Changed ceiling texture offsets to " + nx + ", " + ny + ".");
|
|
}
|
|
|
|
//mxd. Texture scale change
|
|
protected override void ChangeTextureScale(float incrementX, float incrementY) {
|
|
Sector s = GetControlSector();
|
|
float scaleX = s.Fields.GetValue("xscaleceiling", 1.0f);
|
|
float scaleY = s.Fields.GetValue("yscaleceiling", 1.0f);
|
|
|
|
s.Fields.BeforeFieldsChange();
|
|
|
|
if(incrementX != 0) {
|
|
if(scaleX + incrementX == 0)
|
|
scaleX *= -1;
|
|
else
|
|
scaleX += incrementX;
|
|
UDMFTools.SetFloat(s.Fields, "xscaleceiling", scaleX, 1.0f);
|
|
}
|
|
|
|
if(incrementY != 0) {
|
|
if(scaleY + incrementY == 0)
|
|
scaleY *= -1;
|
|
else
|
|
scaleY += incrementY;
|
|
UDMFTools.SetFloat(s.Fields, "yscaleceiling", scaleY, 1.0f);
|
|
}
|
|
|
|
//update geometry
|
|
onTextureChanged();
|
|
|
|
s.UpdateNeeded = true;
|
|
s.UpdateCache();
|
|
if(s.Index != Sector.Sector.Index) {
|
|
Sector.Sector.UpdateNeeded = true;
|
|
Sector.Sector.UpdateCache();
|
|
}
|
|
|
|
mode.SetActionResult("Ceiling scale changed to " + scaleX + ", " + scaleY);
|
|
}
|
|
|
|
//mxd
|
|
public override void OnResetTextureOffset() {
|
|
if(!General.Map.UDMF) return;
|
|
|
|
mode.CreateUndo("Reset texture offsets");
|
|
mode.SetActionResult("Texture offsets reset.");
|
|
Sector.Sector.Fields.BeforeFieldsChange();
|
|
|
|
string[] keys = new string[] { "xpanningceiling", "ypanningceiling", "xscaleceiling", "yscaleceiling", "rotationceiling" };
|
|
|
|
foreach(string key in keys){
|
|
if(Sector.Sector.Fields.ContainsKey(key)) {
|
|
Sector.Sector.Fields.Remove(key);
|
|
Sector.Sector.UpdateNeeded = true;
|
|
}
|
|
}
|
|
|
|
if(Sector.Sector.UpdateNeeded)
|
|
Sector.UpdateSectorGeometry(false);
|
|
}
|
|
|
|
//mxd
|
|
public override void OnResetLocalTextureOffset() {
|
|
OnResetTextureOffset();
|
|
}
|
|
|
|
// Paste texture
|
|
public override void OnPasteTexture()
|
|
{
|
|
if(BuilderPlug.Me.CopiedFlat != null)
|
|
{
|
|
mode.CreateUndo("Paste ceiling '" + BuilderPlug.Me.CopiedFlat + "'");
|
|
mode.SetActionResult("Pasted flat '" + BuilderPlug.Me.CopiedFlat + "' on ceiling.");
|
|
SetTexture(BuilderPlug.Me.CopiedFlat);
|
|
|
|
//mxd. 3D floors may need updating...
|
|
onTextureChanged();
|
|
}
|
|
}
|
|
|
|
// Call to change the height
|
|
public override void OnChangeTargetHeight(int amount)
|
|
{
|
|
// Only do this when not done yet in this call
|
|
// Because we may be able to select the same 3D floor multiple times through multiple sectors
|
|
SectorData sd = mode.GetSectorData(level.sector);
|
|
if(!sd.CeilingChanged)
|
|
{
|
|
sd.CeilingChanged = true;
|
|
base.OnChangeTargetHeight(amount);
|
|
}
|
|
}
|
|
|
|
// This changes the height
|
|
protected override void ChangeHeight(int amount)
|
|
{
|
|
mode.CreateUndo("Change ceiling height", UndoGroup.CeilingHeightChange, level.sector.FixedIndex);
|
|
//mxd. Modify vertex offsets?
|
|
if(General.Map.UDMF && level.sector.Sidedefs.Count == 3 && changeVertexHeight(amount)) {
|
|
mode.SetActionResult("Changed ceiling vertex height by " + amount + ".");
|
|
return;
|
|
}
|
|
|
|
level.sector.CeilHeight += amount;
|
|
mode.SetActionResult("Changed ceiling height to " + level.sector.CeilHeight + ".");
|
|
}
|
|
|
|
//mxd
|
|
private bool changeVertexHeight(int amount) {
|
|
List<Vertex> verts = new List<Vertex>(3);
|
|
|
|
//do this only if all 3 verts have offsets
|
|
foreach(Sidedef side in level.sector.Sidedefs) {
|
|
if(float.IsNaN(side.Line.Start.ZCeiling) || float.IsNaN(side.Line.End.ZCeiling)) return false;
|
|
if(!verts.Contains(side.Line.Start)) verts.Add(side.Line.Start);
|
|
if(!verts.Contains(side.Line.End)) verts.Add(side.Line.End);
|
|
}
|
|
|
|
foreach(Vertex v in verts)
|
|
mode.GetVisualVertex(v, false).OnChangeTargetHeight(amount);
|
|
|
|
return true;
|
|
}
|
|
|
|
//mxd. Sector brightness change
|
|
public override void OnChangeTargetBrightness(bool up) {
|
|
if (level != null && level.sector != Sector.Sector) {
|
|
int index = -1;
|
|
for (int i = 0; i < Sector.ExtraCeilings.Count; i++) {
|
|
if (Sector.ExtraCeilings[i] == this) {
|
|
index = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (index > -1 && index < Sector.ExtraCeilings.Count) {
|
|
((BaseVisualSector)mode.GetVisualSector(Sector.ExtraCeilings[index].level.sector)).Floor.OnChangeTargetBrightness(up);
|
|
} else {
|
|
base.OnChangeTargetBrightness(up);
|
|
}
|
|
} else {
|
|
//if a map is not in UDMF format, or this ceiling is part of 3D-floor...
|
|
if(!General.Map.UDMF || Sector.Sector != level.sector) {
|
|
base.OnChangeTargetBrightness(up);
|
|
return;
|
|
}
|
|
|
|
int light = Sector.Sector.Fields.GetValue("lightceiling", 0);
|
|
bool absolute = Sector.Sector.Fields.GetValue("lightceilingabsolute", false);
|
|
int newLight = 0;
|
|
|
|
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 ceiling brightness", UndoGroup.SurfaceBrightnessChange, Sector.Sector.FixedIndex);
|
|
Sector.Sector.Fields.BeforeFieldsChange();
|
|
|
|
//apply changes
|
|
Sector.Sector.Fields["lightceiling"] = new UniValue(UniversalType.Integer, newLight);
|
|
mode.SetActionResult("Changed ceiling brightness to " + newLight + ".");
|
|
Sector.Sector.UpdateCache();
|
|
|
|
//rebuild sector
|
|
Sector.UpdateSectorGeometry(false);
|
|
}
|
|
}
|
|
|
|
// This performs a fast test in object picking
|
|
public override bool PickFastReject(Vector3D from, Vector3D to, Vector3D dir)
|
|
{
|
|
// Check if our ray starts at the correct side of the plane
|
|
if((innerSide && level.plane.Distance(from) < 0.0f) || (!innerSide && level.plane.Distance(from) > 0.0f)) //mxd
|
|
//if(level.plane.Distance(from) > 0.0f)
|
|
{
|
|
// Calculate the intersection
|
|
if(level.plane.GetIntersection(from, to, ref pickrayu))
|
|
{
|
|
if(pickrayu > 0.0f)
|
|
{
|
|
pickintersect = from + (to - from) * pickrayu;
|
|
|
|
// Intersection point within bbox?
|
|
RectangleF bbox = Sector.Sector.BBox;
|
|
return ((pickintersect.x >= bbox.Left) && (pickintersect.x <= bbox.Right) &&
|
|
(pickintersect.y >= bbox.Top) && (pickintersect.y <= bbox.Bottom));
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// This performs an accurate test for object picking
|
|
public override bool PickAccurate(Vector3D from, Vector3D to, Vector3D dir, ref float u_ray)
|
|
{
|
|
u_ray = pickrayu;
|
|
|
|
// Check on which side of the nearest sidedef we are
|
|
Sidedef sd = MapSet.NearestSidedef(Sector.Sector.Sidedefs, pickintersect);
|
|
float side = sd.Line.SideOfLine(pickintersect);
|
|
return (((side <= 0.0f) && sd.IsFront) || ((side > 0.0f) && !sd.IsFront));
|
|
}
|
|
|
|
// Return texture name
|
|
public override string GetTextureName()
|
|
{
|
|
return level.sector.CeilTexture;
|
|
}
|
|
|
|
// This changes the texture
|
|
protected override void SetTexture(string texturename)
|
|
{
|
|
level.sector.SetCeilTexture(texturename);
|
|
General.Map.Data.UpdateUsedTextures();
|
|
}
|
|
|
|
//mxd
|
|
public override void SelectNeighbours(bool select, bool withSameTexture, bool withSameHeight) {
|
|
if(!withSameTexture && !withSameHeight) return;
|
|
|
|
if(select && !selected) {
|
|
selected = true;
|
|
mode.AddSelectedObject(this);
|
|
} else if(!select && selected) {
|
|
selected = false;
|
|
mode.RemoveSelectedObject(this);
|
|
}
|
|
|
|
List<Sector> neighbours = new List<Sector>();
|
|
|
|
//collect neighbour sectors
|
|
foreach(Sidedef side in level.sector.Sidedefs) {
|
|
if(side.Other != null && side.Other.Sector != level.sector && !neighbours.Contains(side.Other.Sector)) {
|
|
bool add = false;
|
|
|
|
if(withSameTexture && side.Other.Sector.CeilTexture == level.sector.CeilTexture) {
|
|
add = true;
|
|
}
|
|
|
|
if(withSameHeight) {
|
|
add = ((withSameTexture && add) || !withSameTexture) && side.Other.Sector.CeilHeight == level.sector.CeilHeight;
|
|
}
|
|
|
|
if(add) neighbours.Add(side.Other.Sector);
|
|
}
|
|
}
|
|
|
|
//(de)select neighbour sectors
|
|
foreach(Sector s in neighbours) {
|
|
BaseVisualSector vs = mode.GetVisualSector(s) as BaseVisualSector;
|
|
if((select && !vs.Ceiling.Selected) || (!select && vs.Ceiling.Selected))
|
|
vs.Ceiling.SelectNeighbours(select, withSameTexture, withSameHeight);
|
|
}
|
|
}
|
|
|
|
//mxd
|
|
public void AlignTexture(bool alignx, bool aligny) {
|
|
if(!General.Map.UDMF) return;
|
|
|
|
//is is a surface with line slope?
|
|
float slopeAngle = level.plane.Normal.GetAngleZ() - Angle2D.PIHALF;
|
|
|
|
if(slopeAngle == 0) {//it's a horizontal plane
|
|
alignTextureToClosestLine(alignx, aligny);
|
|
} else { //it can be a surface with line slope
|
|
Linedef slopeSource = null;
|
|
bool isFront = false;
|
|
|
|
foreach(Sidedef side in Sector.Sector.Sidedefs) {
|
|
if(side.Line.Action == 181) {
|
|
if(side.Line.Args[1] == 1 && side.Line.Front != null && side.Line.Front == side) {
|
|
slopeSource = side.Line;
|
|
isFront = true;
|
|
break;
|
|
} else if(side.Line.Args[1] == 2 && side.Line.Back != null && side.Line.Back == side) {
|
|
slopeSource = side.Line;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(slopeSource != null && slopeSource.Front != null && slopeSource.Front.Sector != null && slopeSource.Back != null && slopeSource.Back.Sector != null)
|
|
alignTextureToSlopeLine(slopeSource, slopeAngle, isFront, alignx, aligny);
|
|
else
|
|
alignTextureToClosestLine(alignx, aligny);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|