UltimateZoneBuilder/Source/Plugins/VisplaneExplorer/VisplaneExplorerMode.cs
MaxED ea57d45eb3 Added: action argument inputs now support incremental increase/decrease prefixes (+++ and ---).
Probably fixed probable I/O race condition when loading images.
Fixed Visual mode stuttering due to floating point precision degradation when running the editor for several days without restarting (internal timer is now reset when saving the map or creating a new one).
Fixed, Nodes Viewer, cosmetic: Nodes Viewer window position was reset after pressing the "Rebuild Nodes" button.
Added Eternity Game configurations by printz.
Updated ZDoom_ACS.cfg (CheckClass).
Updated ZDoom ACC (CheckClass).
2016-08-29 10:06:16 +00:00

490 lines
15 KiB
C#

#region === Copyright (c) 2010 Pascal van der Heiden ===
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
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,
SupportedMapFormats = new[] { "DoomMapSetIO", "HexenMapSetIO" }, //mxd
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 long nextupdate;
// Are we processing?
private bool processingenabled;
#endregion
#region ================== Properties
#endregion
#region ================== Constructor / Destructor
#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 static Point TileForPoint(float x, float y)
{
return new Point((int)Math.Floor(x / Tile.TILE_SIZE) * Tile.TILE_SIZE, (int)Math.Floor(y / 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.InterfaceForm.ShowHeatmap ? BuilderPlug.Palettes[(int)ViewStats.Heatmap] : 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");
if(!General.Map.ExportToFile(tempfile))
{
//mxd. Abort on export fail
Cursor.Current = Cursors.Default;
General.Interface.DisplayStatus(StatusType.Warning, "Unable to set test environment...");
OnCancel();
return;
}
// 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);
float distancesq = ld.DistanceToSq(pc, true);
if(distancesq > (Tile.TILE_SIZE * Tile.TILE_SIZE))
{
float side = ld.SideOfLine(pc);
if((side > 0.0f) && (ld.Back == null))
continue;
}
Point tp = new Point(x, y);
tiles.Add(tp, new Tile(tp));
}
}
QueuePoints(0);
// Make an image to draw on.
// The BitmapImage for Doom Builder's resources must be Format32bppArgb and NOT using color correction,
// otherwise DB will make a copy of the bitmap when LoadImage() is called! This is normally not a problem,
// but we want to keep drawing to the same bitmap.
int width = General.NextPowerOf2(General.Interface.Display.ClientSize.Width);
int height = General.NextPowerOf2(General.Interface.Display.ClientSize.Height);
canvas = new Bitmap(width, height, PixelFormat.Format32bppArgb);
image = new DynamicBitmapImage(canvas, "_CANVAS_");
image.UseColorCorrection = false;
image.MipMapLevels = 1;
image.LoadImage();
image.CreateTexture();
// Make custom presentation
CustomPresentation p = new CustomPresentation();
p.AddLayer(new PresentLayer(RendererLayer.Overlay, BlendingMode.Mask, 1f, false));
p.AddLayer(new PresentLayer(RendererLayer.Grid, BlendingMode.Mask));
p.AddLayer(new PresentLayer(RendererLayer.Geometry, BlendingMode.Alpha, 1f, true));
renderer.SetPresentation(p);
// Setup processing
nextupdate = Clock.CurrentTime + 100;
General.Interface.EnableProcessing();
processingenabled = true;
RedrawAllTiles();
Cursor.Current = Cursors.Default;
General.Interface.SetCursor(Cursors.Cross);
General.Interface.DisplayReady();
}
// Mode ends
public override void OnDisengage()
{
CleanUp();
base.OnDisengage();
}
// Cancelled
public override void OnCancel()
{
// Cancel base class
base.OnCancel();
// Return to previous mode
General.Editing.ChangeMode(General.Editing.PreviousStableMode.Name);
}
// View position/scale changed!
protected override void OnViewChanged()
{
base.OnViewChanged();
RedrawAllTiles();
// Update the screen sooner
nextupdate = Clock.CurrentTime + 100;
}
// Draw the display
public override void OnRedrawDisplay()
{
base.OnRedrawDisplay();
// Render the overlay
UpdateOverlay();
// Render lines and vertices
if(renderer.StartPlotter(true))
{
renderer.PlotLinedefSet(General.Map.Map.Linedefs);
renderer.PlotVerticesSet(General.Map.Map.Vertices);
renderer.Finish();
}
renderer.Present();
}
// Processing
public override void OnProcess(long deltatime)
{
base.OnProcess(deltatime);
if(Clock.CurrentTime >= nextupdate)
{
// Get the processed points from the VPO manager
List<PointData> points = new List<PointData>();
int pointsleft = BuilderPlug.VPO.DequeueResults(points);
// Queue more points if needed
QueuePoints(pointsleft);
// Apply the points to the tiles
foreach(PointData pd in points)
{
Tile t;
Point tp = TileForPoint(pd.point.x, pd.point.y);
if(tiles.TryGetValue(tp, out t))
t.StorePointData(pd);
}
// Redraw
RedrawAllTiles();
General.Interface.RedrawDisplay();
nextupdate = Clock.CurrentTime + 500;
}
else
{
// Queue more points if needed
QueuePoints(BuilderPlug.VPO.GetRemainingPoints());
}
}
// LMB pressed
protected override void OnSelectBegin()
{
StartMultiSelection();
BuilderPlug.InterfaceForm.HideTooltip();
base.OnSelectBegin();
}
// Multiselecting
protected override void OnUpdateMultiSelection()
{
base.OnUpdateMultiSelection();
UpdateOverlay();
renderer.Present();
}
// Multiselect ends
protected override void OnEndMultiSelection()
{
base.OnEndMultiSelection();
if((selectionrect.Width < 64f) && (selectionrect.Height < 64f))
selectionrect.Inflate(64f, 64f);
base.CenterOnArea(selectionrect, 0.1f);
}
// Mouse moves
public override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if(!selecting)
{
int viewstats = (int)BuilderPlug.InterfaceForm.ViewStats;
// Get the tile data for the current position
Point tp = TileForPoint(mousemappos.x, mousemappos.y);
Tile t;
if(tiles.TryGetValue(tp, out t))
{
int x = (int)Math.Floor(mousemappos.x) - t.Position.X;
int y = (int)Math.Floor(mousemappos.y) - t.Position.Y;
byte b = t.GetPointByte(x, y, viewstats);
if(b != Tile.POINT_VOID_B)
{
// Setup hoverlabel
int value = t.GetPointValue(x, y, viewstats);
Point p = new Point((int)mousepos.x + 5, (int)mousepos.y + 5);
string appendoverflow = (b == Tile.POINT_OVERFLOW_B) ? "+" : "";
BuilderPlug.InterfaceForm.ShowTooltip(value + appendoverflow + " / " + Tile.STATS_LIMITS[viewstats], p);
}
else
{
// Void
BuilderPlug.InterfaceForm.HideTooltip();
}
}
else
{
BuilderPlug.InterfaceForm.HideTooltip();
}
}
}
// Mouse leaves
public override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
BuilderPlug.InterfaceForm.HideTooltip();
}
#endregion
}
}