From 192b69c8af7d6d05e6ee6a6d29e9fc38b8bb75fa Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Thu, 29 Jun 2023 20:37:47 +0200 Subject: [PATCH] Added support for models in IQM format --- Source/Core/Builder.csproj | 12 +- Source/Core/BuilderMono.csproj | 11 +- Source/Core/Data/DataManager.cs | 6 +- Source/Core/GZBuilder/Data/ModelData.cs | 2 +- .../Core/GZBuilder/{md3 => Models}/GZModel.cs | 38 +- .../Core/GZBuilder/Models/IQMModelLoader.cs | 1016 ++++++++++ .../Core/GZBuilder/Models/KVXModelLoader.cs | 302 +++ .../Core/GZBuilder/Models/MD2ModelLoader.cs | 183 ++ .../Core/GZBuilder/Models/MD3ModelLoader.cs | 214 +++ .../Core/GZBuilder/Models/ModelLoadResult.cs | 12 + Source/Core/GZBuilder/Models/ModelLoader.cs | 366 ++++ .../Core/GZBuilder/Models/OBJModelLoader.cs | 371 ++++ .../GZBuilder/Models/UnrealModelLoader.cs | 286 +++ Source/Core/GZBuilder/md3/ModelReader.cs | 1694 ----------------- Source/Core/Rendering/Renderer3D.cs | 2 +- Source/Core/Rendering/Vector3.cs | 10 + Source/Core/Rendering/Vector4.cs | 40 + Source/Core/VisualModes/VisualThing.cs | 2 +- Source/Core/ZDoom/ModeldefStructure.cs | 4 +- 19 files changed, 2846 insertions(+), 1725 deletions(-) rename Source/Core/GZBuilder/{md3 => Models}/GZModel.cs (86%) mode change 100755 => 100644 create mode 100644 Source/Core/GZBuilder/Models/IQMModelLoader.cs create mode 100644 Source/Core/GZBuilder/Models/KVXModelLoader.cs create mode 100644 Source/Core/GZBuilder/Models/MD2ModelLoader.cs create mode 100644 Source/Core/GZBuilder/Models/MD3ModelLoader.cs create mode 100644 Source/Core/GZBuilder/Models/ModelLoadResult.cs create mode 100644 Source/Core/GZBuilder/Models/ModelLoader.cs create mode 100644 Source/Core/GZBuilder/Models/OBJModelLoader.cs create mode 100644 Source/Core/GZBuilder/Models/UnrealModelLoader.cs delete mode 100755 Source/Core/GZBuilder/md3/ModelReader.cs diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj index ddd9b274..2780ba3e 100644 --- a/Source/Core/Builder.csproj +++ b/Source/Core/Builder.csproj @@ -244,6 +244,15 @@ + + + + + + + + + True True @@ -1107,8 +1116,6 @@ - - Form @@ -1603,6 +1610,7 @@ false + echo Current Output Directory: %25cd%25 echo Copying platform-appropriate Updater.ini. diff --git a/Source/Core/BuilderMono.csproj b/Source/Core/BuilderMono.csproj index 821e581b..c6df9fb3 100644 --- a/Source/Core/BuilderMono.csproj +++ b/Source/Core/BuilderMono.csproj @@ -236,6 +236,15 @@ + + + + + + + + + True True @@ -1099,8 +1108,6 @@ - - Form diff --git a/Source/Core/Data/DataManager.cs b/Source/Core/Data/DataManager.cs index 1d04b97e..a0e14850 100755 --- a/Source/Core/Data/DataManager.cs +++ b/Source/Core/Data/DataManager.cs @@ -29,7 +29,7 @@ using System.Windows.Forms; using CodeImp.DoomBuilder.Config; using CodeImp.DoomBuilder.Data.Scripting; using CodeImp.DoomBuilder.GZBuilder.Data; -using CodeImp.DoomBuilder.GZBuilder.MD3; +using CodeImp.DoomBuilder.GZBuilder.Models; using CodeImp.DoomBuilder.Geometry; using CodeImp.DoomBuilder.IO; using CodeImp.DoomBuilder.Map; @@ -830,7 +830,7 @@ namespace CodeImp.DoomBuilder.Data if(modeldefentries[type].LoadState != ModelLoadState.None) return true; //create models - ModelReader.Load(modeldefentries[type], containers); + ModelLoader.Load(modeldefentries[type], containers); if(modeldefentries[type].Model != null) { @@ -3557,7 +3557,7 @@ namespace CodeImp.DoomBuilder.Data // Load the skysphere model... BoundingBoxSizes bbs = new BoundingBoxSizes(); Stream modeldata = General.ThisAssembly.GetManifestResourceStream("CodeImp.DoomBuilder.Resources.SkySphere.md3"); - ModelReader.MD3LoadResult meshes = ModelReader.ReadMD3Model(ref bbs, new Dictionary(), modeldata, 0); + ModelLoadResult meshes = MD3ModelLoader.Load(ref bbs, new Dictionary(), modeldata, 0); if(meshes.Meshes.Count != 3) throw new Exception("Skybox creation failed: " + (string.IsNullOrEmpty(meshes.Errors) ? "skybox model must contain 3 surfaces" : meshes.Errors)); diff --git a/Source/Core/GZBuilder/Data/ModelData.cs b/Source/Core/GZBuilder/Data/ModelData.cs index 5424ba5e..58461086 100755 --- a/Source/Core/GZBuilder/Data/ModelData.cs +++ b/Source/Core/GZBuilder/Data/ModelData.cs @@ -1,7 +1,7 @@ #region ================== Namespaces using System.Collections.Generic; -using CodeImp.DoomBuilder.GZBuilder.MD3; +using CodeImp.DoomBuilder.GZBuilder.Models; using CodeImp.DoomBuilder.Rendering; #endregion diff --git a/Source/Core/GZBuilder/md3/GZModel.cs b/Source/Core/GZBuilder/Models/GZModel.cs old mode 100755 new mode 100644 similarity index 86% rename from Source/Core/GZBuilder/md3/GZModel.cs rename to Source/Core/GZBuilder/Models/GZModel.cs index 119af3c1..42c215ce --- a/Source/Core/GZBuilder/md3/GZModel.cs +++ b/Source/Core/GZBuilder/Models/GZModel.cs @@ -1,20 +1,20 @@ -using System.Collections.Generic; -using CodeImp.DoomBuilder.Rendering; -using CodeImp.DoomBuilder.GZBuilder.Data; - -namespace CodeImp.DoomBuilder.GZBuilder.MD3 -{ - internal class GZModel - { - internal readonly List Meshes; - internal readonly List Textures; - internal float Radius; - internal BoundingBoxSizes BBox; - - internal GZModel() - { - Meshes = new List(); - Textures = new List(); - } - } +using System.Collections.Generic; +using CodeImp.DoomBuilder.Rendering; +using CodeImp.DoomBuilder.GZBuilder.Data; + +namespace CodeImp.DoomBuilder.GZBuilder.Models +{ + internal class GZModel + { + internal readonly List Meshes; + internal readonly List Textures; + internal float Radius; + internal BoundingBoxSizes BBox; + + internal GZModel() + { + Meshes = new List(); + Textures = new List(); + } + } } \ No newline at end of file diff --git a/Source/Core/GZBuilder/Models/IQMModelLoader.cs b/Source/Core/GZBuilder/Models/IQMModelLoader.cs new file mode 100644 index 00000000..de10a61d --- /dev/null +++ b/Source/Core/GZBuilder/Models/IQMModelLoader.cs @@ -0,0 +1,1016 @@ +using CodeImp.DoomBuilder.Geometry; +using CodeImp.DoomBuilder.GZBuilder.Data; +using CodeImp.DoomBuilder.Rendering; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace CodeImp.DoomBuilder.GZBuilder.Models +{ + internal class IQMModelLoader : ModelLoader + { + public static ModelLoadResult Load(ref BoundingBoxSizes bbs, Dictionary skins, Stream s, int frame) + { + try + { + var reader = new IQMFileReader(s); + + if (!reader.ReadBytes(16).SequenceEqual(Encoding.ASCII.GetBytes("INTERQUAKEMODEL\0"))) + throw new Exception("Not an IQM file!"); + + uint version = reader.ReadUInt32(); + if (version != 2) + throw new Exception("Unsupported IQM version"); + + uint filesize = reader.ReadUInt32(); + uint flags = reader.ReadUInt32(); + uint num_text = reader.ReadUInt32(); + uint ofs_text = reader.ReadUInt32(); + uint num_meshes = reader.ReadUInt32(); + uint ofs_meshes = reader.ReadUInt32(); + uint num_vertexarrays = reader.ReadUInt32(); + uint num_vertices = reader.ReadUInt32(); + uint ofs_vertexarrays = reader.ReadUInt32(); + uint num_triangles = reader.ReadUInt32(); + uint ofs_triangles = reader.ReadUInt32(); + uint ofs_adjacency = reader.ReadUInt32(); + uint num_joints = reader.ReadUInt32(); + uint ofs_joints = reader.ReadUInt32(); + uint num_poses = reader.ReadUInt32(); + uint ofs_poses = reader.ReadUInt32(); + uint num_anims = reader.ReadUInt32(); + uint ofs_anims = reader.ReadUInt32(); + uint num_frames = reader.ReadUInt32(); + uint num_framechannels = reader.ReadUInt32(); + uint ofs_frames = reader.ReadUInt32(); + uint ofs_bounds = reader.ReadUInt32(); + uint num_comment = reader.ReadUInt32(); + uint ofs_comment = reader.ReadUInt32(); + uint num_extensions = reader.ReadUInt32(); + uint ofs_extensions = reader.ReadUInt32(); + + if (num_text == 0) + throw new Exception("IQM model needs material names"); + + reader.SeekTo(ofs_text); + var text = reader.ReadBytes((int)num_text); + text[text.Length - 1] = 0; + + var Meshes = new List(); + var Indexes = new int[3 * num_triangles]; + var Adjacency = new int[3 * num_triangles]; + var Joints = new List(); + var Poses = new List(); + var Anims = new List(); + var Bounds = new List(); + var VertexArrays = new List(); + var baseframe = new List(); + var inversebaseframe = new List(); + var TRSData = new List(); + + reader.SeekTo(ofs_meshes); + for (int i = 0; i < num_meshes; i++) + { + var mesh = new IQMMesh(); + mesh.Name = reader.ReadName(text); + mesh.Material = reader.ReadName(text); + mesh.FirstVertex = reader.ReadUInt32(); + mesh.NumVertices = reader.ReadUInt32(); + mesh.FirstTriangle = reader.ReadUInt32(); + mesh.NumTriangles = reader.ReadUInt32(); + Meshes.Add(mesh); + } + + reader.SeekTo(ofs_triangles); + for (int i = 0; i < num_triangles * 3; i++) + { + Indexes[i] = reader.ReadInt32(); + } + + reader.SeekTo(ofs_adjacency); + for (int i = 0; i < num_triangles * 3; i++) + { + Adjacency[i] = reader.ReadInt32(); + } + + reader.SeekTo(ofs_joints); + for (int i = 0; i < num_joints; i++) + { + var joint = new IQMJoint(); + joint.Name = reader.ReadName(text); + joint.Parent = reader.ReadInt32(); + joint.Translate.X = reader.ReadSingle(); + joint.Translate.Y = reader.ReadSingle(); + joint.Translate.Z = reader.ReadSingle(); + joint.Quaternion.X = reader.ReadSingle(); + joint.Quaternion.Y = reader.ReadSingle(); + joint.Quaternion.Z = reader.ReadSingle(); + joint.Quaternion.W = reader.ReadSingle(); + joint.Quaternion.Normalize(); + joint.Scale.X = reader.ReadSingle(); + joint.Scale.Y = reader.ReadSingle(); + joint.Scale.Z = reader.ReadSingle(); + Joints.Add(joint); + } + + reader.SeekTo(ofs_poses); + for (int j = 0; j < num_poses; j++) + { + var pose = new IQMPose(); + pose.Parent = reader.ReadInt32(); + pose.ChannelMask = reader.ReadUInt32(); + for (int i = 0; i < 10; i++) pose.ChannelOffset[i] = reader.ReadSingle(); + for (int i = 0; i < 10; i++) pose.ChannelScale[i] = reader.ReadSingle(); + Poses.Add(pose); + } + + reader.SeekTo(ofs_anims); + for (int i = 0; i < num_anims; i++) + { + var anim = new IQMAnim(); + anim.Name = reader.ReadName(text); + anim.FirstFrame = reader.ReadUInt32(); + anim.NumFrames = reader.ReadUInt32(); + anim.Framerate = reader.ReadSingle(); + anim.Loop = (reader.ReadUInt32() & 1) == 1; + Anims.Add(anim); + } + + for (uint i = 0; i < num_joints; i++) + { + var bf = new IQMMatrix(); + var ibf = new IQMMatrix(); + + IQMJoint j = Joints[(int)i]; + + var m = new IQMMatrix(); + m.LoadIdentity(); + m.Translate(j.Translate.X, j.Translate.Y, j.Translate.Z); + m.MultQuaternion(j.Quaternion); + m.Scale(j.Scale.X, j.Scale.Y, j.Scale.Z); + + var invm = new IQMMatrix(); + invm = m.InverseMatrix(); + + if (j.Parent >= 0) + { + bf.LoadMatrix(baseframe[j.Parent]); + bf.MultMatrix(m); + ibf = invm; + ibf.MultMatrix(inversebaseframe[j.Parent]); + } + else + { + bf = m; + ibf = invm; + } + + baseframe.Add(bf); + inversebaseframe.Add(ibf); + } + + if (num_frames != 0) + { + reader.SeekTo(ofs_frames); + for (uint i = 0; i < num_frames; i++) + { + for (uint j = 0; j < num_poses; j++) + { + IQMPose p = Poses[(int)j]; + + var translate = new Vector3f(); + translate.X = p.ChannelOffset[0]; if ((p.ChannelMask & 0x01) != 0) translate.X += reader.ReadUInt16() * p.ChannelScale[0]; + translate.Y = p.ChannelOffset[1]; if ((p.ChannelMask & 0x02) != 0) translate.Y += reader.ReadUInt16() * p.ChannelScale[1]; + translate.Z = p.ChannelOffset[2]; if ((p.ChannelMask & 0x04) != 0) translate.Z += reader.ReadUInt16() * p.ChannelScale[2]; + + var quaternion = new Vector4f(); + quaternion.X = p.ChannelOffset[3]; if ((p.ChannelMask & 0x08) != 0) quaternion.X += reader.ReadUInt16() * p.ChannelScale[3]; + quaternion.Y = p.ChannelOffset[4]; if ((p.ChannelMask & 0x10) != 0) quaternion.Y += reader.ReadUInt16() * p.ChannelScale[4]; + quaternion.Z = p.ChannelOffset[5]; if ((p.ChannelMask & 0x20) != 0) quaternion.Z += reader.ReadUInt16() * p.ChannelScale[5]; + quaternion.W = p.ChannelOffset[6]; if ((p.ChannelMask & 0x40) != 0) quaternion.W += reader.ReadUInt16() * p.ChannelScale[6]; + quaternion.Normalize(); + + var scale = new Vector3f(); + scale.X = p.ChannelOffset[7]; if ((p.ChannelMask & 0x80) != 0) scale.X += reader.ReadUInt16() * p.ChannelScale[7]; + scale.Y = p.ChannelOffset[8]; if ((p.ChannelMask & 0x100) != 0) scale.Y += reader.ReadUInt16() * p.ChannelScale[8]; + scale.Z = p.ChannelOffset[9]; if ((p.ChannelMask & 0x200) != 0) scale.Z += reader.ReadUInt16() * p.ChannelScale[9]; + + var trs = new TRS(); + trs.translation = translate; + trs.rotation = quaternion; + trs.scaling = scale; + TRSData.Add(trs); + } + } + } + else + { + num_frames = 1; + for (uint j = 0; j < num_joints; j++) + { + var translate = new Vector3f(); + translate.X = Joints[(int)j].Translate.X; + translate.Y = Joints[(int)j].Translate.Y; + translate.Z = Joints[(int)j].Translate.Z; + + var quaternion = new Vector4f(); + quaternion.X = Joints[(int)j].Quaternion.X; + quaternion.Y = Joints[(int)j].Quaternion.Y; + quaternion.Z = Joints[(int)j].Quaternion.Z; + quaternion.W = Joints[(int)j].Quaternion.W; + quaternion.Normalize(); + + var scale = new Vector3f(); + scale.X = Joints[(int)j].Scale.X; + scale.Y = Joints[(int)j].Scale.Y; + scale.Z = Joints[(int)j].Scale.Z; + + var trs = new TRS(); + trs.translation = translate; + trs.rotation = quaternion; + trs.scaling = scale; + TRSData.Add(trs); + } + } + + reader.SeekTo(ofs_bounds); + for (int i = 0; i < num_frames; i++) + { + var bound = new IQMBounds(); + bound.BBMins[0] = reader.ReadSingle(); + bound.BBMins[1] = reader.ReadSingle(); + bound.BBMins[2] = reader.ReadSingle(); + bound.BBMaxs[0] = reader.ReadSingle(); + bound.BBMaxs[1] = reader.ReadSingle(); + bound.BBMaxs[2] = reader.ReadSingle(); + bound.XYRadius = reader.ReadSingle(); + bound.Radius = reader.ReadSingle(); + Bounds.Add(bound); + } + + reader.SeekTo(ofs_vertexarrays); + for (int i = 0; i < num_vertexarrays; i++) + { + var vertexArray = new IQMVertexArray(); + vertexArray.Type = (IQMVertexArrayType)reader.ReadUInt32(); + vertexArray.Flags = reader.ReadUInt32(); + vertexArray.Format = (IQMVertexArrayFormat)reader.ReadUInt32(); + vertexArray.Size = reader.ReadUInt32(); + vertexArray.Offset = reader.ReadUInt32(); + VertexArrays.Add(vertexArray); + } + + var verts = new IQMVertex[num_vertices]; + + foreach (IQMVertexArray vertexArray in VertexArrays) + { + reader.SeekTo(vertexArray.Offset); + if (vertexArray.Type == IQMVertexArrayType.IQM_POSITION) + { + LoadPosition(reader, vertexArray, verts); + } + else if (vertexArray.Type == IQMVertexArrayType.IQM_TEXCOORD) + { + LoadTexcoord(reader, vertexArray, verts); + } + else if (vertexArray.Type == IQMVertexArrayType.IQM_NORMAL) + { + LoadNormal(reader, vertexArray, verts); + } + else if (vertexArray.Type == IQMVertexArrayType.IQM_BLENDINDEXES) + { + LoadBlendIndexes(reader, vertexArray, verts); + } + else if (vertexArray.Type == IQMVertexArrayType.IQM_BLENDWEIGHTS) + { + LoadBlendWeights(reader, vertexArray, verts); + } + } + + // Convert vertices to a single frame by applying the bones: + + if (frame >= num_frames) + frame = 0; + + List bones = CalculateBones(frame, frame, 0.0f, Joints, baseframe, inversebaseframe, TRSData); + List normalbones = ToNormalMatrixBones(bones); + + float angleOfsetCos = (float)Math.Cos(-Angle2D.PIHALF); + float angleOfsetSin = (float)Math.Sin(-Angle2D.PIHALF); + + var worldverts = new WorldVertex[num_vertices]; + for (int i = 0; i < (int)num_vertices; i++) + { + IQMVertex v = verts[i]; + + if (v.boneweightX != 0 || v.boneweightY != 0 || v.boneweightZ != 0 || v.boneweightW != 0) + { + float totalWeight = v.boneweightX + v.boneweightY + v.boneweightZ + v.boneweightW; + var boneWeight = new Vector4f(v.boneweightX / totalWeight, v.boneweightY / totalWeight, v.boneweightZ / totalWeight, v.boneweightW / totalWeight); + + var pos = new Vector3f(0.0f, 0.0f, 0.0f); + var normal = new Vector3f(0.0f, 0.0f, 0.0f); + + if (v.boneweightX != 0) + { + pos += bones[v.boneindexX].MultVector(v.pos) * boneWeight.X; + normal += normalbones[v.boneindexX].MultVector(v.normal) * boneWeight.X; + } + + if (v.boneweightY != 0) + { + pos += bones[v.boneindexY].MultVector(v.pos) * boneWeight.Y; + normal += normalbones[v.boneindexY].MultVector(v.normal) * boneWeight.Y; + } + + if (v.boneweightZ != 0) + { + pos += bones[v.boneindexZ].MultVector(v.pos) * boneWeight.Z; + normal += normalbones[v.boneindexZ].MultVector(v.normal) * boneWeight.Z; + } + + if (v.boneweightW != 0) + { + pos += bones[v.boneindexW].MultVector(v.pos) * boneWeight.W; + normal += normalbones[v.boneindexW].MultVector(v.normal) * boneWeight.W; + } + + v.pos = pos; + v.normal = normal; + } + + // Fix rotation angle + float rx = angleOfsetCos * v.pos.X - angleOfsetSin * v.pos.Y; + float ry = angleOfsetSin * v.pos.X + angleOfsetCos * v.pos.Y; + + worldverts[i].x = rx; + worldverts[i].y = ry; + worldverts[i].z = v.pos.Z; + worldverts[i].nx = v.normal.X; + worldverts[i].ny = v.normal.Y; + worldverts[i].nz = v.normal.Z; + worldverts[i].u = v.u; + worldverts[i].v = v.v; + worldverts[i].c = -1; + } + + // Create the mesh: + + ModelLoadResult result = new ModelLoadResult(); + foreach (IQMMesh mesh in Meshes) + { + // UDB doesn't support sharing vertices between skins. + + var skinverts = new WorldVertex[mesh.NumVertices]; + var skinindexes = new int[mesh.NumTriangles * 3]; + + uint firstVertex = mesh.FirstVertex; + uint firstIndex = mesh.FirstTriangle * 3; + + for (uint i = 0; i < mesh.NumVertices; i++) + { + skinverts[i] = worldverts[mesh.FirstVertex + i]; + } + + for (uint i = 0; i < mesh.NumTriangles * 3; i++) + { + skinindexes[i] = Indexes[firstIndex + i] - (int)firstVertex; + } + + result.Meshes.Add(new Mesh(General.Map.Graphics, skinverts, skinindexes)); + result.Skins.Add(mesh.Material); + } + return result; + } + catch (Exception e) + { + ModelLoadResult result = new ModelLoadResult(); + result.Errors = e.Message; + return result; + } + } + + static void LoadPosition(IQMFileReader reader, IQMVertexArray vertexArray, IQMVertex[] verts) + { + if (vertexArray.Format == IQMVertexArrayFormat.IQM_FLOAT && vertexArray.Size == 3) + { + for (int i = 0; i < verts.Length; i++) + { + verts[i].pos.X = reader.ReadSingle(); + verts[i].pos.Y = reader.ReadSingle(); + verts[i].pos.Z = reader.ReadSingle(); + } + } + else + { + throw new Exception("Unsupported IQM_POSITION vertex format"); + } + } + + static void LoadTexcoord(IQMFileReader reader, IQMVertexArray vertexArray, IQMVertex[] verts) + { + if (vertexArray.Format == IQMVertexArrayFormat.IQM_FLOAT && vertexArray.Size == 2) + { + for (int i = 0; i < verts.Length; i++) + { + verts[i].u = reader.ReadSingle(); + verts[i].v = reader.ReadSingle(); + } + } + else + { + throw new Exception("Unsupported IQM_TEXCOORD vertex format"); + } + } + + static void LoadNormal(IQMFileReader reader, IQMVertexArray vertexArray, IQMVertex[] verts) + { + if (vertexArray.Format == IQMVertexArrayFormat.IQM_FLOAT && vertexArray.Size == 3) + { + for (int i = 0; i < verts.Length; i++) + { + verts[i].normal.X = reader.ReadSingle(); + verts[i].normal.Y = reader.ReadSingle(); + verts[i].normal.Z = reader.ReadSingle(); + } + } + else + { + throw new Exception("Unsupported IQM_NORMAL vertex format"); + } + } + + static void LoadBlendIndexes(IQMFileReader reader, IQMVertexArray vertexArray, IQMVertex[] verts) + { + if (vertexArray.Format == IQMVertexArrayFormat.IQM_UBYTE && vertexArray.Size == 4) + { + for (int i = 0; i < verts.Length; i++) + { + verts[i].boneindexX = reader.ReadByte(); + verts[i].boneindexY = reader.ReadByte(); + verts[i].boneindexZ = reader.ReadByte(); + verts[i].boneindexW = reader.ReadByte(); + } + } + else if (vertexArray.Format == IQMVertexArrayFormat.IQM_INT && vertexArray.Size == 4) + { + for (int i = 0; i < verts.Length; i++) + { + verts[i].boneindexX = (byte)reader.ReadInt32(); + verts[i].boneindexY = (byte)reader.ReadInt32(); + verts[i].boneindexZ = (byte)reader.ReadInt32(); + verts[i].boneindexW = (byte)reader.ReadInt32(); + } + } + else + { + throw new Exception("Unsupported IQM_BLENDINDEXES vertex format"); + } + } + + static void LoadBlendWeights(IQMFileReader reader, IQMVertexArray vertexArray, IQMVertex[] verts) + { + if (vertexArray.Format == IQMVertexArrayFormat.IQM_UBYTE && vertexArray.Size == 4) + { + for (int i = 0; i < verts.Length; i++) + { + verts[i].boneweightX = reader.ReadByte(); + verts[i].boneweightY = reader.ReadByte(); + verts[i].boneweightZ = reader.ReadByte(); + verts[i].boneweightW = reader.ReadByte(); + } + } + else if (vertexArray.Format == IQMVertexArrayFormat.IQM_FLOAT && vertexArray.Size == 4) + { + for (int i = 0; i < verts.Length; i++) + { + verts[i].boneweightX = (byte)Clamp(reader.ReadSingle() * 255.0f, 0.0f, 255.0f); + verts[i].boneweightY = (byte)Clamp(reader.ReadSingle() * 255.0f, 0.0f, 255.0f); + verts[i].boneweightZ = (byte)Clamp(reader.ReadSingle() * 255.0f, 0.0f, 255.0f); + verts[i].boneweightW = (byte)Clamp(reader.ReadSingle() * 255.0f, 0.0f, 255.0f); + } + } + else + { + throw new Exception("Unsupported IQM_BLENDWEIGHTS vertex format"); + } + } + + static List ToNormalMatrixBones(List bones) + { + var normalbones = new List(); + foreach (IQMMatrix m in bones) + { + IQMMatrix nm = new IQMMatrix(); + nm.LoadMatrix(m); + for (int i = 0; i < 4; i++) + { + nm.Matrix[i + 3 * 4] = 0; + nm.Matrix[i * 4 + 3] = 0; + } + normalbones.Add(nm); + } + return normalbones; + } + + static List CalculateBones(int frame1, int frame2, float t, List Joints, List baseframe, List inversebaseframe, List animationFrames) + { + var bones = new List(); + if (Joints.Count != 0) + { + int numbones = Joints.Count; + + frame1 = Clamp(frame1, 0, (animationFrames.Count - 1) / numbones); + frame2 = Clamp(frame2, 0, (animationFrames.Count - 1) / numbones); + + int offset1 = frame1 * numbones; + int offset2 = frame2 * numbones; + float invt = 1.0f - t; + + for (int i = 0; i < numbones; i++) + { + TRS from = animationFrames[offset1 + i]; + TRS to = animationFrames[offset2 + i]; + + var bone = new TRS(); + bone.translation = from.translation * invt + to.translation * t; + bone.rotation = from.rotation * invt; + if (Vector4f.Dot(bone.rotation, to.rotation * t) < 0) + { + bone.rotation.X *= -1; + bone.rotation.Y *= -1; + bone.rotation.Z *= -1; + bone.rotation.W *= -1; + } + bone.rotation += to.rotation * t; + bone.rotation.Normalize(); + bone.scaling = from.scaling * invt + to.scaling * t; + + var m = new IQMMatrix(); + m.LoadIdentity(); + m.Translate(bone.translation.X, bone.translation.Y, bone.translation.Z); + m.MultQuaternion(bone.rotation); + m.Scale(bone.scaling.X, bone.scaling.Y, bone.scaling.Z); + + if (Joints[i].Parent >= 0) + { + var result = new IQMMatrix(); + result.LoadMatrix(bones[Joints[i].Parent]); + result.MultMatrix(baseframe[Joints[i].Parent]); + result.MultMatrix(m); + result.MultMatrix(inversebaseframe[i]); + bones.Add(result); + } + else + { + var result = new IQMMatrix(); + result.LoadMatrix(m); + result.MultMatrix(inversebaseframe[i]); + bones.Add(result); + } + } + } + return bones; + } + + static float Clamp(float v, float minval, float maxval) + { + return Math.Max(Math.Min(v, maxval), minval); + } + + static int Clamp(int v, int minval, int maxval) + { + return Math.Max(Math.Min(v, maxval), minval); + } + } + + class IQMMatrix + { + public float[] Matrix = new float[16]; + + public void LoadIdentity() + { + // fill matrix with 0s + for (int i = 0; i < 16; ++i) + Matrix[i] = 0.0f; + + // fill diagonal with 1s + for (int i = 0; i < 4; ++i) + Matrix[i + i * 4] = 1.0f; + } + + public void LoadMatrix(IQMMatrix m) + { + for (int i = 0; i < 16; i++) + Matrix[i] = m.Matrix[i]; + } + + public void Translate(float x, float y, float z) + { + Matrix[12] = Matrix[0] * x + Matrix[4] * y + Matrix[8] * z + Matrix[12]; + Matrix[13] = Matrix[1] * x + Matrix[5] * y + Matrix[9] * z + Matrix[13]; + Matrix[14] = Matrix[2] * x + Matrix[6] * y + Matrix[10] * z + Matrix[14]; + } + + public void Scale(float x, float y, float z) + { + Matrix[0] *= x; Matrix[1] *= x; Matrix[2] *= x; Matrix[3] *= x; + Matrix[4] *= y; Matrix[5] *= y; Matrix[6] *= y; Matrix[7] *= y; + Matrix[8] *= z; Matrix[9] *= z; Matrix[10] *= z; Matrix[11] *= z; + } + + public void MultMatrix(IQMMatrix m) + { + MultMatrix(m.Matrix); + } + + void MultMatrix(float[] aMatrix) + { + var res = new float[16]; + for (int i = 0; i < 4; ++i) + { + for (int j = 0; j < 4; ++j) + { + res[j * 4 + i] = 0.0f; + for (int k = 0; k < 4; ++k) + { + res[j * 4 + i] += Matrix[k * 4 + i] * aMatrix[j * 4 + k]; + } + } + } + Matrix = res; + } + + public void MultQuaternion(Vector4f q) + { + var m = new float[16]; + m[0 * 4 + 0] = 1.0f - 2.0f * q.Y * q.Y - 2.0f * q.Z * q.Z; + m[1 * 4 + 0] = 2.0f * q.X * q.Y - 2.0f * q.W * q.Z; + m[2 * 4 + 0] = 2.0f * q.X * q.Z + 2.0f * q.W * q.Y; + m[0 * 4 + 1] = 2.0f * q.X * q.Y + 2.0f * q.W * q.Z; + m[1 * 4 + 1] = 1.0f - 2.0f * q.X * q.X - 2.0f * q.Z * q.Z; + m[2 * 4 + 1] = 2.0f * q.Y * q.Z - 2.0f * q.W * q.X; + m[0 * 4 + 2] = 2.0f * q.X * q.Z - 2.0f * q.W * q.Y; + m[1 * 4 + 2] = 2.0f * q.Y * q.Z + 2.0f * q.W * q.X; + m[2 * 4 + 2] = 1.0f - 2.0f * q.X * q.X - 2.0f * q.Y * q.Y; + m[3 * 4 + 3] = 1.0f; + MultMatrix(m); + } + + public Vector3f MultVector(Vector3f v) + { + var result = MultVector(new Vector4f(v, 1.0f)); + return new Vector3f(result.X, result.Y, result.Z); + } + + public Vector4f MultVector(Vector4f v) + { + var result = new Vector4f(); + result.X = Matrix[0 * 4 + 0] * v.X + Matrix[1 * 4 + 0] * v.Y + Matrix[2 * 4 + 0] * v.Z + Matrix[3 * 4 + 0] * v.W; + result.Y = Matrix[0 * 4 + 1] * v.X + Matrix[1 * 4 + 1] * v.Y + Matrix[2 * 4 + 1] * v.Z + Matrix[3 * 4 + 1] * v.W; + result.Z = Matrix[0 * 4 + 2] * v.X + Matrix[1 * 4 + 2] * v.Y + Matrix[2 * 4 + 2] * v.Z + Matrix[3 * 4 + 2] * v.W; + result.W = Matrix[0 * 4 + 3] * v.X + Matrix[1 * 4 + 3] * v.Y + Matrix[2 * 4 + 3] * v.Z + Matrix[3 * 4 + 3] * v.W; + return result; + } + + public IQMMatrix InverseMatrix() + { + var result = new IQMMatrix(); + + // Calculate mat4 determinant + float det = mat4Determinant(Matrix); + + // Inverse unknown when determinant is close to zero + if (Math.Abs(det) < 1e-15) + { + for (int i = 0; i < 16; i++) + result.Matrix[i] = 0.0f; + } + else + { + mat4Adjoint(Matrix, result.Matrix); + + float invDet = 1.0f / det; + for (int i = 0; i < 16; i++) + { + result.Matrix[i] = result.Matrix[i] * invDet; + } + } + + return result; + } + + static float mat3Determinant(float[] mMat3x3) + { + return mMat3x3[0] * (mMat3x3[4] * mMat3x3[8] - mMat3x3[5] * mMat3x3[7]) + + mMat3x3[1] * (mMat3x3[5] * mMat3x3[6] - mMat3x3[8] * mMat3x3[3]) + + mMat3x3[2] * (mMat3x3[3] * mMat3x3[7] - mMat3x3[4] * mMat3x3[6]); + } + + static float mat4Determinant(float[] matrix) + { + var mMat3x3_a = new float[] + { + matrix[1 * 4 + 1], matrix[2 * 4 + 1], matrix[3 * 4 + 1], + matrix[1 * 4 + 2], matrix[2 * 4 + 2], matrix[3 * 4 + 2], + matrix[1 * 4 + 3], matrix[2 * 4 + 3], matrix[3 * 4 + 3] + }; + + var mMat3x3_b = new float[] + { + matrix[1 * 4 + 0], matrix[2 * 4 + 0], matrix[3 * 4 + 0], + matrix[1 * 4 + 2], matrix[2 * 4 + 2], matrix[3 * 4 + 2], + matrix[1 * 4 + 3], matrix[2 * 4 + 3], matrix[3 * 4 + 3] + }; + + var mMat3x3_c = new float[] + { + matrix[1 * 4 + 0], matrix[2 * 4 + 0], matrix[3 * 4 + 0], + matrix[1 * 4 + 1], matrix[2 * 4 + 1], matrix[3 * 4 + 1], + matrix[1 * 4 + 3], matrix[2 * 4 + 3], matrix[3 * 4 + 3] + }; + + var mMat3x3_d = new float[] + { + matrix[1 * 4 + 0], matrix[2 * 4 + 0], matrix[3 * 4 + 0], + matrix[1 * 4 + 1], matrix[2 * 4 + 1], matrix[3 * 4 + 1], + matrix[1 * 4 + 2], matrix[2 * 4 + 2], matrix[3 * 4 + 2] + }; + + float a, b, c, d; + float value; + + a = mat3Determinant(mMat3x3_a); + b = mat3Determinant(mMat3x3_b); + c = mat3Determinant(mMat3x3_c); + d = mat3Determinant(mMat3x3_d); + + value = matrix[0 * 4 + 0] * a; + value -= matrix[0 * 4 + 1] * b; + value += matrix[0 * 4 + 2] * c; + value -= matrix[0 * 4 + 3] * d; + + return value; + } + + static void mat4Adjoint(float[] matrix, float[] result) + { + var mMat3x3_a = new float[] + { + matrix[1 * 4 + 1], matrix[2 * 4 + 1], matrix[3 * 4 + 1], + matrix[1 * 4 + 2], matrix[2 * 4 + 2], matrix[3 * 4 + 2], + matrix[1 * 4 + 3], matrix[2 * 4 + 3], matrix[3 * 4 + 3] + }; + + var mMat3x3_b = new float[] + { + matrix[1 * 4 + 0], matrix[2 * 4 + 0], matrix[3 * 4 + 0], + matrix[1 * 4 + 2], matrix[2 * 4 + 2], matrix[3 * 4 + 2], + matrix[1 * 4 + 3], matrix[2 * 4 + 3], matrix[3 * 4 + 3] + }; + + var mMat3x3_c = new float[] + { + matrix[1 * 4 + 0], matrix[2 * 4 + 0], matrix[3 * 4 + 0], + matrix[1 * 4 + 1], matrix[2 * 4 + 1], matrix[3 * 4 + 1], + matrix[1 * 4 + 3], matrix[2 * 4 + 3], matrix[3 * 4 + 3] + }; + + var mMat3x3_d = new float[] + { + matrix[1 * 4 + 0], matrix[2 * 4 + 0], matrix[3 * 4 + 0], + matrix[1 * 4 + 1], matrix[2 * 4 + 1], matrix[3 * 4 + 1], + matrix[1 * 4 + 2], matrix[2 * 4 + 2], matrix[3 * 4 + 2] + }; + + var mMat3x3_e = new float[] + { + matrix[0 * 4 + 1], matrix[2 * 4 + 1], matrix[3 * 4 + 1], + matrix[0 * 4 + 2], matrix[2 * 4 + 2], matrix[3 * 4 + 2], + matrix[0 * 4 + 3], matrix[2 * 4 + 3], matrix[3 * 4 + 3] + }; + + var mMat3x3_f = new float[] + { + matrix[0 * 4 + 0], matrix[2 * 4 + 0], matrix[3 * 4 + 0], + matrix[0 * 4 + 2], matrix[2 * 4 + 2], matrix[3 * 4 + 2], + matrix[0 * 4 + 3], matrix[2 * 4 + 3], matrix[3 * 4 + 3] + }; + + var mMat3x3_g = new float[] + { + matrix[0 * 4 + 0], matrix[2 * 4 + 0], matrix[3 * 4 + 0], + matrix[0 * 4 + 1], matrix[2 * 4 + 1], matrix[3 * 4 + 1], + matrix[0 * 4 + 3], matrix[2 * 4 + 3], matrix[3 * 4 + 3] + }; + + var mMat3x3_h = new float[] + { + matrix[0 * 4 + 0], matrix[2 * 4 + 0], matrix[3 * 4 + 0], + matrix[0 * 4 + 1], matrix[2 * 4 + 1], matrix[3 * 4 + 1], + matrix[0 * 4 + 2], matrix[2 * 4 + 2], matrix[3 * 4 + 2] + }; + + var mMat3x3_i = new float[] + { + matrix[0 * 4 + 1], matrix[1 * 4 + 1], matrix[3 * 4 + 1], + matrix[0 * 4 + 2], matrix[1 * 4 + 2], matrix[3 * 4 + 2], + matrix[0 * 4 + 3], matrix[1 * 4 + 3], matrix[3 * 4 + 3] + }; + + var mMat3x3_j = new float[] + { + matrix[0 * 4 + 0], matrix[1 * 4 + 0], matrix[3 * 4 + 0], + matrix[0 * 4 + 2], matrix[1 * 4 + 2], matrix[3 * 4 + 2], + matrix[0 * 4 + 3], matrix[1 * 4 + 3], matrix[3 * 4 + 3] + }; + + var mMat3x3_k = new float[] + { + matrix[0 * 4 + 0], matrix[1 * 4 + 0], matrix[3 * 4 + 0], + matrix[0 * 4 + 1], matrix[1 * 4 + 1], matrix[3 * 4 + 1], + matrix[0 * 4 + 3], matrix[1 * 4 + 3], matrix[3 * 4 + 3] + }; + + var mMat3x3_l = new float[] + { + matrix[0 * 4 + 0], matrix[1 * 4 + 0], matrix[3 * 4 + 0], + matrix[0 * 4 + 1], matrix[1 * 4 + 1], matrix[3 * 4 + 1], + matrix[0 * 4 + 2], matrix[1 * 4 + 2], matrix[3 * 4 + 2] + }; + + var mMat3x3_m = new float[] + { + matrix[0 * 4 + 1], matrix[1 * 4 + 1], matrix[2 * 4 + 1], + matrix[0 * 4 + 2], matrix[1 * 4 + 2], matrix[2 * 4 + 2], + matrix[0 * 4 + 3], matrix[1 * 4 + 3], matrix[2 * 4 + 3] + }; + + var mMat3x3_n = new float[] + { + matrix[0 * 4 + 0], matrix[1 * 4 + 0], matrix[2 * 4 + 0], + matrix[0 * 4 + 2], matrix[1 * 4 + 2], matrix[2 * 4 + 2], + matrix[0 * 4 + 3], matrix[1 * 4 + 3], matrix[2 * 4 + 3] + }; + + var mMat3x3_o = new float[] + { + matrix[0 * 4 + 0], matrix[1 * 4 + 0], matrix[2 * 4 + 0], + matrix[0 * 4 + 1], matrix[1 * 4 + 1], matrix[2 * 4 + 1], + matrix[0 * 4 + 3], matrix[1 * 4 + 3], matrix[2 * 4 + 3] + }; + + var mMat3x3_p = new float[] + { + matrix[0 * 4 + 0], matrix[1 * 4 + 0], matrix[2 * 4 + 0], + matrix[0 * 4 + 1], matrix[1 * 4 + 1], matrix[2 * 4 + 1], + matrix[0 * 4 + 2], matrix[1 * 4 + 2], matrix[2 * 4 + 2] + }; + + result[0 * 4 + 0] = mat3Determinant(mMat3x3_a); + result[1 * 4 + 0] = -mat3Determinant(mMat3x3_b); + result[2 * 4 + 0] = mat3Determinant(mMat3x3_c); + result[3 * 4 + 0] = -mat3Determinant(mMat3x3_d); + result[0 * 4 + 1] = -mat3Determinant(mMat3x3_e); + result[1 * 4 + 1] = mat3Determinant(mMat3x3_f); + result[2 * 4 + 1] = -mat3Determinant(mMat3x3_g); + result[3 * 4 + 1] = mat3Determinant(mMat3x3_h); + result[0 * 4 + 2] = mat3Determinant(mMat3x3_i); + result[1 * 4 + 2] = -mat3Determinant(mMat3x3_j); + result[2 * 4 + 2] = mat3Determinant(mMat3x3_k); + result[3 * 4 + 2] = -mat3Determinant(mMat3x3_l); + result[0 * 4 + 3] = -mat3Determinant(mMat3x3_m); + result[1 * 4 + 3] = mat3Determinant(mMat3x3_n); + result[2 * 4 + 3] = -mat3Determinant(mMat3x3_o); + result[3 * 4 + 3] = mat3Determinant(mMat3x3_p); + } + } + + class TRS + { + public Vector3f translation = new Vector3f(0, 0, 0); + public Vector4f rotation = new Vector4f(0, 0, 0, 1); + public Vector3f scaling = new Vector3f(0, 0, 0); + } + + struct IQMVertex + { + public Vector3f pos, normal; + public float u, v; + public byte boneindexX, boneindexY, boneindexZ, boneindexW; + public byte boneweightX, boneweightY, boneweightZ, boneweightW; + } + + class IQMMesh + { + public string Name; + public string Material; + public uint FirstVertex; + public uint NumVertices; + public uint FirstTriangle; + public uint NumTriangles; + }; + + enum IQMVertexArrayType + { + IQM_POSITION = 0, // float, 3 + IQM_TEXCOORD = 1, // float, 2 + IQM_NORMAL = 2, // float, 3 + IQM_TANGENT = 3, // float, 4 + IQM_BLENDINDEXES = 4, // ubyte, 4 + IQM_BLENDWEIGHTS = 5, // ubyte, 4 + IQM_COLOR = 6, // ubyte, 4 + IQM_CUSTOM = 0x10 + }; + + enum IQMVertexArrayFormat + { + IQM_BYTE = 0, + IQM_UBYTE = 1, + IQM_SHORT = 2, + IQM_USHORT = 3, + IQM_INT = 4, + IQM_UINT = 5, + IQM_HALF = 6, + IQM_FLOAT = 7, + IQM_DOUBLE = 8, + }; + + class IQMVertexArray + { + public IQMVertexArrayType Type; + public uint Flags; + public IQMVertexArrayFormat Format; + public uint Size; + public uint Offset; + }; + + class IQMJoint + { + public string Name; + public int Parent; // parent < 0 means this is a root bone + public Vector3f Translate; + public Vector4f Quaternion; + public Vector3f Scale; + }; + + class IQMPose + { + public int Parent; // parent < 0 means this is a root bone + public uint ChannelMask; // mask of which 10 channels are present for this joint pose + public float[] ChannelOffset = new float[10]; + public float[] ChannelScale = new float[10]; + // channels 0..2 are translation and channels 3..6 are quaternion rotation + // rotation is in relative/parent local space + // channels 7..9 are scale + // output = (input*scale)*rotation + translation + }; + + class IQMAnim + { + public string Name; + public uint FirstFrame; + public uint NumFrames; + public float Framerate; + public bool Loop; + }; + + class IQMBounds + { + public float[] BBMins = new float[3]; + public float[] BBMaxs = new float[3]; + public float XYRadius; + public float Radius; + }; + + class IQMFileReader : BinaryReader + { + public IQMFileReader(Stream s) : base(s) + { + } + + public string ReadName(byte[] textBuffer) + { + uint nameOffset = ReadUInt32(); + if (nameOffset >= textBuffer.Length) + throw new Exception("Name offset out of bounds"); + + for (uint i = nameOffset; i < (uint)textBuffer.Length; i++) + { + if (textBuffer[i] == 0) + { + return Encoding.ASCII.GetString(textBuffer, (int)nameOffset, (int)(i - nameOffset)); + } + } + + throw new Exception("Name not null terminated"); + } + + public void SeekTo(uint newPos) + { + BaseStream.Seek(newPos, SeekOrigin.Begin); + } + } +} diff --git a/Source/Core/GZBuilder/Models/KVXModelLoader.cs b/Source/Core/GZBuilder/Models/KVXModelLoader.cs new file mode 100644 index 00000000..6b5e01e7 --- /dev/null +++ b/Source/Core/GZBuilder/Models/KVXModelLoader.cs @@ -0,0 +1,302 @@ +using CodeImp.DoomBuilder.Geometry; +using CodeImp.DoomBuilder.GZBuilder.Data; +using CodeImp.DoomBuilder.Rendering; +using System; +using System.Collections.Generic; +using System.Drawing.Imaging; +using System.Drawing; +using System.IO; +using System.Text; + +namespace CodeImp.DoomBuilder.GZBuilder.Models +{ + internal class KVXModelLoader : ModelLoader + { + public static void Load(ModelData mde, Stream stream) + { + PixelColor[] palette = new PixelColor[256]; + List verts = new List(); + List indices = new List(); + Dictionary verthashes = new Dictionary(); + int xsize, ysize, zsize; + int facescount = 0; + 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 offsets = new List(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 colorIndices = new List(zleng); + for (int i = 0; i < zleng; i++) + { + colorIndices.Add(reader.ReadByte()); + } + + if ((flags & 16) != 0) + { + AddFace(verts, indices, verthashes, 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]); + facescount += 2; + } + + 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, indices, verthashes, 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]); + facescount += 2; + } + if ((flags & 2) != 0) + { + AddFace(verts, indices, verthashes, 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]); + facescount += 2; + } + if ((flags & 4) != 0) + { + AddFace(verts, indices, verthashes, 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]); + facescount += 2; + } + if ((flags & 8) != 0) + { + AddFace(verts, indices, verthashes, 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]); + facescount += 2; + } + + if (c == 0) c++; + z += c; + cstart += c; + } + + if ((flags & 32) != 0) + { + z = ztop + zleng - 1; + AddFace(verts, indices, verthashes, 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]); + facescount += 2; + } + } + } + + 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 new Texture(bmp.Width) + using (Bitmap bmp = CreateVoxelTexture(palette)) + { + mde.Model.Textures.Add(new Texture(General.Map.Graphics, bmp)); + } + + // Create mesh + Mesh mesh = new Mesh(General.Map.Graphics, verts.ToArray(), indices.ToArray()); + + // Add mesh + mde.Model.Meshes.Add(mesh); + } + + // Shameless GZDoom rip-off + private static void AddFace(List verts, List indices, Dictionary hashes, Vector3D v1, Vector3D v2, Vector3D v3, Vector3D v4, Vector3D pivot, int colorIndex) + { + float pu0 = (colorIndex % 16) / 16f; + float pu1 = pu0 + 0.001f; + float pv0 = (colorIndex / 16) / 16f; + float pv1 = pv0 + 0.001f; + + WorldVertex wv1 = new WorldVertex + { + x = (float)(v1.x - pivot.x), + y = (float)(-v1.y + pivot.y), + z = (float)(-v1.z + pivot.z), + c = -1, + u = pu0, + v = pv0 + }; + int i1 = AddVertex(wv1, verts, indices, hashes); + + WorldVertex wv2 = new WorldVertex + { + x = (float)(v2.x - pivot.x), + y = (float)(-v2.y + pivot.y), + z = (float)(-v2.z + pivot.z), + c = -1, + u = pu1, + v = pv1 + }; + AddVertex(wv2, verts, indices, hashes); + + WorldVertex wv4 = new WorldVertex + { + x = (float)(v4.x - pivot.x), + y = (float)(-v4.y + pivot.y), + z = (float)(-v4.z + pivot.z), + c = -1, + u = pu0, + v = pv0 + }; + int i4 = AddVertex(wv4, verts, indices, hashes); + + WorldVertex wv3 = new WorldVertex + { + x = (float)(v3.x - pivot.x), + y = (float)(-v3.y + pivot.y), + z = (float)(-v3.z + pivot.z), + c = -1, + u = pu1, + v = pv1 + }; + AddVertex(wv3, verts, indices, hashes); + + indices.Add(i1); + indices.Add(i4); + } + + // Returns index of added vert + private static int AddVertex(WorldVertex v, List verts, List indices, Dictionary hashes) + { + long hash; + unchecked // Overflow is fine, just wrap + { + hash = 2166136261; + hash = (hash * 16777619) ^ v.x.GetHashCode(); + hash = (hash * 16777619) ^ v.y.GetHashCode(); + hash = (hash * 16777619) ^ v.z.GetHashCode(); + hash = (hash * 16777619) ^ v.u.GetHashCode(); + hash = (hash * 16777619) ^ v.v.GetHashCode(); + } + + if (hashes.ContainsKey(hash)) + { + indices.Add(hashes[hash]); + return hashes[hash]; + } + else + { + verts.Add(v); + hashes.Add(hash, verts.Count - 1); + indices.Add(verts.Count - 1); + return verts.Count - 1; + } + } + + 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; + } + } +} diff --git a/Source/Core/GZBuilder/Models/MD2ModelLoader.cs b/Source/Core/GZBuilder/Models/MD2ModelLoader.cs new file mode 100644 index 00000000..f6234f81 --- /dev/null +++ b/Source/Core/GZBuilder/Models/MD2ModelLoader.cs @@ -0,0 +1,183 @@ +using CodeImp.DoomBuilder.Geometry; +using CodeImp.DoomBuilder.GZBuilder.Data; +using CodeImp.DoomBuilder.Rendering; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace CodeImp.DoomBuilder.GZBuilder.Models +{ + internal class MD2ModelLoader : ModelLoader + { + public static ModelLoadResult Load(ref BoundingBoxSizes bbs, Stream s, int frame, string framename) + { + long start = s.Position; + ModelLoadResult result = new ModelLoadResult(); + + 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 (frame < 0 || frame >= num_frames) + { + result.Errors = "frame " + frame + " is outside of model's frame range [0.." + (num_frames - 1) + "]"; + 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 polyIndecesList = new List(); + List uvIndecesList = new List(); + List uvCoordsList = new List(); + List vertList = new List(); + + // 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 Vector2f((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; + } + + Vector3f scale = new Vector3f(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); + Vector3f translate = new Vector3f(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(General.Map.Graphics, vertList.ToArray(), polyIndecesList.ToArray()); + + //store in result + result.Meshes.Add(mesh); + result.Skins.Add(""); //no skin support for MD2 + } + + return result; + } + } +} diff --git a/Source/Core/GZBuilder/Models/MD3ModelLoader.cs b/Source/Core/GZBuilder/Models/MD3ModelLoader.cs new file mode 100644 index 00000000..689764a8 --- /dev/null +++ b/Source/Core/GZBuilder/Models/MD3ModelLoader.cs @@ -0,0 +1,214 @@ +using CodeImp.DoomBuilder.GZBuilder.Data; +using CodeImp.DoomBuilder.Rendering; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace CodeImp.DoomBuilder.GZBuilder.Models +{ + internal class MD3ModelLoader : ModelLoader + { + internal static ModelLoadResult Load(ref BoundingBoxSizes bbs, Dictionary skins, Stream s, int frame) + { + long start = s.Position; + ModelLoadResult result = new ModelLoadResult(); + + 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 polyIndecesList = new List(); + List vertList = new List(); + + Dictionary>> polyIndecesListsPerTexture = new Dictionary>>(StringComparer.Ordinal); + Dictionary> vertListsPerTexture = new Dictionary>(StringComparer.Ordinal); + Dictionary> vertexOffsets = new Dictionary>(StringComparer.Ordinal); + bool useskins = false; + + 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; + } + + // Pick a skin to use + if (skins == null) + { + // skins is null when Skin MODELDEF property is set + skin = string.Empty; + } + else if (skins.ContainsKey(c)) + { + // Overrtide surface skin with SurfaceSkin MODELDEF property + skin = skins[c]; + } + + if (!string.IsNullOrEmpty(skin)) + { + useskins = true; + + if (polyIndecesListsPerTexture.ContainsKey(skin)) + { + polyIndecesListsPerTexture[skin].Add(polyIndecesList); + vertListsPerTexture[skin].AddRange(vertList.ToArray()); + vertexOffsets[skin].Add(vertList.Count); + } + else + { + polyIndecesListsPerTexture.Add(skin, new List> { polyIndecesList }); + vertListsPerTexture.Add(skin, vertList); + vertexOffsets.Add(skin, new List { vertList.Count }); + } + + //reset lists + polyIndecesList = new List(); + vertList = new List(); + } + } + + if (!useskins) + { + //create mesh + CreateMesh(ref result, vertList, polyIndecesList); + result.Skins.Add(""); + } + else + { + //create a mesh for each surface texture + foreach (KeyValuePair>> group in polyIndecesListsPerTexture) + { + polyIndecesList = new List(); + 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(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 polyIndecesList, List 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 "frame " + frame + " is outside of model's frame range [0.." + (numFrames - 1) + "]"; + } + + // 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 ""; + } + } +} diff --git a/Source/Core/GZBuilder/Models/ModelLoadResult.cs b/Source/Core/GZBuilder/Models/ModelLoadResult.cs new file mode 100644 index 00000000..3378d294 --- /dev/null +++ b/Source/Core/GZBuilder/Models/ModelLoadResult.cs @@ -0,0 +1,12 @@ +using CodeImp.DoomBuilder.Rendering; +using System.Collections.Generic; + +namespace CodeImp.DoomBuilder.GZBuilder.Models +{ + internal class ModelLoadResult + { + public List Skins = new List(); + public List Meshes = new List(); + public string Errors; + } +} diff --git a/Source/Core/GZBuilder/Models/ModelLoader.cs b/Source/Core/GZBuilder/Models/ModelLoader.cs new file mode 100644 index 00000000..a1c4e71c --- /dev/null +++ b/Source/Core/GZBuilder/Models/ModelLoader.cs @@ -0,0 +1,366 @@ +using CodeImp.DoomBuilder.Data; +using CodeImp.DoomBuilder.GZBuilder.Data; +using CodeImp.DoomBuilder.Rendering; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; + +namespace CodeImp.DoomBuilder.GZBuilder.Models +{ + internal class ModelLoader + { + #region ================== Load + + public static void Load(ModelData mde, List containers) + { + if (mde.IsVoxel) LoadKVX(mde, containers); + else LoadModel(mde, containers); + } + + private static void LoadKVX(ModelData mde, List containers) + { + mde.Model = new GZModel(); + string unused = string.Empty; + foreach (string name in mde.ModelNames) + { + //find the model + foreach (DataReader dr in containers) + { + Stream ms = dr.GetVoxelData(name, ref unused); + if (ms == null) continue; + + //load kvx + KVXModelLoader.Load(mde, ms); + + //done + ms.Close(); + break; + } + } + + //clear unneeded data + mde.SkinNames = null; + mde.ModelNames = null; + + if (mde.Model.Meshes == null || mde.Model.Meshes.Count == 0) + { + mde.Model = null; + } + } + + private static void LoadModel(ModelData mde, List containers) + { + mde.Model = new GZModel(); + BoundingBoxSizes bbs = new BoundingBoxSizes(); + ModelLoadResult result = new ModelLoadResult(); + + //load models and textures + for (int i = 0; i < mde.ModelNames.Count; i++) + { + // Use model skins? + // INFO: Skin MODELDEF property overrides both embedded surface names and ones set using SurfaceSkin MODELDEF property + Dictionary skins = null; + if (string.IsNullOrEmpty(mde.SkinNames[i])) + { + skins = (mde.SurfaceSkinNames[i].Count > 0 ? mde.SurfaceSkinNames[i] : new Dictionary()); + } + + // 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 = MD3ModelLoader.Load(ref bbs, skins, ms, mde.FrameIndices[i]); + break; + case ".md2": + result = MD2ModelLoader.Load(ref bbs, ms, mde.FrameIndices[i], mde.FrameNames[i]); + break; + case ".3d": + result = UnrealModelLoader.Load(ref bbs, skins, ms, mde.FrameIndices[i], mde.ModelNames[i], containers); + break; + case ".obj": + // OBJ doesn't support frames, so print out an error + if (mde.FrameIndices[i] > 0) + { + General.ErrorLogger.Add(ErrorType.Error, "Trying to load frame " + mde.FrameIndices[i] + " of model \"" + mde.ModelNames[i] + "\", but OBJ doesn't support frames!"); + continue; + } + result = OBJModelLoader.Load(ref bbs, skins, ms, mde.ModelNames[i]); + break; + case ".iqm": + if (!string.IsNullOrEmpty(mde.FrameNames[i])) + { + General.ErrorLogger.Add(ErrorType.Error, "Error while loading \"" + mde.ModelNames[i] + "\": frame names are not supported for IQM models!"); + continue; + } + result = IQMModelLoader.Load(ref bbs, skins, ms, mde.FrameIndices[i]); + break; + default: + result.Errors = "model format is not supported"; + break; + } + + ms.Close(); + if (result == null) + continue; + + //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); + + //load texture + List errors = new List(); + + // Texture not defined in MODELDEF? + if (skins != null) + { + //try to use model's own skins + for (int m = 0; m < result.Meshes.Count; m++) + { + // biwa. Makes sure to add a dummy texture if the MODELDEF skin definition is erroneous + if (m >= result.Skins.Count) + { + errors.Add("no skin defined for mesh " + m + "."); + mde.Model.Textures.Add(General.Map.Data.UnknownTexture3D.Texture); + continue; + } + + 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); + + if (!String.IsNullOrEmpty(mde.Path)) + path = Path.Combine(mde.Path, path); + + Texture t = GetTexture(containers, path); + + if (t != null) + { + mde.Model.Textures.Add(t); + continue; + } + + // That didn't work, let's try to load the texture without the additional path + path = result.Skins[m].Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + t = GetTexture(containers, path); + + if (t == null) + { + mde.Model.Textures.Add(General.Map.Data.UnknownTexture3D.Texture); + errors.Add("unable to load skin \"" + path + "\""); + continue; + } + + mde.Model.Textures.Add(t); + } + } + //Try to use texture loaded from MODELDEFS + else + { + Texture t = GetTexture(containers, mde.SkinNames[i]); + + if (t == null) + { + mde.Model.Textures.Add(General.Map.Data.UnknownTexture3D.Texture); + errors.Add("unable to load skin \"" + mde.SkinNames[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.SkinNames = 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); + bbs.MaxZ = (int)(bbs.MaxZ * mde.Scale.Z); + bbs.MinZ = (int)(bbs.MinZ * mde.Scale.Z); + + //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))); + mde.Model.BBox = bbs; + } + + private static Texture GetTexture(List containers, string texturename) + { + Texture t = null; + string[] extensions = new string[ModelData.SUPPORTED_TEXTURE_EXTENSIONS.Length + 1]; + + Array.Copy(ModelData.SUPPORTED_TEXTURE_EXTENSIONS, 0, extensions, 1, ModelData.SUPPORTED_TEXTURE_EXTENSIONS.Length); + extensions[0] = ""; + + // Try to load the texture as defined by its path. GZDoom doesn't care about extensions + if (t == null) + { + foreach (string extension in extensions) + { + string name = Path.ChangeExtension(texturename, null) + extension; + name = name.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + + t = LoadTexture(containers, name); + + if (t != null) + break; + } + } + + // Try to use an already defined texture. Again, just try out all extensions + foreach (string extension in extensions) + { + string name = Path.ChangeExtension(texturename, null) + extension; + + if (General.Map.Data.GetTextureExists(name)) + { + ImageData image = General.Map.Data.GetTextureImage(name); + image.LoadImageNow(false); + + t = image.Texture; + + break; + } + } + + // GZDoom can also ignore the path completely (because why not), so let's see if there's a texture with + // just the skin name + if (t == null) + { + string name = Path.ChangeExtension(Path.GetFileName(texturename), null); + + if (General.Map.Data.GetTextureExists(name)) + { + ImageData image = General.Map.Data.GetTextureImage(name); + image.LoadImageNow(false); + + t = image.Texture; + } + } + + // Or maybe it's a sprite + if (t == null) + { + string name = Path.ChangeExtension(texturename, null); + + if (General.Map.Data.GetSpriteExists(name)) + { + ImageData image = General.Map.Data.GetSpriteImage(name); + image.LoadImageNow(false); + + t = image.Texture; + } + } + + return t; + } + + #endregion + + #region ================== Utility + + protected static MemoryStream LoadFile(List 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; + } + + protected static Texture LoadTexture(List containers, string path) + { + if (string.IsNullOrEmpty(path)) return null; + + MemoryStream ms = LoadFile(containers, path, true); + if (ms == null) return null; + + Texture texture = null; + + //create texture + Bitmap bitmap = ImageDataFormat.TryLoadImage(ms); + if (bitmap != null) + { + texture = new Texture(General.Map.Graphics, bitmap); + } + + return texture; + } + + protected static void CreateMesh(ref ModelLoadResult result, List verts, List indices) + { + //create mesh + Mesh mesh = new Mesh(General.Map.Graphics, verts.ToArray(), indices.ToArray()); + + //store in result + result.Meshes.Add(mesh); + } + + protected 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 + } +} diff --git a/Source/Core/GZBuilder/Models/OBJModelLoader.cs b/Source/Core/GZBuilder/Models/OBJModelLoader.cs new file mode 100644 index 00000000..62a65ede --- /dev/null +++ b/Source/Core/GZBuilder/Models/OBJModelLoader.cs @@ -0,0 +1,371 @@ +using CodeImp.DoomBuilder.Geometry; +using CodeImp.DoomBuilder.GZBuilder.Data; +using CodeImp.DoomBuilder.Rendering; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; + +namespace CodeImp.DoomBuilder.GZBuilder.Models +{ + internal class OBJModelLoader : ModelLoader + { + public static ModelLoadResult Load(ref BoundingBoxSizes bbs, Dictionary skins, Stream s, string name) + { + ModelLoadResult result = new ModelLoadResult(); + + using (var reader = new StreamReader(s, Encoding.ASCII)) + { + string line; + int linenum = 1; + string message; + int surfaceskinid = 0; + List vertices = new List(); + List faces = new List(); + List normals = new List(); + List texcoords = new List(); + List worldvertices = new List(); + List polyindiceslist = new List(); + + while ((line = reader.ReadLine()) != null) + { + string[] fields = line.Trim().Split(new[] { ' ', '\t' }, 2, StringSplitOptions.RemoveEmptyEntries); + + // Empty line + if (fields.Length == 0) + { + linenum++; + continue; + } + + // Comment + if (fields[0].Trim() == "#") + { + linenum++; + continue; + } + + string keyword = fields[0].Trim(); + string payload = null; + + if (fields.Length == 2) + payload = fields[1].Trim(); + + switch (keyword) + { + case "v": + Vector3D v = new Vector3D(0, 0, 0); + + if (OBJParseVertex(payload, ref v, out message)) + vertices.Add(v); + else + { + result.Errors = String.Format("Error in line {0}: {1}", linenum, message); + return result; + } + + break; + case "vt": + Vector2D t = new Vector2D(0, 0); + + if (OBJParseTextureCoords(payload, ref t, out message)) + texcoords.Add(t); + else + { + result.Errors = String.Format("Error in line {0}: {1}", linenum, message); + return result; + } + + break; + case "vn": + Vector3D n = new Vector3D(0, 0, 0); + + if (OBJParseNormal(payload, ref n, out message)) + normals.Add(n); + else + { + result.Errors = String.Format("Error in line {0}: {1}", linenum, message); + return result; + } + + break; + case "f": + List fv = new List(); + List vt = new List(); + List vn = new List(); + + if (OBJParseFace(payload, ref fv, ref vt, ref vn, out message)) + { + // Sanity check for vertices + for (int i = 0; i < fv.Count; i++) + if (fv[i] != -1 && fv[i] >= vertices.Count) + { + result.Errors = String.Format("Error in line {0}: vertex {1} does not exist", linenum, fv[i] + 1); + return result; + } + + // Sanity check for texture coordinates + for (int i = 0; i < vt.Count; i++) + if (vt[i] != -1 && vt[i] >= texcoords.Count) + { + result.Errors = String.Format("Error in line {0}: texture coordinate {1} does not exist", linenum, vt[i] + 1); + return result; + } + + // Sanity check for normals + for (int i = 0; i < vn.Count; i++) + if (vn[i] != -1 && vn[i] >= normals.Count) + { + result.Errors = String.Format("Error in line {0}: vertex {1} does not exist", linenum, vn[i] + 1); + return result; + } + + int[] seq; + + // If the face is a quad split it into two triangles + if (fv.Count == 3) + seq = new int[] { 0, 1, 2 }; + else + seq = new int[] { 0, 1, 2, 0, 2, 3 }; + + for (int i = 0; i < seq.Length; i++) + { + WorldVertex wc = new WorldVertex(vertices[fv[seq[i]]]); + + if (vt[seq[i]] != -1) + { + wc.u = (float)texcoords[vt[seq[i]]].x; + wc.v = (float)texcoords[vt[seq[i]]].y; + } + + if (vn[seq[i]] != -1) + { + wc.nx = (float)normals[vn[seq[i]]].x; + wc.ny = (float)normals[vn[seq[i]]].y; + wc.nz = (float)normals[vn[seq[i]]].z; + } + + BoundingBoxTools.UpdateBoundingBoxSizes(ref bbs, wc); + + worldvertices.Add(wc); + polyindiceslist.Add(polyindiceslist.Count); + } + } + else + { + result.Errors = String.Format("Error in line {0}: {1}", linenum, message); + return result; + } + + break; + case "usemtl": + // If there's a new texture defined create a mesh from the current faces and + // start a gather new faces for the next mesh + if (worldvertices.Count > 0) + { + CreateMesh(ref result, worldvertices, polyindiceslist); + worldvertices.Clear(); + polyindiceslist.Clear(); + } + + // Add texture name. It might be in quotes, so remove them. + // See https://github.com/jewalky/UltimateDoomBuilder/issues/758 + if (fields.Length >= 2) + result.Skins.Add(fields[1].Replace("\"", "")); + + surfaceskinid++; + break; + case "": // Empty line + case "#": // Line is a comment + case "s": // Smooth + case "g": // Group + case "o": // Object + default: + break; + } + + linenum++; + } + + CreateMesh(ref result, worldvertices, polyindiceslist); + + // Overwrite internal textures with SurfaceSkin definitions if necessary + if (skins != null) + { + foreach (KeyValuePair group in skins) + { + // Add dummy skins if necessary + while (result.Skins.Count <= group.Key) + result.Skins.Add(String.Empty); + + result.Skins[group.Key] = group.Value; + } + } + } + + return result; + } + + private static bool OBJParseVertex(string payload, ref Vector3D v, out string message) + { + if (String.IsNullOrEmpty(payload)) + { + message = "no arguments given"; + return false; + } + + string[] fields = payload.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + + if (fields.Length < 3) + { + message = "too few arguments"; + return false; + } + + try + { + v.x = float.Parse(fields[0], CultureInfo.InvariantCulture); + v.z = float.Parse(fields[1], CultureInfo.InvariantCulture); + v.y = -float.Parse(fields[2], CultureInfo.InvariantCulture); + + + // Prepare to fix rotation angle + double angleOfsetCos = Math.Cos(-Angle2D.PIHALF); + double angleOfsetSin = Math.Sin(-Angle2D.PIHALF); + + // Fix rotation angle + double rx = angleOfsetCos * v.x - angleOfsetSin * v.y; + double ry = angleOfsetSin * v.x + angleOfsetCos * v.y; + v.x = rx; + v.y = ry; + } + catch (FormatException) + { + message = "field is not a float"; + return false; + } + + message = ""; + return true; + } + + private static bool OBJParseTextureCoords(string payload, ref Vector2D t, out string message) + { + if (String.IsNullOrEmpty(payload)) + { + message = "no arguments given"; + return false; + } + + string[] fields = payload.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + + if (fields.Length < 2) + { + message = "too few arguments"; + return false; + } + + try + { + t.x = float.Parse(fields[0], CultureInfo.InvariantCulture); + + if (fields.Length >= 2) + t.y = 1.0f - float.Parse(fields[1], CultureInfo.InvariantCulture); + else + t.y = 1.0f; + } + catch (FormatException) + { + message = "field is not a float"; + return false; + } + + message = ""; + return true; + } + + private static bool OBJParseNormal(string payload, ref Vector3D normal, out string message) + { + if (String.IsNullOrEmpty(payload)) + { + message = "no arguments given"; + return false; + } + + string[] fields = payload.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + + if (fields.Length < 3) + { + message = "too few arguments"; + return false; + } + + try + { + normal.x = float.Parse(fields[0], CultureInfo.InvariantCulture); + normal.y = float.Parse(fields[1], CultureInfo.InvariantCulture); + normal.z = float.Parse(fields[2], CultureInfo.InvariantCulture); + } + catch (FormatException) + { + message = "field is not a float"; + return false; + } + + message = ""; + return true; + } + + private static bool OBJParseFace(string payload, ref List face, ref List texcoords, ref List normals, out string message) + { + if (String.IsNullOrEmpty(payload)) + { + message = "no arguments given"; + return false; + } + + string[] fields = payload.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + + if (fields.Length < 3) + { + message = "too few arguments"; + return false; + } + + if (fields.Length > 4) + { + message = "faces with more than 4 sides are not supported"; + return false; + } + + try + { + for (int i = 0; i < fields.Length; i++) + { + string[] vertexdata = fields[i].Split('/'); + + face.Add(int.Parse(vertexdata[0], CultureInfo.InvariantCulture) - 1); + + if (vertexdata.Length > 1 && vertexdata[1] != "") + texcoords.Add(int.Parse(vertexdata[1], CultureInfo.InvariantCulture) - 1); + else + texcoords.Add(-1); + + if (vertexdata.Length > 2 && vertexdata[2] != "") + normals.Add(int.Parse(vertexdata[2], CultureInfo.InvariantCulture) - 1); + else + normals.Add(-1); + } + } + catch (FormatException) + { + message = "field is not an integer"; + return false; + } + + message = ""; + return true; + } + } +} diff --git a/Source/Core/GZBuilder/Models/UnrealModelLoader.cs b/Source/Core/GZBuilder/Models/UnrealModelLoader.cs new file mode 100644 index 00000000..d696e678 --- /dev/null +++ b/Source/Core/GZBuilder/Models/UnrealModelLoader.cs @@ -0,0 +1,286 @@ +using CodeImp.DoomBuilder.Data; +using CodeImp.DoomBuilder.Geometry; +using CodeImp.DoomBuilder.GZBuilder.Data; +using CodeImp.DoomBuilder.Rendering; +using System.Collections.Generic; +using System.IO; + +namespace CodeImp.DoomBuilder.GZBuilder.Models +{ + internal class UnrealModelLoader : ModelLoader + { + internal static ModelLoadResult Load(ref BoundingBoxSizes bbs, Dictionary skins, Stream s, int frame, string filename, List containers) + { + Stream stream_d; + Stream stream_a; + + if (filename.IndexOf("_d.3d") == filename.Length - 5) + { + string filename_a = filename.Replace("_d.3d", "_a.3d"); + stream_d = s; + stream_a = LoadFile(containers, filename_a, true); + if (stream_a == null) + { + General.ErrorLogger.Add(ErrorType.Error, "Error while loading \"" + filename + "\": unable to find corresponding \"_a.3d\" file."); + return null; + } + } + else + { + string filename_d = filename.Replace("_a.3d", "_d.3d"); + stream_a = s; + stream_d = LoadFile(containers, filename_d, true); + if (stream_d == null) + { + General.ErrorLogger.Add(ErrorType.Error, "Error while loading \"" + filename + "\": unable to find corresponding \"_d.3d\" file."); + return null; + } + } + + ModelLoadResult result = new ModelLoadResult(); + BinaryReader br_d = new BinaryReader(stream_d); + BinaryReader br_a = new BinaryReader(stream_a); + + // read d3d header + uint d3d_numpolys = br_d.ReadUInt16(); + uint d3d_numverts = br_d.ReadUInt16(); + stream_d.Position += 44; // bogusrot, bogusframe, bogusnorm[3], fixscale, unused[3], padding[12] + + long start_d = stream_d.Position; + + // read a3d header + uint a3d_numframes = br_a.ReadUInt16(); + uint a3d_framesize = br_a.ReadUInt16(); + + long start_a = stream_a.Position; + + // Sanity check + if (frame < 0 || frame >= a3d_numframes) + { + result.Errors = "frame " + frame + " is outside of model's frame range [0.." + (a3d_numframes - 1) + "]"; + return result; + } + + // check for deus ex format + bool isdeusex = false; + if ((a3d_framesize / d3d_numverts) == 8) isdeusex = true; + + // read vertices + WorldVertex[] vertices = new WorldVertex[d3d_numverts]; + for (uint i = 0; i < d3d_numverts; i++) + { + WorldVertex Vert = new WorldVertex(); + if (isdeusex) + { + stream_a.Position = start_a + (i + frame * d3d_numverts) * 8; + int vx = br_a.ReadInt16(); + int vy = br_a.ReadInt16(); + int vz = br_a.ReadInt16(); + Vert.y = -vx; + Vert.z = vz; + Vert.x = -vy; + } + else + { + stream_a.Position = start_a + (i + frame * d3d_numverts) * 4; + int v_uint = br_a.ReadInt32(); + Vert.y = -UnpackUVertex(v_uint, 0); + Vert.z = UnpackUVertex(v_uint, 2); + Vert.x = -UnpackUVertex(v_uint, 1); + } + vertices[i] = Vert; + } + + // read polygons + //int minverthack = 0; + //int minvert = 2147483647; + UE1Poly[] polys = new UE1Poly[d3d_numpolys]; + int[] polyindexlist = new int[d3d_numpolys * 3]; + for (uint i = 0; i < d3d_numpolys; i++) + { + // + stream_d.Position = start_d + 16 * i; + polys[i].V = new int[3]; + polys[i].S = new float[3]; + polys[i].T = new float[3]; + bool brokenpoly = false; + for (int j = 0; j < 3; j++) + { + polyindexlist[i * 3 + j] = polys[i].V[j] = br_d.ReadInt16(); + if (polys[i].V[j] >= vertices.Length || polys[i].V[j] < 0) + brokenpoly = true; + } + + // Resolves polygons that reference out-of-bounds vertices by simply making them null size. + // This is easier than changing array to dynamically sized list. + if (brokenpoly) + { + polys[i].V[0] = 0; + polys[i].V[1] = 0; + polys[i].V[2] = 0; + } + + polys[i].Type = br_d.ReadByte(); + stream_d.Position += 1; // color + for (int j = 0; j < 3; j++) + { + byte u = br_d.ReadByte(); + byte v = br_d.ReadByte(); + polys[i].S[j] = u / 255f; + polys[i].T[j] = v / 255f; + } + polys[i].TexNum = br_d.ReadByte(); + } + + // calculate poly normals + for (uint i = 0; i < d3d_numpolys; i++) + { + Vector3D[] dir = new Vector3D[2]; + Vector3D norm; + dir[0].x = vertices[polys[i].V[1]].x - vertices[polys[i].V[0]].x; + dir[0].y = vertices[polys[i].V[1]].y - vertices[polys[i].V[0]].y; + dir[0].z = vertices[polys[i].V[1]].z - vertices[polys[i].V[0]].z; + dir[1].x = vertices[polys[i].V[2]].x - vertices[polys[i].V[0]].x; + dir[1].y = vertices[polys[i].V[2]].y - vertices[polys[i].V[0]].y; + dir[1].z = vertices[polys[i].V[2]].z - vertices[polys[i].V[0]].z; + norm.x = dir[0].y * dir[1].z - dir[0].z * dir[1].y; + norm.y = dir[0].z * dir[1].x - dir[0].x * dir[1].z; + norm.z = dir[0].x * dir[1].y - dir[0].y * dir[1].x; + polys[i].Normal = norm.GetNormal(); + } + + // calculate vertex normals + for (uint i = 0; i < d3d_numverts; i++) + { + Vector3D nsum = new Vector3D(0, 0, 0); + int total = 0; + for (uint j = 0; j < d3d_numpolys; j++) + { + if ((polys[j].V[0] != i) && (polys[j].V[1] != i) && (polys[j].V[2] != i)) continue; + nsum.x += polys[j].Normal.x; + nsum.y += polys[j].Normal.y; + nsum.z += polys[j].Normal.z; + total++; + } + vertices[i].nx = (float)-nsum.x / total; + vertices[i].ny = (float)-nsum.y / total; + vertices[i].nz = (float)-nsum.z / total; + } + + List exGroups = new List(); + Dictionary textureGroupRemap = new Dictionary(); + for (int i = 0; i < polys.Length; i++) + { + if (exGroups.Contains(polys[i].TexNum)) + continue; + if (exGroups.Count == 0 || + polys[i].TexNum <= exGroups[0]) + exGroups.Insert(0, polys[i].TexNum); + else if (exGroups.Count == 0 || + polys[i].TexNum >= exGroups[exGroups.Count - 1]) + exGroups.Add(polys[i].TexNum); + } + + for (int i = 0; i < exGroups.Count; i++) + textureGroupRemap[exGroups[i]] = i; + + if (skins == null) + { + List out_verts = new List(); + List out_polys = new List(); + + for (int i = 0; i < polys.Length; i++) + { + if ((polys[i].Type & 0x08) != 0) + continue; + for (int j = 0; j < 3; j++) + { + WorldVertex vx = vertices[polys[i].V[j]]; + vx.u = polys[i].S[j]; + vx.v = polys[i].T[j]; + if ((polys[i].Type & 0x20) != 0) + { + vx.nx = (float)polys[i].Normal.x; + vx.ny = (float)polys[i].Normal.y; + vx.nz = (float)polys[i].Normal.z; + } + out_polys.Add(out_verts.Count); + out_verts.Add(vx); + } + } + + CreateMesh(ref result, out_verts, out_polys); + result.Skins.Add(""); + } + else + { + for (int k = 0; k < exGroups.Count; k++) + { + List out_verts = new List(); + List out_polys = new List(); + + for (int i = 0; i < polys.Length; i++) + { + if ((polys[i].Type & 0x08) != 0) + continue; + + if (textureGroupRemap[polys[i].TexNum] != k) + continue; + + for (int j = 0; j < 3; j++) + { + WorldVertex vx = vertices[polys[i].V[j]]; + vx.u = polys[i].S[j]; + vx.v = polys[i].T[j]; + if ((polys[i].Type & 0x20) != 0) + { + vx.nx = (float)polys[i].Normal.x; + vx.ny = (float)polys[i].Normal.y; + vx.nz = (float)polys[i].Normal.z; + } + out_polys.Add(out_verts.Count); + out_verts.Add(vx); + } + } + + CreateMesh(ref result, out_verts, out_polys); + result.Skins.Add(skins.ContainsKey(k) ? skins[k].ToLowerInvariant() : string.Empty); + } + } + + return result; + } + + // there is probably better way to emulate 16-bit cast, but this was easiest for me at 3am + private static int PadInt16(int n) + { + if (n > 32767) + return -(65536 - n); + return n; + } + + private static float UnpackUVertex(int n, int c) + { + switch (c) + { + case 0: + return PadInt16((n & 0x7ff) << 5) / 128f; + case 1: + return PadInt16((((int)n >> 11) & 0x7ff) << 5) / 128f; + case 2: + return PadInt16((((int)n >> 22) & 0x3ff) << 6) / 128f; + default: + return 0f; + } + } + + private struct UE1Poly + { + public int[] V; + public float[] S; + public float[] T; + public int TexNum, Type; + public Vector3D Normal; + } + } +} diff --git a/Source/Core/GZBuilder/md3/ModelReader.cs b/Source/Core/GZBuilder/md3/ModelReader.cs deleted file mode 100755 index 42c716bf..00000000 --- a/Source/Core/GZBuilder/md3/ModelReader.cs +++ /dev/null @@ -1,1694 +0,0 @@ -#region ================== Namespaces - -using System; -using System.IO; -using System.Drawing; -using System.Drawing.Imaging; -using System.Globalization; // biwa -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 CodeImp.DoomBuilder.Geometry; -using System.Linq; - -#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 - - internal class MD3LoadResult - { - public List Skins; - public List Meshes; - public string Errors; - - public MD3LoadResult() - { - Skins = new List(); - Meshes = new List(); - } - } - - #endregion - - #region ================== Load - - public static void Load(ModelData mde, List containers) - { - if(mde.IsVoxel) LoadKVX(mde, containers); - else LoadModel(mde, containers); - } - - private static void LoadKVX(ModelData mde, List containers) - { - mde.Model = new GZModel(); - string unused = string.Empty; - foreach(string name in mde.ModelNames) - { - //find the model - foreach(DataReader dr in containers) - { - Stream ms = dr.GetVoxelData(name, ref unused); - if(ms == null) continue; - - //load kvx - ReadKVX(mde, ms); - - //done - ms.Close(); - break; - } - } - - //clear unneeded data - mde.SkinNames = null; - mde.ModelNames = null; - - if(mde.Model.Meshes == null || mde.Model.Meshes.Count == 0) - { - mde.Model = null; - } - } - - private static void LoadModel(ModelData mde, List containers) - { - 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++) - { - // Use model skins? - // INFO: Skin MODELDEF property overrides both embedded surface names and ones set using SurfaceSkin MODELDEF property - Dictionary skins = null; - if(string.IsNullOrEmpty(mde.SkinNames[i])) - { - skins = (mde.SurfaceSkinNames[i].Count > 0 ? mde.SurfaceSkinNames[i] : new Dictionary()); - } - - // 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, skins, ms, mde.FrameIndices[i]); - break; - case ".md2": - result = ReadMD2Model(ref bbs, ms, mde.FrameIndices[i], mde.FrameNames[i]); - break; - case ".3d": - result = Read3DModel(ref bbs, skins, ms, mde.FrameIndices[i], mde.ModelNames[i], containers); - break; - case ".obj": - // OBJ doesn't support frames, so print out an error - if (mde.FrameIndices[i] > 0) - { - General.ErrorLogger.Add(ErrorType.Error, "Trying to load frame " + mde.FrameIndices[i] + " of model \"" + mde.ModelNames[i] + "\", but OBJ doesn't support frames!"); - continue; - } - result = ReadOBJModel(ref bbs, skins, ms, mde.ModelNames[i]); - break; - default: - result.Errors = "model format is not supported"; - break; - } - - ms.Close(); - if (result == null) - continue; - - //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); - - //load texture - List errors = new List(); - - // Texture not defined in MODELDEF? - if(skins != null) - { - //try to use model's own skins - for(int m = 0; m < result.Meshes.Count; m++) - { - // biwa. Makes sure to add a dummy texture if the MODELDEF skin definition is erroneous - if(m >= result.Skins.Count) - { - errors.Add("no skin defined for mesh " + m + "."); - mde.Model.Textures.Add(General.Map.Data.UnknownTexture3D.Texture); - continue; - } - - 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); - - if(!String.IsNullOrEmpty(mde.Path)) - path = Path.Combine(mde.Path, path); - - Texture t = GetTexture(containers, path); - - if(t != null) - { - mde.Model.Textures.Add(t); - continue; - } - - // That didn't work, let's try to load the texture without the additional path - path = result.Skins[m].Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); - t = GetTexture(containers, path); - - if (t == null) - { - mde.Model.Textures.Add(General.Map.Data.UnknownTexture3D.Texture); - errors.Add("unable to load skin \"" + path + "\""); - continue; - } - - mde.Model.Textures.Add(t); - } - } - //Try to use texture loaded from MODELDEFS - else - { - Texture t = GetTexture(containers, mde.SkinNames[i]); - - if(t == null) - { - mde.Model.Textures.Add(General.Map.Data.UnknownTexture3D.Texture); - errors.Add("unable to load skin \"" + mde.SkinNames[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.SkinNames = 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); - bbs.MaxZ = (int)(bbs.MaxZ * mde.Scale.Z); - bbs.MinZ = (int)(bbs.MinZ * mde.Scale.Z); - - //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))); - mde.Model.BBox = bbs; - } - - private static Texture GetTexture(List containers, string texturename) - { - Texture t = null; - string[] extensions = new string[ModelData.SUPPORTED_TEXTURE_EXTENSIONS.Length + 1]; - - Array.Copy(ModelData.SUPPORTED_TEXTURE_EXTENSIONS, 0, extensions, 1, ModelData.SUPPORTED_TEXTURE_EXTENSIONS.Length); - extensions[0] = ""; - - // Try to load the texture as defined by its path. GZDoom doesn't care about extensions - if (t == null) - { - foreach (string extension in extensions) - { - string name = Path.ChangeExtension(texturename, null) + extension; - name = name.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); - - t = LoadTexture(containers, name); - - if (t != null) - break; - } - } - - // Try to use an already defined texture. Again, just try out all extensions - foreach (string extension in extensions) - { - string name = Path.ChangeExtension(texturename, null) + extension; - - if (General.Map.Data.GetTextureExists(name)) - { - ImageData image = General.Map.Data.GetTextureImage(name); - image.LoadImageNow(false); - - t = image.Texture; - - break; - } - } - - // GZDoom can also ignore the path completely (because why not), so let's see if there's a texture with - // just the skin name - if (t == null) - { - string name = Path.ChangeExtension(Path.GetFileName(texturename), null); - - if (General.Map.Data.GetTextureExists(name)) - { - ImageData image = General.Map.Data.GetTextureImage(name); - image.LoadImageNow(false); - - t = image.Texture; - } - } - - // Or maybe it's a sprite - if(t == null) - { - string name = Path.ChangeExtension(texturename, null); - - if (General.Map.Data.GetSpriteExists(name)) - { - ImageData image = General.Map.Data.GetSpriteImage(name); - image.LoadImageNow(false); - - t = image.Texture; - } - } - - return t; - } - - #endregion - - #region ================== 3D (unreal) - - // there is probably better way to emulate 16-bit cast, but this was easiest for me at 3am - private static int PadInt16(int n) - { - if (n > 32767) - return -(65536 - n); - return n; - } - - private static float UnpackUVertex(int n, int c) - { - switch (c) - { - case 0: - return PadInt16((n & 0x7ff) << 5) / 128f; - case 1: - return PadInt16((((int)n >> 11) & 0x7ff) << 5) / 128f; - case 2: - return PadInt16((((int)n >> 22) & 0x3ff) << 6) / 128f; - default: - return 0f; - } - } - - private struct UE1Poly - { - public int[] V; - public float[] S; - public float[] T; - public int TexNum, Type; - public Vector3D Normal; - } - - internal static MD3LoadResult Read3DModel(ref BoundingBoxSizes bbs, Dictionary skins, Stream s, int frame, string filename, List containers) - { - Stream stream_d; - Stream stream_a; - - if (filename.IndexOf("_d.3d") == filename.Length-5) - { - string filename_a = filename.Replace("_d.3d", "_a.3d"); - stream_d = s; - stream_a = LoadFile(containers, filename_a, true); - if (stream_a == null) - { - General.ErrorLogger.Add(ErrorType.Error, "Error while loading \"" + filename + "\": unable to find corresponding \"_a.3d\" file."); - return null; - } - } - else - { - string filename_d = filename.Replace("_a.3d", "_d.3d"); - stream_a = s; - stream_d = LoadFile(containers, filename_d, true); - if (stream_d == null) - { - General.ErrorLogger.Add(ErrorType.Error, "Error while loading \"" + filename + "\": unable to find corresponding \"_d.3d\" file."); - return null; - } - } - - MD3LoadResult result = new MD3LoadResult(); - BinaryReader br_d = new BinaryReader(stream_d); - BinaryReader br_a = new BinaryReader(stream_a); - - // read d3d header - uint d3d_numpolys = br_d.ReadUInt16(); - uint d3d_numverts = br_d.ReadUInt16(); - stream_d.Position += 44; // bogusrot, bogusframe, bogusnorm[3], fixscale, unused[3], padding[12] - - long start_d = stream_d.Position; - - // read a3d header - uint a3d_numframes = br_a.ReadUInt16(); - uint a3d_framesize = br_a.ReadUInt16(); - - long start_a = stream_a.Position; - - // Sanity check - if (frame < 0 || frame >= a3d_numframes) - { - result.Errors = "frame " + frame + " is outside of model's frame range [0.." + (a3d_numframes - 1) + "]"; - return result; - } - - // check for deus ex format - bool isdeusex = false; - if ( (a3d_framesize/d3d_numverts) == 8 ) isdeusex = true; - - // read vertices - WorldVertex[] vertices = new WorldVertex[d3d_numverts]; - for (uint i = 0; i < d3d_numverts; i++) - { - WorldVertex Vert = new WorldVertex(); - if ( isdeusex ) - { - stream_a.Position = start_a + (i + frame * d3d_numverts) * 8; - int vx = br_a.ReadInt16(); - int vy = br_a.ReadInt16(); - int vz = br_a.ReadInt16(); - Vert.y = -vx; - Vert.z = vz; - Vert.x = -vy; - } - else - { - stream_a.Position = start_a + (i + frame * d3d_numverts) * 4; - int v_uint = br_a.ReadInt32(); - Vert.y = -UnpackUVertex(v_uint, 0); - Vert.z = UnpackUVertex(v_uint, 2); - Vert.x = -UnpackUVertex(v_uint, 1); - } - vertices[i] = Vert; - } - - // read polygons - //int minverthack = 0; - //int minvert = 2147483647; - UE1Poly[] polys = new UE1Poly[d3d_numpolys]; - int[] polyindexlist = new int[d3d_numpolys*3]; - for (uint i = 0; i < d3d_numpolys; i++) - { - // - stream_d.Position = start_d + 16 * i; - polys[i].V = new int[3]; - polys[i].S = new float[3]; - polys[i].T = new float[3]; - bool brokenpoly = false; - for (int j = 0; j < 3; j++) - { - polyindexlist[i * 3 + j] = polys[i].V[j] = br_d.ReadInt16(); - if (polys[i].V[j] >= vertices.Length || polys[i].V[j] < 0) - brokenpoly = true; - } - - // Resolves polygons that reference out-of-bounds vertices by simply making them null size. - // This is easier than changing array to dynamically sized list. - if (brokenpoly) - { - polys[i].V[0] = 0; - polys[i].V[1] = 0; - polys[i].V[2] = 0; - } - - polys[i].Type = br_d.ReadByte(); - stream_d.Position += 1; // color - for (int j = 0; j < 3; j++) - { - byte u = br_d.ReadByte(); - byte v = br_d.ReadByte(); - polys[i].S[j] = u / 255f; - polys[i].T[j] = v / 255f; - } - polys[i].TexNum = br_d.ReadByte(); - } - - // calculate poly normals - for (uint i = 0; i < d3d_numpolys; i++) - { - Vector3D[] dir = new Vector3D[2]; - Vector3D norm; - dir[0].x = vertices[polys[i].V[1]].x-vertices[polys[i].V[0]].x; - dir[0].y = vertices[polys[i].V[1]].y-vertices[polys[i].V[0]].y; - dir[0].z = vertices[polys[i].V[1]].z-vertices[polys[i].V[0]].z; - dir[1].x = vertices[polys[i].V[2]].x-vertices[polys[i].V[0]].x; - dir[1].y = vertices[polys[i].V[2]].y-vertices[polys[i].V[0]].y; - dir[1].z = vertices[polys[i].V[2]].z-vertices[polys[i].V[0]].z; - norm.x = dir[0].y * dir[1].z - dir[0].z * dir[1].y; - norm.y = dir[0].z * dir[1].x - dir[0].x * dir[1].z; - norm.z = dir[0].x * dir[1].y - dir[0].y * dir[1].x; - polys[i].Normal = norm.GetNormal(); - } - - // calculate vertex normals - for (uint i = 0; i < d3d_numverts; i++) - { - Vector3D nsum = new Vector3D(0, 0, 0); - int total = 0; - for (uint j = 0; j < d3d_numpolys; j++) - { - if ((polys[j].V[0] != i) && (polys[j].V[1] != i) && (polys[j].V[2] != i)) continue; - nsum.x += polys[j].Normal.x; - nsum.y += polys[j].Normal.y; - nsum.z += polys[j].Normal.z; - total++; - } - vertices[i].nx = (float)-nsum.x / total; - vertices[i].ny = (float)-nsum.y / total; - vertices[i].nz = (float)-nsum.z / total; - } - - List exGroups = new List(); - Dictionary textureGroupRemap = new Dictionary(); - for (int i = 0; i < polys.Length; i++) - { - if (exGroups.Contains(polys[i].TexNum)) - continue; - if (exGroups.Count == 0 || - polys[i].TexNum <= exGroups[0]) - exGroups.Insert(0, polys[i].TexNum); - else if (exGroups.Count == 0 || - polys[i].TexNum >= exGroups[exGroups.Count - 1]) - exGroups.Add(polys[i].TexNum); - } - - for (int i = 0; i < exGroups.Count; i++) - textureGroupRemap[exGroups[i]] = i; - - if (skins == null) - { - List out_verts = new List(); - List out_polys = new List(); - - for (int i = 0; i < polys.Length; i++) - { - if ( (polys[i].Type&0x08) != 0 ) - continue; - for (int j = 0; j < 3; j++) - { - WorldVertex vx = vertices[polys[i].V[j]]; - vx.u = polys[i].S[j]; - vx.v = polys[i].T[j]; - if ( (polys[i].Type&0x20) != 0 ) - { - vx.nx = (float)polys[i].Normal.x; - vx.ny = (float)polys[i].Normal.y; - vx.nz = (float)polys[i].Normal.z; - } - out_polys.Add(out_verts.Count); - out_verts.Add(vx); - } - } - - CreateMesh(ref result, out_verts, out_polys); - result.Skins.Add(""); - } - else - { - for (int k = 0; k < exGroups.Count; k++) - { - List out_verts = new List(); - List out_polys = new List(); - - for (int i = 0; i < polys.Length; i++) - { - if ( (polys[i].Type&0x08) != 0 ) - continue; - - if (textureGroupRemap[polys[i].TexNum] != k) - continue; - - for (int j = 0; j < 3; j++) - { - WorldVertex vx = vertices[polys[i].V[j]]; - vx.u = polys[i].S[j]; - vx.v = polys[i].T[j]; - if ( (polys[i].Type&0x20) != 0 ) - { - vx.nx = (float)polys[i].Normal.x; - vx.ny = (float)polys[i].Normal.y; - vx.nz = (float)polys[i].Normal.z; - } - out_polys.Add(out_verts.Count); - out_verts.Add(vx); - } - } - - CreateMesh(ref result, out_verts, out_polys); - result.Skins.Add(skins.ContainsKey(k)?skins[k].ToLowerInvariant():string.Empty); - } - } - - return result; - } - - #endregion - - #region ================== MD3 - - internal static MD3LoadResult ReadMD3Model(ref BoundingBoxSizes bbs, Dictionary skins, Stream s, 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 polyIndecesList = new List(); - List vertList = new List(); - - Dictionary>> polyIndecesListsPerTexture = new Dictionary>>(StringComparer.Ordinal); - Dictionary> vertListsPerTexture = new Dictionary>(StringComparer.Ordinal); - Dictionary> vertexOffsets = new Dictionary>(StringComparer.Ordinal); - bool useskins = false; - - 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; - } - - // Pick a skin to use - if(skins == null) - { - // skins is null when Skin MODELDEF property is set - skin = string.Empty; - } - else if(skins.ContainsKey(c)) - { - // Overrtide surface skin with SurfaceSkin MODELDEF property - skin = skins[c]; - } - - if(!string.IsNullOrEmpty(skin)) - { - useskins = true; - - if(polyIndecesListsPerTexture.ContainsKey(skin)) - { - polyIndecesListsPerTexture[skin].Add(polyIndecesList); - vertListsPerTexture[skin].AddRange(vertList.ToArray()); - vertexOffsets[skin].Add(vertList.Count); - } - else - { - polyIndecesListsPerTexture.Add(skin, new List> { polyIndecesList } ); - vertListsPerTexture.Add(skin, vertList); - vertexOffsets.Add(skin, new List { vertList.Count }); - } - - //reset lists - polyIndecesList = new List(); - vertList = new List(); - } - } - - if(!useskins) - { - //create mesh - CreateMesh(ref result, vertList, polyIndecesList); - result.Skins.Add(""); - } - else - { - //create a mesh for each surface texture - foreach(KeyValuePair>> group in polyIndecesListsPerTexture) - { - polyIndecesList = new List(); - 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(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 polyIndecesList, List 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 "frame " + frame + " is outside of model's frame range [0.." + (numFrames - 1) + "]"; - } - - // 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, Stream s, 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(frame < 0 || frame >= num_frames) - { - result.Errors = "frame " + frame + " is outside of model's frame range [0.." + (num_frames - 1) + "]"; - 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 polyIndecesList = new List(); - List uvIndecesList = new List(); - List uvCoordsList = new List(); - List vertList = new List(); - - // 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 Vector2f((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; - } - - Vector3f scale = new Vector3f(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); - Vector3f translate = new Vector3f(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(General.Map.Graphics, vertList.ToArray(), polyIndecesList.ToArray()); - - //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) - { - PixelColor[] palette = new PixelColor[256]; - List verts = new List(); - List indices = new List(); - Dictionary verthashes = new Dictionary(); - int xsize, ysize, zsize; - int facescount = 0; - 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 offsets = new List(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 colorIndices = new List(zleng); - for(int i = 0; i < zleng; i++) - { - colorIndices.Add(reader.ReadByte()); - } - - if((flags & 16) != 0) - { - AddFace(verts, indices, verthashes, 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]); - facescount += 2; - } - - 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, indices, verthashes, 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]); - facescount += 2; - } - if((flags & 2) != 0) - { - AddFace(verts, indices, verthashes, 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]); - facescount += 2; - } - if((flags & 4) != 0) - { - AddFace(verts, indices, verthashes, 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]); - facescount += 2; - } - if((flags & 8) != 0) - { - AddFace(verts, indices, verthashes, 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]); - facescount += 2; - } - - if(c == 0) c++; - z += c; - cstart += c; - } - - if((flags & 32) != 0) - { - z = ztop + zleng - 1; - AddFace(verts, indices, verthashes, 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]); - facescount += 2; - } - } - } - - 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 new Texture(bmp.Width) - using(Bitmap bmp = CreateVoxelTexture(palette)) - { - mde.Model.Textures.Add(new Texture(General.Map.Graphics, bmp)); - } - - // Create mesh - Mesh mesh = new Mesh(General.Map.Graphics, verts.ToArray(), indices.ToArray()); - - // Add mesh - mde.Model.Meshes.Add(mesh); - } - - // Shameless GZDoom rip-off - private static void AddFace(List verts, List indices, Dictionary hashes, Vector3D v1, Vector3D v2, Vector3D v3, Vector3D v4, Vector3D pivot, int colorIndex) - { - float pu0 = (colorIndex % 16) / 16f; - float pu1 = pu0 + 0.001f; - float pv0 = (colorIndex / 16) / 16f; - float pv1 = pv0 + 0.001f; - - WorldVertex wv1 = new WorldVertex - { - x = (float)(v1.x - pivot.x), - y = (float)(-v1.y + pivot.y), - z = (float)(-v1.z + pivot.z), - c = -1, - u = pu0, - v = pv0 - }; - int i1 = AddVertex(wv1, verts, indices, hashes); - - WorldVertex wv2 = new WorldVertex - { - x = (float)(v2.x - pivot.x), - y = (float)(-v2.y + pivot.y), - z = (float)(-v2.z + pivot.z), - c = -1, - u = pu1, - v = pv1 - }; - AddVertex(wv2, verts, indices, hashes); - - WorldVertex wv4 = new WorldVertex - { - x = (float)(v4.x - pivot.x), - y = (float)(-v4.y + pivot.y), - z = (float)(-v4.z + pivot.z), - c = -1, - u = pu0, - v = pv0 - }; - int i4 = AddVertex(wv4, verts, indices, hashes); - - WorldVertex wv3 = new WorldVertex - { - x = (float)(v3.x - pivot.x), - y = (float)(-v3.y + pivot.y), - z = (float)(-v3.z + pivot.z), - c = -1, - u = pu1, - v = pv1 - }; - AddVertex(wv3, verts, indices, hashes); - - indices.Add(i1); - indices.Add(i4); - } - - // Returns index of added vert - private static int AddVertex(WorldVertex v, List verts, List indices, Dictionary hashes) - { - long hash; - unchecked // Overflow is fine, just wrap - { - hash = 2166136261; - hash = (hash * 16777619) ^ v.x.GetHashCode(); - hash = (hash * 16777619) ^ v.y.GetHashCode(); - hash = (hash * 16777619) ^ v.z.GetHashCode(); - hash = (hash * 16777619) ^ v.u.GetHashCode(); - hash = (hash * 16777619) ^ v.v.GetHashCode(); - } - - if(hashes.ContainsKey(hash)) - { - indices.Add(hashes[hash]); - return hashes[hash]; - } - else - { - verts.Add(v); - hashes.Add(hash, verts.Count - 1); - indices.Add(verts.Count - 1); - return verts.Count - 1; - } - } - - 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 ================== OBJ - - private static MD3LoadResult ReadOBJModel(ref BoundingBoxSizes bbs, Dictionary skins, Stream s, string name) - { - MD3LoadResult result = new MD3LoadResult(); - - using (var reader = new StreamReader(s, Encoding.ASCII)) - { - string line; - int linenum = 1; - string message; - int surfaceskinid = 0; - List vertices = new List(); - List faces = new List(); - List normals = new List(); - List texcoords = new List(); - List worldvertices = new List(); - List polyindiceslist = new List(); - - while ((line = reader.ReadLine()) != null) { - string[] fields = line.Trim().Split(new[] { ' ', '\t' }, 2, StringSplitOptions.RemoveEmptyEntries); - - // Empty line - if (fields.Length == 0) - { - linenum++; - continue; - } - - // Comment - if (fields[0].Trim() == "#") - { - linenum++; - continue; - } - - string keyword = fields[0].Trim(); - string payload = null; - - if (fields.Length == 2) - payload = fields[1].Trim(); - - switch(keyword) - { - case "v": - Vector3D v = new Vector3D(0, 0, 0); - - if (OBJParseVertex(payload, ref v, out message)) - vertices.Add(v); - else - { - result.Errors = String.Format("Error in line {0}: {1}", linenum, message); - return result; - } - - break; - case "vt": - Vector2D t = new Vector2D(0, 0); - - if (OBJParseTextureCoords(payload, ref t, out message)) - texcoords.Add(t); - else - { - result.Errors = String.Format("Error in line {0}: {1}", linenum, message); - return result; - } - - break; - case "vn": - Vector3D n = new Vector3D(0, 0, 0); - - if (OBJParseNormal(payload, ref n, out message)) - normals.Add(n); - else - { - result.Errors = String.Format("Error in line {0}: {1}", linenum, message); - return result; - } - - break; - case "f": - List fv = new List(); - List vt = new List(); - List vn = new List(); - - if (OBJParseFace(payload, ref fv, ref vt, ref vn, out message)) - { - // Sanity check for vertices - for (int i=0; i < fv.Count; i++) - if(fv[i] != -1 && fv[i] >= vertices.Count) - { - result.Errors = String.Format("Error in line {0}: vertex {1} does not exist", linenum, fv[i] + 1); - return result; - } - - // Sanity check for texture coordinates - for (int i=0; i < vt.Count; i++) - if(vt[i] != -1 && vt[i] >= texcoords.Count) - { - result.Errors = String.Format("Error in line {0}: texture coordinate {1} does not exist", linenum, vt[i] + 1); - return result; - } - - // Sanity check for normals - for (int i = 0; i < vn.Count; i++) - if (vn[i] != -1 && vn[i] >= normals.Count) - { - result.Errors = String.Format("Error in line {0}: vertex {1} does not exist", linenum, vn[i] + 1); - return result; - } - - int[] seq; - - // If the face is a quad split it into two triangles - if (fv.Count == 3) - seq = new int[] { 0, 1, 2 }; - else - seq = new int[] { 0, 1, 2, 0, 2, 3 }; - - for (int i = 0; i < seq.Length; i++) { - WorldVertex wc = new WorldVertex(vertices[fv[seq[i]]]); - - if(vt[seq[i]] != -1) - { - wc.u = (float)texcoords[vt[seq[i]]].x; - wc.v = (float)texcoords[vt[seq[i]]].y; - } - - if (vn[seq[i]] != -1) - { - wc.nx = (float)normals[vn[seq[i]]].x; - wc.ny = (float)normals[vn[seq[i]]].y; - wc.nz = (float)normals[vn[seq[i]]].z; - } - - BoundingBoxTools.UpdateBoundingBoxSizes(ref bbs, wc); - - worldvertices.Add(wc); - polyindiceslist.Add(polyindiceslist.Count); - } - } - else - { - result.Errors = String.Format("Error in line {0}: {1}", linenum, message); - return result; - } - - break; - case "usemtl": - // If there's a new texture defined create a mesh from the current faces and - // start a gather new faces for the next mesh - if(worldvertices.Count > 0) - { - CreateMesh(ref result, worldvertices, polyindiceslist); - worldvertices.Clear(); - polyindiceslist.Clear(); - } - - // Add texture name. It might be in quotes, so remove them. - // See https://github.com/jewalky/UltimateDoomBuilder/issues/758 - if (fields.Length >= 2) - result.Skins.Add(fields[1].Replace("\"", "")); - - surfaceskinid++; - break; - case "": // Empty line - case "#": // Line is a comment - case "s": // Smooth - case "g": // Group - case "o": // Object - default: - break; - } - - linenum++; - } - - CreateMesh(ref result, worldvertices, polyindiceslist); - - // Overwrite internal textures with SurfaceSkin definitions if necessary - if (skins != null) - { - foreach (KeyValuePair group in skins) - { - // Add dummy skins if necessary - while (result.Skins.Count <= group.Key) - result.Skins.Add(String.Empty); - - result.Skins[group.Key] = group.Value; - } - } - } - - return result; - } - - private static bool OBJParseVertex(string payload, ref Vector3D v, out string message) - { - if(String.IsNullOrEmpty(payload)) - { - message = "no arguments given"; - return false; - } - - string[] fields = payload.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); - - if(fields.Length < 3) - { - message = "too few arguments"; - return false; - } - - try - { - v.x = float.Parse(fields[0], CultureInfo.InvariantCulture); - v.z = float.Parse(fields[1], CultureInfo.InvariantCulture); - v.y = -float.Parse(fields[2], CultureInfo.InvariantCulture); - - - // Prepare to fix rotation angle - double angleOfsetCos = Math.Cos(-Angle2D.PIHALF); - double angleOfsetSin = Math.Sin(-Angle2D.PIHALF); - - // Fix rotation angle - double rx = angleOfsetCos * v.x - angleOfsetSin * v.y; - double ry = angleOfsetSin * v.x + angleOfsetCos * v.y; - v.x = rx; - v.y = ry; - } - catch (FormatException) - { - message = "field is not a float"; - return false; - } - - message = ""; - return true; - } - - private static bool OBJParseTextureCoords(string payload, ref Vector2D t, out string message) - { - if (String.IsNullOrEmpty(payload)) - { - message = "no arguments given"; - return false; - } - - string[] fields = payload.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); - - if (fields.Length < 2) - { - message = "too few arguments"; - return false; - } - - try - { - t.x = float.Parse(fields[0], CultureInfo.InvariantCulture); - - if (fields.Length >= 2) - t.y = 1.0f - float.Parse(fields[1], CultureInfo.InvariantCulture); - else - t.y = 1.0f; - } - catch (FormatException) - { - message = "field is not a float"; - return false; - } - - message = ""; - return true; - } - - private static bool OBJParseNormal(string payload, ref Vector3D normal, out string message) - { - if (String.IsNullOrEmpty(payload)) - { - message = "no arguments given"; - return false; - } - - string[] fields = payload.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); - - if (fields.Length < 3) - { - message = "too few arguments"; - return false; - } - - try - { - normal.x = float.Parse(fields[0], CultureInfo.InvariantCulture); - normal.y = float.Parse(fields[1], CultureInfo.InvariantCulture); - normal.z = float.Parse(fields[2], CultureInfo.InvariantCulture); - } - catch (FormatException) - { - message = "field is not a float"; - return false; - } - - message = ""; - return true; - } - - private static bool OBJParseFace(string payload, ref List face, ref List texcoords, ref List normals, out string message) - { - if (String.IsNullOrEmpty(payload)) - { - message = "no arguments given"; - return false; - } - - string[] fields = payload.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); - - if (fields.Length < 3) - { - message = "too few arguments"; - return false; - } - - if(fields.Length > 4) - { - message = "faces with more than 4 sides are not supported"; - return false; - } - - try - { - for (int i = 0; i < fields.Length; i++) - { - string[] vertexdata = fields[i].Split('/'); - - face.Add(int.Parse(vertexdata[0], CultureInfo.InvariantCulture) - 1); - - if (vertexdata.Length > 1 && vertexdata[1] != "") - texcoords.Add(int.Parse(vertexdata[1], CultureInfo.InvariantCulture) - 1); - else - texcoords.Add(-1); - - if (vertexdata.Length > 2 && vertexdata[2] != "") - normals.Add(int.Parse(vertexdata[2], CultureInfo.InvariantCulture) - 1); - else - normals.Add(-1); - } - } - catch(FormatException) - { - message = "field is not an integer"; - return false; - } - - message = ""; - return true; - } - - #endregion - - #region ================== Utility - - private static MemoryStream LoadFile(List 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 containers, string path) - { - if(string.IsNullOrEmpty(path)) return null; - - MemoryStream ms = LoadFile(containers, path, true); - if(ms == null) return null; - - Texture texture = null; - - //create texture - Bitmap bitmap = ImageDataFormat.TryLoadImage(ms); - if(bitmap != null) - { - texture = new Texture(General.Map.Graphics, bitmap); - } - - return texture; - } - - private static void CreateMesh(ref MD3LoadResult result, List verts, List indices) - { - //create mesh - Mesh mesh = new Mesh(General.Map.Graphics, verts.ToArray(), indices.ToArray()); - - //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 - } -} diff --git a/Source/Core/Rendering/Renderer3D.cs b/Source/Core/Rendering/Renderer3D.cs index f1c72145..5c2bc27d 100755 --- a/Source/Core/Rendering/Renderer3D.cs +++ b/Source/Core/Rendering/Renderer3D.cs @@ -23,7 +23,7 @@ using System.Drawing.Drawing2D; using CodeImp.DoomBuilder.Data; using CodeImp.DoomBuilder.Geometry; using CodeImp.DoomBuilder.GZBuilder.Data; -using CodeImp.DoomBuilder.GZBuilder.MD3; +using CodeImp.DoomBuilder.GZBuilder.Models; using CodeImp.DoomBuilder.Map; using CodeImp.DoomBuilder.VisualModes; using CodeImp.DoomBuilder.GZBuilder; diff --git a/Source/Core/Rendering/Vector3.cs b/Source/Core/Rendering/Vector3.cs index ab15ff82..7475589e 100644 --- a/Source/Core/Rendering/Vector3.cs +++ b/Source/Core/Rendering/Vector3.cs @@ -116,6 +116,16 @@ namespace CodeImp.DoomBuilder.Rendering return X.GetHashCode() + Y.GetHashCode() + Z.GetHashCode(); } + public static Vector3f operator *(Vector3f vec, float scalar) + { + return new Vector3f(vec.X * scalar, vec.Y * scalar, vec.Z * scalar); + } + + public static Vector3f operator *(float scalar, Vector3f vec) + { + return new Vector3f(vec.X * scalar, vec.Y * scalar, vec.Z * scalar); + } + public static Vector3f operator +(Vector3f left, Vector3f right) { return new Vector3f(left.X + right.X, left.Y + right.Y, left.Z + right.Z); diff --git a/Source/Core/Rendering/Vector4.cs b/Source/Core/Rendering/Vector4.cs index 92bb48c0..fd027ca9 100644 --- a/Source/Core/Rendering/Vector4.cs +++ b/Source/Core/Rendering/Vector4.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Drawing.Printing; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows; namespace CodeImp.DoomBuilder.Rendering { @@ -45,6 +47,34 @@ namespace CodeImp.DoomBuilder.Rendering public float Z; public float W; + public static float Dot(Vector4f a, Vector4f b) + { + return a.X * b.X + a.Y * b.Y + a.Z * b.Z + a.W * b.W; + } + + public float Length() + { + return (float)Math.Sqrt(Dot(this, this)); + } + + public static Vector4f Normalize(Vector4f v) + { + v.Normalize(); + return v; + } + + public void Normalize() + { + float len = Length(); + if (len > 0.0f) + { + X /= len; + Y /= len; + Z /= len; + W /= len; + } + } + public override bool Equals(object o) { if (o is Vector4f) @@ -58,6 +88,16 @@ namespace CodeImp.DoomBuilder.Rendering } } + public static Vector4f operator *(Vector4f vec, float scalar) + { + return new Vector4f(vec.X * scalar, vec.Y * scalar, vec.Z * scalar, vec.W * scalar); + } + + public static Vector4f operator *(float scalar, Vector4f vec) + { + return new Vector4f(vec.X * scalar, vec.Y * scalar, vec.Z * scalar, vec.W * scalar); + } + public static Vector4f operator +(Vector4f left, Vector4f right) { return new Vector4f(left.X + right.X, left.Y + right.Y, left.Z + right.Z, left.W + right.W); diff --git a/Source/Core/VisualModes/VisualThing.cs b/Source/Core/VisualModes/VisualThing.cs index cec2d0f7..3f407ac2 100755 --- a/Source/Core/VisualModes/VisualThing.cs +++ b/Source/Core/VisualModes/VisualThing.cs @@ -26,7 +26,7 @@ using CodeImp.DoomBuilder.Map; using CodeImp.DoomBuilder.Rendering; using Plane = CodeImp.DoomBuilder.Geometry.Plane; using CodeImp.DoomBuilder.GZBuilder; -using CodeImp.DoomBuilder.GZBuilder.MD3; +using CodeImp.DoomBuilder.GZBuilder.Models; #endregion diff --git a/Source/Core/ZDoom/ModeldefStructure.cs b/Source/Core/ZDoom/ModeldefStructure.cs index dee2d052..4b02c4c2 100755 --- a/Source/Core/ZDoom/ModeldefStructure.cs +++ b/Source/Core/ZDoom/ModeldefStructure.cs @@ -146,9 +146,9 @@ namespace CodeImp.DoomBuilder.ZDoom return false; } - if(modelext != ".md3" && modelext != ".md2" && modelext != ".3d" && modelext != ".obj") + if(modelext != ".md3" && modelext != ".md2" && modelext != ".3d" && modelext != ".obj" && modelext != ".iqm") { - parser.ReportError("Model \"" + token + "\" won't be loaded. Only Unreal 3D, MD2, MD3, and OBJ models are supported"); + parser.ReportError("Model \"" + token + "\" won't be loaded. Only Unreal 3D, MD2, MD3, OBJ and IQM models are supported"); return false; }