GLS.FileQ3MD3.pas 9.3 KB

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