GXS.FileOBJ.pas 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413
  1. //
  2. // The graphics engine GLXEngine. The unit of GXScene for Delphi
  3. //
  4. unit GXS.FileOBJ;
  5. (*
  6. Support-Code to load Wavefront OBJ Files into TgxFreeForm-Components
  7. in GLScene.
  8. Note that you must manually add this unit to one of your project's uses
  9. to enable support for OBJ & OBJF at run-time.
  10. *)
  11. { .$DEFINE STATS } // Define to display statistics after loading.
  12. interface
  13. {$I Stage.Defines.inc}
  14. uses
  15. Winapi.OpenGL,
  16. Winapi.OpenGLext,
  17. System.Classes,
  18. System.SysUtils,
  19. Stage.VectorTypes,
  20. Stage.VectorGeometry,
  21. Stage.Strings,
  22. GXS.Context,
  23. GXS.MeshUtils,
  24. GXS.ImageUtils,
  25. GXS.ApplicationFileIO,
  26. GXS.PersistentClasses,
  27. GXS.Scene,
  28. GXS.VectorFileObjects,
  29. GXS.VectorLists,
  30. GXS.Texture,
  31. GXS.Color,
  32. GXS.RenderContextInfo,
  33. GXS.XOpenGL,
  34. GXS.Material;
  35. const
  36. BufSize = 10240; // Load input data in chunks of BufSize Bytes.
  37. LineLen = 100; // Allocate memory for the current line in chunks of LineLen Bytes
  38. type
  39. TgxOBJVectorFile = class(TgxVectorFile)
  40. private
  41. FSourceStream: TStream; // Load from this stream
  42. FBuffer: AnsiString; // Buffer
  43. FLine: string; // current line
  44. FLineNo: Integer; // current Line number - for error messages
  45. FEof: Boolean; // Stream done?
  46. FBufPos: Integer; // Position in the buffer
  47. protected
  48. // Read a single line of text from the source stream, set FEof to true when done.
  49. procedure ReadLine;
  50. // Raise a class-specific exception
  51. procedure Error(const msg: string);
  52. procedure CalcMissingOBJNormals(mesh: TgxMeshObject);
  53. public
  54. class function Capabilities: TDataFileCapabilities; override;
  55. procedure LoadFromStream(aStream: TStream); override;
  56. procedure SaveToStream(aStream: TStream); override;
  57. end;
  58. EGLOBJFileError = class(Exception)
  59. private
  60. FLineNo: Integer;
  61. public
  62. property LineNo: Integer read FLineNo;
  63. end;
  64. (* A simple class that know how to extract infos from a mtl file.
  65. mtl files are companion files of the obj, they store material
  66. information. Guessed content (imported ones denoted with a '*',
  67. please help if you know more):
  68. materials begin with a 'newmtl' command followed by material name
  69. *Kd command defines the diffuse color
  70. *map_Kd command defines the diffuse texture image
  71. *Ka/map_Ka define the ambient color and texmap
  72. *Ks/map_Ks define the specular color and texmap
  73. *Ke/map_Ke define the self-illumination/lightmap texmap
  74. map_Bump specifies the bump map
  75. *d specifies transparency (alpha-channel, range [0; 1])
  76. map_d specifies the opcaity texture map
  77. Ns defines the specular exponent or shininess or phong specular (?)
  78. Ni is the refraction index (greater than 1)
  79. *illum defines the illumination model (0 for no lighting, 1 for
  80. ambient and diffuse, 2 for full lighting) *)
  81. TgxMTLFile = class(TStringList)
  82. public
  83. procedure Prepare;
  84. function MaterialStringProperty(const materialName, propertyName: string): string;
  85. function MaterialVectorProperty(const materialName, propertyName: string; const defaultValue: TVector4f): TVector4f;
  86. end;
  87. var
  88. // If enabled, main mesh will be splitted into multiple mesh from facegroup data.
  89. vFileOBJ_SplitMesh: Boolean = False;
  90. // ===================================================================
  91. implementation
  92. // ===================================================================
  93. function StreamEOF(S: TStream): Boolean;
  94. begin
  95. // Is the stream at its end?
  96. Result := (S.Position >= S.Size);
  97. end;
  98. function Rest(const S: string; Count: Integer): string;
  99. // Return the right part of s including s[Count].
  100. begin
  101. Result := copy(S, Count, Length(S) - Count + 1);
  102. end;
  103. function NextToken(var S: string; delimiter: Char): string;
  104. (* Return the next Delimiter-delimited Token from the string s and
  105. remove it from s *)
  106. var
  107. p: Integer;
  108. begin
  109. p := Pos(delimiter, S);
  110. if p = 0 then
  111. begin
  112. Result := S;
  113. S := '';
  114. end
  115. else
  116. begin
  117. Result := copy(S, 1, p - 1);
  118. S := TrimLeft(Rest(S, p + 1));
  119. end;
  120. end;
  121. (**** TOBJFGVertexNormalTexIndexList ****
  122. - based on TFGVertexNormalTexIndexList (GXS.VectorFileObjects.pas)
  123. - adds support for polygons and for "missing" normals and
  124. texture-coordinates. Pass -1 to Add for the index of a missing object.
  125. - Polygons are defined by counting off the number of vertices added to the
  126. PolygonVertices-property. So a PolygonVertices-List of
  127. [3,4,6]
  128. says "Vertex indices 0,1 and 2 make up a triangle, 3,4,5 and 6 a quad and
  129. 7,8,9,10,11 and 12 a hexagon". *)
  130. type
  131. TOBJFGMode = (objfgmmPolygons, objfgmmTriangleStrip);
  132. TOBJFGVertexNormalTexIndexList = class(TFGVertexNormalTexIndexList)
  133. private
  134. FMode: TOBJFGMode;
  135. FName: string;
  136. FPolygonVertices: TgxIntegerList;
  137. FCurrentVertexCount: Integer;
  138. FShowNormals: Boolean;
  139. procedure PolygonComplete; (* Current polygon completed. Adds FCurrentVertexCount
  140. to FPolygonVertices and sets the variable to 0 *)
  141. procedure SetMode(aMode: TOBJFGMode);
  142. public
  143. procedure Assign(Source: TPersistent); override;
  144. constructor CreateOwned(aOwner: TgxFaceGroups); override;
  145. destructor Destroy; override;
  146. procedure WriteToFiler(writer: TgxVirtualWriter); override;
  147. procedure ReadFromFiler(reader: TgxVirtualReader); override;
  148. procedure Add(VertexIdx, NormalIdx, TexCoordIdx: Integer);
  149. procedure BuildList(var mrci: TgxRenderContextInfo); override;
  150. procedure AddToTriangles(aList: TgxAffineVectorList; aTexCoords: TgxAffineVectorList = nil;
  151. aNormals: TgxAffineVectorList = nil); override;
  152. function TriangleCount: Integer; override;
  153. property Mode: TOBJFGMode read FMode write SetMode;
  154. property Name: string read FName write FName;
  155. property PolygonVertices: TgxIntegerList read FPolygonVertices;
  156. property ShowNormals: Boolean read FShowNormals write FShowNormals;
  157. end;
  158. constructor TOBJFGVertexNormalTexIndexList.CreateOwned(aOwner: TgxFaceGroups);
  159. begin
  160. inherited CreateOwned(aOwner);
  161. FMode := objfgmmTriangleStrip;
  162. // FShowNormals:=True;
  163. end;
  164. destructor TOBJFGVertexNormalTexIndexList.Destroy;
  165. begin
  166. FPolygonVertices.Free;
  167. inherited Destroy;
  168. end;
  169. procedure TOBJFGVertexNormalTexIndexList.Add(VertexIdx, NormalIdx, TexCoordIdx: Integer);
  170. begin
  171. inherited Add(VertexIdx, NormalIdx, TexCoordIdx);
  172. inc(FCurrentVertexCount);
  173. end;
  174. procedure TOBJFGVertexNormalTexIndexList.PolygonComplete;
  175. begin
  176. Assert(FMode = objfgmmPolygons, 'PolygonComplete may only be called for Facegroups with Mode=objfgmmPolygons.');
  177. FPolygonVertices.Add(FCurrentVertexCount);
  178. FCurrentVertexCount := 0;
  179. end;
  180. procedure TOBJFGVertexNormalTexIndexList.SetMode(aMode: TOBJFGMode);
  181. begin
  182. if aMode = FMode then
  183. exit;
  184. Assert(VertexIndices.Count = 0, 'Decide on the mode before adding vertices.');
  185. FMode := aMode;
  186. if FMode = objfgmmPolygons then
  187. FPolygonVertices := TgxIntegerList.Create
  188. else
  189. begin
  190. FPolygonVertices.Free;
  191. FPolygonVertices := nil;
  192. end;
  193. end;
  194. procedure TOBJFGVertexNormalTexIndexList.BuildList(var mrci: TgxRenderContextInfo);
  195. var
  196. VertexPool: PAffineVectorArray;
  197. NormalPool: PAffineVectorArray;
  198. TexCoordPool: PAffineVectorArray;
  199. ColorPool: PVectorArray;
  200. GotColor: Boolean;
  201. procedure BuildPolygons;
  202. var
  203. Polygon, Index, j, idx: Integer;
  204. N: TAffineVector;
  205. begin
  206. // Build it. Ignoring texture-coordinates and normals that are missing.
  207. Index := 0; // Current index into the Index-Lists.
  208. // For every Polygon
  209. for Polygon := 0 to FPolygonVertices.Count - 1 do
  210. begin
  211. glBegin(GL_POLYGON);
  212. try
  213. // For every Vertex in the current Polygon
  214. for j := 0 to FPolygonVertices[Polygon] - 1 do
  215. begin
  216. Assert(NormalIndices.List <> nil);
  217. idx := NormalIndices.List^[Index];
  218. if idx >= 0 then
  219. glNormal3fv(@NormalPool[idx]);
  220. if GotColor then
  221. glColor4fv(@ColorPool[VertexIndices.List^[Index]]);
  222. if Assigned(TexCoordPool) then
  223. begin
  224. idx := TexCoordIndices.List^[Index];
  225. if idx >= 0 then
  226. begin
  227. glMultiTexCoord2fv(GL_TEXTURE0, @TexCoordPool[idx]);
  228. glMultiTexCoord2fv(GL_TEXTURE1, @TexCoordPool[idx]);
  229. end;
  230. end;
  231. glVertex3fv(@VertexPool[VertexIndices.List^[Index]]);
  232. inc(Index);
  233. end;
  234. finally
  235. glEnd;
  236. end;
  237. end;
  238. // Visible normals, rather moronic and mainly for debugging.
  239. if FShowNormals then
  240. begin
  241. Index := 0;
  242. for Polygon := 0 to FPolygonVertices.Count - 1 do
  243. begin
  244. // For every Vertex in the current Polygon
  245. for j := 0 to FPolygonVertices[Polygon] - 1 do
  246. begin
  247. idx := NormalIndices.List^[Index];
  248. if idx <> -1 then
  249. begin
  250. glBegin(GL_LINES);
  251. try
  252. glVertex3fv(@VertexPool^[VertexIndices.List^[Index]]);
  253. N := VectorAdd(VertexPool^[VertexIndices.List^[Index]], VectorScale(NormalPool^[idx], 0.1));
  254. glVertex3fv(@N);
  255. finally
  256. glEnd;
  257. end;
  258. end;
  259. inc(Index);
  260. end;
  261. end;
  262. end;
  263. end;
  264. procedure BuildTriangleStrip;
  265. (*
  266. begin
  267. Owner.Owner.DeclareArraysToOpenGL(False);
  268. glDrawElements(GL_TRIANGLE_STRIP,VertexIndices.Count,
  269. GL_UNSIGNED_INT,VertexIndices.List);
  270. end;
  271. *)
  272. var
  273. Index, idx: Integer;
  274. begin
  275. // Build it. Ignoring texture-coordinates and normals that are missing.
  276. glBegin(GL_TRIANGLE_STRIP);
  277. try
  278. for Index := 0 to VertexIndices.Count - 1 do
  279. begin
  280. idx := NormalIndices.List^[Index];
  281. if idx <> -1 then
  282. glNormal3fv(@NormalPool^[idx]);
  283. if Assigned(TexCoordPool) then
  284. begin
  285. idx := TexCoordIndices.List^[Index];
  286. if idx <> -1 then
  287. xglTexCoord2fv(@TexCoordPool^[idx]);
  288. end;
  289. glVertex3fv(@VertexPool^[VertexIndices.List^[Index]]);
  290. end;
  291. finally
  292. glEnd;
  293. end;
  294. end;
  295. begin
  296. Assert(((TexCoordIndices.Count = 0) or (VertexIndices.Count <= TexCoordIndices.Count)) and
  297. (VertexIndices.Count <= NormalIndices.Count),
  298. 'Number of Vertices does not match number of Normals or Texture coordinates.');
  299. // Shorthand notations.
  300. VertexPool := Owner.Owner.Vertices.List;
  301. NormalPool := Owner.Owner.Normals.List;
  302. ColorPool := Owner.Owner.Colors.List;
  303. if TexCoordIndices.Count = 0 then
  304. TexCoordPool := nil
  305. else
  306. TexCoordPool := Owner.Owner.TexCoords.List;
  307. GotColor := (Owner.Owner.Vertices.Count = Owner.Owner.Colors.Count);
  308. case FMode of
  309. objfgmmPolygons:
  310. BuildPolygons;
  311. objfgmmTriangleStrip:
  312. BuildTriangleStrip;
  313. end;
  314. end;
  315. procedure TOBJFGVertexNormalTexIndexList.AddToTriangles(aList: TgxAffineVectorList; aTexCoords: TgxAffineVectorList = nil;
  316. aNormals: TgxAffineVectorList = nil);
  317. var
  318. i, j, N, n0: Integer;
  319. vertexList, texCoordList, normalsList: TgxAffineVectorList;
  320. begin
  321. vertexList := Owner.Owner.Vertices;
  322. texCoordList := Owner.Owner.TexCoords;
  323. normalsList := Owner.Owner.Normals;
  324. case FMode of
  325. objfgmmPolygons:
  326. begin
  327. N := 0;
  328. for i := 0 to FPolygonVertices.Count - 1 do
  329. begin
  330. n0 := N;
  331. for j := 0 to FPolygonVertices[i] - 1 do
  332. begin
  333. if j > 1 then
  334. begin
  335. aList.Add(vertexList[VertexIndices[n0]], vertexList[VertexIndices[N - 1]], vertexList[VertexIndices[N]]);
  336. if Assigned(aTexCoords) then
  337. begin
  338. if texCoordList.Count > 0 then
  339. aTexCoords.Add(texCoordList[VertexIndices[n0]], texCoordList[VertexIndices[N - 1]],
  340. texCoordList[VertexIndices[N]])
  341. else
  342. aTexCoords.AddNulls(3);
  343. end;
  344. if Assigned(aNormals) then
  345. begin
  346. if normalsList.Count > 0 then
  347. aNormals.Add(normalsList[VertexIndices[n0]], normalsList[VertexIndices[N - 1]], normalsList[VertexIndices[N]])
  348. else
  349. aNormals.AddNulls(3);
  350. end;
  351. end;
  352. inc(N);
  353. end;
  354. end;
  355. end;
  356. objfgmmTriangleStrip:
  357. begin
  358. ConvertStripToList(vertexList, VertexIndices, aList);
  359. N := (VertexIndices.Count - 2) * 3;
  360. if Assigned(aTexCoords) then
  361. begin
  362. if texCoordList.Count > 0 then
  363. ConvertStripToList(texCoordList, VertexIndices, aTexCoords)
  364. else
  365. aTexCoords.AddNulls(N);
  366. end;
  367. if Assigned(aNormals) then
  368. begin
  369. if normalsList.Count > 0 then
  370. ConvertStripToList(normalsList, VertexIndices, aNormals)
  371. else
  372. aNormals.AddNulls(N);
  373. end;
  374. end;
  375. else
  376. Assert(False);
  377. end;
  378. end;
  379. function TOBJFGVertexNormalTexIndexList.TriangleCount: Integer;
  380. var
  381. i: Integer;
  382. begin
  383. case FMode of
  384. objfgmmPolygons:
  385. begin
  386. Result := 0;
  387. for i := 0 to FPolygonVertices.Count - 1 do
  388. begin
  389. Result := Result + FPolygonVertices[i] - 2;
  390. end;
  391. end;
  392. objfgmmTriangleStrip:
  393. begin
  394. Result := VertexIndices.Count - 2;
  395. if Result < 0 then
  396. Result := 0;
  397. end;
  398. else
  399. Result := 0;
  400. Assert(False);
  401. end;
  402. end;
  403. // ------------------
  404. // ------------------ TgxOBJVectorFile ------------------
  405. // ------------------
  406. procedure TgxOBJVectorFile.ReadLine;
  407. var
  408. j: Integer;
  409. procedure FillBuffer;
  410. var
  411. l: Integer;
  412. begin
  413. l := FSourceStream.Size - FSourceStream.Position;
  414. if l > BufSize then
  415. l := BufSize;
  416. SetLength(FBuffer, l);
  417. FSourceStream.Read(FBuffer[1], l);
  418. FBufPos := 1;
  419. end;
  420. begin
  421. inc(FLineNo);
  422. if FBufPos < 1 then
  423. FillBuffer;
  424. j := 1;
  425. while True do
  426. begin
  427. if FBufPos > Length(FBuffer) then
  428. begin
  429. if StreamEOF(FSourceStream) then
  430. begin
  431. FEof := True;
  432. break;
  433. end
  434. else
  435. FillBuffer
  436. end
  437. else
  438. begin
  439. case FBuffer[FBufPos] of
  440. #10, #13:
  441. begin
  442. inc(FBufPos);
  443. if FBufPos > Length(FBuffer) then
  444. if StreamEOF(FSourceStream) then
  445. break
  446. else
  447. FillBuffer;
  448. if (FBuffer[FBufPos] = #10) or (FBuffer[FBufPos] = #13) then
  449. inc(FBufPos);
  450. break;
  451. end;
  452. else
  453. if j > Length(FLine) then
  454. SetLength(FLine, Length(FLine) + LineLen);
  455. if FBuffer[FBufPos] = #9 then
  456. FLine[j] := #32
  457. else
  458. FLine[j] := Char(FBuffer[FBufPos]);
  459. inc(FBufPos);
  460. inc(j);
  461. end;
  462. end;
  463. end;
  464. SetLength(FLine, j - 1);
  465. end;
  466. procedure TgxOBJVectorFile.Error(const msg: string);
  467. var
  468. E: EGLOBJFileError;
  469. begin
  470. E := EGLOBJFileError.Create(msg);
  471. E.FLineNo := FLineNo;
  472. raise E;
  473. end;
  474. class function TgxOBJVectorFile.Capabilities: TDataFileCapabilities;
  475. begin
  476. Result := [dfcRead, dfcWrite];
  477. end;
  478. procedure TgxOBJVectorFile.CalcMissingOBJNormals(mesh: TgxMeshObject);
  479. var
  480. VertexPool: PAffineVectorArray;
  481. N: TAffineVector;
  482. p: array [1 .. 3] of PAffineVector;
  483. face, Index: Integer;
  484. fg: TOBJFGVertexNormalTexIndexList;
  485. procedure DoCalcNormal;
  486. var
  487. idx: Integer;
  488. begin
  489. idx := TOBJFGVertexNormalTexIndexList(mesh.FaceGroups[face]).NormalIndices.List^[Index];
  490. if idx < 0 then
  491. begin
  492. N := CalcPlaneNormal(p[1]^, p[2]^, p[3]^);
  493. idx := mesh.Normals.Add(N);
  494. TOBJFGVertexNormalTexIndexList(mesh.FaceGroups[face]).NormalIndices.List^[Index] := idx;
  495. end;
  496. end;
  497. procedure CalcForPolygons;
  498. var
  499. Polygon, firstVertexIndex, j: Integer;
  500. begin
  501. with fg do
  502. begin
  503. (* Walk the polygons and calculate normals for those vertices that
  504. are missing. *)
  505. Index := 0; { Current index into the Index-List of this Facegroup. }
  506. // For every Polygon
  507. for Polygon := 0 to FPolygonVertices.Count - 1 do
  508. begin
  509. // Init
  510. firstVertexIndex := Index;
  511. FillChar(p, SizeOf(p), 0);
  512. // Last Vertex in this polygon
  513. p[2] := @VertexPool^[VertexIndices.List^[Index + FPolygonVertices[Polygon] - 1]];
  514. // First Vertex in this polygon
  515. p[3] := @VertexPool^[VertexIndices.List^[Index]];
  516. // For every Vertex in the current Polygon but the last.
  517. for j := 0 to FPolygonVertices[Polygon] - 2 do
  518. begin
  519. Move(p[2], p[1], 2 * SizeOf(PAffineVector));
  520. p[3] := @VertexPool^[VertexIndices.List^[Index + 1]];
  521. DoCalcNormal;
  522. inc(Index);
  523. end;
  524. // For the last vertex use the first as partner to span the plane.
  525. Move(p[2], p[1], 2 * SizeOf(PAffineVector));
  526. p[3] := @VertexPool^[VertexIndices.List^[firstVertexIndex]];
  527. DoCalcNormal;
  528. inc(Index);
  529. end; // of for FPolygonVertices
  530. end; // of with Facegroup
  531. end;
  532. procedure CalcForTriangleStrip;
  533. begin
  534. end;
  535. begin
  536. // Shorthand notations.
  537. VertexPool := mesh.Vertices.List;
  538. for face := 0 to mesh.FaceGroups.Count - 1 do
  539. begin
  540. fg := TOBJFGVertexNormalTexIndexList(mesh.FaceGroups[face]);
  541. case fg.Mode of
  542. objfgmmPolygons:
  543. CalcForPolygons;
  544. objfgmmTriangleStrip:
  545. CalcForTriangleStrip;
  546. end;
  547. end;
  548. end;
  549. procedure TgxOBJVectorFile.LoadFromStream(aStream: TStream);
  550. var
  551. hv: THomogeneousVector;
  552. av: TAffineVector;
  553. mesh: TgxMeshObject;
  554. faceGroup: TOBJFGVertexNormalTexIndexList;
  555. faceGroupNames: TStringList;
  556. procedure ReadHomogeneousVector;
  557. // Read a vector with a maximum of 4 elements from the current line.
  558. var
  559. i, c: Integer;
  560. f: string;
  561. begin
  562. FillChar(hv, SizeOf(hv), 0);
  563. i := 0;
  564. while (FLine <> '') and (i < 4) do
  565. begin
  566. f := NextToken(FLine, ' ');
  567. Val(f, hv.V[i], c);
  568. if c <> 0 then
  569. Error(Format('''%s'' is not a valid floating-point constant.', [f]));
  570. inc(i);
  571. end;
  572. end;
  573. procedure ReadAffineVector;
  574. // Read a vector with a maximum of 3 elements from the current line.
  575. var
  576. i, c: Integer;
  577. f: string;
  578. begin
  579. FillChar(av, SizeOf(av), 0);
  580. i := 0;
  581. while (FLine <> '') and (i < 3) do
  582. begin
  583. f := NextToken(FLine, ' ');
  584. Val(f, av.V[i], c);
  585. if c <> 0 then
  586. Error(Format('''%s'' is not a valid floating-point constant.', [f]));
  587. inc(i);
  588. end;
  589. end;
  590. procedure SetCurrentFaceGroup(aName: string; const matlName: string);
  591. var
  592. i: Integer;
  593. begin
  594. i := faceGroupNames.IndexOf(aName);
  595. if i >= 0 then
  596. begin
  597. faceGroup := TOBJFGVertexNormalTexIndexList(faceGroupNames.Objects[i]);
  598. if faceGroup.materialName <> matlName then
  599. begin
  600. i := faceGroupNames.IndexOf(aName);
  601. if i >= 0 then
  602. begin
  603. faceGroup := TOBJFGVertexNormalTexIndexList(faceGroupNames.Objects[i]);
  604. if faceGroup.materialName <> matlName then
  605. faceGroup := nil;
  606. end;
  607. end;
  608. end;
  609. if (faceGroup = nil) or (faceGroup.Name <> aName) or (faceGroup.PolygonVertices.Count > 0) or
  610. (faceGroup.materialName <> matlName) then
  611. begin
  612. faceGroup := TOBJFGVertexNormalTexIndexList.CreateOwned(mesh.FaceGroups);
  613. faceGroup.FName := aName;
  614. faceGroup.Mode := objfgmmPolygons;
  615. faceGroup.materialName := matlName;
  616. faceGroupNames.AddObject(aName, faceGroup);
  617. end;
  618. end;
  619. procedure AddFaceVertex(faceVertices: string);
  620. function GetIndex(Count: Integer): Integer;
  621. var
  622. S: string;
  623. begin
  624. S := NextToken(faceVertices, '/');
  625. Result := StrToIntDef(S, 0);
  626. if Result = 0 then
  627. Result := -1 // Missing
  628. else if Result < 0 then
  629. begin
  630. // Relative, make absolute. "-1" means last, "-2" second last.
  631. Result := Count + Result
  632. end
  633. else
  634. begin
  635. // Absolute, correct for zero-base.
  636. Dec(Result);
  637. end;
  638. end;
  639. var
  640. vIdx, tIdx, nIdx: Integer;
  641. begin
  642. vIdx := GetIndex(mesh.Vertices.Count);
  643. tIdx := GetIndex(mesh.TexCoords.Count);
  644. nIdx := GetIndex(mesh.Normals.Count);
  645. faceGroup.Add(vIdx, nIdx, tIdx);
  646. end;
  647. procedure ReadFace(const curMtlName: string);
  648. var
  649. faceVertices: string;
  650. begin
  651. if FLine <> '' then
  652. begin
  653. if not Assigned(faceGroup) then
  654. SetCurrentFaceGroup('', curMtlName);
  655. try
  656. while FLine <> '' do
  657. begin
  658. faceVertices := NextToken(FLine, ' ');
  659. AddFaceVertex(faceVertices);
  660. end;
  661. finally
  662. faceGroup.PolygonComplete;
  663. end;
  664. end;
  665. end;
  666. procedure ReadTriangleStripContinued;
  667. var
  668. faceVertices: string;
  669. begin
  670. if faceGroup = nil then
  671. Error('q-line without preceding t-line.');
  672. while FLine <> '' do
  673. begin
  674. faceVertices := NextToken(FLine, ' ');
  675. AddFaceVertex(faceVertices);
  676. end;
  677. end;
  678. procedure ReadTriangleStrip;
  679. begin
  680. // Start a new Facegroup, mode=triangle strip
  681. faceGroup := TOBJFGVertexNormalTexIndexList.CreateOwned(mesh.FaceGroups);
  682. faceGroup.Mode := objfgmmTriangleStrip;
  683. // The rest is the same as for continuation of a strip.
  684. ReadTriangleStripContinued;
  685. end;
  686. function GetOrAllocateMaterial(const libName, matName: string): string;
  687. var
  688. fs: TStream;
  689. objMtl: TgxMTLFile;
  690. matLib: TgxMaterialLibrary;
  691. libMat, libMat2: TgxLibMaterial;
  692. texName: string;
  693. libFilename: string;
  694. begin
  695. if GetOwner is TgxBaseMesh then
  696. begin
  697. // got a linked material library?
  698. matLib := TgxBaseMesh(GetOwner).MaterialLibrary;
  699. if Assigned(matLib) then
  700. begin
  701. Result := matName;
  702. libMat := matLib.Materials.GetLibMaterialByName(matName);
  703. if not Assigned(libMat) then
  704. begin
  705. // spawn a new material
  706. libMat := matLib.Materials.Add;
  707. libMat.Name := matName;
  708. // get full path for material file to be load
  709. if matLib.TexturePaths = EmptyStr then
  710. libFilename := libName
  711. else
  712. libFilename := IncludeTrailingPathDelimiter(matLib.TexturePaths) + libName;
  713. try
  714. fs := TFileStream.Create(libFilename, fmOpenReadWrite);
  715. except
  716. fs := nil;
  717. end;
  718. if Assigned(fs) then
  719. begin
  720. objMtl := TgxMTLFile.Create;
  721. try
  722. objMtl.LoadFromStream(fs);
  723. objMtl.Prepare;
  724. // setup material colors
  725. with libMat.Material.FrontProperties do
  726. begin
  727. Ambient.Color := objMtl.MaterialVectorProperty(matName, 'Ka', clrGray20);
  728. Diffuse.Color := objMtl.MaterialVectorProperty(matName, 'Kd', clrGray80);
  729. Diffuse.Alpha := StrToFloatDef(objMtl.MaterialStringProperty(matName, 'd'), 1);
  730. if Diffuse.Alpha < 1 then
  731. libMat.Material.BlendingMode := bmTransparency;
  732. case StrToIntDef(objMtl.MaterialStringProperty(matName, 'illum'), 1) of
  733. 0:
  734. begin // non-lit material
  735. libMat.Material.MaterialOptions := [moNoLighting];
  736. end;
  737. 1:
  738. ; // flat, non-shiny material
  739. 2:
  740. begin // specular material
  741. Specular.Color := objMtl.MaterialVectorProperty(matName, 'Ks', clrTransparent);
  742. end;
  743. else
  744. // unknown, assume unlit
  745. libMat.Material.MaterialOptions := [moNoLighting];
  746. Diffuse.Color := clrGray80;
  747. end;
  748. Shininess := StrToIntDef(objMtl.MaterialStringProperty(matName, 'Ns'), 1);
  749. end;
  750. // setup texture
  751. texName := objMtl.MaterialStringProperty(matName, 'map_Kd');
  752. if texName <> '' then
  753. begin
  754. try
  755. with libMat.Material.Texture do
  756. begin
  757. Image.LoadFromFile(texName);
  758. Disabled := False;
  759. TextureMode := tmModulate;
  760. end;
  761. except
  762. on E: ETexture do
  763. begin
  764. if not Owner.IgnoreMissingTextures then
  765. raise;
  766. end;
  767. end;
  768. end;
  769. // setup lightmap (self-illumination) texture
  770. texName := objMtl.MaterialStringProperty(matName, 'map_Ke');
  771. if texName <> '' then
  772. begin
  773. // spawn a new material
  774. libMat2 := matLib.Materials.Add;
  775. libMat2.Name := matName + '_lm';
  776. // Use the GLScene built-in second texture support (note: the mesh LightmapProperty MUST be empty)
  777. libMat.Texture2Name := libMat2.Name;
  778. try
  779. with libMat2.Material.Texture do
  780. begin
  781. Image.LoadFromFile(texName);
  782. Disabled := False;
  783. minFilter := miLinear;
  784. TextureWrap := twNone;
  785. TextureFormat := tfRGB;
  786. TextureMode := tmModulate;
  787. end;
  788. except
  789. on E: ETexture do
  790. begin
  791. if not Owner.IgnoreMissingTextures then
  792. raise;
  793. end;
  794. end;
  795. end;
  796. finally
  797. objMtl.Free;
  798. fs.Free;
  799. end;
  800. end;
  801. end
  802. else
  803. Result := matName;
  804. end
  805. else
  806. Result := '';
  807. end;
  808. end;
  809. procedure SplitMesh;
  810. var
  811. i, j, Count: Integer;
  812. newMesh: TgxMeshObject;
  813. newfaceGroup: TOBJFGVertexNormalTexIndexList;
  814. VertexIdx, NormalIdx, TexCoordIdx: Integer;
  815. AffineVector: TAffineVector;
  816. begin
  817. for i := 0 to mesh.FaceGroups.Count - 1 do
  818. begin
  819. faceGroup := mesh.FaceGroups[i] as TOBJFGVertexNormalTexIndexList;
  820. newMesh := TgxMeshObject.CreateOwned(Owner.MeshObjects);
  821. newMesh.Mode := momFaceGroups;
  822. newMesh.Name := faceGroup.Name;
  823. newfaceGroup := TOBJFGVertexNormalTexIndexList.CreateOwned(newMesh.FaceGroups);
  824. newfaceGroup.Assign(faceGroup);
  825. newfaceGroup.FName := faceGroup.Name;
  826. newfaceGroup.Mode := faceGroup.Mode;
  827. newfaceGroup.materialName := faceGroup.materialName;
  828. // SendInteger('VertexIndices', faceGroup.VertexIndices.Count);
  829. // SendInteger('TexCoords', faceGroup.TexCoordIndices.Count);
  830. // SendInteger('Normals', faceGroup.NormalIndices.Count);
  831. Count := faceGroup.VertexIndices.Count;
  832. for j := 0 to Count - 1 do
  833. begin
  834. VertexIdx := faceGroup.VertexIndices[j];
  835. AffineVector := mesh.Vertices[VertexIdx];
  836. VertexIdx := newMesh.Vertices.Add(AffineVector);
  837. TexCoordIdx := faceGroup.TexCoordIndices[j];
  838. AffineVector := mesh.TexCoords[TexCoordIdx];
  839. TexCoordIdx := newMesh.TexCoords.Add(AffineVector);
  840. NormalIdx := faceGroup.NormalIndices[j];
  841. AffineVector := mesh.Normals[NormalIdx];
  842. NormalIdx := newMesh.Normals.Add(AffineVector);
  843. newfaceGroup.Add(VertexIdx, NormalIdx, TexCoordIdx);
  844. end;
  845. end;
  846. Owner.MeshObjects.RemoveAndFree(mesh);
  847. end;
  848. var
  849. command, objMtlFileName, curMtlName: string;
  850. {$IFDEF STATS}
  851. t0, t1, t2: Integer;
  852. {$ENDIF}
  853. begin
  854. {$IFDEF STATS}
  855. t0 := GLGetTickCount;
  856. {$ENDIF}
  857. FEof := False;
  858. FSourceStream := aStream;
  859. FLineNo := 0;
  860. objMtlFileName := '';
  861. curMtlName := '';
  862. mesh := TgxMeshObject.CreateOwned(Owner.MeshObjects);
  863. mesh.Mode := momFaceGroups;
  864. faceGroupNames := TStringList.Create;
  865. faceGroupNames.Duplicates := dupAccept;
  866. faceGroupNames.Sorted := True;
  867. try
  868. faceGroup := nil;
  869. while not FEof do
  870. begin
  871. ReadLine;
  872. if FLine = '' then
  873. Continue; // Skip blank line
  874. if CharInSet(FLine[1], ['#', '$']) then
  875. Continue; // Skip comment and alternate comment
  876. command := AnsiUpperCase(NextToken(FLine, ' '));
  877. if command = 'V' then
  878. begin
  879. ReadHomogeneousVector;
  880. mesh.Vertices.Add(hv.X, hv.Y, hv.Z);
  881. end
  882. else if command = 'VT' then
  883. begin
  884. ReadAffineVector;
  885. mesh.TexCoords.Add(av.X, av.Y, 0);
  886. end
  887. else if command = 'VN' then
  888. begin
  889. ReadAffineVector;
  890. mesh.Normals.Add(av.X, av.Y, av.Z);
  891. end
  892. else if command = 'VP' then
  893. begin
  894. // Parameter Space Vertex: Ignore
  895. end
  896. else if command = 'G' then
  897. begin
  898. // Only the first name on the line, multiple groups not supported.
  899. SetCurrentFaceGroup(NextToken(FLine, ' '), curMtlName);
  900. end
  901. else if command = 'F' then
  902. begin
  903. ReadFace(curMtlName);
  904. end
  905. else if command = 'O' then
  906. begin
  907. // Object Name: Ignore
  908. end
  909. else if command = 'MTLLIB' then
  910. begin
  911. objMtlFileName := NextToken(FLine, ' ');
  912. end
  913. else if command = 'USEMTL' then
  914. begin
  915. curMtlName := GetOrAllocateMaterial(objMtlFileName, NextToken(FLine, ' '));
  916. if faceGroup = nil then
  917. SetCurrentFaceGroup('', curMtlName)
  918. else
  919. SetCurrentFaceGroup(faceGroup.FName, curMtlName);
  920. end
  921. else if command = 'S' then
  922. begin
  923. // Smooth Group: Ignore
  924. end
  925. else if command = 'T' then
  926. begin
  927. ReadTriangleStrip;
  928. end
  929. else if command = 'Q' then
  930. begin
  931. ReadTriangleStripContinued;
  932. end
  933. else
  934. Error('Unsupported Command ''' + command + '''');
  935. end;
  936. mesh.FaceGroups.SortByMaterial;
  937. {$IFDEF STATS}
  938. t1 := GLGetTickCount;
  939. {$ENDIF}
  940. CalcMissingOBJNormals(mesh);
  941. {$IFDEF STATS}
  942. t2 := GLGetTickCount;
  943. InformationDlg(Format('TgxOBJVectorFile Loaded in %dms'#13 + #13 + ' %dms spent reading'#13 +
  944. ' %dms spent calculating normals'#13 + ' %d Geometric Vertices'#13 + ' %d Texture Vertices'#13 +
  945. ' %d Normals'#13 + ' %d FaceGroups/Strips', [t2 - t0, t1 - t0, t2 - t1, mesh.Vertices.Count, mesh.TexCoords.Count,
  946. mesh.Normals.Count, mesh.FaceGroups.Count]));
  947. {$ENDIF}
  948. if vFileOBJ_SplitMesh then
  949. SplitMesh;
  950. finally
  951. faceGroupNames.Free;
  952. end;
  953. end;
  954. procedure TgxOBJVectorFile.SaveToStream(aStream: TStream);
  955. var
  956. OldDecimalSeparator: Char;
  957. procedure Write(const S: AnsiString);
  958. begin
  959. if S <> '' then
  960. aStream.Write(S[1], Length(S));
  961. end;
  962. procedure WriteLn(const S: string);
  963. begin
  964. Write(AnsiString(S));
  965. Write(#13#10);
  966. end;
  967. procedure WriteHeader;
  968. begin
  969. WriteLn('# OBJ-File exported by GLScene');
  970. WriteLn('');
  971. end;
  972. procedure WriteVertices;
  973. var
  974. S: string;
  975. j, i, N: Integer;
  976. begin
  977. N := 0;
  978. for j := 0 to Owner.MeshObjects.Count - 1 do
  979. begin
  980. WriteLn(Format('# Mesh %d', [j + 1]));
  981. with Owner.MeshObjects[j].Vertices do
  982. begin
  983. for i := 0 to Count - 1 do
  984. begin
  985. S := Format('v %g %g %g', [List^[i].X, List^[i].Y, List^[i].Z]);
  986. WriteLn(S);
  987. end;
  988. inc(N, Count);
  989. end;
  990. end;
  991. WriteLn(Format('# %d Vertices', [N]));
  992. WriteLn('');
  993. end;
  994. procedure WriteNormals;
  995. var
  996. S: string;
  997. j, i, N: Integer;
  998. begin
  999. N := 0;
  1000. for j := 0 to Owner.MeshObjects.Count - 1 do
  1001. begin
  1002. WriteLn(Format('# Mesh %d', [j + 1]));
  1003. with Owner.MeshObjects[j].Normals do
  1004. begin
  1005. for i := 0 to Count - 1 do
  1006. begin
  1007. S := Format('vn %g %g %g', [List^[i].X, List^[i].Y, List^[i].Z]);
  1008. WriteLn(S);
  1009. end;
  1010. inc(N, Count);
  1011. end;
  1012. end;
  1013. WriteLn(Format('# %d Normals', [N]));
  1014. WriteLn('');
  1015. end;
  1016. procedure WriteTexCoords;
  1017. var
  1018. S: string;
  1019. j, i, N: Integer;
  1020. begin
  1021. N := 0;
  1022. for j := 0 to Owner.MeshObjects.Count - 1 do
  1023. begin
  1024. WriteLn(Format('# Mesh %d', [j + 1]));
  1025. with Owner.MeshObjects[j].TexCoords do
  1026. begin
  1027. for i := 0 to Count - 1 do
  1028. begin
  1029. S := Format('vt %g %g', [List^[i].X, List^[i].Y]);
  1030. WriteLn(S);
  1031. end;
  1032. inc(N, Count);
  1033. end;
  1034. end;
  1035. WriteLn(Format('# %d Texture-Coordinates', [N]));
  1036. WriteLn('');
  1037. end;
  1038. procedure WriteOBJFaceGroup(aFaceGroup: TOBJFGVertexNormalTexIndexList; o: Integer = 0);
  1039. var
  1040. vIdx, nIdx, tIdx: Integer;
  1041. i, Index, Polygon: Integer;
  1042. Line, t: string;
  1043. begin
  1044. with aFaceGroup do
  1045. begin
  1046. Index := 0;
  1047. for Polygon := 0 to PolygonVertices.Count - 1 do
  1048. begin
  1049. Line := 'f ';
  1050. for i := 1 to PolygonVertices[Polygon] do
  1051. begin
  1052. vIdx := VertexIndices[Index] + 1 + o;
  1053. nIdx := NormalIndices[Index] + 1 + o;
  1054. tIdx := TexCoordIndices[Index] + 1 + o;
  1055. t := IntToStr(vIdx) + '/';
  1056. if tIdx = -1 then
  1057. t := t + '/'
  1058. else
  1059. t := t + IntToStr(tIdx) + '/';
  1060. if nIdx = -1 then
  1061. t := t + '/'
  1062. else
  1063. t := t + IntToStr(nIdx) + '/';
  1064. Line := Line + copy(t, 1, Length(t) - 1) + ' ';
  1065. inc(Index);
  1066. end;
  1067. WriteLn(Line);
  1068. end;
  1069. end;
  1070. WriteLn('');
  1071. end;
  1072. procedure WriteVertexIndexList(fg: TgxFGVertexIndexList; o: Integer = 0);
  1073. var
  1074. i, N: Integer;
  1075. begin
  1076. case fg.Mode of
  1077. fgmmTriangles:
  1078. begin
  1079. N := fg.VertexIndices.Count - 3;
  1080. i := 0;
  1081. while i <= N do
  1082. begin
  1083. WriteLn(Format('f %d/%0:d %d/%1:d %d/%2:d', [fg.VertexIndices[i] + 1 + o, fg.VertexIndices[i + 1] + 1 + o,
  1084. fg.VertexIndices[i + 2] + 1 + o]));
  1085. inc(i, 3);
  1086. end;
  1087. end;
  1088. fgmmTriangleFan:
  1089. begin
  1090. Write('f ');
  1091. N := fg.VertexIndices.Count - 1;
  1092. i := 0;
  1093. while i <= N do
  1094. begin
  1095. if i < N then
  1096. Write(AnsiString(Format('%d/%0:d ', [fg.VertexIndices[i] + 1 + o])))
  1097. else
  1098. WriteLn(Format('%d/%0:d', [fg.VertexIndices[i] + 1 + o]));
  1099. inc(i);
  1100. end;
  1101. end;
  1102. fgmmTriangleStrip:
  1103. begin
  1104. N := fg.VertexIndices.Count - 3;
  1105. i := 0;
  1106. while i <= N do
  1107. begin
  1108. WriteLn(Format('f %d/%0:d %d/%1:d %d/%2:d', [fg.VertexIndices[i] + 1 + o, fg.VertexIndices[i + 1] + 1 + o,
  1109. fg.VertexIndices[i + 2] + 1 + o]));
  1110. inc(i);
  1111. end;
  1112. end;
  1113. end;
  1114. end;
  1115. procedure WriteFaceGroups;
  1116. var
  1117. j, i, k: Integer;
  1118. fg: TgxFaceGroup;
  1119. MoName: string;
  1120. begin
  1121. k := 0;
  1122. for j := 0 to Owner.MeshObjects.Count - 1 do
  1123. begin
  1124. MoName := Owner.MeshObjects[j].Name;
  1125. if MoName = '' then
  1126. MoName := Format('Mesh%d', [j + 1]);
  1127. WriteLn('g ' + MoName);
  1128. for i := 0 to Owner.MeshObjects[j].FaceGroups.Count - 1 do
  1129. begin
  1130. WriteLn(Format('s %d', [i + 1]));
  1131. fg := Owner.MeshObjects[j].FaceGroups[i];
  1132. if fg is TOBJFGVertexNormalTexIndexList then
  1133. WriteOBJFaceGroup(TOBJFGVertexNormalTexIndexList(fg), k)
  1134. else if fg is TgxFGVertexIndexList then
  1135. WriteVertexIndexList(TgxFGVertexIndexList(fg), k)
  1136. else
  1137. Assert(False); // unsupported face group
  1138. end;
  1139. // advance vertex index offset
  1140. inc(k, Owner.MeshObjects[j].Vertices.Count);
  1141. end;
  1142. end;
  1143. begin
  1144. Assert(Owner is TgxFreeForm, 'Can only save FreeForms.');
  1145. OldDecimalSeparator := FormatSettings.DecimalSeparator;
  1146. FormatSettings.DecimalSeparator := '.';
  1147. // Better not call anything that wants the system-locale intact from this block
  1148. try
  1149. WriteHeader;
  1150. WriteVertices;
  1151. WriteNormals;
  1152. WriteTexCoords;
  1153. WriteFaceGroups;
  1154. finally
  1155. FormatSettings.DecimalSeparator := OldDecimalSeparator;
  1156. end;
  1157. end;
  1158. // ------------------
  1159. // ------------------ TgxMTLFile ------------------
  1160. // ------------------
  1161. procedure TgxMTLFile.Prepare;
  1162. var
  1163. i: Integer;
  1164. buf: string;
  1165. begin
  1166. // "standardize" the mtl file lines
  1167. for i := Count - 1 downto 0 do
  1168. begin
  1169. buf := UpperCase(Trim(Strings[i]));
  1170. if (buf = '') or CharInSet(buf[1], ['#', '$']) then
  1171. Delete(i)
  1172. else
  1173. begin
  1174. Strings[i] := StringReplace(buf, #9, #32, [rfIgnoreCase]);
  1175. end;
  1176. end;
  1177. end;
  1178. function TgxMTLFile.MaterialStringProperty(const materialName, propertyName: string): string;
  1179. var
  1180. i, N: Integer;
  1181. buf, Line: string;
  1182. begin
  1183. buf := 'NEWMTL ' + UpperCase(materialName);
  1184. i := IndexOf(buf);
  1185. if i >= 0 then
  1186. begin
  1187. buf := UpperCase(propertyName) + ' ';
  1188. N := Length(buf);
  1189. inc(i);
  1190. while i < Count do
  1191. begin
  1192. Line := Strings[i];
  1193. if copy(Line, 1, 7) = 'NEWMTL ' then
  1194. break;
  1195. if copy(Line, 1, N) = buf then
  1196. begin
  1197. Result := copy(Strings[i], N + 1, MaxInt);
  1198. exit;
  1199. end;
  1200. inc(i);
  1201. end;
  1202. end;
  1203. Result := '';
  1204. end;
  1205. function TgxMTLFile.MaterialVectorProperty(const materialName, propertyName: string; const defaultValue: TVector4f): TVector4f;
  1206. var
  1207. i: Integer;
  1208. sl: TStringList;
  1209. begin
  1210. sl := TStringList.Create;
  1211. try
  1212. sl.CommaText := MaterialStringProperty(materialName, propertyName);
  1213. if sl.Count > 0 then
  1214. begin
  1215. Result := NullHmgVector;
  1216. for i := 0 to 3 do
  1217. if sl.Count > i then
  1218. Result.V[i] := StrToFloatDef(sl[i], 0)
  1219. else
  1220. break;
  1221. end
  1222. else
  1223. Result := defaultValue;
  1224. finally
  1225. sl.Free;
  1226. end;
  1227. end;
  1228. procedure TOBJFGVertexNormalTexIndexList.Assign(Source: TPersistent);
  1229. begin
  1230. if Source is TOBJFGVertexNormalTexIndexList then
  1231. begin
  1232. FMode := TOBJFGVertexNormalTexIndexList(Source).FMode;
  1233. FName := TOBJFGVertexNormalTexIndexList(Source).FName;
  1234. FCurrentVertexCount := TOBJFGVertexNormalTexIndexList(Source).FCurrentVertexCount;
  1235. FShowNormals := TOBJFGVertexNormalTexIndexList(Source).FShowNormals;
  1236. if TOBJFGVertexNormalTexIndexList(Source).FPolygonVertices = nil then
  1237. FreeAndNil(FPolygonVertices)
  1238. else
  1239. begin
  1240. if FPolygonVertices = nil then
  1241. FPolygonVertices := TgxIntegerList.Create;
  1242. FPolygonVertices.Assign(TOBJFGVertexNormalTexIndexList(Source).FPolygonVertices);
  1243. end;
  1244. end
  1245. else
  1246. inherited;
  1247. end;
  1248. procedure TOBJFGVertexNormalTexIndexList.ReadFromFiler(reader: TgxVirtualReader);
  1249. var
  1250. archiveVersion: Integer;
  1251. begin
  1252. inherited ReadFromFiler(reader);
  1253. archiveVersion := reader.ReadInteger;
  1254. if archiveVersion = 0 then
  1255. begin
  1256. FMode := TOBJFGMode(reader.ReadInteger);
  1257. FName := reader.ReadString;
  1258. FCurrentVertexCount := reader.ReadInteger;
  1259. FShowNormals := reader.ReadBoolean;
  1260. if FMode = objfgmmPolygons then
  1261. begin
  1262. FPolygonVertices := TgxIntegerList.Create;
  1263. FPolygonVertices.ReadFromFiler(reader);
  1264. end;
  1265. end
  1266. else
  1267. RaiseFilerException(archiveVersion);
  1268. end;
  1269. procedure TOBJFGVertexNormalTexIndexList.WriteToFiler(writer: TgxVirtualWriter);
  1270. begin
  1271. inherited WriteToFiler(writer);
  1272. with writer do
  1273. begin
  1274. WriteInteger(0); // Archive Version 0
  1275. writer.WriteInteger(Ord(FMode));
  1276. writer.WriteString(FName);
  1277. writer.WriteInteger(FCurrentVertexCount);
  1278. writer.WriteBoolean(FShowNormals);
  1279. if FPolygonVertices <> nil then
  1280. FPolygonVertices.WriteToFiler(writer);
  1281. end;
  1282. end;
  1283. //------------------------------------------------------
  1284. initialization
  1285. //------------------------------------------------------
  1286. GXS.VectorFileObjects.RegisterVectorFileFormat('obj', 'WaveFront model file', TgxOBJVectorFile);
  1287. GXS.VectorFileObjects.RegisterVectorFileFormat('objf', 'Stripe model file', TgxOBJVectorFile);
  1288. RegisterClass(TOBJFGVertexNormalTexIndexList);
  1289. end.