GLFileSMD.pas 11 KB

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