mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-12-12 21:21:48 +00:00
480 lines
13 KiB
C#
Executable file
480 lines
13 KiB
C#
Executable file
|
|
#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 CodeImp.DoomBuilder.Map;
|
|
using CodeImp.DoomBuilder.Geometry;
|
|
using System.Drawing;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
|
|
#endregion
|
|
|
|
namespace CodeImp.DoomBuilder.VisualModes
|
|
{
|
|
public sealed class VisualBlockMap
|
|
{
|
|
#region ================== Constants
|
|
|
|
public const int BLOCK_SIZE_SHIFT = 7;
|
|
public const int BLOCK_SIZE = 1 << BLOCK_SIZE_SHIFT;
|
|
public const float BLOCK_RADIUS = BLOCK_SIZE * Angle2D.SQRT2;
|
|
|
|
#endregion
|
|
|
|
#region ================== Variables
|
|
|
|
// Blocks
|
|
#if DICTIONARY_BLOCKMAP
|
|
private Dictionary<ulong, VisualBlockEntry> blockmap;
|
|
#else
|
|
private VisualBlockEntry[,] blockmap;
|
|
#endif
|
|
|
|
// State
|
|
private bool isdisposed;
|
|
|
|
#endregion
|
|
|
|
#region ================== Properties
|
|
|
|
public bool IsDisposed { get { return isdisposed; } }
|
|
|
|
#endregion
|
|
|
|
#region ================== Constructor / Disposer
|
|
|
|
// Constructor
|
|
internal VisualBlockMap()
|
|
{
|
|
#if DICTIONARY_BLOCKMAP
|
|
// Initialize
|
|
blockmap = new Dictionary<ulong,VisualBlockEntry>();
|
|
#else
|
|
blockmap = new VisualBlockEntry[(1 << 16) / BLOCK_SIZE, (1 << 16) / BLOCK_SIZE]; // 1 megabyte per blockmap
|
|
#endif
|
|
}
|
|
|
|
// Disposer
|
|
internal void Dispose()
|
|
{
|
|
// Not already disposed?
|
|
if(!isdisposed)
|
|
{
|
|
// Clean up
|
|
blockmap = null;
|
|
|
|
// Done
|
|
isdisposed = true;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Methods
|
|
|
|
// This returns the block coordinates
|
|
public Point GetBlockCoordinates(Vector2D v)
|
|
{
|
|
return new Point((int)v.x >> BLOCK_SIZE_SHIFT,
|
|
(int)v.y >> BLOCK_SIZE_SHIFT);
|
|
}
|
|
|
|
// This returns the block center in world coordinates
|
|
public Vector2D GetBlockCenter(Point p)
|
|
{
|
|
return new Vector2D((p.X << BLOCK_SIZE_SHIFT) + (BLOCK_SIZE >> 1),
|
|
(p.Y << BLOCK_SIZE_SHIFT) + (BLOCK_SIZE >> 1));
|
|
}
|
|
|
|
// This returns the key for a block at the given coordinates
|
|
// TODO: Could we just use the Point struct as key?
|
|
private static ulong GetBlockKey(Point p)
|
|
{
|
|
return unchecked( ((ulong)(uint)p.X << 32) + (uint)p.Y );
|
|
}
|
|
|
|
// This returns the block with the given coordinates
|
|
// Creates the block if it doesn't exist yet
|
|
public VisualBlockEntry GetBlock(Point p)
|
|
{
|
|
#if DICTIONARY_BLOCKMAP
|
|
ulong k = GetBlockKey(p);
|
|
VisualBlockEntry vbe;
|
|
|
|
if (blockmap.TryGetValue(k, out vbe))
|
|
return vbe;
|
|
else
|
|
return (blockmap[k] = new VisualBlockEntry());
|
|
#else
|
|
int blockX = p.X % blockmap.GetLength(0);
|
|
int blockY = p.Y % blockmap.GetLength(1);
|
|
if (blockX < 0) blockX += blockmap.GetLength(0);
|
|
if (blockY < 0) blockY += blockmap.GetLength(1);
|
|
if (blockmap[blockX, blockY] == null)
|
|
blockmap[blockX, blockY] = new VisualBlockEntry();
|
|
return blockmap[blockX, blockY];
|
|
#endif
|
|
}
|
|
|
|
// This clears the blockmap
|
|
public void Clear()
|
|
{
|
|
#if DICTIONARY_BLOCKMAP
|
|
blockmap = new Dictionary<ulong,VisualBlockEntry>();
|
|
#else
|
|
blockmap = new VisualBlockEntry[(1 << 16) / BLOCK_SIZE, (1 << 16) / BLOCK_SIZE]; // ok this a little bit expensive..
|
|
#endif
|
|
}
|
|
|
|
// This returns a range of blocks in a square
|
|
public List<VisualBlockEntry> GetSquareRange(RectangleF rect)
|
|
{
|
|
// Calculate block coordinates
|
|
Point lt = GetBlockCoordinates(new Vector2D(rect.Left, rect.Top));
|
|
Point rb = GetBlockCoordinates(new Vector2D(rect.Right, rect.Bottom));
|
|
|
|
// Go through the range to make a list
|
|
int entriescount = (rb.X - lt.X) * (rb.Y - lt.Y);
|
|
List<VisualBlockEntry> entries = new List<VisualBlockEntry>(entriescount);
|
|
for(int x = lt.X; x <= rb.X; x++)
|
|
{
|
|
for(int y = lt.Y; y <= rb.Y; y++)
|
|
{
|
|
entries.Add(GetBlock(new Point(x, y)));
|
|
}
|
|
}
|
|
|
|
// Return list
|
|
return entries;
|
|
}
|
|
|
|
// This returns a range of blocks in a frustum
|
|
public List<VisualBlockEntry> GetFrustumRange(ProjectedFrustum2D frustum)
|
|
{
|
|
// Make square range from frustum circle
|
|
// This will be the range in which we will test blocks
|
|
Point lb = GetBlockCoordinates(frustum.Center - frustum.Radius);
|
|
Point rt = GetBlockCoordinates(frustum.Center + frustum.Radius);
|
|
|
|
Vector2D maplb = new Vector2D();
|
|
Vector2D maprt = new Vector2D();
|
|
|
|
Vertex firstvertex = General.Map.Map.Vertices.OfType<Vertex>().FirstOrDefault();
|
|
|
|
if (firstvertex != null)
|
|
maplb = maprt = firstvertex.Position;
|
|
|
|
// Get maximum dimensions of the map. First vertices...
|
|
foreach (Vertex v in General.Map.Map.Vertices)
|
|
{
|
|
if (v.Position.x < maplb.x) maplb.x = v.Position.x;
|
|
if (v.Position.y < maplb.y) maplb.y = v.Position.y;
|
|
if (v.Position.x > maprt.x) maprt.x = v.Position.x;
|
|
if (v.Position.y > maprt.y) maprt.y = v.Position.y;
|
|
}
|
|
|
|
// ... then things
|
|
foreach (Thing t in General.Map.Map.Things)
|
|
{
|
|
if (t.Position.x < maplb.x) maplb.x = t.Position.x;
|
|
if (t.Position.y < maplb.y) maplb.y = t.Position.y;
|
|
if (t.Position.x > maprt.x) maprt.x = t.Position.x;
|
|
if (t.Position.y > maprt.y) maprt.y = t.Position.y;
|
|
}
|
|
|
|
Point mlb = GetBlockCoordinates(maplb);
|
|
Point mrt = GetBlockCoordinates(maprt);
|
|
|
|
// Make sure that the checked region does not exceed the dimensions where something is to be displayed
|
|
if (lb.X < mlb.X) lb.X = mlb.X;
|
|
if (lb.Y < mlb.Y) lb.Y = mlb.Y;
|
|
if (rt.X > mrt.X) rt.X = mrt.X;
|
|
if (rt.Y > mrt.Y) rt.Y = mrt.Y;
|
|
|
|
// Constants we need
|
|
float blockfrustumdistance2 = (frustum.Radius * frustum.Radius) + (BLOCK_RADIUS * BLOCK_RADIUS);
|
|
|
|
// Go through the range to make a list
|
|
int entriescount = (rt.X - lb.X) * (rt.Y - lb.Y);
|
|
List<VisualBlockEntry> entries = new List<VisualBlockEntry>(entriescount);
|
|
|
|
for (int x = lb.X; x <= rt.X; x++)
|
|
{
|
|
for(int y = lb.Y; y <= rt.Y; y++)
|
|
{
|
|
// First check if the block circle is intersecting the frustum circle
|
|
Point block = new Point(x, y);
|
|
Vector2D blockcenter = GetBlockCenter(block);
|
|
if(Vector2D.DistanceSq(frustum.Center, blockcenter) < blockfrustumdistance2)
|
|
{
|
|
// Add the block if the block circle is inside the frustum
|
|
if (frustum.IntersectCircle(blockcenter, BLOCK_RADIUS)) entries.Add(GetBlock(block));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return list
|
|
return entries;
|
|
}
|
|
|
|
// This returns all blocks along the given line
|
|
public List<VisualBlockEntry> GetLineBlocks(Vector2D v1, Vector2D v2)
|
|
{
|
|
// Estimate number of blocks we will go through and create list
|
|
int entriescount = (int)(Vector2D.ManhattanDistance(v1, v2) * 2.0f) / BLOCK_SIZE;
|
|
List<VisualBlockEntry> entries = new List<VisualBlockEntry>(entriescount);
|
|
|
|
// Find start and end block
|
|
Point pos = GetBlockCoordinates(v1);
|
|
Point end = GetBlockCoordinates(v2);
|
|
|
|
// Add this block
|
|
entries.Add(GetBlock(pos));
|
|
|
|
// Moving outside the block?
|
|
if(pos != end)
|
|
{
|
|
// Calculate current block edges
|
|
float cl = pos.X * BLOCK_SIZE;
|
|
float cr = (pos.X + 1) * BLOCK_SIZE;
|
|
float ct = pos.Y * BLOCK_SIZE;
|
|
float cb = (pos.Y + 1) * BLOCK_SIZE;
|
|
|
|
// Line directions
|
|
int dirx = Math.Sign(v2.x - v1.x);
|
|
int diry = Math.Sign(v2.y - v1.y);
|
|
|
|
// Calculate offset and delta movement over x
|
|
float posx, deltax;
|
|
if(dirx >= 0)
|
|
{
|
|
posx = (cr - v1.x) / (v2.x - v1.x);
|
|
deltax = BLOCK_SIZE / (v2.x - v1.x);
|
|
}
|
|
else
|
|
{
|
|
// Calculate offset and delta movement over x
|
|
posx = (v1.x - cl) / (v1.x - v2.x);
|
|
deltax = BLOCK_SIZE / (v1.x - v2.x);
|
|
}
|
|
|
|
// Calculate offset and delta movement over y
|
|
float posy, deltay;
|
|
if(diry >= 0)
|
|
{
|
|
posy = (cb - v1.y) / (v2.y - v1.y);
|
|
deltay = BLOCK_SIZE / (v2.y - v1.y);
|
|
}
|
|
else
|
|
{
|
|
posy = (v1.y - ct) / (v1.y - v2.y);
|
|
deltay = BLOCK_SIZE / (v1.y - v2.y);
|
|
}
|
|
|
|
// Continue while not reached the end
|
|
while(pos != end)
|
|
{
|
|
// Check in which direction to move
|
|
if(posx < posy)
|
|
{
|
|
// Move horizontally
|
|
posx += deltax;
|
|
if(pos.X != end.X) pos.X += dirx;
|
|
}
|
|
else
|
|
{
|
|
// Move vertically
|
|
posy += deltay;
|
|
if(pos.Y != end.Y) pos.Y += diry;
|
|
}
|
|
|
|
// Add lines to this block
|
|
entries.Add(GetBlock(pos));
|
|
}
|
|
}
|
|
|
|
// Return list
|
|
return entries;
|
|
}
|
|
|
|
// This puts a thing in the blockmap
|
|
public void AddThingsSet(ICollection<Thing> things)
|
|
{
|
|
foreach(Thing t in things) AddThing(t);
|
|
}
|
|
|
|
// This puts a thing in the blockmap
|
|
public void AddThing(Thing t)
|
|
{
|
|
//mxd
|
|
Point p1 = GetBlockCoordinates(new Vector2D(t.Position.x - t.RenderSize, t.Position.y - t.RenderSize));
|
|
Point p2 = GetBlockCoordinates(new Vector2D(t.Position.x + t.RenderSize, t.Position.y + t.RenderSize));
|
|
for(int x = p1.X; x <= p2.X; x++)
|
|
{
|
|
for(int y = p1.Y; y <= p2.Y; y++)
|
|
{
|
|
VisualBlockEntry block = GetBlock(new Point(x, y));
|
|
block.Things.Add(t);
|
|
}
|
|
}
|
|
}
|
|
|
|
// This puts a secotr in the blockmap
|
|
public void AddSectorsSet(ICollection<Sector> sectors)
|
|
{
|
|
foreach(Sector s in sectors) AddSector(s);
|
|
}
|
|
|
|
// This puts a sector in the blockmap
|
|
public void AddSector(Sector s)
|
|
{
|
|
Point p1 = GetBlockCoordinates(new Vector2D(s.BBox.Left, s.BBox.Top));
|
|
Point p2 = GetBlockCoordinates(new Vector2D(s.BBox.Right, s.BBox.Bottom));
|
|
for(int x = p1.X; x <= p2.X; x++)
|
|
{
|
|
for(int y = p1.Y; y <= p2.Y; y++)
|
|
{
|
|
VisualBlockEntry block = GetBlock(new Point(x, y));
|
|
block.Sectors.Add(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
// This puts a whole set of linedefs in the blocks they cross
|
|
public void AddLinedefsSet(ICollection<Linedef> lines)
|
|
{
|
|
foreach(Linedef l in lines) AddLinedef(l);
|
|
}
|
|
|
|
// This puts a single linedef in all blocks it crosses
|
|
public void AddLinedef(Linedef line)
|
|
{
|
|
// Get coordinates
|
|
Vector2D v1 = line.Start.Position;
|
|
Vector2D v2 = line.End.Position;
|
|
|
|
// Find start and end block
|
|
Point pos = GetBlockCoordinates(v1);
|
|
Point end = GetBlockCoordinates(v2);
|
|
|
|
// Horizontal straight line?
|
|
if(pos.Y == end.Y)
|
|
{
|
|
// Simple loop
|
|
int dirx = Math.Sign(v2.x - v1.x);
|
|
for(int x = pos.X; x != end.X; x += dirx)
|
|
{
|
|
GetBlock(new Point(x, pos.Y)).Lines.Add(line);
|
|
}
|
|
GetBlock(end).Lines.Add(line);
|
|
}
|
|
// Vertical straight line?
|
|
else if(pos.X == end.X)
|
|
{
|
|
// Simple loop
|
|
int diry = Math.Sign(v2.y - v1.y);
|
|
for(int y = pos.Y; y != end.Y; y += diry)
|
|
{
|
|
GetBlock(new Point(pos.X, y)).Lines.Add(line);
|
|
}
|
|
GetBlock(end).Lines.Add(line);
|
|
}
|
|
else
|
|
{
|
|
// Add lines to this block
|
|
GetBlock(pos).Lines.Add(line);
|
|
|
|
// Moving outside the block?
|
|
if(pos != end)
|
|
{
|
|
// Calculate current block edges
|
|
float cl = pos.X * BLOCK_SIZE;
|
|
float cr = (pos.X + 1) * BLOCK_SIZE;
|
|
float ct = pos.Y * BLOCK_SIZE;
|
|
float cb = (pos.Y + 1) * BLOCK_SIZE;
|
|
|
|
// Line directions
|
|
int dirx = Math.Sign(v2.x - v1.x);
|
|
int diry = Math.Sign(v2.y - v1.y);
|
|
|
|
// Calculate offset and delta movement over x
|
|
float posx, deltax;
|
|
if(dirx == 0)
|
|
{
|
|
posx = float.MaxValue;
|
|
deltax = float.MaxValue;
|
|
}
|
|
else if(dirx > 0)
|
|
{
|
|
posx = (cr - v1.x) / (v2.x - v1.x);
|
|
deltax = BLOCK_SIZE / (v2.x - v1.x);
|
|
}
|
|
else
|
|
{
|
|
// Calculate offset and delta movement over x
|
|
posx = (v1.x - cl) / (v1.x - v2.x);
|
|
deltax = BLOCK_SIZE / (v1.x - v2.x);
|
|
}
|
|
|
|
// Calculate offset and delta movement over y
|
|
float posy, deltay;
|
|
if(diry == 0)
|
|
{
|
|
posy = float.MaxValue;
|
|
deltay = float.MaxValue;
|
|
}
|
|
else if(diry > 0)
|
|
{
|
|
posy = (cb - v1.y) / (v2.y - v1.y);
|
|
deltay = BLOCK_SIZE / (v2.y - v1.y);
|
|
}
|
|
else
|
|
{
|
|
posy = (v1.y - ct) / (v1.y - v2.y);
|
|
deltay = BLOCK_SIZE / (v1.y - v2.y);
|
|
}
|
|
|
|
// Continue while not reached the end
|
|
while(pos != end)
|
|
{
|
|
// Check in which direction to move
|
|
if(posx < posy)
|
|
{
|
|
// Move horizontally
|
|
posx += deltax;
|
|
if(pos.X != end.X) pos.X += dirx;
|
|
}
|
|
else
|
|
{
|
|
// Move vertically
|
|
posy += deltay;
|
|
if(pos.Y != end.Y) pos.Y += diry;
|
|
}
|
|
|
|
// Add lines to this block
|
|
GetBlock(pos).Lines.Add(line);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|