UltimateZoneBuilder/Source/Plugins/SoundPropagationMode/SoundNode.cs
biwa 4389870162
Sound Propagation Mode: added sound leak finder (#944)
In Sound Propagation Mode you can set the start (default: Shift+S) and end (default: Shift+E) sectors between which a path the sound can travel will be found and displayed.
2023-08-13 21:19:25 +02:00

148 lines
4.6 KiB
C#

#region ================== Copyright (c) 2023 Boris Iwanski
/*
* This program is free software: you can redistribute it and/or modify
*
* it under the terms of the GNU General Public License as published by
*
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program.If not, see<http://www.gnu.org/licenses/>.
*/
#endregion
using System.Collections.Generic;
using System.Drawing;
using CodeImp.DoomBuilder.Geometry;
using CodeImp.DoomBuilder.Map;
using CodeImp.DoomBuilder.Rendering;
namespace CodeImp.DoomBuilder.SoundPropagationMode
{
internal class SoundNode
{
public Vector2D Position { get; set; }
public List<SoundNode> Neighbors { get; set; }
public SoundNode From { get; set; }
public double G { get; set; }
public double H { get; }
public double F { get; set; } // It's G + H, but computing it on the fly is too expensive
public bool IsBlocking { get; }
public bool IsSkip { get; set; }
public SoundNode(Vector2D position)
{
Position = position;
G = double.MaxValue;
H = double.MaxValue;
IsBlocking = false;
IsSkip = false;
Neighbors = new List<SoundNode>();
}
public SoundNode(Vector2D position, SoundNode destination): this(position)
{
H = Vector2D.Distance(Position, destination.Position);
}
public SoundNode(Linedef linedef, SoundNode destination) : this(linedef.Line.GetCoordinatesAt(0.5), destination)
{
IsBlocking = linedef.IsFlagSet(SoundPropagationMode.BlockSoundFlag);
}
/// <summary>
/// Recomputes the values for the sound node's neighbors
/// </summary>
/// <param name="openset">The open set, the add the neighbor to if necessary</param>
/// <param name="start">The start sound node</param>
public void ProcessNeighbors(List<SoundNode> openset, SoundNode start)
{
bool blockinginpath = HasBlockingInPath(start);
foreach (SoundNode neighbor in Neighbors)
{
// Skip neighbors that are blocking if there's already a blocking sound node in the path
// Also skip neighbors that are set to be skipped
if ((neighbor.IsBlocking && blockinginpath) || neighbor.IsSkip)
continue;
double newg = G + Vector2D.Distance(Position, neighbor.Position);
// Compute new values if the path is better
if (newg < neighbor.G)
{
neighbor.From = this;
neighbor.G = newg;
neighbor.F = neighbor.G + neighbor.H;
if (!openset.Contains(neighbor))
openset.Add(neighbor);
}
}
}
/// <summary>
/// Checks if the path from this sound node to the start sound node has a blocking sound node
/// </summary>
/// <param name="start">The start sound node</param>
/// <returns>true if there is a blocking sound node in the path, false if there isn't</returns>
private bool HasBlockingInPath(SoundNode start)
{
SoundNode current = this;
while(current != start)
{
if (current.IsBlocking)
return true;
current = current.From;
}
return false;
}
/// <summary>
/// Resets the sound node's G and F values, and the sound node that leads here
/// </summary>
public void Reset()
{
From = null;
G = double.MaxValue;
F = double.MaxValue;
}
/// <summary>
/// Renders the path from this node to the beginning. Traces the path from this sound node back to the start sound node
/// </summary>
/// <param name="renderer">The Renderer2D to render with</param>
internal void RenderPath(IRenderer2D renderer)
{
SoundNode current = this;
// If the current node is null we have reached the beginning
while(current != null)
{
// Do not render the start and end sound nodes
if (current != this && current.From != null)
{
RectangleF rectangle = new RectangleF((float)(current.Position.x - 4 / renderer.Scale), (float)(current.Position.y - 4 / renderer.Scale), 8 / renderer.Scale, 8 / renderer.Scale);
renderer.RenderRectangleFilled(rectangle, PixelColor.FromColor(Color.Red), true);
}
if(current.From != null)
renderer.RenderLine(current.Position, current.From.Position, 1.0f, PixelColor.FromColor(Color.Red), true);
// One step back
current = current.From;
}
}
}
}