context: #region === Copyright (c) 2010 Pascal van der Heiden ===
  
  using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Drawing;
  using System.Drawing.Drawing2D;
  using System.Globalization;
  using System.IO;
  using System.Runtime.InteropServices;
  using System.Text;
  using System.Windows.Forms;
  using CodeImp.DoomBuilder.Data;
  using CodeImp.DoomBuilder.Editing;
  using System.Drawing.Imaging;
  using CodeImp.DoomBuilder.Geometry;
  using CodeImp.DoomBuilder.Map;
  using CodeImp.DoomBuilder.Rendering;
  using CodeImp.DoomBuilder.Windows;
  
  #endregion
  
  namespace CodeImp.DoomBuilder.Plugins.VisplaneExplorer
  {
  	[EditMode(DisplayName = "Visplane Explorer",
  			  SwitchAction = "visplaneexplorermode",
  			  ButtonImage = "Gauge.png",
  			  ButtonOrder = 300,
  			  ButtonGroup = "002_tools",
  			  Volatile = true,
  			  UseByDefault = true,
  			  AllowCopyPaste = false)]
  	public class VisplaneExplorerMode : ClassicMode
  	{
  		#region ================== Constants
  
  		#endregion
  		
  		#region ================== APIs
  
  		[DllImport("kernel32.dll")]
  		static extern void RtlZeroMemory(IntPtr dst, int length);
  
  		#endregion
  
  		#region ================== Variables
  
  		// The image is the ImageData resource for Doom Builder to work with
  		private DynamicBitmapImage image;
  		
  		// This is the bitmap that we will be drawing on
  		private Bitmap canvas;
  		private ViewStats lastviewstats;
  		
  		// Temporary WAD file written for the vpo.dll library
  		private string tempfile;
  
  		// Rectangle around the map
  		private Rectangle mapbounds;
  
  		// 64x64 tiles in map space. These are discarded when outside view.
  		private Dictionary<Point, Tile> tiles = new Dictionary<Point, Tile>();
  
  		// Time when to do another update
  		private DateTime nextupdate;
  
  		// Are we processing?
  		private bool processingenabled;
  		
  		#endregion
  
  		#region ================== Properties
  
  		#endregion
  
  		#region ================== Constructor / Destructor
  
  		// Constructor
  		public VisplaneExplorerMode()
  		{
  		}
  
  		// Disposer
  		public override void Dispose()
  		{
  			base.Dispose();
  		}
  
  		#endregion
  
  		#region ================== Methods
  
  		// This cleans up anything we used for this mode
  		private void CleanUp()
  		{
  			BuilderPlug.VPO.Stop();
  
  			if(processingenabled)
  			{
  				General.Interface.DisableProcessing();
  				processingenabled = false;
  			}
  			
  			if(!string.IsNullOrEmpty(tempfile))
  			{
  				File.Delete(tempfile);
  				tempfile = null;
  			}
  
  			if(image != null)
  			{
  				image.Dispose();
  				image = null;
  			}
  
  			if(canvas != null)
  			{
  				canvas.Dispose();
  				canvas = null;
  			}
  
  			tiles.Clear();
  			BuilderPlug.InterfaceForm.HideTooltip();
  			BuilderPlug.InterfaceForm.RemoveFromInterface();
  		}
  
  		// This returns the tile position for the given map coordinate
  		private Point TileForPoint(float x, float y)
  		{
  			return new Point((int)Math.Floor(x / (float)Tile.TILE_SIZE) * Tile.TILE_SIZE, (int)Math.Floor(y / (float)Tile.TILE_SIZE) * Tile.TILE_SIZE);
  		}
  
  		// This draws all tiles on the image
  		// THIS MUST BE FAST! TOP PERFORMANCE REQUIRED!
  		private unsafe void RedrawAllTiles()
  		{
  			if(canvas == null) return;
  
  			// Determine viewport rectangle in map space
  			Vector2D mapleftbot = Renderer.DisplayToMap(new Vector2D(0f, 0f));
  			Vector2D maprighttop = Renderer.DisplayToMap(new Vector2D(General.Interface.Display.ClientSize.Width, General.Interface.Display.ClientSize.Height));
  			Rectangle mapviewrect = new Rectangle((int)mapleftbot.x - Tile.TILE_SIZE, (int)maprighttop.y - Tile.TILE_SIZE, (int)maprighttop.x - (int)mapleftbot.x + Tile.TILE_SIZE, (int)mapleftbot.y - (int)maprighttop.y + Tile.TILE_SIZE);
  
  			int viewstats = (int)BuilderPlug.InterfaceForm.ViewStats;
  			Palette pal = BuilderPlug.Palettes[viewstats];
  			
  			Size canvassize = canvas.Size;
  			BitmapData bd = canvas.LockBits(new Rectangle(0, 0, canvassize.Width, canvassize.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
  			RtlZeroMemory(bd.Scan0, bd.Width * bd.Height * 4);
  			int* p = (int*)bd.Scan0.ToPointer();
  
  			foreach(KeyValuePair<Point, Tile> t in tiles)
  			{
  				if(!mapviewrect.Contains(t.Key)) continue;
  				
  				// Map this tile to screen space
  				Vector2D lb = Renderer.MapToDisplay(new Vector2D(t.Value.Position.X, t.Value.Position.Y));
  				Vector2D rt = Renderer.MapToDisplay(new Vector2D(t.Value.Position.X + Tile.TILE_SIZE, t.Value.Position.Y + Tile.TILE_SIZE));
  
  				// Make sure the coordinates are aligned with canvas pixels
  				float x1 = (float)Math.Round(lb.x);
  				float x2 = (float)Math.Round(rt.x);
  				float y1 = (float)Math.Round(rt.y);
  				float y2 = (float)Math.Round(lb.y);
  
  				// Determine width and height of the screen space area for this tile
  				float w = x2 - x1;
  				float h = y2 - y1;
  				float winv = 1f / w;
  				float hinv = 1f / h;
  
  				// Loop ranges. These are relative to the left-top of the tile.
  				float sx = 0f;
  				float sy = 0f;
  				float ex = w;
  				float ey = h;
  				int screenx = (int)x1;
  				int screenystart = (int)y1;
  				
  				// Clipping the loop ranges against canvas boundary.
  				if(x1 < 0f) { sx = -x1; screenx = 0; }
  				if(y1 < 0f) { sy = -y1; screenystart = 0; }
  				if(x2 > bd.Width) ex = w - (x2 - bd.Width);
  				if(y2 > bd.Height) ey = h - (y2 - bd.Height);
  				
  				// Draw all pixels within this tile
  				for(float x = sx; x < ex; x++, screenx++)
  				{
  					int screeny = screenystart;
  					for(float y = sy; y < ey; y++, screeny++)
  					{
  						// Calculate the relative offset in map coordinates for this pixel
  						float ux = x * winv * Tile.TILE_SIZE;
  						float uy = y * hinv * Tile.TILE_SIZE;
  
  						// Get the data and apply the color
  						byte value = t.Value.GetPointByte((int)ux, Tile.TILE_SIZE - 1 - (int)uy, viewstats);
  						p[screeny * bd.Width + screenx] = pal.Colors[value];
  					}
  				}
  			}
  			
  			canvas.UnlockBits(bd);
  			image.UpdateTexture();
  		}
  
  		// This queues points for all current tiles
  		private void QueuePoints(int pointsleft)
  		{
  			// Determine viewport rectangle in map space
  			Vector2D mapleftbot = Renderer.DisplayToMap(new Vector2D(0f, 0f));
  			Vector2D maprighttop = Renderer.DisplayToMap(new Vector2D(General.Interface.Display.ClientSize.Width, General.Interface.Display.ClientSize.Height));
  			Rectangle mapviewrect = new Rectangle((int)mapleftbot.x - Tile.TILE_SIZE, (int)maprighttop.y - Tile.TILE_SIZE, (int)maprighttop.x - (int)mapleftbot.x + Tile.TILE_SIZE, (int)mapleftbot.y - (int)maprighttop.y + Tile.TILE_SIZE);
  			
  			while(pointsleft < (VPOManager.POINTS_PER_ITERATION * BuilderPlug.VPO.NumThreads * 5))
  			{
  				// Collect points from the tiles in the current view
  				List<TilePoint> newpoints = new List<TilePoint>(tiles.Count);
  				foreach(KeyValuePair<Point, Tile> t in tiles)
  					if((!t.Value.IsComplete) && (mapviewrect.Contains(t.Key))) newpoints.Add(t.Value.GetNextPoint());
  				
  				// If the current view is complete, try getting points from all tiles
  				if(newpoints.Count == 0)
  				{
  					foreach(KeyValuePair<Point, Tile> t in tiles)
  						if(!t.Value.IsComplete) newpoints.Add(t.Value.GetNextPoint());
  				}
  				
  				if(newpoints.Count == 0) break;
  				pointsleft = BuilderPlug.VPO.EnqueuePoints(newpoints);
  			}
  		}
  
  		// This updates the overlay
  		private void UpdateOverlay()
  		{
  			// We must redraw the tiles to the canvas when the stats to view has changed
  			if(lastviewstats != BuilderPlug.InterfaceForm.ViewStats)
  			{
  				RedrawAllTiles();
  				lastviewstats = BuilderPlug.InterfaceForm.ViewStats;
  			}
  			
  			// Render the overlay
  			if(renderer.StartOverlay(true))
  			{
  				// Render the canvas to screen
  				RectangleF r = new RectangleF(0, 0, canvas.Width, canvas.Height);
  				renderer.RenderRectangleFilled(r, PixelColor.FromColor(Color.White), false, image);
  
  				// Render any selection
  				if(selecting) RenderMultiSelection();
  
  				// Finish our rendering to this layer.
  				renderer.Finish();
  			}
  		}
  
  		#endregion
  
  		#region ================== Events
  
  		// Mode starts
  		public override void OnEngage()
  		{
  			Cursor.Current = Cursors.WaitCursor;
  			base.OnEngage();
  			General.Interface.DisplayStatus(StatusType.Busy, "Setting up test environment...");
  			
  			CleanUp();
  
  			BuilderPlug.InterfaceForm.AddToInterface();
  			lastviewstats = BuilderPlug.InterfaceForm.ViewStats;
  			
  			// Export the current map to a temporary WAD file
  			tempfile = BuilderPlug.MakeTempFilename(".wad");
  			General.Map.ExportToFile(tempfile);
  			
  			// Load the map in VPO_DLL
  			BuilderPlug.VPO.Start(tempfile, General.Map.Options.LevelName);
  
  			// Determine map boundary
  			mapbounds = Rectangle.Round(MapSet.CreateArea(General.Map.Map.Vertices));
  
  			// Create tiles for all points inside the map
  			Point lt = TileForPoint(mapbounds.Left - Tile.TILE_SIZE, mapbounds.Top - Tile.TILE_SIZE);
  			Point rb = TileForPoint(mapbounds.Right + Tile.TILE_SIZE, mapbounds.Bottom + Tile.TILE_SIZE);
  			Rectangle tilesrect = new Rectangle(lt.X, lt.Y, rb.X - lt.X, rb.Y - lt.Y);
  			NearestLineBlockmap blockmap = new NearestLineBlockmap(tilesrect);
  			for(int x = tilesrect.X; x <= tilesrect.Right; x += Tile.TILE_SIZE)
  			{
  				for(int y = tilesrect.Y; y <= tilesrect.Bottom; y += Tile.TILE_SIZE)
  				{
  					// If the tile is obviously outside the map, don't create it
  					Vector2D pc = new Vector2D(x + (Tile.TILE_SIZE >> 1), y + (Tile.TILE_SIZE >> 1));
  					Linedef ld = MapSet.NearestLinedef(blockmap.GetBlockAt(pc).Lines, pc);
  
gt: 					float distancesq = ld.DistanceToSq(pc, true);