mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-27 22:22:32 +00:00
dbcc57b7a6
Fixed, Script Editor: in some cases clicking on an error in the errors list didn't navigate to the error location. Fixed, Script Editor: in some cases incorrect error line number was shown. Fixed, Text lump parsers: fixed a crash when trying to get a filename from a quoted string with missing closing quote. Fixed, Text lump parsers: in several cases parsing errors were ignored by overlaying data structures. Fixed: in some cases Thing Filter thing flags were cleared when switching game configurations in the "Game Configurations" window. Changed, PK3 reader: loading of files with invalid path chars is now skipped instead of skipping loading of the whole resource. Also more helpful warning message is now displayed. Updated SharpCompress library to v.0.11.2.0.
988 lines
No EOL
30 KiB
C#
988 lines
No EOL
30 KiB
C#
#region ================== Namespaces
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
using System.Text;
|
|
using System.Collections.Generic;
|
|
using CodeImp.DoomBuilder.IO;
|
|
using CodeImp.DoomBuilder.Data;
|
|
using CodeImp.DoomBuilder.Rendering;
|
|
using CodeImp.DoomBuilder.GZBuilder.Data;
|
|
using SlimDX;
|
|
using SlimDX.Direct3D9;
|
|
using CodeImp.DoomBuilder.Geometry;
|
|
|
|
#endregion
|
|
|
|
//mxd. Original version taken from here: http://colladadotnet.codeplex.com/SourceControl/changeset/view/40680
|
|
namespace CodeImp.DoomBuilder.GZBuilder.MD3
|
|
{
|
|
internal static class ModelReader
|
|
{
|
|
#region ================== Variables
|
|
|
|
private class MD3LoadResult
|
|
{
|
|
public List<string> Skins;
|
|
public List<Mesh> Meshes;
|
|
public string Errors;
|
|
|
|
public MD3LoadResult()
|
|
{
|
|
Skins = new List<string>();
|
|
Meshes = new List<Mesh>();
|
|
}
|
|
}
|
|
|
|
private static readonly VertexElement[] vertexElements = new[]
|
|
{
|
|
new VertexElement(0, 0, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Position, 0),
|
|
new VertexElement(0, 12, DeclarationType.Color, DeclarationMethod.Default, DeclarationUsage.Color, 0),
|
|
new VertexElement(0, 16, DeclarationType.Float2, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 0),
|
|
new VertexElement(0, 24, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Normal, 0),
|
|
VertexElement.VertexDeclarationEnd
|
|
};
|
|
|
|
#endregion
|
|
|
|
#region ================== Load
|
|
|
|
public static void Load(ModelData mde, List<DataReader> containers, Device device)
|
|
{
|
|
if(mde.IsVoxel) LoadKVX(mde, containers, device);
|
|
else LoadModel(mde, containers, device);
|
|
}
|
|
|
|
private static void LoadKVX(ModelData mde, List<DataReader> containers, Device device)
|
|
{
|
|
mde.Model = new GZModel();
|
|
|
|
for(int i = 0; i < mde.ModelNames.Count; i++)
|
|
{
|
|
//find the model
|
|
Stream ms;
|
|
|
|
foreach(DataReader dr in containers)
|
|
{
|
|
ms = dr.GetVoxelData(mde.ModelNames[i]);
|
|
if(ms == null) continue;
|
|
|
|
//load kvx
|
|
ReadKVX(mde, ms, device);
|
|
|
|
//done
|
|
ms.Close();
|
|
ms.Dispose();
|
|
break;
|
|
}
|
|
}
|
|
|
|
//clear unneeded data
|
|
mde.TextureNames = null;
|
|
mde.ModelNames = null;
|
|
|
|
if(mde.Model.Meshes == null || mde.Model.Meshes.Count == 0)
|
|
{
|
|
mde.Model = null;
|
|
}
|
|
}
|
|
|
|
private static void LoadModel(ModelData mde, List<DataReader> containers, Device device)
|
|
{
|
|
mde.Model = new GZModel();
|
|
BoundingBoxSizes bbs = new BoundingBoxSizes();
|
|
MD3LoadResult result = new MD3LoadResult();
|
|
|
|
//load models and textures
|
|
for(int i = 0; i < mde.ModelNames.Count; i++)
|
|
{
|
|
//need to use model skins?
|
|
bool useSkins = string.IsNullOrEmpty(mde.TextureNames[i]);
|
|
|
|
//load mesh
|
|
MemoryStream ms = LoadFile(containers, mde.ModelNames[i], true);
|
|
if(ms == null)
|
|
{
|
|
General.ErrorLogger.Add(ErrorType.Error, "Error while loading '" + mde.ModelNames[i] + "': unable to find file.");
|
|
continue;
|
|
}
|
|
|
|
string ext = Path.GetExtension(mde.ModelNames[i]);
|
|
switch(ext)
|
|
{
|
|
case ".md3":
|
|
if(!string.IsNullOrEmpty(mde.FrameNames[i]))
|
|
{
|
|
General.ErrorLogger.Add(ErrorType.Error, "Error while loading '" + mde.ModelNames[i] + "': frame names are not supported for MD3 models!");
|
|
continue;
|
|
}
|
|
result = ReadMD3Model(ref bbs, useSkins, ms, device, mde.FrameIndices[i]);
|
|
break;
|
|
case ".md2":
|
|
result = ReadMD2Model(ref bbs, ms, device, mde.FrameIndices[i], mde.FrameNames[i]);
|
|
break;
|
|
default:
|
|
result.Errors = "model format is not supported";
|
|
break;
|
|
}
|
|
|
|
ms.Close();
|
|
ms.Dispose();
|
|
|
|
//got errors?
|
|
if(!String.IsNullOrEmpty(result.Errors))
|
|
{
|
|
General.ErrorLogger.Add(ErrorType.Error, "Error while loading '" + mde.ModelNames[i] + "': " + result.Errors);
|
|
}
|
|
else
|
|
{
|
|
//add loaded data to ModeldefEntry
|
|
mde.Model.Meshes.AddRange(result.Meshes);
|
|
|
|
//prepare UnknownTexture3D... just in case :)
|
|
if(General.Map.Data.UnknownTexture3D.Texture == null || General.Map.Data.UnknownTexture3D.Texture.Disposed)
|
|
General.Map.Data.UnknownTexture3D.CreateTexture();
|
|
|
|
//load texture
|
|
List<string> errors = new List<string>();
|
|
|
|
// Texture not defined in MODELDEF?
|
|
if(useSkins)
|
|
{
|
|
//try to use model's own skins
|
|
for(int m = 0; m < result.Meshes.Count; m++)
|
|
{
|
|
if(string.IsNullOrEmpty(result.Skins[m]))
|
|
{
|
|
mde.Model.Textures.Add(General.Map.Data.UnknownTexture3D.Texture);
|
|
errors.Add("texture not found in MODELDEF or model skin.");
|
|
continue;
|
|
}
|
|
|
|
string path = result.Skins[m].Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
|
ext = Path.GetExtension(path);
|
|
|
|
if(Array.IndexOf(ModelData.SUPPORTED_TEXTURE_EXTENSIONS, ext) == -1)
|
|
{
|
|
mde.Model.Textures.Add(General.Map.Data.UnknownTexture3D.Texture);
|
|
errors.Add("image format '" + ext + "' is not supported!");
|
|
continue;
|
|
}
|
|
|
|
//relative path?
|
|
if(path.IndexOf(Path.DirectorySeparatorChar) == -1)
|
|
path = Path.Combine(Path.GetDirectoryName(mde.ModelNames[useSkins ? i : m]), path);
|
|
|
|
Texture t = LoadTexture(containers, path, device);
|
|
|
|
if(t == null)
|
|
{
|
|
mde.Model.Textures.Add(General.Map.Data.UnknownTexture3D.Texture);
|
|
errors.Add("unable to load skin '" + result.Skins[m] + "'");
|
|
continue;
|
|
}
|
|
|
|
mde.Model.Textures.Add(t);
|
|
}
|
|
}
|
|
//Try to use texture loaded from MODELDEFS
|
|
else
|
|
{
|
|
Texture t = LoadTexture(containers, mde.TextureNames[i], device);
|
|
if(t == null)
|
|
{
|
|
mde.Model.Textures.Add(General.Map.Data.UnknownTexture3D.Texture);
|
|
errors.Add("unable to load texture '" + mde.TextureNames[i] + "'");
|
|
}
|
|
else
|
|
{
|
|
mde.Model.Textures.Add(t);
|
|
}
|
|
}
|
|
|
|
//report errors
|
|
if(errors.Count > 0)
|
|
{
|
|
foreach(string e in errors)
|
|
General.ErrorLogger.Add(ErrorType.Error, "Error while loading '" + mde.ModelNames[i] + "': " + e);
|
|
}
|
|
}
|
|
}
|
|
|
|
//clear unneeded data
|
|
mde.TextureNames = null;
|
|
mde.ModelNames = null;
|
|
|
|
if(mde.Model.Meshes == null || mde.Model.Meshes.Count == 0)
|
|
{
|
|
mde.Model = null;
|
|
return;
|
|
}
|
|
|
|
//scale bbs
|
|
bbs.MaxX = (int)(bbs.MaxX * mde.Scale.X);
|
|
bbs.MinX = (int)(bbs.MinX * mde.Scale.X);
|
|
bbs.MaxY = (int)(bbs.MaxY * mde.Scale.Y);
|
|
bbs.MinY = (int)(bbs.MinY * mde.Scale.Y);
|
|
|
|
//calculate model radius
|
|
mde.Model.Radius = Math.Max(Math.Max(Math.Abs(bbs.MinY), Math.Abs(bbs.MaxY)), Math.Max(Math.Abs(bbs.MinX), Math.Abs(bbs.MaxX)));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== MD3
|
|
|
|
private static MD3LoadResult ReadMD3Model(ref BoundingBoxSizes bbs, bool useSkins, MemoryStream s, Device device, int frame)
|
|
{
|
|
long start = s.Position;
|
|
MD3LoadResult result = new MD3LoadResult();
|
|
|
|
using(var br = new BinaryReader(s, Encoding.ASCII))
|
|
{
|
|
string magic = ReadString(br, 4);
|
|
if(magic != "IDP3")
|
|
{
|
|
result.Errors = "unknown header: expected 'IDP3', but got '" + magic + "'";
|
|
return result;
|
|
}
|
|
|
|
int modelVersion = br.ReadInt32();
|
|
if(modelVersion != 15) //MD3 version. Must be equal to 15
|
|
{
|
|
result.Errors = "expected MD3 version 15, but got " + modelVersion;
|
|
return result;
|
|
}
|
|
|
|
s.Position += 76;
|
|
int numSurfaces = br.ReadInt32();
|
|
s.Position += 12;
|
|
int ofsSurfaces = br.ReadInt32();
|
|
|
|
s.Position = ofsSurfaces + start;
|
|
|
|
List<int> polyIndecesList = new List<int>();
|
|
List<WorldVertex> vertList = new List<WorldVertex>();
|
|
|
|
Dictionary<string, List<List<int>>> polyIndecesListsPerTexture = new Dictionary<string, List<List<int>>>(StringComparer.Ordinal);
|
|
Dictionary<string, List<WorldVertex>> vertListsPerTexture = new Dictionary<string, List<WorldVertex>>(StringComparer.Ordinal);
|
|
Dictionary<string, List<int>> vertexOffsets = new Dictionary<string, List<int>>(StringComparer.Ordinal);
|
|
|
|
for(int c = 0; c < numSurfaces; c++)
|
|
{
|
|
string skin = "";
|
|
string error = ReadSurface(ref bbs, ref skin, br, polyIndecesList, vertList, frame);
|
|
|
|
if(!string.IsNullOrEmpty(error))
|
|
{
|
|
result.Errors = error;
|
|
return result;
|
|
}
|
|
|
|
if(useSkins)
|
|
{
|
|
if(polyIndecesListsPerTexture.ContainsKey(skin))
|
|
{
|
|
polyIndecesListsPerTexture[skin].Add(polyIndecesList);
|
|
vertListsPerTexture[skin].AddRange(vertList.ToArray());
|
|
vertexOffsets[skin].Add(vertList.Count);
|
|
}
|
|
else
|
|
{
|
|
polyIndecesListsPerTexture.Add(skin, new List<List<int>> { polyIndecesList } );
|
|
vertListsPerTexture.Add(skin, vertList);
|
|
vertexOffsets.Add(skin, new List<int> { vertList.Count });
|
|
}
|
|
|
|
//reset lists
|
|
polyIndecesList = new List<int>();
|
|
vertList = new List<WorldVertex>();
|
|
}
|
|
}
|
|
|
|
if(!useSkins)
|
|
{
|
|
//create mesh
|
|
CreateMesh(device, ref result, vertList, polyIndecesList);
|
|
result.Skins.Add("");
|
|
}
|
|
else
|
|
{
|
|
//create a mesh for each surface texture
|
|
foreach(KeyValuePair<string, List<List<int>>> group in polyIndecesListsPerTexture)
|
|
{
|
|
polyIndecesList = new List<int>();
|
|
int offset = 0;
|
|
|
|
//collect indices, fix vertex offsets
|
|
for(int i = 0; i < group.Value.Count; i++)
|
|
{
|
|
if(i > 0)
|
|
{
|
|
//TODO: Damn I need to rewrite all of this stuff from scratch...
|
|
offset += vertexOffsets[group.Key][i - 1];
|
|
for(int c = 0; c < group.Value[i].Count; c++)
|
|
group.Value[i][c] += offset;
|
|
}
|
|
polyIndecesList.AddRange(group.Value[i].ToArray());
|
|
}
|
|
|
|
CreateMesh(device, ref result, vertListsPerTexture[group.Key], polyIndecesList);
|
|
result.Skins.Add(group.Key.ToLowerInvariant());
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static string ReadSurface(ref BoundingBoxSizes bbs, ref string skin, BinaryReader br, List<int> polyIndecesList, List<WorldVertex> vertList, int frame)
|
|
{
|
|
int vertexOffset = vertList.Count;
|
|
long start = br.BaseStream.Position;
|
|
|
|
string magic = ReadString(br, 4);
|
|
if(magic != "IDP3") return "error while reading surface. Unknown header: expected 'IDP3', but got '" + magic + "'";
|
|
|
|
string name = ReadString(br, 64);
|
|
int flags = br.ReadInt32();
|
|
int numFrames = br.ReadInt32(); //Number of animation frames. This should match NUM_FRAMES in the MD3 header.
|
|
int numShaders = br.ReadInt32(); //Number of Shader objects defined in this Surface, with a limit of MD3_MAX_SHADERS. Current value of MD3_MAX_SHADERS is 256.
|
|
int numVerts = br.ReadInt32(); //Number of Vertex objects defined in this Surface, up to MD3_MAX_VERTS. Current value of MD3_MAX_VERTS is 4096.
|
|
int numTriangles = br.ReadInt32(); //Number of Triangle objects defined in this Surface, maximum of MD3_MAX_TRIANGLES. Current value of MD3_MAX_TRIANGLES is 8192.
|
|
int ofsTriangles = br.ReadInt32(); //Relative offset from SURFACE_START where the list of Triangle objects starts.
|
|
int ofsShaders = br.ReadInt32();
|
|
int ofsST = br.ReadInt32(); //Relative offset from SURFACE_START where the list of ST objects (s-t texture coordinates) starts.
|
|
int ofsNormal = br.ReadInt32(); //Relative offset from SURFACE_START where the list of Vertex objects (X-Y-Z-N vertices) starts.
|
|
int ofsEnd = br.ReadInt32(); //Relative offset from SURFACE_START to where the Surface object ends.
|
|
|
|
// Sanity check
|
|
if(frame < 0 || frame > numFrames)
|
|
{
|
|
return "invalid frame number! (frame number: " + frame + ", total frames: " + numFrames + ")";
|
|
}
|
|
|
|
// Polygons
|
|
if(start + ofsTriangles != br.BaseStream.Position)
|
|
br.BaseStream.Position = start + ofsTriangles;
|
|
|
|
for(int i = 0; i < numTriangles * 3; i++)
|
|
polyIndecesList.Add(vertexOffset + br.ReadInt32());
|
|
|
|
// Shaders
|
|
if(start + ofsShaders != br.BaseStream.Position)
|
|
br.BaseStream.Position = start + ofsShaders;
|
|
|
|
skin = ReadString(br, 64); //we are interested only in the first one
|
|
|
|
// Vertices
|
|
if(start + ofsST != br.BaseStream.Position)
|
|
br.BaseStream.Position = start + ofsST;
|
|
|
|
for(int i = 0; i < numVerts; i++)
|
|
{
|
|
WorldVertex v = new WorldVertex();
|
|
v.c = -1; //white
|
|
v.u = br.ReadSingle();
|
|
v.v = br.ReadSingle();
|
|
|
|
vertList.Add(v);
|
|
}
|
|
|
|
// Positions and normals
|
|
long vertoffset = start + ofsNormal + numVerts * 8 * frame; // The length of Vertex struct is 8 bytes
|
|
if(br.BaseStream.Position != vertoffset) br.BaseStream.Position = vertoffset;
|
|
|
|
for(int i = vertexOffset; i < vertexOffset + numVerts; i++)
|
|
{
|
|
WorldVertex v = vertList[i];
|
|
|
|
//read vertex
|
|
v.y = -(float)br.ReadInt16() / 64;
|
|
v.x = (float)br.ReadInt16() / 64;
|
|
v.z = (float)br.ReadInt16() / 64;
|
|
|
|
//bounding box
|
|
BoundingBoxTools.UpdateBoundingBoxSizes(ref bbs, v);
|
|
|
|
var lat = br.ReadByte() * (2 * Math.PI) / 255.0;
|
|
var lng = br.ReadByte() * (2 * Math.PI) / 255.0;
|
|
|
|
v.nx = (float)(Math.Sin(lng) * Math.Sin(lat));
|
|
v.ny = -(float)(Math.Cos(lng) * Math.Sin(lat));
|
|
v.nz = (float)(Math.Cos(lat));
|
|
|
|
vertList[i] = v;
|
|
}
|
|
|
|
if(start + ofsEnd != br.BaseStream.Position)
|
|
br.BaseStream.Position = start + ofsEnd;
|
|
return "";
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== MD2
|
|
|
|
private static MD3LoadResult ReadMD2Model(ref BoundingBoxSizes bbs, MemoryStream s, Device device, int frame, string framename)
|
|
{
|
|
long start = s.Position;
|
|
MD3LoadResult result = new MD3LoadResult();
|
|
|
|
using(var br = new BinaryReader(s, Encoding.ASCII))
|
|
{
|
|
string magic = ReadString(br, 4);
|
|
if(magic != "IDP2") //magic number: "IDP2"
|
|
{
|
|
result.Errors = "unknown header: expected 'IDP2', but got '" + magic + "'";
|
|
return result;
|
|
}
|
|
|
|
int modelVersion = br.ReadInt32();
|
|
if(modelVersion != 8) //MD2 version. Must be equal to 8
|
|
{
|
|
result.Errors = "expected MD3 version 15, but got " + modelVersion;
|
|
return result;
|
|
}
|
|
|
|
int texWidth = br.ReadInt32();
|
|
int texHeight = br.ReadInt32();
|
|
int framesize = br.ReadInt32(); // Size of one frame in bytes
|
|
s.Position += 4; //Number of textures
|
|
int num_verts = br.ReadInt32(); //Number of vertices
|
|
int num_uv = br.ReadInt32(); //The number of UV coordinates in the model
|
|
int num_tris = br.ReadInt32(); //Number of triangles
|
|
s.Position += 4; //Number of OpenGL commands
|
|
int num_frames = br.ReadInt32(); //Total number of frames
|
|
|
|
// Sanity checks
|
|
if(num_frames == 0)
|
|
{
|
|
result.Errors = "model has 0 frames.";
|
|
return result;
|
|
}
|
|
|
|
if(num_frames < frame || frame < 0)
|
|
{
|
|
result.Errors = "invalid target frame! (target frame: " + frame + ", total frames: " + num_frames + ")";
|
|
return result;
|
|
}
|
|
|
|
s.Position += 4; //Offset to skin names (each skin name is an unsigned char[64] and are null terminated)
|
|
int ofs_uv = br.ReadInt32();//Offset to s-t texture coordinates
|
|
int ofs_tris = br.ReadInt32(); //Offset to triangles
|
|
int ofs_animFrame = br.ReadInt32(); //An offset to the first animation frame
|
|
|
|
List<int> polyIndecesList = new List<int>();
|
|
List<int> uvIndecesList = new List<int>();
|
|
List<Vector2> uvCoordsList = new List<Vector2>();
|
|
List<WorldVertex> vertList = new List<WorldVertex>();
|
|
|
|
// Polygons
|
|
s.Position = ofs_tris + start;
|
|
|
|
for(int i = 0; i < num_tris; i++)
|
|
{
|
|
polyIndecesList.Add(br.ReadUInt16());
|
|
polyIndecesList.Add(br.ReadUInt16());
|
|
polyIndecesList.Add(br.ReadUInt16());
|
|
|
|
uvIndecesList.Add(br.ReadUInt16());
|
|
uvIndecesList.Add(br.ReadUInt16());
|
|
uvIndecesList.Add(br.ReadUInt16());
|
|
}
|
|
|
|
// UV coords
|
|
s.Position = ofs_uv + start;
|
|
|
|
for(int i = 0; i < num_uv; i++)
|
|
uvCoordsList.Add(new Vector2((float)br.ReadInt16() / texWidth, (float)br.ReadInt16() / texHeight));
|
|
|
|
// Frames
|
|
// Find correct frame
|
|
if(!string.IsNullOrEmpty(framename))
|
|
{
|
|
// Skip frames untill frame name matches
|
|
bool framefound = false;
|
|
for(int i = 0; i < num_frames; i++)
|
|
{
|
|
s.Position = ofs_animFrame + start + i * framesize;
|
|
s.Position += 24; // Skip scale and translate
|
|
string curframename = ReadString(br, 16).ToLowerInvariant();
|
|
|
|
if(curframename == framename)
|
|
{
|
|
// Step back so scale and translate can be read
|
|
s.Position -= 40;
|
|
framefound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// No dice? Bail out!
|
|
if(!framefound)
|
|
{
|
|
result.Errors = "unable to find frame '" + framename + "'!";
|
|
return result;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we have frame number, we can go directly to target frame
|
|
s.Position = ofs_animFrame + start + frame * framesize;
|
|
}
|
|
|
|
Vector3 scale = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
|
|
Vector3 translate = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
|
|
|
|
s.Position += 16; // Skip frame name
|
|
|
|
// Prepare to fix rotation angle
|
|
float angleOfsetCos = (float)Math.Cos(-Angle2D.PIHALF);
|
|
float angleOfsetSin = (float)Math.Sin(-Angle2D.PIHALF);
|
|
|
|
//verts
|
|
for(int i = 0; i < num_verts; i++)
|
|
{
|
|
WorldVertex v = new WorldVertex();
|
|
|
|
v.x = (br.ReadByte() * scale.X + translate.X);
|
|
v.y = (br.ReadByte() * scale.Y + translate.Y);
|
|
v.z = (br.ReadByte() * scale.Z + translate.Z);
|
|
|
|
// Fix rotation angle
|
|
float rx = angleOfsetCos * v.x - angleOfsetSin * v.y;
|
|
float ry = angleOfsetSin * v.x + angleOfsetCos * v.y;
|
|
v.y = ry;
|
|
v.x = rx;
|
|
|
|
vertList.Add(v);
|
|
s.Position += 1; //vertex normal
|
|
}
|
|
|
|
for(int i = 0; i < polyIndecesList.Count; i++)
|
|
{
|
|
WorldVertex v = vertList[polyIndecesList[i]];
|
|
|
|
//bounding box
|
|
BoundingBoxTools.UpdateBoundingBoxSizes(ref bbs, new WorldVertex(v.y, v.x, v.z));
|
|
|
|
//uv
|
|
float tu = uvCoordsList[uvIndecesList[i]].X;
|
|
float tv = uvCoordsList[uvIndecesList[i]].Y;
|
|
|
|
//uv-coordinates already set?
|
|
if(v.c == -1 && (v.u != tu || v.v != tv))
|
|
{
|
|
//add a new vertex
|
|
vertList.Add(new WorldVertex(v.x, v.y, v.z, -1, tu, tv));
|
|
polyIndecesList[i] = vertList.Count - 1;
|
|
}
|
|
else
|
|
{
|
|
v.u = tu;
|
|
v.v = tv;
|
|
v.c = -1; //set color to white
|
|
|
|
//return to proper place
|
|
vertList[polyIndecesList[i]] = v;
|
|
}
|
|
}
|
|
|
|
//mesh
|
|
Mesh mesh = new Mesh(device, polyIndecesList.Count / 3, vertList.Count, MeshFlags.Use32Bit | MeshFlags.IndexBufferManaged | MeshFlags.VertexBufferManaged, vertexElements);
|
|
|
|
using(DataStream stream = mesh.LockVertexBuffer(LockFlags.None))
|
|
{
|
|
stream.WriteRange(vertList.ToArray());
|
|
}
|
|
mesh.UnlockVertexBuffer();
|
|
|
|
using(DataStream stream = mesh.LockIndexBuffer(LockFlags.None))
|
|
{
|
|
stream.WriteRange(polyIndecesList.ToArray());
|
|
}
|
|
mesh.UnlockIndexBuffer();
|
|
|
|
mesh.OptimizeInPlace(MeshOptimizeFlags.AttributeSort);
|
|
|
|
//store in result
|
|
result.Meshes.Add(mesh);
|
|
result.Skins.Add(""); //no skin support for MD2
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== KVX
|
|
|
|
private static void ReadKVX(ModelData mde, Stream stream, Device device)
|
|
{
|
|
PixelColor[] palette = new PixelColor[256];
|
|
List<WorldVertex> verts = new List<WorldVertex>();
|
|
int xsize, ysize, zsize;
|
|
Vector3D pivot;
|
|
|
|
using(BinaryReader reader = new BinaryReader(stream, Encoding.ASCII))
|
|
{
|
|
reader.ReadInt32(); //numbytes, we don't use that
|
|
xsize = reader.ReadInt32();
|
|
ysize = reader.ReadInt32();
|
|
zsize = reader.ReadInt32();
|
|
|
|
pivot = new Vector3D();
|
|
pivot.x = reader.ReadInt32() / 256f;
|
|
pivot.y = reader.ReadInt32() / 256f;
|
|
pivot.z = 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(!mde.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];
|
|
}
|
|
}
|
|
|
|
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 slab
|
|
while(reader.BaseStream.Position < next)
|
|
{
|
|
int ztop = reader.ReadByte();
|
|
int zleng = reader.ReadByte();
|
|
if(ztop + zleng > zsize) break;
|
|
int flags = reader.ReadByte();
|
|
|
|
if(zleng > 0)
|
|
{
|
|
List<int> colorIndices = new List<int>(zleng);
|
|
for(int i = 0; i < zleng; i++)
|
|
{
|
|
colorIndices.Add(reader.ReadByte());
|
|
}
|
|
|
|
if((flags & 16) != 0)
|
|
{
|
|
AddFace(verts, new Vector3D(x, y, ztop), new Vector3D(x + 1, y, ztop), new Vector3D(x, y + 1, ztop), new Vector3D(x + 1, y + 1, ztop), pivot, colorIndices[0]);
|
|
}
|
|
|
|
int z = ztop;
|
|
int cstart = 0;
|
|
while(z < ztop + zleng)
|
|
{
|
|
int c = 0;
|
|
while(z + c < ztop + zleng && colorIndices[cstart + c] == colorIndices[cstart]) c++;
|
|
|
|
if((flags & 1) != 0)
|
|
{
|
|
AddFace(verts, new Vector3D(x, y, z), new Vector3D(x, y + 1, z), new Vector3D(x, y, z + c), new Vector3D(x, y + 1, z + c), pivot, colorIndices[cstart]);
|
|
}
|
|
if((flags & 2) != 0)
|
|
{
|
|
AddFace(verts, new Vector3D(x + 1, y + 1, z), new Vector3D(x + 1, y, z), new Vector3D(x + 1, y + 1, z + c), new Vector3D(x + 1, y, z + c), pivot, colorIndices[cstart]);
|
|
}
|
|
if((flags & 4) != 0)
|
|
{
|
|
AddFace(verts, new Vector3D(x + 1, y, z), new Vector3D(x, y, z), new Vector3D(x + 1, y, z + c), new Vector3D(x, y, z + c), pivot, colorIndices[cstart]);
|
|
}
|
|
if((flags & 8) != 0)
|
|
{
|
|
AddFace(verts, new Vector3D(x, y + 1, z), new Vector3D(x + 1, y + 1, z), new Vector3D(x, y + 1, z + c), new Vector3D(x + 1, y + 1, z + c), pivot, colorIndices[cstart]);
|
|
}
|
|
|
|
if(c == 0) c++;
|
|
z += c;
|
|
cstart += c;
|
|
}
|
|
|
|
if((flags & 32) != 0)
|
|
{
|
|
z = ztop + zleng - 1;
|
|
AddFace(verts, new Vector3D(x + 1, y, z + 1), new Vector3D(x, y, z + 1), new Vector3D(x + 1, y + 1, z + 1), new Vector3D(x, y + 1, z + 1), pivot, colorIndices[zleng - 1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
counter++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// get model extents
|
|
int minX = (int)((xsize / 2f - pivot.x) * mde.Scale.X);
|
|
int maxX = (int)((xsize / 2f + pivot.x) * mde.Scale.X);
|
|
int minY = (int)((ysize / 2f - pivot.y) * mde.Scale.Y);
|
|
int maxY = (int)((ysize / 2f + pivot.y) * mde.Scale.Y);
|
|
|
|
// calculate model radius
|
|
mde.Model.Radius = Math.Max(Math.Max(Math.Abs(minY), Math.Abs(maxY)), Math.Max(Math.Abs(minX), Math.Abs(maxX)));
|
|
|
|
//create texture
|
|
MemoryStream memstream = new MemoryStream((4096 * 4) + 4096);
|
|
using (Bitmap bmp = CreateVoxelTexture(palette)) bmp.Save(memstream, ImageFormat.Bmp);
|
|
memstream.Seek(0, SeekOrigin.Begin);
|
|
|
|
Texture texture = Texture.FromStream(device, memstream, (int)memstream.Length, 64, 64, 0, Usage.None, Format.Unknown, Pool.Managed, Filter.Point, Filter.Box, 0);
|
|
memstream.Dispose();
|
|
|
|
//add texture
|
|
mde.Model.Textures.Add(texture);
|
|
|
|
//create mesh
|
|
int[] indices = new int[verts.Count];
|
|
for(int i = 0; i < verts.Count; i++)
|
|
{
|
|
indices[i] = i;
|
|
}
|
|
|
|
Mesh mesh = new Mesh(device, verts.Count / 3, verts.Count, MeshFlags.Use32Bit | MeshFlags.IndexBufferManaged | MeshFlags.VertexBufferManaged, vertexElements);
|
|
|
|
DataStream mstream = mesh.VertexBuffer.Lock(0, 0, LockFlags.None);
|
|
mstream.WriteRange(verts.ToArray());
|
|
mesh.VertexBuffer.Unlock();
|
|
|
|
mstream = mesh.IndexBuffer.Lock(0, 0, LockFlags.None);
|
|
mstream.WriteRange(indices);
|
|
mesh.IndexBuffer.Unlock();
|
|
|
|
mesh.OptimizeInPlace(MeshOptimizeFlags.AttributeSort);
|
|
|
|
//add mesh
|
|
mde.Model.Meshes.Add(mesh);
|
|
}
|
|
|
|
// Shameless GZDoom rip-off
|
|
private static void AddFace(List<WorldVertex> verts, Vector3D v1, Vector3D v2, Vector3D v3, Vector3D v4, Vector3D pivot, int colorIndex)
|
|
{
|
|
float pu0 = (colorIndex % 16) / 16f;
|
|
float pu1 = pu0 + 0.0001f;
|
|
float pv0 = (colorIndex / 16) / 16f;
|
|
float pv1 = pv0 + 0.0001f;
|
|
|
|
WorldVertex wv1 = new WorldVertex();
|
|
wv1.x = v1.x - pivot.x;
|
|
wv1.y = -v1.y + pivot.y;
|
|
wv1.z = -v1.z + pivot.z;
|
|
wv1.c = -1;
|
|
wv1.u = pu0;
|
|
wv1.v = pv0;
|
|
verts.Add(wv1);
|
|
|
|
WorldVertex wv2 = new WorldVertex();
|
|
wv2.x = v2.x - pivot.x;
|
|
wv2.y = -v2.y + pivot.y;
|
|
wv2.z = -v2.z + pivot.z;
|
|
wv2.c = -1;
|
|
wv2.u = pu1;
|
|
wv2.v = pv1;
|
|
verts.Add(wv2);
|
|
|
|
WorldVertex wv4 = new WorldVertex();
|
|
wv4.x = v4.x - pivot.x;
|
|
wv4.y = -v4.y + pivot.y;
|
|
wv4.z = -v4.z + pivot.z;
|
|
wv4.c = -1;
|
|
wv4.u = pu0;
|
|
wv4.v = pv0;
|
|
verts.Add(wv4);
|
|
|
|
WorldVertex wv3 = new WorldVertex();
|
|
wv3.x = v3.x - pivot.x;
|
|
wv3.y = -v3.y + pivot.y;
|
|
wv3.z = -v3.z + pivot.z;
|
|
wv3.c = -1;
|
|
wv3.u = pu1;
|
|
wv3.v = pv1;
|
|
verts.Add(wv3);
|
|
|
|
verts.Add(wv1);
|
|
verts.Add(wv4);
|
|
}
|
|
|
|
private unsafe static Bitmap CreateVoxelTexture(PixelColor[] palette)
|
|
{
|
|
Bitmap bmp = new Bitmap(16, 16);
|
|
BitmapData bmpdata = bmp.LockBits(new Rectangle(0, 0, 16, 16), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
|
|
|
|
if(bmpdata != null)
|
|
{
|
|
PixelColor* pixels = (PixelColor*)(bmpdata.Scan0.ToPointer());
|
|
const int numpixels = 256;
|
|
int i = 255;
|
|
|
|
for(PixelColor* cp = pixels + numpixels - 1; cp >= pixels; cp--, i--)
|
|
{
|
|
cp->r = palette[i].r;
|
|
cp->g = palette[i].g;
|
|
cp->b = palette[i].b;
|
|
cp->a = palette[i].a;
|
|
}
|
|
bmp.UnlockBits(bmpdata);
|
|
}
|
|
|
|
//scale bitmap, so colors stay (almost) the same when bilinear filtering is enabled
|
|
Bitmap scaled = new Bitmap(64, 64);
|
|
using(Graphics gs = Graphics.FromImage(scaled))
|
|
{
|
|
gs.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
|
|
gs.DrawImage(bmp, new Rectangle(0, 0, 64, 64), new Rectangle(0, 0, 16, 16), GraphicsUnit.Pixel);
|
|
}
|
|
bmp.Dispose();
|
|
|
|
return scaled;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Utility
|
|
|
|
private static MemoryStream LoadFile(List<DataReader> containers, string path, bool isModel)
|
|
{
|
|
foreach(DataReader dr in containers)
|
|
{
|
|
if(isModel && dr is WADReader) continue; //models cannot be stored in WADs
|
|
|
|
//load file
|
|
if(dr.FileExists(path)) return dr.LoadFile(path);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static Texture LoadTexture(List<DataReader> containers, string path, Device device)
|
|
{
|
|
if(string.IsNullOrEmpty(path)) return null;
|
|
|
|
MemoryStream ms = LoadFile(containers, path, false);
|
|
if(ms == null) return null;
|
|
|
|
Texture texture = null;
|
|
|
|
//create texture
|
|
if(Path.GetExtension(path) == ".pcx") //pcx format requires special handling...
|
|
{
|
|
FileImageReader fir = new FileImageReader();
|
|
Bitmap bitmap = fir.ReadAsBitmap(ms);
|
|
|
|
ms.Close();
|
|
ms.Dispose();
|
|
|
|
if(bitmap != null)
|
|
{
|
|
BitmapData bmlock = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
|
|
texture = new Texture(device, bitmap.Width, bitmap.Height, 1, Usage.None, Format.A8R8G8B8, Pool.Managed);
|
|
|
|
DataRectangle textureLock = texture.LockRectangle(0, LockFlags.None);
|
|
textureLock.Data.WriteRange(bmlock.Scan0, bmlock.Height * bmlock.Stride);
|
|
|
|
bitmap.UnlockBits(bmlock);
|
|
texture.UnlockRectangle(0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
texture = Texture.FromStream(device, ms);
|
|
|
|
ms.Close();
|
|
ms.Dispose();
|
|
}
|
|
|
|
return texture;
|
|
}
|
|
|
|
private static void CreateMesh(Device device, ref MD3LoadResult result, List<WorldVertex> verts, List<int> indices)
|
|
{
|
|
//create mesh
|
|
Mesh mesh = new Mesh(device, indices.Count / 3, verts.Count, MeshFlags.Use32Bit | MeshFlags.IndexBufferManaged | MeshFlags.VertexBufferManaged, vertexElements);
|
|
|
|
using(DataStream stream = mesh.LockVertexBuffer(LockFlags.None))
|
|
{
|
|
stream.WriteRange(verts.ToArray());
|
|
}
|
|
mesh.UnlockVertexBuffer();
|
|
|
|
using(DataStream stream = mesh.LockIndexBuffer(LockFlags.None))
|
|
{
|
|
stream.WriteRange(indices.ToArray());
|
|
}
|
|
mesh.UnlockIndexBuffer();
|
|
|
|
mesh.OptimizeInPlace(MeshOptimizeFlags.AttributeSort);
|
|
|
|
//store in result
|
|
result.Meshes.Add(mesh);
|
|
}
|
|
|
|
private static string ReadString(BinaryReader br, int len)
|
|
{
|
|
string result = string.Empty;
|
|
int i;
|
|
|
|
for(i = 0; i < len; ++i)
|
|
{
|
|
var c = br.ReadChar();
|
|
if(c == '\0')
|
|
{
|
|
++i;
|
|
break;
|
|
}
|
|
result += c;
|
|
}
|
|
|
|
for(; i < len; ++i) br.ReadChar();
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |