2
0

GLS.FileMDC.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. //
  2. // The multimedia graphics platform GLScene https://github.com/glscene
  3. //
  4. unit GLS.FileMDC;
  5. (*
  6. Code for loading animated MDC files into GLScene FreeForms
  7. and Actors.
  8. This file format uses in Return To Castle Wolfenstein instead
  9. of MD3 files. It has got all MD3 features (such as Tag frames)
  10. plus very small data!
  11. Original code by Osman Turan ([email protected])
  12. *)
  13. interface
  14. {$I GLScene.inc}
  15. uses
  16. System.Classes,
  17. System.SysUtils,
  18. GLS.VectorFileObjects,
  19. GLS.Material,
  20. GLS.ApplicationFileIO,
  21. GLS.VectorGeometry;
  22. const
  23. MDCFILE_IDENTITY = 'IDPC';
  24. MDCFILE_VERSION = 2;
  25. MDC_BASEVERTEX_FACTOR = 0.015625; // 1/64;
  26. MDC_COMPVERTEX_FACTOR = 0.046875; // 3/64;
  27. type
  28. TMDCPoint = array [0 .. 2] of Single;
  29. TMDCAngle = TMDCPoint;
  30. TMDCFileHeader = packed record
  31. Ident: array [0 .. 3] of AnsiChar;
  32. Version: Cardinal;
  33. Name: array [0 .. 63] of AnsiChar;
  34. Flags: Cardinal;
  35. NumFrames: Cardinal;
  36. NumTags: Cardinal;
  37. NumSurfaces: Cardinal;
  38. NumSkins: Cardinal;
  39. OffsetBorderFrames: Cardinal;
  40. OffsetTagNames: Cardinal;
  41. OffsetTagFrames: Cardinal;
  42. OffsetSurfaces: Cardinal;
  43. OffsetEnd: Cardinal;
  44. end;
  45. TMDCBorderFrame = record
  46. BBMin, BBMax: TMDCPoint;
  47. LocalOrigin: TMDCPoint;
  48. Radius: Single;
  49. Name: array [0 .. 15] of AnsiChar;
  50. end;
  51. PMDCTagName = ^TMDCTagName;
  52. TMDCTagName = packed record
  53. Name: array [0 .. 63] of AnsiChar;
  54. end;
  55. PMDCTagFrame = ^TMDCTagFrame;
  56. TMDCTagFrame = packed record
  57. TagPosition: array [0 .. 2] of Word; // or ShortInt?
  58. TagAngle: array [0 .. 2] of Word; // or ShortInt?
  59. end;
  60. TMDCTag = packed record
  61. TagName: PMDCTagName;
  62. TagFrame: PMDCTagFrame;
  63. end;
  64. TMDCSurfaceHeader = packed record
  65. Ident: array [0 .. 3] of AnsiChar;
  66. Name: array [0 .. 63] of AnsiChar;
  67. Flags: Cardinal;
  68. NumCompFrames: Cardinal;
  69. NumBaseFrames: Cardinal;
  70. NumSkins: Cardinal;
  71. NumVertices: Cardinal;
  72. NumTriangles: Cardinal;
  73. OffsetTriangles: Cardinal;
  74. OffsetSkins: Cardinal;
  75. OffsetTexCoords: Cardinal;
  76. OffsetBaseVerts: Cardinal;
  77. OffsetCompVerts: Cardinal;
  78. OffsetFrameBaseFrames: Cardinal;
  79. OffsetFrameCompFrames: Cardinal;
  80. OffsetEnd: Cardinal;
  81. end;
  82. TMDCTriangle = array [0 .. 2] of Cardinal;
  83. TMDCSkin = packed record
  84. Shader: array [0 .. 63] of AnsiChar;
  85. Flags: Cardinal;
  86. end;
  87. TMDCTexCoord = array [0 .. 1] of Single;
  88. TMDCBaseVertex = array [0 .. 3] of SmallInt;
  89. TMDCCompVertex = array [0 .. 3] of Byte;
  90. TMDCBaseFrame = packed record
  91. BaseVertices: array of TMDCBaseVertex;
  92. end;
  93. TMDCCompFrame = packed record
  94. CompVertices: array of TMDCCompVertex;
  95. end;
  96. type
  97. TGLMDCVectorFile = class(TGLVectorFile)
  98. public
  99. class function Capabilities: TGLDataFileCapabilities; override;
  100. procedure LoadFromStream(AStream: TStream); override;
  101. end;
  102. // ------------------------------------------------------------------
  103. implementation
  104. // ------------------------------------------------------------------
  105. // ------------------
  106. // ------------------ TGLMDCVectorFile ------------------
  107. // ------------------
  108. class function TGLMDCVectorFile.Capabilities: TGLDataFileCapabilities;
  109. begin
  110. Result := [DfcRead];
  111. end;
  112. procedure TGLMDCVectorFile.LoadFromStream(AStream: TStream);
  113. type
  114. PPackedNormal = ^TPackedNormal;
  115. TPackedNormal = array [0 .. 1] of Byte;
  116. var
  117. I, J, K, NumVerts, Numtris: Integer;
  118. Mesh: TGLMorphableMeshObject;
  119. FaceGroup: TFGIndexTexCoordList;
  120. MorphTarget: TGLMeshMorphTarget;
  121. function UnpackNormal(Pn: TPackedNormal): TAffineVector;
  122. var
  123. Lat, Lng: Single;
  124. begin
  125. // The MDC normal is a latitude/longitude value that needs
  126. // to be calculated into cartesian space.
  127. Lat := (Pn[0]) * (2 * Pi) / 255;
  128. Lng := (Pn[1]) * (2 * Pi) / 255;
  129. Result.X := Cos(Lat) * Sin(Lng);
  130. Result.Y := Sin(Lat) * Sin(Lng);
  131. Result.Z := Cos(Lng);
  132. end;
  133. procedure AllocateMaterial(Meshname: string);
  134. var
  135. LibMat: TGLLibMaterial;
  136. begin
  137. // If a material library is assigned to the actor/freeform the
  138. // mesh name will be added as a material.
  139. if Assigned(Owner.MaterialLibrary) then
  140. with Owner.MaterialLibrary do
  141. begin
  142. if Assigned(Materials.GetLibMaterialByName(Meshname)) then
  143. Exit;
  144. LibMat := Materials.Add;
  145. LibMat.Name := Meshname;
  146. LibMat.Material.Texture.Disabled := False;
  147. end;
  148. end;
  149. var
  150. Fileheader: TMDCFileHeader;
  151. Surfheader: TMDCSurfaceHeader;
  152. Borderframes: array of TMDCBorderFrame;
  153. Baseframetable, Compframetable: array of Word;
  154. Baseframe: TMDCBaseFrame;
  155. Compframe: TMDCCompFrame;
  156. Xyz, Normal: TAffineVector;
  157. St: array of array [0 .. 1] of Single;
  158. Triangles: array of TMDCTriangle;
  159. FrameOffset: Cardinal;
  160. begin
  161. AStream.Read(Fileheader, SizeOf(Fileheader));
  162. Assert(Fileheader.Ident = MDCFILE_IDENTITY, 'Incorrect MDC file Ident');
  163. Assert(Fileheader.Version = MDCFILE_VERSION, 'Incorrect MDC version number');
  164. try
  165. AStream.Seek(Fileheader.OffsetBorderFrames, SoFromBeginning);
  166. SetLength(Borderframes, Fileheader.NumFrames);
  167. AStream.Read(Borderframes[0], SizeOf(TMDCBorderFrame) * Fileheader.NumFrames);
  168. FrameOffset := Fileheader.OffsetSurfaces;
  169. for I := 0 to Fileheader.NumSurfaces - 1 do
  170. begin
  171. // read header
  172. AStream.Position := FrameOffset;
  173. AStream.Read(Surfheader, SizeOf(TMDCSurfaceHeader));
  174. // triangles for this surface
  175. SetLength(Triangles, Surfheader.NumTriangles);
  176. AStream.Position := FrameOffset + Surfheader.OffsetTriangles;
  177. AStream.Read(Triangles[0], SizeOf(TMDCTriangle) * Surfheader.NumTriangles);
  178. // texture coordinates for this surface
  179. SetLength(St, Surfheader.NumVertices);
  180. AStream.Position := FrameOffset + Surfheader.OffsetTexCoords;
  181. AStream.Read(St[0], 2 * SizeOf(Single) * Surfheader.NumVertices);
  182. // base frame table for this surface (for only loading)
  183. SetLength(Baseframetable, Fileheader.NumFrames);
  184. AStream.Position := FrameOffset + Surfheader.OffsetFrameBaseFrames;
  185. AStream.Read(Baseframetable[0], SizeOf(Word) * Fileheader.NumFrames);
  186. // compressed frame table for this surface (for only loading)
  187. SetLength(Compframetable, Fileheader.NumFrames);
  188. AStream.Position := FrameOffset + Surfheader.OffsetFrameCompFrames;
  189. AStream.Read(Compframetable[0], SizeOf(Word) * Fileheader.NumFrames);
  190. Mesh := TGLMorphableMeshObject.CreateOwned(Owner.MeshObjects);
  191. // easiest way to convert a char array to string ;)
  192. Mesh.Name := Trim(string(PChar(Surfheader.Name[0])));
  193. with Mesh do
  194. begin
  195. Mode := MomFaceGroups;
  196. FaceGroup := TFGIndexTexCoordList.CreateOwned(FaceGroups);
  197. with FaceGroup do
  198. begin
  199. AllocateMaterial(Mesh.Name);
  200. MaterialName := Mesh.Name;
  201. Numtris := Surfheader.NumTriangles;
  202. VertexIndices.Capacity := Numtris * 3;
  203. TexCoords.Capacity := Numtris * 3;
  204. // Get the vertex indices and texture coordinates
  205. for J := 0 to Surfheader.NumTriangles - 1 do
  206. begin
  207. Add(Triangles[J, 0], St[Triangles[J, 0]][0], 1 - St[Triangles[J, 0]][1]);
  208. Add(Triangles[J, 2], St[Triangles[J, 2]][0], 1 - St[Triangles[J, 2]][1]);
  209. Add(Triangles[J, 1], St[Triangles[J, 1]][0], 1 - St[Triangles[J, 1]][1]);
  210. end;
  211. end;
  212. // Get the mesh data for each morph frame
  213. for J := 0 to Fileheader.NumFrames - 1 do
  214. begin
  215. MorphTarget := TGLMeshMorphTarget.CreateOwned(MorphTargets);
  216. MorphTarget.Name := Trim(string(PChar(Surfheader.Name[0]))) + '[' + IntToStr(J) + ']';
  217. NumVerts := Surfheader.NumVertices;
  218. MorphTarget.Vertices.Capacity := NumVerts;
  219. // base frames
  220. SetLength(Baseframe.BaseVertices, Surfheader.NumVertices);
  221. AStream.Position := FrameOffset + Surfheader.OffsetBaseVerts + Baseframetable[J] * Surfheader.NumVertices * 8;
  222. AStream.Read(Baseframe.BaseVertices[0], SizeOf(TMDCBaseVertex) * Surfheader.NumVertices);
  223. // compressed frames
  224. if Compframetable[J] <> $FFFF then // is there a valid frame?
  225. begin
  226. SetLength(Compframe.CompVertices, Surfheader.NumVertices);
  227. AStream.Position := FrameOffset + Surfheader.OffsetCompVerts + Compframetable[J] * Surfheader.NumVertices * 4;
  228. AStream.Read(Compframe.CompVertices[0], SizeOf(TMDCCompVertex) * Surfheader.NumVertices);
  229. end;
  230. for K := 0 to Surfheader.NumVertices - 1 do
  231. begin
  232. Xyz.X := (Baseframe.BaseVertices[K, 0] * MDC_BASEVERTEX_FACTOR) + Borderframes[J].LocalOrigin[0];
  233. Xyz.Y := (Baseframe.BaseVertices[K, 1] * MDC_BASEVERTEX_FACTOR) + Borderframes[J].LocalOrigin[1];
  234. Xyz.Z := (Baseframe.BaseVertices[K, 2] * MDC_BASEVERTEX_FACTOR) + Borderframes[J].LocalOrigin[2];
  235. Normal := UnpackNormal(PPackedNormal(@Baseframe.BaseVertices[K, 3])^);
  236. if Compframetable[J] <> $FFFF then
  237. begin
  238. Xyz.X := Xyz.X + ((Compframe.CompVertices[K, 0] - 128) * MDC_COMPVERTEX_FACTOR);
  239. Xyz.Y := Xyz.Y + ((Compframe.CompVertices[K, 1] - 128) * MDC_COMPVERTEX_FACTOR);
  240. Xyz.Z := Xyz.Z + ((Compframe.CompVertices[K, 2] - 128) * MDC_COMPVERTEX_FACTOR);
  241. // FIXME:
  242. // I'm sure compframe.CompVertices[3] points a packed normal.
  243. // And it must be add the current normal like xyz.
  244. // But, I don't know a way to unpacked this value
  245. // I found a precalculated normal list in RTCW 1.41 mod source (q_math.c)
  246. //
  247. // NUMVERTEXNORMALS = 162
  248. // vec3_t bytedirs[NUMVERTEXNORMALS] = {
  249. // {-0.525731, 0.000000, 0.850651}, (...)
  250. //
  251. // But, I had noticed some compframe.CompVertices[3] value is bigger
  252. // than NUMVERTEXNORMALS constant. So, there must be another list.
  253. // Can you find it?
  254. // Osman Turan ([email protected])
  255. end;
  256. // all id Sofware based games uses Z axis as up instead of Y. So, convert them
  257. MorphTarget.Vertices.Add(Xyz.X, Xyz.Z, -Xyz.Y);
  258. MorphTarget.Normals.Add(Normal.X, Normal.Z, -Normal.Y);
  259. end;
  260. end;
  261. end;
  262. FrameOffset := FrameOffset + Surfheader.OffsetEnd;
  263. if Mesh.MorphTargets.Count > 0 then
  264. Mesh.MorphTo(0);
  265. end;
  266. finally
  267. // save memory free space
  268. Borderframes := nil;
  269. Baseframetable := nil;
  270. Compframetable := nil;
  271. St := nil;
  272. Triangles := nil;
  273. end;
  274. end;
  275. // ------------------------------------------------------------------
  276. initialization
  277. // ------------------------------------------------------------------
  278. RegisterVectorFileFormat('mdc', 'MDC files', TGLMDCVectorFile);
  279. end.