GLFileSMD.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. //
  2. // This unit is part of the GLScene Engine, http://glscene.org
  3. //
  4. unit GLFileSMD;
  5. (* SMD vector file format implementation *)
  6. interface
  7. uses
  8. System.Classes,
  9. System.SysUtils,
  10. GLVectorFileObjects,
  11. GLVectorLists,
  12. GLTexture,
  13. GLPersistentClasses,
  14. GLApplicationFileIO,
  15. GLVectorTypes,
  16. GLVectorGeometry,
  17. GLMaterial,
  18. GLS.Strings;
  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. TGLSMDVectorFile = class(TGLVectorFile)
  27. public
  28. class function Capabilities: TGLDataFileCapabilities; override;
  29. procedure LoadFromStream(aStream: TStream); override;
  30. procedure SaveToStream(aStream: TStream); override;
  31. end;
  32. // ------------------------------------------------------------------
  33. implementation
  34. // ------------------------------------------------------------------
  35. // ------------------
  36. // ------------------ TGLSMDVectorFile ------------------
  37. // ------------------
  38. class function TGLSMDVectorFile.Capabilities: TGLDataFileCapabilities;
  39. begin
  40. Result := [dfcRead, dfcWrite];
  41. end;
  42. procedure TGLSMDVectorFile.LoadFromStream(aStream: TStream);
  43. procedure AllocateMaterial(const name: String);
  44. var
  45. matLib: TGLMaterialLibrary;
  46. begin
  47. if Owner is TGLBaseMesh then
  48. begin
  49. matLib := TGLBaseMesh(GetOwner).MaterialLibrary;
  50. if Assigned(matLib) then
  51. begin
  52. if matLib.Materials.GetLibMaterialByName(name) = nil then
  53. begin
  54. if CompareText(name, 'null.bmp') <> 0 then
  55. begin
  56. try
  57. matLib.AddTextureMaterial(name, name)
  58. except
  59. on E: ETexture do
  60. begin
  61. if not Owner.IgnoreMissingTextures then
  62. raise;
  63. end;
  64. end;
  65. end
  66. else
  67. matLib.AddTextureMaterial(name, '');
  68. end;
  69. end;
  70. end;
  71. end;
  72. var
  73. i, j, k, nVert, nTex, firstFrame: Integer;
  74. nbBones, boneID: Integer;
  75. mesh: TGLSkeletonMeshObject;
  76. sl, tl: TStringList;
  77. bone: TGLSkeletonBone;
  78. frame: TGLSkeletonFrame;
  79. faceGroup: TFGVertexNormalTexIndexList;
  80. v: TAffineVector;
  81. boneIDs: TVertexBoneWeightDynArray;
  82. weightCount: Integer;
  83. begin
  84. sl := TStringList.Create;
  85. tl := TStringList.Create;
  86. try
  87. sl.LoadFromStream(aStream);
  88. if sl[0] <> 'version 1' then
  89. raise Exception.Create('SMD version 1 required');
  90. if sl[1] <> 'nodes' then
  91. raise Exception.Create('nodes not found');
  92. if sl.IndexOf('triangles') >= 0 then
  93. begin
  94. mesh := TGLSkeletonMeshObject.CreateOwned(Owner.MeshObjects);
  95. mesh.Mode := momFaceGroups;
  96. end
  97. else if Owner.MeshObjects.Count > 0 then
  98. mesh := (Owner.MeshObjects[0] as TGLSkeletonMeshObject)
  99. else
  100. raise Exception.Create('SMD is an animation, load model SMD first.');
  101. // read skeleton nodes
  102. i := 2;
  103. if Owner.Skeleton.RootBones.Count = 0 then
  104. begin
  105. // new bone structure
  106. while sl[i] <> 'end' do
  107. begin
  108. tl.CommaText := sl[i];
  109. with Owner.Skeleton do
  110. if (tl[2] <> '-1') then
  111. bone := TGLSkeletonBone.CreateOwned
  112. (RootBones.BoneByID(StrToInt(tl[2])))
  113. else
  114. bone := TGLSkeletonBone.CreateOwned(RootBones);
  115. if Assigned(bone) then
  116. begin
  117. bone.boneID := StrToInt(tl[0]);
  118. bone.name := tl[1];
  119. end;
  120. Inc(i);
  121. end;
  122. end
  123. else
  124. begin
  125. // animation file, skip structure
  126. while sl[i] <> 'end' do
  127. Inc(i);
  128. end;
  129. Inc(i);
  130. if sl[i] <> 'skeleton' then
  131. raise Exception.Create('skeleton not found');
  132. Inc(i);
  133. // read animation time frames
  134. nbBones := Owner.Skeleton.RootBones.BoneCount - 1;
  135. firstFrame := Owner.Skeleton.Frames.Count;
  136. while sl[i] <> 'end' do
  137. begin
  138. if Copy(sl[i], 1, 5) <> 'time ' then
  139. raise Exception.Create('time not found, got: ' + sl[i]);
  140. frame := TGLSkeletonFrame.CreateOwned(Owner.Skeleton.Frames);
  141. frame.name := ResourceName + ' ' + sl[i];
  142. Inc(i);
  143. while Pos(Copy(sl[i], 1, 1), ' 1234567890') > 0 do
  144. begin
  145. tl.CommaText := sl[i];
  146. while StrToInt(tl[0]) > frame.Position.Count do
  147. begin
  148. frame.Position.Add(NullVector);
  149. frame.Rotation.Add(NullVector);
  150. end;
  151. frame.Position.Add(StrToFloatDef(tl[1],0),
  152. StrToFloatDef(tl[2],0), StrToFloatDef(tl[3],0));
  153. v := AffineVectorMake(StrToFloatDef(tl[4],0),
  154. StrToFloatDef(tl[5],0), StrToFloatDef(tl[6],0));
  155. frame.Rotation.Add(v);
  156. Inc(i);
  157. end;
  158. while frame.Position.Count < nbBones do
  159. begin
  160. frame.Position.Add(NullVector);
  161. frame.Rotation.Add(NullVector);
  162. end;
  163. Assert(frame.Position.Count = nbBones, 'Invalid number of bones in frame '
  164. + IntToStr(Owner.Skeleton.Frames.Count));
  165. end;
  166. if Owner is TGLActor then
  167. with TGLActor(Owner).Animations.Add do
  168. begin
  169. k := Pos('.', ResourceName);
  170. if k > 0 then
  171. Name := Copy(ResourceName, 1, k - 1)
  172. else
  173. Name := ResourceName;
  174. Reference := aarSkeleton;
  175. StartFrame := firstFrame;
  176. EndFrame := Self.Owner.Skeleton.Frames.Count - 1;
  177. end;
  178. Inc(i);
  179. if (i < sl.Count) and (sl[i] = 'triangles') then
  180. begin
  181. // read optional mesh data
  182. Inc(i);
  183. if mesh.BonesPerVertex < 1 then
  184. mesh.BonesPerVertex := 1;
  185. faceGroup := nil;
  186. while sl[i] <> 'end' do
  187. begin
  188. if (faceGroup = nil) or (faceGroup.MaterialName <> sl[i]) then
  189. begin
  190. faceGroup := TFGVertexNormalTexIndexList.CreateOwned(mesh.FaceGroups);
  191. faceGroup.Mode := fgmmTriangles;
  192. faceGroup.MaterialName := sl[i];
  193. AllocateMaterial(sl[i]);
  194. end;
  195. Inc(i);
  196. for k := 1 to 3 do
  197. with mesh do
  198. begin
  199. tl.CommaText := sl[i];
  200. if tl.Count > 9 then
  201. begin
  202. // Half-Life 2 SMD, specifies bones and weights
  203. weightCount := StrToInt(tl[9]);
  204. SetLength(boneIDs, weightCount);
  205. for j := 0 to weightCount - 1 do
  206. begin
  207. boneIDs[j].boneID := StrToInt(tl[10 + j * 2]);
  208. boneIDs[j].Weight := StrToFloatDef(tl[11 + j * 2],0);
  209. end;
  210. nVert := FindOrAdd(boneIDs,
  211. AffineVectorMake(StrToFloatDef(tl[1],0),
  212. StrToFloatDef(tl[2],0), StrToFloatDef(tl[3],0)),
  213. AffineVectorMake(StrToFloatDef(tl[4],0),
  214. StrToFloatDef(tl[5],0), StrToFloatDef(tl[6],0)));
  215. nTex := TexCoords.FindOrAdd
  216. (AffineVectorMake(StrToFloatDef(tl[7],0),
  217. StrToFloatDef(tl[8],0), 0));
  218. faceGroup.Add(nVert, nVert, nTex);
  219. Inc(i);
  220. end
  221. else
  222. begin
  223. // Half-Life 1 simple format
  224. boneID := StrToInt(tl[0]);
  225. nVert := FindOrAdd(boneID,
  226. AffineVectorMake(StrToFloatDef(tl[1],0),
  227. StrToFloatDef(tl[2],0), StrToFloatDef(tl[3],0)),
  228. AffineVectorMake(StrToFloatDef(tl[4],0),
  229. StrToFloatDef(tl[5],0), StrToFloatDef(tl[6],0)));
  230. nTex := TexCoords.FindOrAdd
  231. (AffineVectorMake(StrToFloatDef(tl[7],0),
  232. StrToFloatDef(tl[8],0), 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 TGLSMDVectorFile.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: TGLSkeletonBone; 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 TGLSkeletonMeshObject then
  296. with TGLSkeletonMeshObject(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. // ------------------------------------------------------------------
  328. initialization
  329. // ------------------------------------------------------------------
  330. RegisterVectorFileFormat('smd', 'Half-Life SMD files', TGLSMDVectorFile);
  331. end.