mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-23 20:32:34 +00:00
7ab0a86a92
Changed, Sound Propagation mode: all sound zones are now shown when no sector is highlighted. Changed, Sound Environments mode: the mode is now available only in UDMF map format. Changed, Color Picker plugin: the plugin functionality is no longer available in Doom map format. Restored the ability to create superimposed lines by dragging them with "Snap to Geometry" mode disabled. Fixed, Sound Propagation mode: fixed a crash when a single-sided linedef had "Block Sound" flag. Fixed, Find & Replace mode: in some cases "Find Sector/Sidedef/Linedef/Thing flags" search modes failed to find map elements with required flags. Fixed, Edit Selection mode: in some cases incorrect geometry was created after applying multipart sector edit when "Replace with Dragged Geometry" mode was enabled. Fixed a crash caused by eventual GDI font objects overflow.
498 lines
17 KiB
C#
498 lines
17 KiB
C#
|
|
#region ================== Copyright (c) 2014 Boris Iwanski
|
|
|
|
/*
|
|
* Copyright (c) 2014 Boris Iwanski
|
|
* 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.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using CodeImp.DoomBuilder.Editing;
|
|
using CodeImp.DoomBuilder.Geometry;
|
|
using CodeImp.DoomBuilder.Map;
|
|
using CodeImp.DoomBuilder.Plugins;
|
|
using CodeImp.DoomBuilder.Rendering;
|
|
|
|
#endregion
|
|
|
|
namespace CodeImp.DoomBuilder.SoundPropagationMode
|
|
{
|
|
//
|
|
// MANDATORY: The plug!
|
|
// This is an important class to the Doom Builder core. Every plugin must
|
|
// have exactly 1 class that inherits from Plug. When the plugin is loaded,
|
|
// this class is instantiated and used to receive events from the core.
|
|
// Make sure the class is public, because only public classes can be seen
|
|
// by the core.
|
|
//
|
|
public class BuilderPlug : Plug
|
|
{
|
|
#region ================== Constants
|
|
|
|
internal const int SOUND_ENVIROMNEMT_THING_TYPE = 9048; //mxd
|
|
internal const float LINE_LENGTH_SCALER = 0.001f; //mxd
|
|
|
|
#endregion
|
|
|
|
#region ================== Variables
|
|
|
|
// Interface
|
|
private MenusForm menusform;
|
|
|
|
// Colors
|
|
private PixelColor highlightcolor;
|
|
private PixelColor level1color;
|
|
private PixelColor level2color;
|
|
private PixelColor blocksoundcolor;
|
|
private PixelColor nosoundcolor;
|
|
|
|
private List<PixelColor> distinctcolors;
|
|
private List<Bitmap> distincticons; //mxd
|
|
private List<SoundEnvironment> soundenvironments;
|
|
private List<Linedef> blockinglinedefs;
|
|
private FlatVertex[] overlayGeometry;
|
|
private bool soundenvironmentisupdated;
|
|
private bool dataisdirty;
|
|
|
|
#endregion
|
|
|
|
#region ================== Properties
|
|
|
|
// Interface
|
|
public MenusForm MenusForm { get { return menusform; } }
|
|
internal List<Bitmap> DistinctIcons { get { return distincticons; } } //mxd
|
|
internal List<PixelColor> DistinctColors { get { return distinctcolors; } } //mxd
|
|
|
|
// Colors
|
|
public PixelColor HighlightColor { get { return highlightcolor; } set { highlightcolor = value; } }
|
|
public PixelColor Level1Color { get { return level1color; } set { level1color = value; } }
|
|
public PixelColor Level2Color { get { return level2color; } set { level2color = value; } }
|
|
public PixelColor BlockSoundColor { get { return blocksoundcolor; } set { blocksoundcolor = value; } }
|
|
public PixelColor NoSoundColor { get { return nosoundcolor; } set { nosoundcolor = value; } }
|
|
|
|
public List<SoundEnvironment> SoundEnvironments { get { return soundenvironments; } set { soundenvironments = value; } }
|
|
public List<Linedef> BlockingLinedefs { get { return blockinglinedefs; } set { blockinglinedefs = value; } }
|
|
public FlatVertex[] OverlayGeometry { get { return overlayGeometry; } set { overlayGeometry = value; } }
|
|
public bool SoundEnvironmentIsUpdated { get { return soundenvironmentisupdated; } }
|
|
public bool DataIsDirty { get { return dataisdirty; } set { dataisdirty = value; } }
|
|
|
|
#endregion
|
|
|
|
// Static instance. We can't use a real static class, because BuilderPlug must
|
|
// be instantiated by the core, so we keep a static reference. (this technique
|
|
// should be familiar to object-oriented programmers)
|
|
private static BuilderPlug me;
|
|
|
|
// Static property to access the BuilderPlug
|
|
public static BuilderPlug Me { get { return me; } }
|
|
|
|
// This plugin relies on some functionality that wasn't there in older versions
|
|
public override int MinimumRevision { get { return 2201; } }
|
|
|
|
// This event is called when the plugin is initialized
|
|
public override void OnInitialize()
|
|
{
|
|
base.OnInitialize();
|
|
|
|
highlightcolor = PixelColor.FromInt(General.Settings.ReadPluginSetting("highlightcolor", new PixelColor(255, 0, 192, 0).ToInt()));
|
|
level1color = PixelColor.FromInt(General.Settings.ReadPluginSetting("level1color", new PixelColor(255, 0, 255, 0).ToInt()));
|
|
level2color = PixelColor.FromInt(General.Settings.ReadPluginSetting("level2color", new PixelColor(255, 255, 255, 0).ToInt()));
|
|
nosoundcolor = PixelColor.FromInt(General.Settings.ReadPluginSetting("nosoundcolor", new PixelColor(255, 160, 160, 160).ToInt()));
|
|
blocksoundcolor = PixelColor.FromInt(General.Settings.ReadPluginSetting("blocksoundcolor", new PixelColor(255, 255, 0, 0).ToInt()));
|
|
|
|
distinctcolors = new List<PixelColor>
|
|
{
|
|
PixelColor.FromInt(0x84d5a4),
|
|
PixelColor.FromInt(0xc059cb),
|
|
PixelColor.FromInt(0xd0533d),
|
|
PixelColor.FromInt(0x415354),
|
|
PixelColor.FromInt(0xcea953),
|
|
PixelColor.FromInt(0x91d44b),
|
|
PixelColor.FromInt(0xcd5b89),
|
|
PixelColor.FromInt(0xa8b6c0),
|
|
PixelColor.FromInt(0x797ecb),
|
|
PixelColor.FromInt(0x567539),
|
|
PixelColor.FromInt(0x72422f),
|
|
PixelColor.FromInt(0x5d3762),
|
|
PixelColor.FromInt(0xffed6f),
|
|
PixelColor.FromInt(0xccebc5),
|
|
PixelColor.FromInt(0xbc80bd),
|
|
PixelColor.FromInt(0xd9d9d9),
|
|
PixelColor.FromInt(0xfccde5),
|
|
PixelColor.FromInt(0x80b1d3),
|
|
PixelColor.FromInt(0xfdb462),
|
|
PixelColor.FromInt(0xb3de69),
|
|
PixelColor.FromInt(0xfb8072),
|
|
PixelColor.FromInt(0xbebada),
|
|
PixelColor.FromInt(0xffffb3),
|
|
PixelColor.FromInt(0x8dd3c7),
|
|
};
|
|
|
|
//mxd. Create coloured icons
|
|
distincticons = new List<Bitmap>(distinctcolors.Count);
|
|
foreach(PixelColor color in distinctcolors)
|
|
{
|
|
distincticons.Add(MakeTintedImage(Properties.Resources.Status0, color));
|
|
}
|
|
|
|
soundenvironments = new List<SoundEnvironment>();
|
|
blockinglinedefs = new List<Linedef>();
|
|
soundenvironmentisupdated = false;
|
|
dataisdirty = true;
|
|
|
|
// This binds the methods in this class that have the BeginAction
|
|
// and EndAction attributes with their actions. Without this, the
|
|
// attributes are useless. Note that in classes derived from EditMode
|
|
// this is not needed, because they are bound automatically when the
|
|
// editing mode is engaged.
|
|
General.Actions.BindMethods(this);
|
|
|
|
menusform = new MenusForm();
|
|
|
|
// TODO: Add DB2 version check so that old DB2 versions won't crash
|
|
// General.ErrorLogger.Add(ErrorType.Error, "zomg!");
|
|
|
|
// Keep a static reference
|
|
me = this;
|
|
}
|
|
|
|
public override void OnMapOpenBegin()
|
|
{
|
|
base.OnMapOpenBegin();
|
|
ResetData();
|
|
}
|
|
|
|
public override void OnMapNewBegin()
|
|
{
|
|
base.OnMapNewBegin();
|
|
ResetData();
|
|
}
|
|
|
|
public override void OnMapSaveBegin(SavePurpose purpose)
|
|
{
|
|
base.OnMapSaveBegin(purpose);
|
|
soundenvironmentisupdated = false;
|
|
}
|
|
|
|
public override void OnEditEngage(EditMode oldmode, EditMode newmode)
|
|
{
|
|
base.OnEditEngage(oldmode, newmode);
|
|
ResetData();
|
|
}
|
|
|
|
// This is called when the plugin is terminated
|
|
public override void Dispose()
|
|
{
|
|
base.Dispose();
|
|
|
|
//mxd
|
|
foreach(Bitmap b in distincticons) b.Dispose();
|
|
|
|
// This must be called to remove bound methods for actions.
|
|
General.Actions.UnbindMethods(this);
|
|
}
|
|
|
|
// Resets all data. This will trigger both rediscovering sound environments and recalculating
|
|
// sound propagation domains
|
|
private void ResetData()
|
|
{
|
|
dataisdirty = true;
|
|
soundenvironmentisupdated = false;
|
|
soundenvironments = new List<SoundEnvironment>();
|
|
blockinglinedefs = new List<Linedef>();
|
|
}
|
|
|
|
public void UpdateSoundEnvironments(object sender, DoWorkEventArgs e)
|
|
{
|
|
List<Sector> sectorstocheck = new List<Sector>();
|
|
List<Sector> checkedsectors = new List<Sector>();
|
|
List<Sector> allsectors = new List<Sector>();
|
|
BackgroundWorker worker = sender as BackgroundWorker;
|
|
List<FlatVertex> vertsList = new List<FlatVertex>();
|
|
Dictionary<Thing, PixelColor> secolor = new Dictionary<Thing, PixelColor>();
|
|
Dictionary<Thing, int> senumber = new Dictionary<Thing, int>();
|
|
|
|
soundenvironments.Clear();
|
|
blockinglinedefs.Clear();
|
|
SoundEnvironmentMode.SoundEnvironmentPanel.BeginUpdate(); //mxd
|
|
|
|
// Keep track of all the sectors in the map. Sectors that belong to a sound environment
|
|
// will be removed from the list, so in the end only sectors that don't belong to any
|
|
// sound environment will be in this list
|
|
foreach(Sector s in General.Map.Map.Sectors) allsectors.Add(s);
|
|
|
|
List<Thing> soundenvironmenthings = GetSoundEnvironmentThings(General.Map.Map.Sectors.ToList());
|
|
int numthings = soundenvironmenthings.Count;
|
|
|
|
// Assign each thing a color and a number, so each sound environment will always have the same color
|
|
// and id, no matter in what order they are discovered
|
|
for(int i = 0; i < soundenvironmenthings.Count; i++)
|
|
{
|
|
//mxd. Make sure same environments use the same color
|
|
int seid = (soundenvironmenthings[i].Args[0] << 8) + soundenvironmenthings[i].Args[1];
|
|
secolor[soundenvironmenthings[i]] = distinctcolors[seid % distinctcolors.Count];
|
|
senumber.Add(soundenvironmenthings[i], i + 1);
|
|
}
|
|
|
|
while(soundenvironmenthings.Count > 0 && !worker.CancellationPending)
|
|
{
|
|
// Sort things by distance to center of the screen, so that sound environments the user want to look at will (hopefully) be discovered first
|
|
Vector2D center = General.Map.Renderer2D.DisplayToMap(new Vector2D(General.Interface.Display.Width / 2f, General.Interface.Display.Height / 2f));
|
|
soundenvironmenthings = soundenvironmenthings.OrderBy(o => Math.Abs(Vector2D.Distance(center, o.Position))).ToList();
|
|
|
|
Thing thing = soundenvironmenthings[0];
|
|
if(thing.Sector == null) thing.DetermineSector();
|
|
|
|
// Ignore things that are outside the map
|
|
if(thing.Sector == null)
|
|
{
|
|
soundenvironmenthings.Remove(thing);
|
|
continue;
|
|
}
|
|
|
|
SoundEnvironment environment = new SoundEnvironment();
|
|
|
|
// Add initial sector. Additional adjacant sectors will be added later
|
|
// as they are discovered
|
|
sectorstocheck.Add(thing.Sector);
|
|
|
|
while(sectorstocheck.Count > 0)
|
|
{
|
|
Sector sector = sectorstocheck[0];
|
|
|
|
if(!environment.Sectors.Contains(sector)) environment.Sectors.Add(sector);
|
|
if(!checkedsectors.Contains(sector)) checkedsectors.Add(sector);
|
|
|
|
sectorstocheck.Remove(sector);
|
|
allsectors.Remove(sector);
|
|
|
|
// Find adjacant sectors and add them to the list of sectors to check if necessary
|
|
foreach(Sidedef sd in sector.Sidedefs)
|
|
{
|
|
if(LinedefBlocksSoundEnvironment(sd.Line))
|
|
{
|
|
if(!environment.Linedefs.Contains(sd.Line)) environment.Linedefs.Add(sd.Line);
|
|
continue;
|
|
}
|
|
|
|
if(sd.Other == null) continue;
|
|
|
|
Sector oppositesector = (sd.Line.Front.Sector == sector ? sd.Line.Back.Sector : sd.Line.Front.Sector);
|
|
|
|
if(!sectorstocheck.Contains(oppositesector) && !checkedsectors.Contains(oppositesector))
|
|
sectorstocheck.Add(oppositesector);
|
|
}
|
|
}
|
|
|
|
// Get all things that are in the current sound environment...
|
|
environment.Things = GetSoundEnvironmentThings(environment.Sectors);
|
|
|
|
// ... and remove them from the list of sound environment things to check, because we know that
|
|
// they already belong to a sound environment
|
|
foreach(Thing t in environment.Things)
|
|
{
|
|
if(soundenvironmenthings.Contains(t)) soundenvironmenthings.Remove(t);
|
|
}
|
|
|
|
// Set color and id of the sound environment
|
|
environment.Color = secolor[environment.Things[0]];
|
|
environment.ID = senumber[environment.Things[0]];
|
|
|
|
// Create the data for the overlay geometry
|
|
List<FlatVertex> severtslist = new List<FlatVertex>(environment.Sectors.Count * 3); //mxd
|
|
foreach(Sector s in environment.Sectors)
|
|
{
|
|
FlatVertex[] fv = new FlatVertex[s.FlatVertices.Length];
|
|
s.FlatVertices.CopyTo(fv, 0);
|
|
for(int j = 0; j < fv.Length; j++) fv[j].c = environment.Color.WithAlpha(128).ToInt();
|
|
|
|
vertsList.AddRange(fv);
|
|
severtslist.AddRange(fv); //mxd
|
|
|
|
// Get all Linedefs that will block sound environments
|
|
foreach(Sidedef sd in s.Sidedefs)
|
|
{
|
|
if(!LinedefBlocksSoundEnvironment(sd.Line)) continue;
|
|
lock(blockinglinedefs)
|
|
{
|
|
blockinglinedefs.Add(sd.Line);
|
|
}
|
|
}
|
|
}
|
|
|
|
//mxd. Store sector environment verts
|
|
environment.SectorsGeometry = severtslist.ToArray();
|
|
|
|
// Update the overlay geometry with the newly added sectors
|
|
if(overlayGeometry == null)
|
|
{
|
|
overlayGeometry = vertsList.ToArray();
|
|
}
|
|
else
|
|
{
|
|
lock(overlayGeometry)
|
|
{
|
|
overlayGeometry = vertsList.ToArray();
|
|
}
|
|
}
|
|
|
|
environment.Things = environment.Things.OrderBy(o => o.Index).ToList();
|
|
environment.Linedefs = environment.Linedefs.OrderBy(o => o.Index).ToList();
|
|
|
|
//mxd. Find the first non-dormant thing
|
|
Thing activeenv = null;
|
|
foreach(Thing t in environment.Things)
|
|
{
|
|
if(!ThingDormant(t))
|
|
{
|
|
activeenv = t;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//mxd. Update environment name
|
|
if(activeenv != null)
|
|
{
|
|
foreach(KeyValuePair<string, KeyValuePair<int, int>> group in General.Map.Data.Reverbs)
|
|
{
|
|
if(group.Value.Key == activeenv.Args[0] && group.Value.Value == activeenv.Args[1])
|
|
{
|
|
environment.Name = group.Key + " (" + activeenv.Args[0] + " " + activeenv.Args[1] + ")";
|
|
break;
|
|
}
|
|
}
|
|
|
|
//mxd. No suitable name found?..
|
|
if(environment.Name == SoundEnvironment.DEFAULT_NAME)
|
|
{
|
|
environment.Name += " (" + activeenv.Args[0] + " " + activeenv.Args[1] + ")";
|
|
}
|
|
}
|
|
|
|
//mxd. Still no suitable name?..
|
|
if(environment.Name == SoundEnvironment.DEFAULT_NAME) environment.Name += " " + environment.ID;
|
|
|
|
lock(soundenvironments)
|
|
{
|
|
soundenvironments.Add(environment);
|
|
}
|
|
|
|
// Tell the worker that discovering a sound environment is finished. This will update the tree view, and also
|
|
// redraw the interface, so the sectors of this sound environment are colored
|
|
worker.ReportProgress((int)((1.0f - (soundenvironmenthings.Count / (float)numthings)) * 100), environment);
|
|
}
|
|
|
|
// Create overlay geometry for sectors that don't belong to a sound environment
|
|
foreach(Sector s in allsectors)
|
|
{
|
|
FlatVertex[] fv = new FlatVertex[s.FlatVertices.Length];
|
|
s.FlatVertices.CopyTo(fv, 0);
|
|
for(int j = 0; j < fv.Length; j++) fv[j].c = NoSoundColor.WithAlpha(128).ToInt();
|
|
vertsList.AddRange(fv);
|
|
}
|
|
|
|
if(overlayGeometry == null)
|
|
{
|
|
overlayGeometry = vertsList.ToArray();
|
|
}
|
|
else
|
|
{
|
|
lock(overlayGeometry)
|
|
{
|
|
overlayGeometry = vertsList.ToArray();
|
|
}
|
|
}
|
|
|
|
soundenvironmentisupdated = true;
|
|
SoundEnvironmentMode.SoundEnvironmentPanel.EndUpdate(); //mxd
|
|
}
|
|
|
|
private static List<Thing> GetSoundEnvironmentThings(List<Sector> sectors)
|
|
{
|
|
List<Thing> things = new List<Thing>();
|
|
|
|
foreach(Thing thing in General.Map.Map.Things)
|
|
{
|
|
// SoundEnvironment thing, see http://zdoom.org/wiki/Classes:SoundEnvironment
|
|
if(thing.Type != SOUND_ENVIROMNEMT_THING_TYPE) continue;
|
|
if(thing.Sector == null) thing.DetermineSector();
|
|
if(thing.Sector != null && sectors.Contains(thing.Sector)) things.Add(thing);
|
|
}
|
|
|
|
return things;
|
|
}
|
|
|
|
private static bool LinedefBlocksSoundEnvironment(Linedef linedef)
|
|
{
|
|
if(General.Map.UDMF) return linedef.IsFlagSet(SoundEnvironmentMode.ZoneBoundaryFlag); //mxd. Fancier this way :)
|
|
|
|
// In Hexen format the line must have action 121 (Line_SetIdentification) and bit 1 of
|
|
// the second argument set (see http://zdoom.org/wiki/Line_SetIdentification)
|
|
return (linedef.Action == 121 && (linedef.Args[1] & 1) == 1); //mxd. Fancier this way :)
|
|
}
|
|
|
|
//mxd
|
|
internal static bool ThingDormant(Thing thing)
|
|
{
|
|
return thing.IsFlagSet(General.Map.UDMF ? "dormant" : "14");
|
|
}
|
|
|
|
//mxd
|
|
internal static void SetThingDormant(Thing thing, bool dormant)
|
|
{
|
|
thing.SetFlag(General.Map.UDMF ? "dormant" : "14", dormant);
|
|
}
|
|
|
|
//mxd. Based on http://www.getcodesamples.com/src/792A0BB0/6CD40E7B
|
|
private static Bitmap MakeTintedImage(Bitmap source, PixelColor tint)
|
|
{
|
|
BitmapData sourcedata = source.LockBits(new Rectangle(0, 0, source.Width, source.Height),
|
|
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
|
|
|
byte[] pixelBuffer = new byte[sourcedata.Stride * sourcedata.Height];
|
|
Marshal.Copy(sourcedata.Scan0, pixelBuffer, 0, pixelBuffer.Length);
|
|
source.UnlockBits(sourcedata);
|
|
|
|
//Translate tint color to 0.0-1.0 range
|
|
float redtint = tint.r / 256.0f;
|
|
float greentint = tint.g / 256.0f;
|
|
float bluetint = tint.b / 256.0f;
|
|
|
|
for(int k = 0; k + 4 < pixelBuffer.Length; k += 4)
|
|
{
|
|
float blue = 60 + pixelBuffer[k] * bluetint;
|
|
float green = 60 + pixelBuffer[k + 1] * greentint;
|
|
float red = 60 + pixelBuffer[k + 2] * redtint;
|
|
|
|
pixelBuffer[k] = (byte)Math.Min(255, blue);
|
|
pixelBuffer[k + 1] = (byte)Math.Min(255, green);
|
|
pixelBuffer[k + 2] = (byte)Math.Min(255, red);
|
|
}
|
|
|
|
Bitmap result = new Bitmap(source.Width, source.Height);
|
|
BitmapData resultdata = result.LockBits(new Rectangle(0, 0, result.Width, result.Height),
|
|
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
|
|
|
|
Marshal.Copy(pixelBuffer, 0, resultdata.Scan0, pixelBuffer.Length);
|
|
result.UnlockBits(resultdata);
|
|
|
|
return result;
|
|
}
|
|
}
|
|
}
|