UltimateZoneBuilder/Source/Core/Data/VoxelImage.cs

315 lines
8.8 KiB
C#
Executable file

#region ================== Namespaces
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using CodeImp.DoomBuilder.Rendering;
using CodeImp.DoomBuilder.Windows;
#endregion
namespace CodeImp.DoomBuilder.Data
{
public sealed class VoxelImage : ImageData, ISpriteImage
{
#region ================== Variables
private int offsetx;
private int offsety;
private readonly string voxelname;
private bool overridepalette;
private int angleoffset;
#endregion
#region ================== Properties
public int OffsetX { get { return offsetx; } }
public int OffsetY { get { return offsety; } }
public string VoxelName { get { return voxelname; } }
public bool OverridePalette { get { return overridepalette; } internal set { overridepalette = value; } }
public int AngleOffset { get { return angleoffset; } internal set { angleoffset = value; } }
#endregion
#region ================== Constructor / Disposer
// Constructor
internal VoxelImage(string name, string voxelname)
{
// Initialize
SetName(name);
this.voxelname = voxelname;
// We have no destructor
GC.SuppressFinalize(this);
}
#endregion
#region ================== Methods
override public void LoadImage(bool notify)
{
// Do the loading
base.LoadImage(false);
// Notify the main thread about the change to redraw display
if (notify) General.MainWindow.SpriteDataLoaded(this.Name);
}
// This loads the image
protected unsafe override LocalLoadResult LocalLoadImage()
{
Bitmap bitmap = null;
string error = null;
int imgoffsetx = 0;
int pivotz = 0;
// Get the lump data stream
string voxellocation = string.Empty; //mxd
Stream lumpdata = General.Map.Data.GetVoxelData(voxelname, ref voxellocation);
if(lumpdata != null)
{
// Copy lump data to memory
lumpdata.Seek(0, SeekOrigin.Begin);
byte[] membytes = new byte[(int)lumpdata.Length];
lumpdata.Read(membytes, 0, (int)lumpdata.Length);
using(MemoryStream mem = new MemoryStream(membytes))
{
mem.Seek(0, SeekOrigin.Begin);
PixelColor[] palette = new PixelColor[256];
// Create front projection image from the KVX
using(BinaryReader reader = new BinaryReader(mem, Encoding.ASCII))
{
reader.ReadInt32(); //numbytes, we don't use that
int xsize = reader.ReadInt32();
int ysize = reader.ReadInt32();
int zsize = reader.ReadInt32();
// Sanity check
if(xsize == 0 || ysize == 0 || zsize == 0)
{
error = "Cannot create sprite image for voxel \"" + Path.Combine(voxellocation, voxelname)
+ "\" for voxel drawing: voxel has invalid size (width: " + xsize + ", height: " + zsize + ", depth: " + ysize;
return new LocalLoadResult(null, error);
}
int pivotx = (int)Math.Round(reader.ReadInt32() / 256f);
int pivoty = (int)Math.Round(reader.ReadInt32() / 256f);
pivotz = (int)Math.Round(reader.ReadInt32() / 256f);
// Read offsets
int[] xoffset = new int[xsize + 1]; // why is it xsize + 1, not xsize?..
short[,] xyoffset = new short[xsize, ysize + 1]; // why is it ysize + 1, not ysize?..
for(int i = 0; i < xoffset.Length; i++)
{
xoffset[i] = reader.ReadInt32();
}
for(int x = 0; x < xsize; x++)
{
for(int y = 0; y < ysize + 1; y++)
{
xyoffset[x, y] = reader.ReadInt16();
}
}
// Read slabs
List<int> offsets = new List<int>(xsize * ysize);
for(int x = 0; x < xsize; x++)
{
for(int y = 0; y < ysize; y++)
{
offsets.Add(xoffset[x] + xyoffset[x, y] + 28); // for some reason offsets are counted from start of xoffset[]...
}
}
int counter = 0;
int slabsend = (int)(reader.BaseStream.Length - 768);
// Read palette
if(!overridepalette)
{
reader.BaseStream.Position = slabsend;
for(int i = 0; i < 256; i++)
{
byte r = (byte)(reader.ReadByte() * 4);
byte g = (byte)(reader.ReadByte() * 4);
byte b = (byte)(reader.ReadByte() * 4);
palette[i] = new PixelColor(255, r, g, b);
}
}
else
{
for(int i = 0; i < 256; i++) palette[i] = General.Map.Data.Palette[i];
}
// Populate projection pixels array
int imgwidth, imgheight;
bool checkalpha = false;
// Convert angleoffsets to the nearest cardinal direction...
angleoffset = General.ClampAngle((angleoffset + 45) / 90 * 90);
switch(angleoffset)
{
case 0:
imgwidth = xsize;
imgheight = zsize;
imgoffsetx = pivotx;
break;
case 90:
imgwidth = ysize;
imgheight = zsize;
imgoffsetx = imgwidth - pivoty;
checkalpha = true;
break;
case 180:
imgwidth = xsize;
imgheight = zsize;
imgoffsetx = imgwidth - pivotx;
checkalpha = true;
break;
case 270:
imgwidth = ysize;
imgheight = zsize;
imgoffsetx = pivoty;
break;
default: throw new InvalidDataException("Invalid AngleOffset");
}
int numpixels = imgwidth * imgheight;
PixelColor[] pixelsarr = new PixelColor[numpixels];
// Read pixel colors
for(int x = 0; x < xsize; x++)
{
for(int y = 0; y < ysize; y++)
{
reader.BaseStream.Position = offsets[counter];
int next = (counter < offsets.Count - 1 ? offsets[counter + 1] : slabsend);
// Read first color from the slab
while(reader.BaseStream.Position < next)
{
int ztop = reader.ReadByte();
int zleng = reader.ReadByte();
if(ztop + zleng > zsize) break;
byte flags = reader.ReadByte();
if(zleng > 0)
{
// Skip slab if no flags are given (otherwise some garbage pixels may be drawn)
if(flags == 0)
{
reader.BaseStream.Position += zleng;
continue;
}
List<int> colorindices = new List<int>(zleng);
for(int i = 0; i < zleng; i++)
{
colorindices.Add(reader.ReadByte());
}
int z = ztop;
int cstart = 0;
while(z < ztop + zleng)
{
// Get pixel position
int pixelpos;
switch(angleoffset)
{
case 0: pixelpos = x + z * xsize; break;
case 90: pixelpos = y + z * ysize; break;
case 180: pixelpos = xsize - x - 1 + z * xsize; break;
case 270: pixelpos = ysize - y - 1 + z * ysize; break;
default: throw new InvalidDataException("Invalid AngleOffset");
}
// Add to projection pixels array
if((checkalpha && pixelsarr[pixelpos].a == 0) || !checkalpha)
pixelsarr[pixelpos] = palette[colorindices[cstart]];
// Increment counters
cstart++;
z++;
}
}
}
counter++;
}
}
// Draw to bitmap
bitmap = new Bitmap(imgwidth, imgheight, PixelFormat.Format32bppArgb);
BitmapData bmpdata = null;
try
{
bmpdata = bitmap.LockBits(new Rectangle(0, 0, imgwidth, imgheight), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
}
catch(Exception e)
{
error = "Cannot lock image for drawing voxel \""
+ Path.Combine(voxellocation, voxelname) + "\". " + e.GetType().Name + ": " + e.Message;
bitmap = null;
}
if(bmpdata != null)
{
// Apply pixels to image
PixelColor* pixels = (PixelColor*)bmpdata.Scan0.ToPointer();
int i = 0;
for(PixelColor* cp = pixels; cp < pixels + numpixels; cp++, i++)
{
if(pixelsarr[i].a == 255)
{
cp->r = pixelsarr[i].r;
cp->g = pixelsarr[i].g;
cp->b = pixelsarr[i].b;
cp->a = 255;
}
}
bitmap.UnlockBits(bmpdata);
}
}
}
lumpdata.Dispose();
}
else
{
// Missing voxel lump!
error = "Missing voxel lump \"" + voxelname + "\". Forgot to include required resources?";
}
return new LocalLoadResult(bitmap, error, () =>
{
scale.x = 1.0f;
scale.y = 1.0f;
offsetx = imgoffsetx;
offsety = pivotz;
});
}
#endregion
}
}