GXS.FileSMD.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. //
  2. // The graphics engine GLXEngine. The unit of GXScene for Delphi
  3. //
  4. unit GXS.FileSMD;
  5. (* SMD vector file format implementation *)
  6. interface
  7. uses
  8. System.Classes,
  9. System.SysUtils,
  10. Stage.VectorTypes,
  11. Stage.VectorGeometry,
  12. Stage.Utils,
  13. GXS.VectorLists,
  14. GXS.VectorFileObjects,
  15. GXS.Texture,
  16. GXS.ApplicationFileIO,
  17. GXS.Material,
  18. GXS.ImageUtils;
  19. type
  20. (* The SMD vector file is Half-life's skeleton format.
  21. The SMD is a text-based file format. They come in two flavors: one that
  22. old Skeleton and triangle (mesh) data, and animation files that store
  23. Skeleton frames.
  24. This reader curently reads both, but requires that the main file
  25. (the one with mesh data) be read first. *)
  26. TGXSMDVectorFile = class(TgxVectorFile)
  27. public
  28. class function Capabilities: TDataFileCapabilities; override;
  29. procedure LoadFromStream(aStream: TStream); override;
  30. procedure SaveToStream(aStream: TStream); override;
  31. end;
  32. implementation // ------------------------------------------------------------
  33. // ------------------
  34. // ------------------ TGXSMDVectorFile ------------------
  35. // ------------------
  36. class function TGXSMDVectorFile.Capabilities: TDataFileCapabilities;
  37. begin
  38. Result := [dfcRead, dfcWrite];
  39. end;
  40. procedure TGXSMDVectorFile.LoadFromStream(aStream: TStream);
  41. procedure AllocateMaterial(const name: String);
  42. var
  43. matLib: TgxMaterialLibrary;
  44. begin
  45. if Owner is TgxBaseMesh then
  46. begin
  47. matLib := TgxBaseMesh(GetOwner).MaterialLibrary;
  48. if Assigned(matLib) then
  49. begin
  50. if matLib.Materials.GetLibMaterialByName(name) = nil then
  51. begin
  52. if CompareText(name, 'null.bmp') <> 0 then
  53. begin
  54. try
  55. matLib.AddTextureMaterial(name, name)
  56. except
  57. on E: ETexture do
  58. begin
  59. if not Owner.IgnoreMissingTextures then
  60. raise;
  61. end;
  62. end;
  63. end
  64. else
  65. matLib.AddTextureMaterial(name, '');
  66. end;
  67. end;
  68. end;
  69. end;
  70. var
  71. i, j, k, nVert, nTex, firstFrame: Integer;
  72. nbBones, boneID: Integer;
  73. mesh: TgxSkeletonMeshObject;
  74. sl, tl: TStringList;
  75. bone: TgxSkeletonBone;
  76. frame: TgxSkeletonFrame;
  77. faceGroup: TFGVertexNormalTexIndexList;
  78. v: TAffineVector;
  79. boneIDs: TgxVertexBoneWeightDynArray;
  80. weightCount: Integer;
  81. begin
  82. sl := TStringList.Create;
  83. tl := TStringList.Create;
  84. try
  85. sl.LoadFromStream(aStream);
  86. if sl[0] <> 'version 1' then
  87. raise Exception.Create('SMD version 1 required');
  88. if sl[1] <> 'nodes' then
  89. raise Exception.Create('nodes not found');
  90. if sl.IndexOf('triangles') >= 0 then
  91. begin
  92. mesh := TgxSkeletonMeshObject.CreateOwned(Owner.MeshObjects);
  93. mesh.Mode := momFaceGroups;
  94. end
  95. else if Owner.MeshObjects.Count > 0 then
  96. mesh := (Owner.MeshObjects[0] as TgxSkeletonMeshObject)
  97. else
  98. raise Exception.Create('SMD is an animation, load model SMD first.');
  99. // read skeleton nodes
  100. i := 2;
  101. if Owner.Skeleton.RootBones.Count = 0 then
  102. begin
  103. // new bone structure
  104. while sl[i] <> 'end' do
  105. begin
  106. tl.CommaText := sl[i];
  107. with Owner.Skeleton do
  108. if (tl[2] <> '-1') then
  109. bone := TgxSkeletonBone.CreateOwned
  110. (RootBones.BoneByID(StrToInt(tl[2])))
  111. else
  112. bone := TgxSkeletonBone.CreateOwned(RootBones);
  113. if Assigned(bone) then
  114. begin
  115. bone.boneID := StrToInt(tl[0]);
  116. bone.name := tl[1];
  117. end;
  118. Inc(i);
  119. end;
  120. end
  121. else
  122. begin
  123. // animation file, skip structure
  124. while sl[i] <> 'end' do
  125. Inc(i);
  126. end;
  127. Inc(i);
  128. if sl[i] <> 'skeleton' then
  129. raise Exception.Create('skeleton not found');
  130. Inc(i);
  131. // read animation time frames
  132. nbBones := Owner.Skeleton.RootBones.BoneCount - 1;
  133. firstFrame := Owner.Skeleton.Frames.Count;
  134. while sl[i] <> 'end' do
  135. begin
  136. if Copy(sl[i], 1, 5) <> 'time ' then
  137. raise Exception.Create('time not found, got: ' + sl[i]);
  138. frame := TgxSkeletonFrame.CreateOwned(Owner.Skeleton.Frames);
  139. frame.name := ResourceName + ' ' + sl[i];
  140. Inc(i);
  141. while Pos(Copy(sl[i], 1, 1), ' 1234567890') > 0 do
  142. begin
  143. tl.CommaText := sl[i];
  144. while StrToInt(tl[0]) > frame.Position.Count do
  145. begin
  146. frame.Position.Add(NullVector);
  147. frame.Rotation.Add(NullVector);
  148. end;
  149. frame.Position.Add(StrToFloatDef(tl[1]),
  150. StrToFloatDef(tl[2]), StrToFloatDef(tl[3]));
  151. v := AffineVectorMake(StrToFloatDef(tl[4]),
  152. StrToFloatDef(tl[5]), StrToFloatDef(tl[6]));
  153. frame.Rotation.Add(v);
  154. Inc(i);
  155. end;
  156. while frame.Position.Count < nbBones do
  157. begin
  158. frame.Position.Add(NullVector);
  159. frame.Rotation.Add(NullVector);
  160. end;
  161. Assert(frame.Position.Count = nbBones, 'Invalid number of bones in frame '
  162. + IntToStr(Owner.Skeleton.Frames.Count));
  163. end;
  164. if Owner is TgxActor then
  165. with TgxActor(Owner).Animations.Add do
  166. begin
  167. k := Pos('.', ResourceName);
  168. if k > 0 then
  169. Name := Copy(ResourceName, 1, k - 1)
  170. else
  171. Name := ResourceName;
  172. Reference := aarSkeleton;
  173. StartFrame := firstFrame;
  174. EndFrame := Self.Owner.Skeleton.Frames.Count - 1;
  175. end;
  176. Inc(i);
  177. if (i < sl.Count) and (sl[i] = 'triangles') then
  178. begin
  179. // read optional mesh data
  180. Inc(i);
  181. if mesh.BonesPerVertex < 1 then
  182. mesh.BonesPerVertex := 1;
  183. faceGroup := nil;
  184. while sl[i] <> 'end' do
  185. begin
  186. if (faceGroup = nil) or (faceGroup.MaterialName <> sl[i]) then
  187. begin
  188. faceGroup := TFGVertexNormalTexIndexList.CreateOwned(mesh.FaceGroups);
  189. faceGroup.Mode := fgmmTriangles;
  190. faceGroup.MaterialName := sl[i];
  191. AllocateMaterial(sl[i]);
  192. end;
  193. Inc(i);
  194. for k := 1 to 3 do
  195. with mesh do
  196. begin
  197. tl.CommaText := sl[i];
  198. if tl.Count > 9 then
  199. begin
  200. // Half-Life 2 SMD, specifies bones and weights
  201. weightCount := StrToInt(tl[9]);
  202. SetLength(boneIDs, weightCount);
  203. for j := 0 to weightCount - 1 do
  204. begin
  205. boneIDs[j].boneID := StrToInt(tl[10 + j * 2]);
  206. boneIDs[j].Weight := StrToFloatDef(tl[11 + j * 2]);
  207. end;
  208. nVert := FindOrAdd(boneIDs,
  209. AffineVectorMake(StrToFloatDef(tl[1]),
  210. StrToFloatDef(tl[2]), StrToFloatDef(tl[3])),
  211. AffineVectorMake(StrToFloatDef(tl[4]),
  212. StrToFloatDef(tl[5]),
  213. StrToFloatDef(tl[6])));
  214. nTex := TexCoords.FindOrAdd
  215. (AffineVectorMake(StrToFloatDef(tl[7]),
  216. StrToFloatDef(tl[8]), 0));
  217. faceGroup.Add(nVert, nVert, nTex);
  218. Inc(i);
  219. end
  220. else
  221. begin
  222. // Half-Life 1 simple format
  223. boneID := StrToInt(tl[0]);
  224. nVert := FindOrAdd(boneID,
  225. AffineVectorMake(StrToFloatDef(tl[1]),
  226. StrToFloatDef(tl[2]), StrToFloatDef(tl[3])),
  227. AffineVectorMake(StrToFloatDef(tl[4]),
  228. StrToFloatDef(tl[5]),
  229. StrToFloatDef(tl[6])));
  230. nTex := TexCoords.FindOrAdd
  231. (AffineVectorMake(StrToFloatDef(tl[7]),
  232. StrToFloatDef(tl[8]), 0));
  233. faceGroup.Add(nVert, nVert, nTex);
  234. Inc(i);
  235. end;
  236. end;
  237. end;
  238. Owner.Skeleton.RootBones.PrepareGlobalMatrices;
  239. mesh.PrepareBoneMatrixInvertedMeshes;
  240. end;
  241. finally
  242. tl.Free;
  243. sl.Free;
  244. end;
  245. end;
  246. procedure TGXSMDVectorFile.SaveToStream(aStream: TStream);
  247. var
  248. str, nodes: TStrings;
  249. i, j, k, l, b: Integer;
  250. p, r, v, n, t: TAffineVector;
  251. procedure GetNodesFromBonesRecurs(bone: TgxSkeletonBone; ParentID: Integer;
  252. bl: TStrings);
  253. var
  254. i: Integer;
  255. begin
  256. bl.Add(Format('%3d "%s" %3d', [bone.boneID, bone.name, ParentID]));
  257. for i := 0 to bone.Count - 1 do
  258. GetNodesFromBonesRecurs(bone.Items[i], bone.boneID, bl);
  259. end;
  260. begin
  261. str := TStringList.Create;
  262. nodes := TStringList.Create;
  263. try
  264. str.Add('version 1');
  265. // Add the bones
  266. str.Add('nodes');
  267. for i := 0 to Owner.Skeleton.RootBones.Count - 1 do
  268. begin
  269. GetNodesFromBonesRecurs(Owner.Skeleton.RootBones[i], -1, nodes);
  270. end;
  271. str.AddStrings(nodes);
  272. str.Add('end');
  273. // Now add the relavent frames
  274. if Owner.Skeleton.Frames.Count > 0 then
  275. begin
  276. str.Add('skeleton');
  277. for i := 0 to Owner.Skeleton.Frames.Count - 1 do
  278. begin
  279. str.Add(Format('time %d', [i]));
  280. for j := 0 to Owner.Skeleton.Frames[i].Position.Count - 1 do
  281. begin
  282. p := Owner.Skeleton.Frames[i].Position[j];
  283. r := Owner.Skeleton.Frames[i].Rotation[j];
  284. str.Add(StringReplace(Format('%3d %.6f %.6f %.6f %.6f %.6f %.6f',
  285. [j, p.X, p.Y, p.Z, r.X, r.Y, r.Z]), ',', '.', [rfReplaceAll]));
  286. end;
  287. end;
  288. str.Add('end');
  289. end;
  290. // Add the mesh data
  291. if Owner.MeshObjects.Count > 0 then
  292. begin
  293. str.Add('triangles');
  294. for i := 0 to Owner.MeshObjects.Count - 1 do
  295. if Owner.MeshObjects[i] is TgxSkeletonMeshObject then
  296. with TgxSkeletonMeshObject(Owner.MeshObjects[i]) do
  297. begin
  298. for j := 0 to FaceGroups.Count - 1 do
  299. with TFGVertexNormalTexIndexList(FaceGroups[j]) do
  300. begin
  301. for k := 0 to (VertexIndices.Count div 3) - 1 do
  302. begin
  303. str.Add(MaterialName);
  304. for l := 0 to 2 do
  305. begin
  306. v := Vertices[VertexIndices[3 * k + l]];
  307. n := Normals[NormalIndices[3 * k + l]];
  308. t := TexCoords[TexCoordIndices[3 * k + l]];
  309. b := VerticesBonesWeights^[VertexIndices[3 * k + l]]^
  310. [0].boneID;
  311. str.Add(StringReplace
  312. (Format('%3d %.4f %.4f %.4f %.4f %.4f %.4f %.4f %.4f',
  313. [b, v.X, v.Y, v.Z, n.X, n.Y, n.Z, t.X, t.Y]), ',', '.',
  314. [rfReplaceAll]));
  315. end;
  316. end;
  317. end;
  318. end;
  319. str.Add('end');
  320. end;
  321. str.SaveToStream(aStream);
  322. finally
  323. str.Free;
  324. nodes.Free;
  325. end;
  326. end;
  327. initialization // ------------------------------------------------------------
  328. RegisterVectorFileFormat('smd', 'Half-Life SMD files', TGXSMDVectorFile);
  329. end.