123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- //
- // The graphics engine GXScene https://github.com/glscene
- //
- unit GXS.FileMDC;
- (*
- Code for loading animated MDC files into FreeForms
- and Actors.
- This file format uses in Return To Castle Wolfenstein instead
- of MD3 files. It has got all MD3 features (such as Tag frames)
- plus very small data!
- *)
- interface
- {$I Stage.Defines.inc}
- uses
- System.Classes,
- System.SysUtils,
- GXS.VectorFileObjects,
- GXS.Material,
- GXS.ApplicationFileIO,
- Stage.VectorGeometry;
- const
- MDCFILE_IDENTITY = 'IDPC';
- MDCFILE_VERSION = 2;
- MDC_BASEVERTEX_FACTOR = 0.015625; // 1/64;
- MDC_COMPVERTEX_FACTOR = 0.046875; // 3/64;
- type
- TMDCPoint = array [0 .. 2] of Single;
- TMDCAngle = TMDCPoint;
- TMDCFileHeader = packed record
- Ident: array [0 .. 3] of AnsiChar;
- Version: Cardinal;
- Name: array [0 .. 63] of AnsiChar;
- Flags: Cardinal;
- NumFrames: Cardinal;
- NumTags: Cardinal;
- NumSurfaces: Cardinal;
- NumSkins: Cardinal;
- OffsetBorderFrames: Cardinal;
- OffsetTagNames: Cardinal;
- OffsetTagFrames: Cardinal;
- OffsetSurfaces: Cardinal;
- OffsetEnd: Cardinal;
- end;
- TMDCBorderFrame = packed record
- BBMin, BBMax: TMDCPoint;
- LocalOrigin: TMDCPoint;
- Radius: Single;
- Name: array [0 .. 15] of AnsiChar;
- end;
- PMDCTagName = ^TMDCTagName;
- TMDCTagName = packed record
- Name: array [0 .. 63] of AnsiChar;
- end;
- PMDCTagFrame = ^TMDCTagFrame;
- TMDCTagFrame = packed record
- TagPosition: array [0 .. 2] of Word; // or ShortInt?
- TagAngle: array [0 .. 2] of Word; // or ShortInt?
- end;
- TMDCTag = packed record
- TagName: PMDCTagName;
- TagFrame: PMDCTagFrame;
- end;
- TMDCSurfaceHeader = packed record
- Ident: array [0 .. 3] of AnsiChar;
- Name: array [0 .. 63] of AnsiChar;
- Flags: Cardinal;
- NumCompFrames: Cardinal;
- NumBaseFrames: Cardinal;
- NumSkins: Cardinal;
- NumVertices: Cardinal;
- NumTriangles: Cardinal;
- OffsetTriangles: Cardinal;
- OffsetSkins: Cardinal;
- OffsetTexCoords: Cardinal;
- OffsetBaseVerts: Cardinal;
- OffsetCompVerts: Cardinal;
- OffsetFrameBaseFrames: Cardinal;
- OffsetFrameCompFrames: Cardinal;
- OffsetEnd: Cardinal;
- end;
- TMDCTriangle = array [0 .. 2] of Cardinal;
- TMDCSkin = packed record
- Shader: array [0 .. 63] of AnsiChar;
- Flags: Cardinal;
- end;
- TMDCTexCoord = array [0 .. 1] of Single;
- TMDCBaseVertex = array [0 .. 3] of SmallInt;
- TMDCCompVertex = array [0 .. 3] of Byte;
- TMDCBaseFrame = packed record
- BaseVertices: array of TMDCBaseVertex;
- end;
- TMDCCompFrame = packed record
- CompVertices: array of TMDCCompVertex;
- end;
- type
- TgxMDCVectorFile = class(TgxVectorFile)
- public
- class function Capabilities: TDataFileCapabilities; override;
- procedure LoadFromStream(AStream: TStream); override;
- end;
- // ------------------------------------------------------------------
- implementation
- // ------------------------------------------------------------------
- // ------------------
- // ------------------ TgxMDCVectorFile ------------------
- // ------------------
- class function TgxMDCVectorFile.Capabilities: TDataFileCapabilities;
- begin
- Result := [DfcRead];
- end;
- procedure TgxMDCVectorFile.LoadFromStream(AStream: TStream);
- type
- PPackedNormal = ^TPackedNormal;
- TPackedNormal = array [0 .. 1] of Byte;
- var
- I, J, K, NumVerts, Numtris: Integer;
- Mesh: TgxMorphableMeshObject;
- FaceGroup: TgxFGIndexTexCoordList;
- MorphTarget: TgxMeshMorphTarget;
- function UnpackNormal(Pn: TPackedNormal): TAffineVector;
- var
- Lat, Lng: Single;
- begin
- // The MDC normal is a latitude/longitude value that needs
- // to be calculated into cartesian space.
- Lat := (Pn[0]) * (2 * Pi) / 255;
- Lng := (Pn[1]) * (2 * Pi) / 255;
- Result.X := Cos(Lat) * Sin(Lng);
- Result.Y := Sin(Lat) * Sin(Lng);
- Result.Z := Cos(Lng);
- end;
- procedure AllocateMaterial(Meshname: string);
- var
- LibMat: TgxLibMaterial;
- begin
- // If a material library is assigned to the actor/freeform the
- // mesh name will be added as a material.
- if Assigned(Owner.MaterialLibrary) then
- with Owner.MaterialLibrary do
- begin
- if Assigned(Materials.GetLibMaterialByName(Meshname)) then
- Exit;
- LibMat := Materials.Add;
- LibMat.Name := Meshname;
- LibMat.Material.Texture.Disabled := False;
- end;
- end;
- var
- Fileheader: TMDCFileHeader;
- Surfheader: TMDCSurfaceHeader;
- Borderframes: array of TMDCBorderFrame;
- Baseframetable, Compframetable: array of Word;
- Baseframe: TMDCBaseFrame;
- Compframe: TMDCCompFrame;
- Xyz, Normal: TAffineVector;
- St: array of array [0 .. 1] of Single;
- Triangles: array of TMDCTriangle;
- FrameOffset: Cardinal;
- begin
- AStream.Read(Fileheader, SizeOf(Fileheader));
- Assert(Fileheader.Ident = MDCFILE_IDENTITY, 'Incorrect MDC file Ident');
- Assert(Fileheader.Version = MDCFILE_VERSION, 'Incorrect MDC version number');
- try
- AStream.Seek(Fileheader.OffsetBorderFrames, SoFromBeginning);
- SetLength(Borderframes, Fileheader.NumFrames);
- AStream.Read(Borderframes[0], SizeOf(TMDCBorderFrame) *
- Fileheader.NumFrames);
- FrameOffset := Fileheader.OffsetSurfaces;
- for I := 0 to Fileheader.NumSurfaces - 1 do
- begin
- // read header
- AStream.Position := FrameOffset;
- AStream.Read(Surfheader, SizeOf(TMDCSurfaceHeader));
- // triangles for this surface
- SetLength(Triangles, Surfheader.NumTriangles);
- AStream.Position := FrameOffset + Surfheader.OffsetTriangles;
- AStream.Read(Triangles[0], SizeOf(TMDCTriangle) *
- Surfheader.NumTriangles);
- // texture coordinates for this surface
- SetLength(St, Surfheader.NumVertices);
- AStream.Position := FrameOffset + Surfheader.OffsetTexCoords;
- AStream.Read(St[0], 2 * SizeOf(Single) * Surfheader.NumVertices);
- // base frame table for this surface (for only loading)
- SetLength(Baseframetable, Fileheader.NumFrames);
- AStream.Position := FrameOffset + Surfheader.OffsetFrameBaseFrames;
- AStream.Read(Baseframetable[0], SizeOf(Word) * Fileheader.NumFrames);
- // compressed frame table for this surface (for only loading)
- SetLength(Compframetable, Fileheader.NumFrames);
- AStream.Position := FrameOffset + Surfheader.OffsetFrameCompFrames;
- AStream.Read(Compframetable[0], SizeOf(Word) * Fileheader.NumFrames);
- Mesh := TgxMorphableMeshObject.CreateOwned(Owner.MeshObjects);
- // easiest way to convert a char array to string ;)
- Mesh.Name := Trim(string(PChar(Surfheader.Name[0])));
- with Mesh do
- begin
- Mode := MomFaceGroups;
- FaceGroup := TgxFGIndexTexCoordList.CreateOwned(FaceGroups);
- with FaceGroup do
- begin
- AllocateMaterial(Mesh.Name);
- MaterialName := Mesh.Name;
- NumTris := Surfheader.NumTriangles;
- VertexIndices.Capacity := NumTris * 3;
- TexCoords.Capacity := NumTris * 3;
- // Get the vertex indices and texture coordinates
- for J := 0 to Surfheader.NumTriangles - 1 do
- begin
- Add(Triangles[J, 0], St[Triangles[J, 0]][0],
- 1 - St[Triangles[J, 0]][1]);
- Add(Triangles[J, 2], St[Triangles[J, 2]][0],
- 1 - St[Triangles[J, 2]][1]);
- Add(Triangles[J, 1], St[Triangles[J, 1]][0],
- 1 - St[Triangles[J, 1]][1]);
- end;
- end;
- // Get the mesh data for each morph frame
- for J := 0 to Fileheader.NumFrames - 1 do
- begin
- MorphTarget := TgxMeshMorphTarget.CreateOwned(MorphTargets);
- MorphTarget.Name := Trim(string(Pchar(Surfheader.Name[0]))) + '[' +
- IntToStr(J) + ']';
- NumVerts := Surfheader.NumVertices;
- MorphTarget.Vertices.Capacity := NumVerts;
- // base frames
- SetLength(Baseframe.BaseVertices, Surfheader.NumVertices);
- AStream.Seek(FrameOffset + Surfheader.OffsetBaseVerts + Baseframetable
- [J] * Surfheader.NumVertices * 8, SoFromBeginning);
- AStream.Read(Baseframe.BaseVertices[0], SizeOf(TMDCBaseVertex) *
- Surfheader.NumVertices);
- // compressed frames
- if Compframetable[J] <> $FFFF then // is there a valid frame?
- begin
- SetLength(Compframe.CompVertices, Surfheader.NumVertices);
- AStream.Seek(FrameOffset + Surfheader.OffsetCompVerts +
- Compframetable[J] * Surfheader.NumVertices * 4, SoFromBeginning);
- AStream.Read(Compframe.CompVertices[0], SizeOf(TMDCCompVertex) *
- Surfheader.NumVertices);
- end;
- for K := 0 to Surfheader.NumVertices - 1 do
- begin
- Xyz.X :=
- (Baseframe.BaseVertices[K, 0] * MDC_BASEVERTEX_FACTOR) +
- Borderframes[J].Localorigin[0];
- Xyz.Y :=
- (Baseframe.BaseVertices[K, 1] * MDC_BASEVERTEX_FACTOR) +
- Borderframes[J].Localorigin[1];
- Xyz.Z :=
- (Baseframe.BaseVertices[K, 2] * MDC_BASEVERTEX_FACTOR) +
- Borderframes[J].Localorigin[2];
- Normal := UnpackNormal
- (PPackedNormal(@Baseframe.BaseVertices[K, 3])^);
- if Compframetable[J] <> $FFFF then
- begin
- Xyz.X := Xyz.X +
- ((Compframe.CompVertices[K, 0] - 128) * MDC_COMPVERTEX_FACTOR);
- Xyz.Y := Xyz.Y +
- ((Compframe.CompVertices[K, 1] - 128) * MDC_COMPVERTEX_FACTOR);
- Xyz.Z := Xyz.Z +
- ((Compframe.CompVertices[K, 2] - 128) * MDC_COMPVERTEX_FACTOR);
- // FIXME:
- // I'm sure compframe.CompVertices[3] points a packed normal.
- // And it must be add the current normal like xyz.
- // But, I don't know a way to unpacked this value
- // I found a precalculated normal list in RTCW 1.41 mod source (q_math.c)
- //
- // NUMVERTEXNORMALS = 162
- // vec3_t bytedirs[NUMVERTEXNORMALS] = {
- // {-0.525731, 0.000000, 0.850651}, (...)
- //
- // But, I had noticed some compframe.CompVertices[3] value is bigger
- // than NUMVERTEXNORMALS constant. So, there must be another list.
- // Can you find it?
- // Osman Turan ([email protected])
- end;
- // all id Sofware based games uses Z axis as up instead of Y. So, convert them
- MorphTarget.Vertices.Add(Xyz.X, Xyz.Z, -Xyz.Y);
- MorphTarget.Normals.Add(Normal.X, Normal.Z,
- -Normal.Y);
- end;
- end;
- end;
- FrameOffset := FrameOffset + Surfheader.OffsetEnd;
- if Mesh.MorphTargets.Count > 0 then
- Mesh.MorphTo(0);
- end;
- finally
- // save memory free space
- Borderframes := nil;
- Baseframetable := nil;
- Compframetable := nil;
- St := nil;
- Triangles := nil;
- end;
- end;
- // ------------------------------------------------------------------
- initialization
- // ------------------------------------------------------------------
- RegisterVectorFileFormat('mdc', 'MDC files', TgxMDCVectorFile);
- end.
|