Formatx.Q3MD3.pas 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. //
  2. // The graphics engine GXScene https://github.com/glscene
  3. //
  4. unit Formatx.Q3MD3;
  5. (* Helper classes and methods for Quake3 MD3 actors *)
  6. interface
  7. {$I GLScene.Defines.inc}
  8. uses
  9. System.Classes,
  10. System.SysUtils,
  11. GLScene.VectorTypes,
  12. GLScene.VectorGeometry,
  13. GXS.VectorLists,
  14. GXS.ApplicationFileIO,
  15. GXS.VectorFileObjects,
  16. GXS.Material,
  17. Formatx.MD3;
  18. type
  19. (* This class is used to extract the tag transform information
  20. stored in the MD3 files. The data is used to offset each
  21. part of the model based on the parent parts animation state. *)
  22. TMD3TagList = class
  23. private
  24. FTags: array of TMD3Tag;
  25. FNumTags, FNumFrames: Integer;
  26. function GetTag(index: Integer): TMD3Tag;
  27. public
  28. procedure LoadFromFile(FileName: String);
  29. procedure LoadFromStream(AStream: TStream);
  30. function GetTransform(TagName: string; Frame: Integer): TMatrix4f;
  31. property TagCount: Integer read FNumTags;
  32. property FrameCount: Integer read FNumFrames;
  33. property Tags[index: Integer]: TMD3Tag read GetTag;
  34. end;
  35. (* These procedures are helpers to load the Quake3 animation file data
  36. into an animation list. The NamePrefix parameter is used to determine
  37. which class of animation is extracted. eg NamePrefix='TORSO' will load
  38. all animations starting with 'TORSO_' like 'TORSO_STAND' *)
  39. procedure LoadQ3Anims(Animations: TgxActorAnimations; FileName: string;
  40. NamePrefix: string); overload;
  41. procedure LoadQ3Anims(Animations: TgxActorAnimations; Strings: TStrings;
  42. NamePrefix: string); overload;
  43. (* Quake3 Skin loading procedure. Use this procedure to apply textures
  44. to a GLActor. This doens't use the actors original preloaded materials so
  45. it may be a good idea to clear the actors material library before
  46. running this to keep everything nice and clean. *)
  47. procedure LoadQ3Skin(FileName: string; Actor: TgxActor);
  48. // ---------------------------------------------------------------------------
  49. implementation
  50. // ---------------------------------------------------------------------------
  51. procedure LoadQ3Anims(Animations: TgxActorAnimations; FileName: string;
  52. NamePrefix: string);
  53. var
  54. AnimStrings: TStrings;
  55. begin
  56. AnimStrings := TStringList.Create;
  57. AnimStrings.LoadFromFile(FileName);
  58. LoadQ3Anims(Animations, AnimStrings, NamePrefix);
  59. AnimStrings.Free;
  60. end;
  61. procedure LoadQ3Anims(Animations: TgxActorAnimations; Strings: TStrings;
  62. NamePrefix: string);
  63. var
  64. anim: TStringList;
  65. val: array [0 .. 3] of Integer;
  66. strindex, valindex, i: Integer;
  67. GotValues: Boolean;
  68. commatext, str1: string;
  69. TorsoStartFrame, LegsStartFrame: Integer;
  70. // Used to Fix LEGS Frame values red from CFG file
  71. function StrIsNumber(str: string): Boolean;
  72. var
  73. i: Integer;
  74. begin
  75. result := false;
  76. for i := 1 to Length(str) do
  77. if (Ord(str[i]) >= Ord('0')) and (Ord(str[i]) <= Ord('9')) then
  78. result := true
  79. else
  80. begin
  81. result := false;
  82. break;
  83. end;
  84. end;
  85. begin
  86. anim := TStringList.Create;
  87. TorsoStartFrame := 0;
  88. LegsStartFrame := 0;
  89. FillChar(val[0], SizeOf(val), $00);
  90. for strindex := 0 to Strings.Count - 1 do
  91. begin
  92. commatext := Strings.Strings[strindex];
  93. while Pos(' ', commatext) > 0 do
  94. commatext := StringReplace(commatext, ' ', ' ', [rfReplaceAll]);
  95. commatext := StringReplace(commatext, ' ', ',', [rfReplaceAll]);
  96. anim.commatext := commatext;
  97. GotValues := false;
  98. valindex := 0;
  99. str1 := '';
  100. if anim.Count >= 5 then
  101. begin
  102. for i := 0 to anim.Count - 1 do
  103. begin
  104. if GotValues then
  105. begin
  106. // Store start values to Fix LEGS
  107. if (TorsoStartFrame = 0) and
  108. (Pos('TORSO_', Uppercase(anim.Strings[i])) > 0) then
  109. TorsoStartFrame := val[0];
  110. if (LegsStartFrame = 0) and
  111. (Pos('LEGS_', Uppercase(anim.Strings[i])) > 0) then
  112. LegsStartFrame := val[0];
  113. if (anim.Strings[i] <> '//') and
  114. (Pos(NamePrefix + '_', anim.Strings[i]) > 0) then
  115. begin
  116. str1 := StringReplace(anim.Strings[i], '//', '', [rfReplaceAll]);
  117. break;
  118. end;
  119. end
  120. else
  121. begin
  122. if StrIsNumber(anim.Strings[i]) then
  123. begin
  124. val[valindex] := StrToInt(anim.Strings[i]);
  125. Inc(valindex);
  126. if valindex = 4 then
  127. GotValues := true;
  128. end
  129. else
  130. break;
  131. end;
  132. end;
  133. end;
  134. if GotValues and (str1 <> '') then
  135. begin
  136. // Values ready for new animation.
  137. with Animations.Add do
  138. begin
  139. // Fix frame value for Legs
  140. if Uppercase(NamePrefix) = 'LEGS' then
  141. val[0] := val[0] - LegsStartFrame + TorsoStartFrame;
  142. Name := str1;
  143. StartFrame := val[0];
  144. EndFrame := val[0] + val[1] - 1;
  145. Reference := aarMorph;
  146. // Need a way in TgxActorAnimation to tell whether it is
  147. // a looping type animation or a play once type and
  148. // the framerate (interval) it uses. Both of these can
  149. // be determined here and loaded.
  150. end;
  151. end;
  152. end;
  153. anim.Free;
  154. end;
  155. procedure LoadQ3Skin(FileName: string; Actor: TgxActor);
  156. const
  157. // This list can be expanded if necessary
  158. ExtList: array [0 .. 3] of string = ('.jpg', '.jpeg', '.tga', '.bmp');
  159. var
  160. SkinStrings, temp: TStrings;
  161. i, j: Integer;
  162. libmat: TgxLibMaterial;
  163. mesh: TgxMeshObject;
  164. texture, textureNoDir: string;
  165. textureFound, meshFound: Boolean;
  166. function GetMeshObjectByName(MeshObjects: TgxMeshObjectList; Name: string;
  167. var mesh: TgxMeshObject): Boolean;
  168. var
  169. i: Integer;
  170. begin
  171. result := false;
  172. if (trim(Name) = '') or not Assigned(MeshObjects) then
  173. exit;
  174. for i := 0 to MeshObjects.Count - 1 do
  175. begin
  176. if MeshObjects[i].Name = Name then
  177. begin
  178. mesh := MeshObjects[i];
  179. result := true;
  180. break;
  181. end;
  182. end;
  183. end;
  184. begin
  185. if (not FileExists(FileName)) or (not Assigned(Actor)) then
  186. exit;
  187. if (not Assigned(Actor.MaterialLibrary)) then
  188. exit;
  189. SkinStrings := TStringList.Create;
  190. temp := TStringList.Create;
  191. temp.LoadFromFile(FileName);
  192. for i := 0 to temp.Count - 1 do
  193. begin
  194. SkinStrings.commatext := temp.Strings[i];
  195. if SkinStrings.Count > 1 then
  196. begin
  197. libmat := Actor.MaterialLibrary.Materials.GetLibMaterialByName
  198. (SkinStrings.Strings[1]);
  199. meshFound := GetMeshObjectByName(Actor.MeshObjects,
  200. SkinStrings.Strings[0], mesh);
  201. if meshFound then
  202. begin
  203. if not Assigned(libmat) then
  204. begin
  205. libmat := Actor.MaterialLibrary.Materials.Add;
  206. libmat.Name := SkinStrings.Strings[1];
  207. // Search for the texture file
  208. textureFound := false;
  209. for j := 0 to Length(ExtList) - 1 do
  210. begin
  211. texture := StringReplace(SkinStrings.Strings[1], '/', '\',
  212. [rfReplaceAll]);
  213. texture := ChangeFileExt(texture, ExtList[j]);
  214. if FileExists(texture) then
  215. begin
  216. libmat.Material.texture.Image.LoadFromFile(texture);
  217. libmat.Material.texture.Disabled := false;
  218. textureFound := true;
  219. end
  220. else
  221. begin
  222. textureNoDir := ExtractFileName(texture);
  223. if FileExists(textureNoDir) then
  224. begin
  225. libmat.Material.texture.Image.LoadFromFile(textureNoDir);
  226. libmat.Material.texture.Disabled := false;
  227. textureFound := true;
  228. end;
  229. end;
  230. if textureFound then
  231. break;
  232. end;
  233. end;
  234. for j := 0 to mesh.FaceGroups.Count - 1 do
  235. mesh.FaceGroups[j].MaterialName := libmat.Name;
  236. end;
  237. end;
  238. end;
  239. temp.Free;
  240. SkinStrings.Free;
  241. end;
  242. // ------------------
  243. // ------------------ TMD3TagList ------------------
  244. // ------------------
  245. procedure TMD3TagList.LoadFromFile(FileName: String);
  246. var
  247. fs: TStream;
  248. begin
  249. if FileName <> '' then
  250. begin
  251. fs := TFileStream.Create(FileName, fmOpenRead + fmShareDenyWrite);
  252. try
  253. LoadFromStream(fs);
  254. finally
  255. fs.Free;
  256. end;
  257. end;
  258. end;
  259. procedure TMD3TagList.LoadFromStream(AStream: TStream);
  260. var
  261. MD3Header: TMD3Header;
  262. begin
  263. // Load the MD3 header
  264. AStream.Read(MD3Header, SizeOf(MD3Header));
  265. // Test for correct file ID and version
  266. Assert(MD3Header.fileID = 'IDP3', 'Incorrect MD3 file ID');
  267. Assert(MD3Header.version = 15, 'Incorrect MD3 version number');
  268. // Get the tags from the file
  269. FNumTags := MD3Header.numTags;
  270. FNumFrames := MD3Header.numFrames;
  271. SetLength(FTags, FNumTags * FNumFrames);
  272. AStream.Position := MD3Header.tagStart;
  273. AStream.Read(FTags[0], FNumTags * FNumFrames * SizeOf(TMD3Tag));
  274. end;
  275. function TMD3TagList.GetTag(index: Integer): TMD3Tag;
  276. begin
  277. result := FTags[index];
  278. end;
  279. function TMD3TagList.GetTransform(TagName: string; Frame: Integer): TMatrix4f;
  280. var
  281. TagIdx, i, j: Integer;
  282. Tag: TMD3Tag;
  283. begin
  284. result := IdentityHMGMatrix;
  285. TagIdx := -1;
  286. for i := 0 to FNumTags do
  287. if lowercase(trim(TagName)) = lowercase(trim(string(FTags[i].strName))) then
  288. begin
  289. TagIdx := i;
  290. break;
  291. end;
  292. if TagIdx = -1 then
  293. exit;
  294. Tag := FTags[TagIdx + Frame * FNumTags];
  295. for j := 0 to 2 do
  296. for i := 0 to 2 do
  297. result.V[i].V[j] := Tag.rotation.V[i].V[j];
  298. for i := 0 to 2 do
  299. result.W.V[i] := Tag.vPosition.V[i];
  300. end;
  301. end.