2
// This unit is part of the GLScene Engine https://github.com/glscene
5
Code for loading animated MDC files into GLScene FreeForms
8
This file format uses in Return To Castle Wolfenstein instead
9
of MD3 files. It has got all MD3 features (such as Tag frames)
12
Original code by Osman Turan (osmanturancom@yahoo.com)
15
06/06/10 - Yar - Fixes for Linux x64
16
08/11/09 - DaStr - Compatibility fix for FPC
17
(thanks Predator) (BugtrackerID = 2893580)
18
16/10/08 - UweR - Compatibility fix for Delphi 2009
19
31/03/07 - DaStr - Added $I GLScene.inc
20
11/05/04 - SG - Added to CVS
21
07/02/04 - OT - Creation (Osman Turan)
33
GLVectorFileObjects, GLMaterial, GLApplicationFileIO,
37
MDCFILE_IDENTITY = 'IDPC';
40
MDC_BASEVERTEX_FACTOR = 0.015625; // 1/64;
41
MDC_COMPVERTEX_FACTOR = 0.046875; // 3/64;
44
TMDCPoint = array [0 .. 2] of Single;
45
TMDCAngle = TMDCPoint;
47
TMDCFileHeader = packed record
48
Ident: array [0 .. 3] of AnsiChar;
50
Name: array [0 .. 63] of AnsiChar;
54
NumSurfaces: Cardinal;
56
OffsetBorderFrames: Cardinal;
57
OffsetTagNames: Cardinal;
58
OffsetTagFrames: Cardinal;
59
OffsetSurfaces: Cardinal;
63
TMDCBorderFrame = packed record
64
BBMin, BBMax: TMDCPoint;
65
LocalOrigin: TMDCPoint;
67
Name: array [0 .. 15] of AnsiChar;
70
PMDCTagName = ^TMDCTagName;
72
TMDCTagName = packed record
73
Name: array [0 .. 63] of AnsiChar;
76
PMDCTagFrame = ^TMDCTagFrame;
78
TMDCTagFrame = packed record
79
TagPosition: array [0 .. 2] of Word; // or ShortInt?
80
TagAngle: array [0 .. 2] of Word; // or ShortInt?
83
TMDCTag = packed record
85
TagFrame: PMDCTagFrame;
88
TMDCSurfaceHeader = packed record
89
Ident: array [0 .. 3] of AnsiChar;
90
Name: array [0 .. 63] of AnsiChar;
92
NumCompFrames: Cardinal;
93
NumBaseFrames: Cardinal;
95
NumVertices: Cardinal;
96
NumTriangles: Cardinal;
97
OffsetTriangles: Cardinal;
98
OffsetSkins: Cardinal;
99
OffsetTexCoords: Cardinal;
100
OffsetBaseVerts: Cardinal;
101
OffsetCompVerts: Cardinal;
102
OffsetFrameBaseFrames: Cardinal;
103
OffsetFrameCompFrames: Cardinal;
107
TMDCTriangle = array [0 .. 2] of Cardinal;
109
TMDCSkin = packed record
110
Shader: array [0 .. 63] of AnsiChar;
114
TMDCTexCoord = array [0 .. 1] of Single;
116
TMDCBaseVertex = array [0 .. 3] of SmallInt;
118
TMDCCompVertex = array [0 .. 3] of Byte;
120
TMDCBaseFrame = packed record
121
BaseVertices: array of TMDCBaseVertex;
124
TMDCCompFrame = packed record
125
CompVertices: array of TMDCCompVertex;
129
TGLMDCVectorFile = class(TGLVectorFile)
131
class function Capabilities: TGLDataFileCapabilities; override;
132
procedure LoadFromStream(AStream: TStream); override;
135
// ------------------------------------------------------------------
136
// ------------------------------------------------------------------
137
// ------------------------------------------------------------------
140
// ------------------------------------------------------------------
141
// ------------------------------------------------------------------
142
// ------------------------------------------------------------------
145
// ------------------ TGLMDCVectorFile ------------------
150
class function TGLMDCVectorFile.Capabilities: TGLDataFileCapabilities;
157
procedure TGLMDCVectorFile.LoadFromStream(AStream: TStream);
160
PPackedNormal = ^TPackedNormal;
161
TPackedNormal = array [0 .. 1] of Byte;
164
I, J, K, NumVerts, Numtris: Integer;
165
Mesh: TMorphableMeshObject;
166
FaceGroup: TFGIndexTexCoordList;
167
MorphTarget: TMeshMorphTarget;
169
function UnpackNormal(Pn: TPackedNormal): TAffineVector;
173
// The MDC normal is a latitude/longitude value that needs
174
// to be calculated into cartesian space.
175
Lat := (Pn[0]) * (2 * Pi) / 255;
176
Lng := (Pn[1]) * (2 * Pi) / 255;
177
Result.V[0] := Cos(Lat) * Sin(Lng);
178
Result.V[1] := Sin(Lat) * Sin(Lng);
179
Result.V[2] := Cos(Lng);
182
procedure AllocateMaterial(Meshname: string);
184
LibMat: TGLLibMaterial;
186
// If a material library is assigned to the actor/freeform the
187
// mesh name will be added as a material.
188
if Assigned(Owner.MaterialLibrary) then
189
with Owner.MaterialLibrary do
191
if Assigned(Materials.GetLibMaterialByName(Meshname)) then
193
LibMat := Materials.Add;
194
LibMat.Name := Meshname;
195
LibMat.Material.Texture.Disabled := False;
200
Fileheader: TMDCFileHeader;
201
Surfheader: TMDCSurfaceHeader;
202
Borderframes: array of TMDCBorderFrame;
203
Baseframetable, Compframetable: array of Word;
204
Baseframe: TMDCBaseFrame;
205
Compframe: TMDCCompFrame;
206
Xyz, Normal: TAffineVector;
207
St: array of array [0 .. 1] of Single;
208
Triangles: array of TMDCTriangle;
209
FrameOffset: Cardinal;
211
AStream.Read(Fileheader, SizeOf(Fileheader));
212
Assert(Fileheader.Ident = MDCFILE_IDENTITY, 'Incorrect MDC file Ident');
213
Assert(Fileheader.Version = MDCFILE_VERSION, 'Incorrect MDC version number');
216
AStream.Seek(Fileheader.OffsetBorderFrames, SoFromBeginning);
217
SetLength(Borderframes, Fileheader.NumFrames);
218
AStream.Read(Borderframes[0], SizeOf(TMDCBorderFrame) *
219
Fileheader.NumFrames);
221
FrameOffset := Fileheader.OffsetSurfaces;
223
for I := 0 to Fileheader.NumSurfaces - 1 do
226
AStream.Seek(FrameOffset, SoFromBeginning);
227
AStream.Read(Surfheader, SizeOf(TMDCSurfaceHeader));
229
// triangles for this surface
230
SetLength(Triangles, Surfheader.NumTriangles);
231
AStream.Seek(FrameOffset + Surfheader.OffsetTriangles, SoFromBeginning);
232
AStream.Read(Triangles[0], SizeOf(TMDCTriangle) *
233
Surfheader.NumTriangles);
235
// texture coordinates for this surface
236
SetLength(St, Surfheader.NumVertices);
237
AStream.Seek(FrameOffset + Surfheader.OffsetTexCoords, SoFromBeginning);
238
AStream.Read(St[0], 2 * SizeOf(Single) * Surfheader.NumVertices);
240
// base frame table for this surface (for only loading)
241
SetLength(Baseframetable, Fileheader.NumFrames);
242
AStream.Seek(FrameOffset + Surfheader.OffsetFrameBaseFrames,
244
AStream.Read(Baseframetable[0], SizeOf(Word) * Fileheader.NumFrames);
245
// compressed frame table for this surface (for only loading)
246
SetLength(Compframetable, Fileheader.NumFrames);
247
AStream.Seek(FrameOffset + Surfheader.OffsetFrameCompFrames,
249
AStream.Read(Compframetable[0], SizeOf(Word) * Fileheader.NumFrames);
251
Mesh := TMorphableMeshObject.CreateOwned(Owner.MeshObjects);
252
// easiest way to convert a char array to string ;)
253
Mesh.Name := Trim(string(PChar(Surfheader.Name[0])));
256
Mode := MomFaceGroups;
257
FaceGroup := TFGIndexTexCoordList.CreateOwned(FaceGroups);
260
AllocateMaterial(Mesh.Name);
261
MaterialName := Mesh.Name;
262
NumTris := Surfheader.NumTriangles;
263
VertexIndices.Capacity := NumTris * 3;
264
TexCoords.Capacity := NumTris * 3;
265
// Get the vertex indices and texture coordinates
266
for J := 0 to Surfheader.NumTriangles - 1 do
268
Add(Triangles[J, 0], St[Triangles[J, 0]][0],
269
1 - St[Triangles[J, 0]][1]);
270
Add(Triangles[J, 2], St[Triangles[J, 2]][0],
271
1 - St[Triangles[J, 2]][1]);
272
Add(Triangles[J, 1], St[Triangles[J, 1]][0],
273
1 - St[Triangles[J, 1]][1]);
277
// Get the mesh data for each morph frame
278
for J := 0 to Fileheader.NumFrames - 1 do
280
MorphTarget := TMeshMorphTarget.CreateOwned(MorphTargets);
281
MorphTarget.Name := Trim(string(Pchar(Surfheader.Name[0]))) + '[' +
283
NumVerts := Surfheader.NumVertices;
284
MorphTarget.Vertices.Capacity := NumVerts;
287
SetLength(Baseframe.BaseVertices, Surfheader.NumVertices);
288
AStream.Seek(FrameOffset + Surfheader.OffsetBaseVerts + Baseframetable
289
[J] * Surfheader.NumVertices * 8, SoFromBeginning);
290
AStream.Read(Baseframe.BaseVertices[0], SizeOf(TMDCBaseVertex) *
291
Surfheader.NumVertices);
294
if Compframetable[J] <> $FFFF then // is there a valid frame?
296
SetLength(Compframe.CompVertices, Surfheader.NumVertices);
297
AStream.Seek(FrameOffset + Surfheader.OffsetCompVerts +
298
Compframetable[J] * Surfheader.NumVertices * 4, SoFromBeginning);
299
AStream.Read(Compframe.CompVertices[0], SizeOf(TMDCCompVertex) *
300
Surfheader.NumVertices);
303
for K := 0 to Surfheader.NumVertices - 1 do
306
(Baseframe.BaseVertices[K, 0] * MDC_BASEVERTEX_FACTOR) +
307
Borderframes[J].Localorigin[0];
309
(Baseframe.BaseVertices[K, 1] * MDC_BASEVERTEX_FACTOR) +
310
Borderframes[J].Localorigin[1];
312
(Baseframe.BaseVertices[K, 2] * MDC_BASEVERTEX_FACTOR) +
313
Borderframes[J].Localorigin[2];
314
Normal := UnpackNormal
315
(PPackedNormal(@Baseframe.BaseVertices[K, 3])^);
317
if Compframetable[J] <> $FFFF then
319
Xyz.V[0] := Xyz.V[0] +
320
((Compframe.CompVertices[K, 0] - 128) * MDC_COMPVERTEX_FACTOR);
321
Xyz.V[1] := Xyz.V[1] +
322
((Compframe.CompVertices[K, 1] - 128) * MDC_COMPVERTEX_FACTOR);
323
Xyz.V[2] := Xyz.V[2] +
324
((Compframe.CompVertices[K, 2] - 128) * MDC_COMPVERTEX_FACTOR);
326
// I'm sure compframe.CompVertices[3] points a packed normal.
327
// And it must be add the current normal like xyz.
328
// But, I don't know a way to unpacked this value
329
// I found a precalculated normal list in RTCW 1.41 mod source (q_math.c)
331
// NUMVERTEXNORMALS = 162
332
// vec3_t bytedirs[NUMVERTEXNORMALS] = {
333
// {-0.525731, 0.000000, 0.850651}, (...)
335
// But, I had noticed some compframe.CompVertices[3] value is bigger
336
// than NUMVERTEXNORMALS constant. So, there must be another list.
338
// Osman Turan (osmanturancom@yahoo.com)
341
// all id Sofware based games uses Z axis as up instead of Y. So, convert them
342
MorphTarget.Vertices.Add(Xyz.V[0], Xyz.V[2], -Xyz.V[1]);
343
MorphTarget.Normals.Add(Normal.V[0], Normal.V[2],
349
FrameOffset := FrameOffset + Surfheader.OffsetEnd;
351
if Mesh.MorphTargets.Count > 0 then
355
// save memory free space
357
Baseframetable := nil;
358
Compframetable := nil;
364
// ------------------------------------------------------------------
365
// ------------------------------------------------------------------
366
// ------------------------------------------------------------------
369
// ------------------------------------------------------------------
370
// ------------------------------------------------------------------
371
// ------------------------------------------------------------------
373
RegisterVectorFileFormat('mdc', 'MDC files', TGLMDCVectorFile);