UltimateZoneBuilder/Source/Core/GZBuilder/md3/ModelReader.cs
MaxED eb86ab60af GZDoom Builder 1.12d:
TEXTURES patches can now be loaded from anywhere inside resource.
Configs: things from "Cameras and Interpolation" and "Boom Items" categories weren't rendered in Visual modes.
Configs: several new icons are now used for things.
Reverted sectors rendering from 1.12b, because it was done in too hackish way and was causing more and more problems. Models rendered height is now 25% lower instead.
Models rendering height can be toggled between normal and 85% in Preferences->Appearance->"Scale models in Visual modes"
Fixed several small bugs here and there.
2012-08-10 12:08:08 +00:00

459 lines
No EOL
20 KiB
C#

using System;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;
using System.Collections.Generic;
using CodeImp.DoomBuilder;
using CodeImp.DoomBuilder.IO;
using CodeImp.DoomBuilder.Data;
using CodeImp.DoomBuilder.Rendering;
using CodeImp.DoomBuilder.GZBuilder.Data;
using CodeImp.DoomBuilder.GZBuilder.GZDoom;
using SlimDX;
using SlimDX.Direct3D9;
//mxd. Original version taken from here: http://colladadotnet.codeplex.com/SourceControl/changeset/view/40680
namespace CodeImp.DoomBuilder.GZBuilder.MD3
{
internal static class ModelReader
{
private const float VERTICAL_STRETCH = 1 / 1.2f;
public static void Parse(ref ModeldefEntry mde, PK3StructuredReader reader, Device D3DDevice) {
string[] modelNames = new string[mde.ModelNames.Count];
string[] textureNames = new string[mde.TextureNames.Count];
mde.ModelNames.CopyTo(modelNames);
mde.TextureNames.CopyTo(textureNames);
mde.Model = new GZModel();
mde.Model.NUM_MESHES = (byte)modelNames.Length;
BoundingBoxSizes bbs = new BoundingBoxSizes();
for (int i = 0; i < modelNames.Length; i++) {
string modelPath = Path.Combine(mde.Path, modelNames[i]);
if (reader.FileExists(modelPath)) {
MemoryStream stream = reader.LoadFile(modelPath);
General.WriteLogLine("ModelLoader: loading '" + modelPath + "'");
//mesh
string ext = modelNames[i].Substring(modelNames[i].Length - 4);
string error = "";
if (ext == ".md3")
error = ReadMD3Model(ref bbs, ref mde, stream, D3DDevice);
else if (ext == ".md2")
error = ReadMD2Model(ref bbs, ref mde, stream, D3DDevice);
//texture
if (string.IsNullOrEmpty(error)) {
string texturePath = Path.Combine(mde.Path, textureNames[i]);
if (textureNames[i] != TextureData.INVALID_TEXTURE && reader.FileExists(texturePath)) {
if (Path.GetExtension(texturePath) == ".pcx") { //pcx format requires special handling...
FileImageReader fir = new FileImageReader();
Bitmap bitmap = fir.ReadAsBitmap(reader.LoadFile(texturePath));
if (bitmap != null) {
BitmapData bmlock = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
Texture texture = new Texture(D3DDevice, 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);
mde.Model.Textures.Add(texture);
} else {
mde.Model.Textures.Add(General.Map.Data.UnknownTexture3D.Texture);
GZBuilder.GZGeneral.LogAndTraceWarning("ModelLoader: unable to load texture '" + texturePath + "'");
}
} else {
mde.Model.Textures.Add(Texture.FromStream(D3DDevice, reader.LoadFile(texturePath)));
}
} else {
mde.Model.Textures.Add(General.Map.Data.UnknownTexture3D.Texture);
if (textureNames[i] != TextureData.INVALID_TEXTURE)
GZBuilder.GZGeneral.LogAndTraceWarning("ModelLoader: unable to load texture '" + texturePath + "' - no such file");
}
} else {
GZBuilder.GZGeneral.LogAndTraceWarning("ModelLoader: error while loading " + modelPath + ": " + error);
mde.Model.NUM_MESHES--;
}
stream.Dispose();
} else {
GZBuilder.GZGeneral.LogAndTraceWarning("ModelLoader: unable to load model '" + modelPath + "' - no such file");
mde.Model.NUM_MESHES--;
}
}
if (mde.Model.NUM_MESHES <= 0) {
mde.Model = null;
return;
}
mde.Model.BoundingBox = BoundingBoxTools.CalculateBoundingBox(bbs);
}
private static string ReadMD3Model(ref BoundingBoxSizes bbs, ref ModeldefEntry mde, MemoryStream s, Device D3DDevice) {
long start = s.Position;
using (var br = new BinaryReader(s, Encoding.ASCII)) {
string magic = ReadString(br, 4);
if (magic != "IDP3")
return "magic should be 'IDP3', not '" + magic + "'";
s.Position += 80;
int numSurfaces = br.ReadInt32();
s.Position += 12;
int ofsSurfaces = br.ReadInt32();
if (s.Position != ofsSurfaces + start)
s.Position = ofsSurfaces + start;
List<short> polyIndecesList = new List<short>();
List<WorldVertex> vertList = new List<WorldVertex>();
string error = "";
for (int c = 0; c < numSurfaces; ++c) {
error = ReadSurface(ref bbs, br, polyIndecesList, vertList, mde);
if (!string.IsNullOrEmpty(error))
return error;
}
//indeces for rendering current mesh in 2d
short[] indeces2d_arr = CreateLineListIndeces(polyIndecesList);
//mesh
Mesh mesh = new Mesh(D3DDevice, polyIndecesList.Count / 3, vertList.Count, MeshFlags.IndexBufferManaged | MeshFlags.VertexBufferManaged, General.Map.Graphics.Shaders.World3D.VertexElements);
DataStream stream = mesh.VertexBuffer.Lock(0, 0, LockFlags.None);
stream.WriteRange(vertList.ToArray());
mesh.VertexBuffer.Unlock();
stream = mesh.IndexBuffer.Lock(0, 0, LockFlags.None);
stream.WriteRange(polyIndecesList.ToArray());
mesh.IndexBuffer.Unlock();
mesh.OptimizeInPlace(MeshOptimizeFlags.AttributeSort);
mde.Model.Meshes.Add(mesh);
//2d data
IndexBuffer indeces2d = new IndexBuffer(D3DDevice, 2 * indeces2d_arr.Length, Usage.WriteOnly, Pool.Managed, true);
stream = indeces2d.Lock(0, 0, LockFlags.None);
stream.WriteRange(indeces2d_arr);
indeces2d.Unlock();
mde.Model.Indeces2D.Add(indeces2d);
mde.Model.NumIndeces2D.Add((short)polyIndecesList.Count);
}
return "";
}
private static string ReadSurface(ref BoundingBoxSizes bbs, BinaryReader br, List<short> polyIndecesList, List<WorldVertex> vertList, ModeldefEntry mde) {
int vertexOffset = vertList.Count;
long start = br.BaseStream.Position;
string magic = ReadString(br, 4);
if (magic != "IDP3")
return "error while reading surface: Magic should be 'IDP3', not '" + magic + "'";
br.BaseStream.Position += 76;
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.
br.BaseStream.Position += 4;
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.
//polygons
if (start + ofsTriangles != br.BaseStream.Position)
br.BaseStream.Position = start + ofsTriangles;
for (int i = 0; i < numTriangles * 3; i++)
polyIndecesList.Add( (short)(vertexOffset + br.ReadInt32()) );
//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);
}
//Normals
if (start + ofsNormal != br.BaseStream.Position)
br.BaseStream.Position = start + ofsNormal;
//rotation angles
float angleOfsetCos = (float)Math.Cos(mde.AngleOffset);
float angleOfsetSin = (float)Math.Sin(mde.AngleOffset);
float pitchOfsetCos = (float)Math.Cos(-mde.PitchOffset);
float pitchOfsetSin = (float)Math.Sin(-mde.PitchOffset);
float rollOfsetCos = (float)Math.Cos(mde.RollOffset);
float rollOfsetSin = (float)Math.Sin(mde.RollOffset);
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;
//rotate it
if (mde.AngleOffset != 0) {
float rx = angleOfsetCos * v.x - angleOfsetSin * v.y;
float ry = angleOfsetSin * v.x + angleOfsetCos * v.y;
v.y = ry;
v.x = rx;
}
if (mde.PitchOffset != 0) {
float ry = pitchOfsetCos * v.y - pitchOfsetSin * v.z;
float rz = pitchOfsetSin * v.y + pitchOfsetCos * v.z;
v.z = rz;
v.y = ry;
}
if (mde.RollOffset != 0) {
float rx = rollOfsetCos * v.x - rollOfsetSin * v.z;
float rz = rollOfsetSin * v.x + rollOfsetCos * v.z;
v.z = rz;
v.x = rx;
}
//scale it
v.y *= mde.Scale.X;
v.x *= mde.Scale.Y;
v.z *= mde.Scale.Z;
if(General.Settings.GZStretchModels) v.z *= VERTICAL_STRETCH; //GZDoom vertical stretch hack
//add zOffset
v.z += mde.zOffset;
//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 "";
}
private static string ReadMD2Model(ref BoundingBoxSizes bbs, ref ModeldefEntry mde, MemoryStream s, Device D3DDevice) {
long start = s.Position;
using (var br = new BinaryReader(s, Encoding.ASCII)) {
string magic = ReadString(br, 4);
if (magic != "IDP2") //magic number: "IDP2"
return "magic should be 'IDP2', not '" + magic + "'";
int modelVersion = br.ReadInt32();
if (modelVersion != 8) //MD2 version. Must be equal to 8
return "MD2 version must be 8 but is " + modelVersion;
int texWidth = br.ReadInt32();
int texHeight = br.ReadInt32();
s.Position += 8; //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
if (br.ReadInt32() == 0) //Total number of frames
return "model has 0 frames";
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<short> polyIndecesList = new List<short>();
List<short> uvIndecesList = new List<short>();
List<Vector2> uvCoordsList = new List<Vector2>();
List<WorldVertex> vertList = new List<WorldVertex>();
//polygons
if (s.Position != ofs_tris + start)
s.Position = ofs_tris + start;
for (int i = 0; i < num_tris; i++) {
polyIndecesList.Add((short)br.ReadInt16());
polyIndecesList.Add((short)br.ReadInt16());
polyIndecesList.Add((short)br.ReadInt16());
uvIndecesList.Add((short)br.ReadInt16());
uvIndecesList.Add((short)br.ReadInt16());
uvIndecesList.Add((short)br.ReadInt16());
}
//UV coords
if (s.Position != ofs_uv + start)
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));
}
//first frame
//header
if (s.Position != ofs_animFrame + start)
s.Position = ofs_animFrame + start;
Vector3 scale = new Vector3((float)br.ReadSingle(), (float)br.ReadSingle(), (float)br.ReadSingle());
Vector3 translate = new Vector3((float)br.ReadSingle(), (float)br.ReadSingle(), (float)br.ReadSingle());
s.Position += 16; //frame name
//rotation angles
float angle = mde.AngleOffset - 0.5f * (float)Math.PI; //subtract 90 degrees to get correct rotation
float angleOfsetCos = (float)Math.Cos(angle);
float angleOfsetSin = (float)Math.Sin(angle);
float pitchOfsetCos = (float)Math.Cos(-mde.PitchOffset);
float pitchOfsetSin = (float)Math.Sin(-mde.PitchOffset);
float rollOfsetCos = (float)Math.Cos(mde.RollOffset);
float rollOfsetSin = (float)Math.Sin(mde.RollOffset);
//verts
for (int i = 0; i < num_verts; i++) {
WorldVertex v = new WorldVertex();
v.x = ((float)br.ReadByte() * scale.X + translate.X);
v.y = ((float)br.ReadByte() * scale.Y + translate.Y);
v.z = ((float)br.ReadByte() * scale.Z + translate.Z);
//rotate it
float rx = angleOfsetCos * v.x - angleOfsetSin * v.y;
float ry = angleOfsetSin * v.x + angleOfsetCos * v.y;
v.y = ry;
v.x = rx;
if (mde.PitchOffset != 0) {
ry = pitchOfsetCos * v.y - pitchOfsetSin * v.z;
float rz = pitchOfsetSin * v.y + pitchOfsetCos * v.z;
v.z = rz;
v.y = ry;
}
if (mde.RollOffset != 0) {
rx = rollOfsetCos * v.x - rollOfsetSin * v.z;
float rz = rollOfsetSin * v.x + rollOfsetCos * v.z;
v.z = rz;
v.x = rx;
}
//scale it
v.x *= mde.Scale.X;
v.y *= mde.Scale.Y;
v.z *= mde.Scale.Z;
if(General.Settings.GZStretchModels) v.z *= VERTICAL_STRETCH; //GZDoom vertical stretch hack
//add zOffset
v.z += mde.zOffset;
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
v.u = uvCoordsList[uvIndecesList[i]].X;
v.v = uvCoordsList[uvIndecesList[i]].Y;
//color
v.c = -1; //white
vertList[polyIndecesList[i]] = v;
}
//indeces for rendering current mesh in 2d
short[] indeces2d_arr = CreateLineListIndeces(polyIndecesList);
//mesh
Mesh mesh = new Mesh(D3DDevice, polyIndecesList.Count / 3, vertList.Count, MeshFlags.IndexBufferManaged | MeshFlags.VertexBufferManaged, General.Map.Graphics.Shaders.World3D.VertexElements);
DataStream stream = mesh.VertexBuffer.Lock(0, 0, LockFlags.None);
stream.WriteRange(vertList.ToArray());
mesh.VertexBuffer.Unlock();
stream = mesh.IndexBuffer.Lock(0, 0, LockFlags.None);
stream.WriteRange(polyIndecesList.ToArray());
mesh.IndexBuffer.Unlock();
mesh.OptimizeInPlace(MeshOptimizeFlags.AttributeSort);
mde.Model.Meshes.Add(mesh);
//2d data
IndexBuffer indeces2d = new IndexBuffer(D3DDevice, 2 * indeces2d_arr.Length, Usage.WriteOnly, Pool.Managed, true);
stream = indeces2d.Lock(0, 0, LockFlags.None);
stream.WriteRange(indeces2d_arr);
indeces2d.Unlock();
mde.Model.Indeces2D.Add(indeces2d);
mde.Model.NumIndeces2D.Add((short)polyIndecesList.Count);
}
return "";
}
//this creates list of vertex indeces for rendering using LineList method
private static short[] CreateLineListIndeces(List<short> polyIndecesList) {
short[] indeces2d_arr = new short[polyIndecesList.Count * 2];
short ind1, ind2, ind3;
for (short i = 0; i < polyIndecesList.Count; i += 3) {
ind1 = polyIndecesList[i];
ind2 = polyIndecesList[i + 1];
ind3 = polyIndecesList[i + 2];
indeces2d_arr[i * 2] = ind1;
indeces2d_arr[i * 2 + 1] = ind2;
indeces2d_arr[i * 2 + 2] = ind2;
indeces2d_arr[i * 2 + 3] = ind3;
indeces2d_arr[i * 2 + 4] = ind3;
indeces2d_arr[i * 2 + 5] = ind1;
}
return indeces2d_arr;
}
private static string ReadString(BinaryReader br, int len) {
var NAME = string.Empty;
int i = 0;
for (i = 0; i < len; ++i) {
var c = br.ReadChar();
if (c == '\0') {
++i;
break;
}
NAME += c;
}
for (; i < len; ++i) {
br.ReadChar();
}
return NAME;
}
}
}