rw-parser-ng 2.0.3 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,596 +1,611 @@
1
- import { RwFile } from '../RwFile';
2
- import { RwSections } from '../RwSections';
3
- import { RwParseStructureNotFoundError } from '../errors/RwParseError';
4
- import RwVersion from '../utils/RwVersion';
5
- import { DffModelType } from './DffModelType';
6
- import { RwColor, RwMatrix3, RwMatrix4, RwSphere, RwTextureCoordinate, RwTriangle, RwVector3 } from '../common/types';
7
-
8
- export interface RwDff {
9
- modelType: DffModelType,
10
- version: string,
11
- versionNumber: number,
12
- geometryList: RwGeometryList | null,
13
- frameList: RwFrameList | null,
14
- atomics: number[],
15
- dummies: string[],
16
- animNodes: RwAnimNode[],
17
- }
18
-
19
- export interface RwClump {
20
- atomicCount: number,
21
- lightCount?: number,
22
- cameraCount?: number,
23
- }
24
-
25
- export interface RwAnimNode {
26
- boneId: number,
27
- bonesCount: number,
28
- bones: RwBone[],
29
- }
30
-
31
- export interface RwBone {
32
- boneId: number,
33
- boneIndex: number,
34
- flags: number,
35
- }
36
-
37
-
38
- export interface RwFrame {
39
- rotationMatrix: RwMatrix3,
40
- coordinatesOffset: RwVector3,
41
- parentFrame: number,
42
- }
43
-
44
- export interface RwFrameList {
45
- frameCount: number,
46
- frames: RwFrame[],
47
- }
48
-
49
- export interface RwTexture {
50
- textureFiltering: number,
51
- uAddressing: number,
52
- vAddressing: number,
53
- usesMipLevels: boolean,
54
- textureName: string,
55
- }
56
-
57
- export interface RwMaterial {
58
- color: RwColor,
59
- isTextured: boolean,
60
- ambient?: number,
61
- specular?: number,
62
- diffuse?: number,
63
- texture?: RwTexture,
64
- }
65
-
66
- export interface RwMaterialList {
67
- materialInstanceCount: number,
68
- materialData: RwMaterial[],
69
- }
70
-
71
- export interface RwGeometry {
72
- vertexColorInformation: RwColor[],
73
- textureCoordinatesCount: number,
74
- textureMappingInformation: RwTextureCoordinate[][],
75
- hasVertices: boolean,
76
- hasNormals: boolean,
77
- triangleInformation: RwTriangle[],
78
- vertexInformation: RwVector3[],
79
- normalInformation: RwVector3[],
80
- boundingSphere?: RwSphere,
81
- materialList: RwMaterialList,
82
- binMesh: RwBinMesh,
83
- skin?: RwSkin,
84
- }
85
-
86
- export interface RwGeometryList {
87
- geometricObjectCount: number,
88
- geometries: RwGeometry[],
89
- }
90
-
91
- export interface RwAtomic {
92
- frameIndex: number,
93
- geometryIndex: number,
94
- flags: number,
95
- }
96
-
97
- export interface RwBinMesh {
98
- meshCount: number,
99
- meshes: RwMesh[],
100
- }
101
-
102
- export interface RwSkin {
103
- boneCount: number,
104
- usedBoneCount: number,
105
- maxWeightsPerVertex: number,
106
- boneVertexIndices: number[][],
107
- vertexWeights: number[][],
108
- inverseBoneMatrices: RwMatrix4[],
109
- }
110
-
111
- export interface RwMesh {
112
- materialIndex: number,
113
- indexCount: number,
114
- indices: number[],
115
- }
116
-
117
- export class DffParser extends RwFile {
118
-
119
- constructor(buffer: Buffer) {
120
- super(buffer);
121
- }
122
-
123
- parse(): RwDff {
124
- let version: string | undefined;
125
- let versionNumber: number | undefined;
126
- let atomics: number[] = [];
127
- let dummies: string[] = [];
128
- let animNodes: RwAnimNode[] = [];
129
- let geometryList: RwGeometryList | null = null;
130
- let frameList: RwFrameList | null = null;
131
-
132
- while (this.getPosition() < this.getSize()) {
133
- const header = this.readSectionHeader();
134
-
135
- if (header.sectionType === 0) {
136
- break;
137
- }
138
-
139
- if (header.sectionSize == 0) {
140
- continue;
141
- }
142
-
143
- switch (header.sectionType) {
144
- case RwSections.RwClump:
145
- // Multiple clumps are used in SA player models, so we should eventually support it
146
- versionNumber = RwVersion.unpackVersion(header.versionNumber);
147
- version = RwVersion.versions[versionNumber];
148
- break;
149
- case RwSections.RwFrameList:
150
- frameList = this.readFrameList();
151
- break;
152
- case RwSections.RwExtension:
153
- const extensionHeader = this.readSectionHeader();
154
- switch (extensionHeader.sectionType) {
155
- case RwSections.RwNodeName:
156
- dummies.push(this.readString(extensionHeader.sectionSize));
157
- break;
158
- case RwSections.RwAnim:
159
- animNodes.push(this.readAnimNode());
160
- break;
161
- default:
162
- console.debug(`Extension type ${extensionHeader.sectionType} (${extensionHeader.sectionType.toString(16)}) not found at offset (${this.getPosition().toString(16)}). Skipping ${extensionHeader.sectionSize} bytes.`);
163
- this.skip(extensionHeader.sectionSize);
164
- break;
165
- }
166
- break;
167
- case RwSections.RwGeometryList:
168
- geometryList = this.readGeometryList();
169
- break;
170
- case RwSections.RwAtomic:
171
- const atomic = this.readAtomic();
172
- atomics[atomic.geometryIndex] = atomic.frameIndex;
173
- break;
174
- case RwSections.RwNodeName:
175
- // For some reason, this frame is outside RwExtension.
176
- dummies.push(this.readString(header.sectionSize));
177
- break;
178
- case RwSections.RwAnim:
179
- // For III / VC models
180
- animNodes.push(this.readAnimNode());
181
- break;
182
- default:
183
- console.debug(`Section type ${header.sectionType} (${header.sectionType.toString(16)}) not found at offset (${this.getPosition().toString(16)}). Skipping ${header.sectionSize} bytes.`);
184
- this.skip(header.sectionSize);
185
- break;
186
- }
187
- }
188
-
189
- if (!version || !versionNumber) {
190
- throw new RwParseStructureNotFoundError('version');
191
- }
192
-
193
- let modelType = DffModelType.GENERIC;
194
- if (geometryList?.geometries.some(g => g.skin)) {
195
- modelType = DffModelType.SKIN;
196
- } else if (dummies.some(d => d.toLowerCase().includes('wheel') || d.toLowerCase().includes('chassis'))) {
197
- modelType = DffModelType.VEHICLE;
198
- }
199
-
200
- return {
201
- modelType,
202
- version: version,
203
- versionNumber: versionNumber,
204
- geometryList: geometryList,
205
- frameList: frameList,
206
- atomics: atomics,
207
- dummies: dummies,
208
- animNodes: animNodes,
209
- };
210
- }
211
-
212
- public readClump(): RwClump {
213
- const { versionNumber } = this.readSectionHeader();
214
-
215
- const atomicCount = this.readUint32();
216
-
217
- let lightCount;
218
- let cameraCount;
219
- if (versionNumber > 0x33000) {
220
- lightCount = this.readUint32();
221
- cameraCount = this.readUint32();
222
- }
223
-
224
- return { atomicCount, lightCount, cameraCount };
225
- }
226
-
227
- public readFrameList(): RwFrameList {
228
- this.readSectionHeader();
229
-
230
- const frameCount = this.readUint32();
231
-
232
- let frames: RwFrame[] = [];
233
-
234
- for (let i = 0; i < frameCount; i++) {
235
- // All these could probably be moved to readFrameData()
236
-
237
- const rotationMatrix: RwMatrix3 = {
238
- right: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() },
239
- up: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() },
240
- at: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() },
241
- }
242
-
243
- const coordinatesOffset: RwVector3 = { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() };
244
-
245
- const parentFrame = this.readInt32();
246
-
247
- // Skip matrix creation internal flags
248
- // They are read by the game but are not used
249
- this.skip(4);
250
-
251
- frames.push({ rotationMatrix, coordinatesOffset, parentFrame });
252
- }
253
-
254
- return { frameCount, frames };
255
- }
256
-
257
- public readGeometryList(): RwGeometryList {
258
- const header = this.readSectionHeader();
259
-
260
- const geometricObjectCount = this.readUint32();
261
-
262
- let geometries: RwGeometry[] = [];
263
-
264
- for (let i = 0; i < geometricObjectCount; i++) {
265
- this.readSectionHeader();
266
- this.readSectionHeader();
267
- const versionNumber = RwVersion.unpackVersion(header.versionNumber);
268
- const geometryData = this.readGeometry(versionNumber);
269
- geometries.push(geometryData);
270
- }
271
-
272
- return { geometricObjectCount, geometries };
273
- }
274
-
275
- public readGeometry(versionNumber: number): RwGeometry {
276
- const flags = this.readUint16();
277
- const textureCoordinatesCount = this.readUint8();
278
- const _nativeGeometryFlags = this.readUint8();
279
- const triangleCount = this.readUint32();
280
- const vertexCount = this.readUint32();
281
- const _morphTargetCount = this.readUint32();
282
-
283
- // Surface properties
284
- let _ambient;
285
- let _specular;
286
- let _diffuse;
287
-
288
- if (versionNumber < 0x34000) {
289
- _ambient = this.readFloat();
290
- _specular = this.readFloat();
291
- _diffuse = this.readFloat();
292
- }
293
-
294
- const _isTriangleStrip = (flags & (1 << 0)) !== 0;
295
- const _vertexTranslation = (flags & (1 << 1)) !== 0;
296
- const isTexturedUV1 = (flags & (1 << 2)) !== 0;
297
- const isGeometryPrelit = (flags & (1 << 3)) !== 0;
298
- const _hasNormals = (flags & (1 << 4)) !== 0;
299
- const _isGeometryLit = (flags & (1 << 5)) !== 0;
300
- const _shouldModulateMaterialColor = (flags & (1 << 6)) !== 0;
301
- const isTexturedUV2 = (flags & (1 << 7)) !== 0;
302
-
303
- const vertexColorInformation: RwColor[] = [];
304
- const textureMappingInformation: RwTextureCoordinate[][] = [];
305
- const triangleInformation: RwTriangle[] = [];
306
-
307
- // Geometry is marked as prelit
308
- if (isGeometryPrelit) {
309
- for (let i = 0; i < vertexCount; i++) {
310
- vertexColorInformation[i] = { r: this.readUint8(), g: this.readUint8(), b: this.readUint8(), a: this.readUint8() };
311
- }
312
- }
313
-
314
- // Geometry either has first or second texture
315
- if (isTexturedUV1 || isTexturedUV2) {
316
- for (let textureCoordinateIndex = 0; textureCoordinateIndex < textureCoordinatesCount; textureCoordinateIndex++) {
317
- textureMappingInformation[textureCoordinateIndex] = [];
318
- for (let vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
319
- textureMappingInformation[textureCoordinateIndex][vertexIndex] = { u: this.readFloat(), v: this.readFloat() };
320
- }
321
- }
322
- }
323
-
324
- for (let i = 0; i < triangleCount; i++) {
325
- // Information is written in this order
326
- const vertex2 = this.readUint16();
327
- const vertex1 = this.readUint16();
328
- const materialId = this.readUint16();
329
- const vertex3 = this.readUint16();
330
- triangleInformation[i] = { vector: { x: vertex1, y: vertex2, z: vertex3 }, materialId: materialId }
331
- }
332
-
333
- // We are sure that there's only one morph target, but if
334
- // we are wrong, we have to loop these through morphTargetCount
335
-
336
- const boundingSphere: RwSphere = {
337
- vector: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() },
338
- radius: this.readFloat(),
339
- };
340
-
341
- const hasVertices = !!this.readUint32();
342
- const hasNormals = !!this.readUint32();
343
-
344
- const vertexInformation = [];
345
- if (hasVertices) {
346
- for (let i = 0; i < vertexCount; i++) {
347
- vertexInformation[i] = { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() };
348
- }
349
- }
350
-
351
- const normalInformation = [];
352
- if (hasNormals) {
353
- for (let i = 0; i < vertexCount; i++) {
354
- normalInformation[i] = { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() };
355
- }
356
- }
357
-
358
- let materialList = this.readMaterialList();
359
- let sectionSize = this.readSectionHeader().sectionSize;
360
- let position = this.getPosition();
361
- let binMesh = this.readBinMesh();
362
- let skin = undefined;
363
-
364
- if (this.readSectionHeader().sectionType == RwSections.RwSkin) {
365
- skin = this.readSkin(vertexCount);
366
- }
367
-
368
- this.setPosition(position + sectionSize);
369
-
370
- return {
371
- textureCoordinatesCount,
372
- textureMappingInformation,
373
- boundingSphere,
374
- hasVertices,
375
- hasNormals,
376
- vertexColorInformation,
377
- vertexInformation,
378
- normalInformation,
379
- triangleInformation,
380
- materialList,
381
- binMesh,
382
- skin,
383
- };
384
- }
385
-
386
- public readBinMesh(): RwBinMesh {
387
- this.readSectionHeader();
388
-
389
- // Flags (0: triangle list, 1: triangle strip)
390
- this.skip(4);
391
-
392
- const meshCount = this.readUint32();
393
-
394
- // Total number of indices
395
- this.skip(4);
396
-
397
- const meshes: RwMesh[] = [];
398
-
399
- for (let i = 0; i < meshCount; i++) {
400
- meshes.push(this.readMesh());
401
- }
402
-
403
- return {
404
- meshCount, meshes
405
- };
406
- }
407
-
408
- public readSkin(vertexCount : number): RwSkin {
409
- const boneCount = this.readUint8();
410
- const usedBoneCount = this.readUint8();
411
- const maxWeightsPerVertex = this.readUint8();
412
-
413
- this.skip(1); // Padding
414
- this.skip(usedBoneCount); // Skipping special indices
415
-
416
- const boneVertexIndices: number[][] = [];
417
- const vertexWeights: number[][] = [];
418
- const inverseBoneMatrices: RwMatrix4[] = [];
419
-
420
- for (let i = 0; i < vertexCount; i++) {
421
- const indices: number[] = [];
422
- for (let j = 0; j < 4; j++) {
423
- indices.push(this.readUint8());
424
- }
425
- boneVertexIndices.push(indices);
426
- }
427
-
428
- for (let i = 0; i < vertexCount; i++) {
429
- const weights: number[] = [];
430
- for (let j = 0; j < 4; j++) {
431
- weights.push(this.readFloat());
432
- }
433
- vertexWeights.push(weights);
434
- }
435
-
436
- for (let i = 0; i < boneCount; i++) {
437
- const matrix4x4: RwMatrix4 = {
438
- right: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat(), t: this.readFloat() },
439
- up: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat(), t: this.readFloat() },
440
- at: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat(), t: this.readFloat() },
441
- transform: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat(), t: this.readFloat() },
442
- };
443
-
444
- inverseBoneMatrices.push(matrix4x4);
445
- }
446
-
447
- return {
448
- boneCount,
449
- usedBoneCount,
450
- maxWeightsPerVertex,
451
- boneVertexIndices,
452
- vertexWeights,
453
- inverseBoneMatrices,
454
- }
455
- }
456
-
457
- public readAnimNode() :RwAnimNode {
458
- this.skip(4); // Skipping AnimVersion property (0x100)
459
- const boneId = this.readInt32();
460
- const boneCount = this.readInt32();
461
- const bones :RwBone[] = [];
462
-
463
- if (boneId == 0) {
464
- this.skip(8); // Skipping flags and keyFrameSize properties
465
- }
466
-
467
- if (boneCount > 0) {
468
- for (let i = 0; i < boneCount; i++){
469
- bones.push({
470
- boneId: this.readInt32(),
471
- boneIndex: this.readInt32(),
472
- flags: this.readInt32()
473
- });
474
- }
475
- }
476
-
477
- return {
478
- boneId: boneId,
479
- bonesCount: boneCount,
480
- bones: bones
481
- }
482
- }
483
-
484
- public readMesh(): RwMesh {
485
- const indexCount = this.readUint32();
486
- const materialIndex = this.readUint32();
487
-
488
- const indices: number[] = [];
489
-
490
- for (let i = 0; i < indexCount; i++) {
491
- indices.push(this.readUint32());
492
- }
493
-
494
- return {
495
- indexCount, materialIndex, indices
496
- };
497
- }
498
-
499
- public readMaterialList(): RwMaterialList {
500
- this.readSectionHeader();
501
- this.readSectionHeader();
502
-
503
- const materialInstanceCount = this.readUint32();
504
- const materialIndices: number[] = [];
505
-
506
- for (let i = 0; i < materialInstanceCount; i++) {
507
- const materialIndex = this.readInt32();
508
- materialIndices.push(materialIndex);
509
- }
510
-
511
- const materialData: RwMaterial[] = [];
512
-
513
- for (let i = 0; i < materialInstanceCount; i++) {
514
- let materialIndex = materialIndices[i];
515
-
516
- if (materialIndex == -1) {
517
- materialData.push(this.readMaterial());
518
- } else {
519
- materialData.push(materialData[materialIndex]);
520
- }
521
- }
522
-
523
- return { materialInstanceCount, materialData };
524
- }
525
-
526
- public readMaterial(): RwMaterial {
527
- this.readSectionHeader();
528
- const header = this.readSectionHeader();
529
-
530
- // Flags - not used
531
- this.skip(4);
532
-
533
- const color: RwColor = { r: this.readUint8(), g: this.readUint8(), b: this.readUint8(), a: this.readUint8() };
534
-
535
- // Unknown - not used
536
- this.skip(4);
537
-
538
- const isTextured = this.readUint32() > 0;
539
-
540
- // Surface properties
541
- let ambient;
542
- let specular;
543
- let diffuse;
544
-
545
- if (header.versionNumber > 0x30400) {
546
- ambient = this.readFloat();
547
- specular = this.readFloat();
548
- diffuse = this.readFloat();
549
- }
550
-
551
- let texture;
552
-
553
- if (isTextured) {
554
- texture = this.readTexture();
555
- }
556
-
557
- // Skip various unused extensions
558
- this.skip(this.readSectionHeader().sectionSize);
559
-
560
- return { color, isTextured, ambient, specular, diffuse, texture };
561
- }
562
-
563
- public readTexture(): RwTexture {
564
- this.readSectionHeader();
565
- this.readSectionHeader();
566
-
567
- const textureData = this.readUint32();
568
-
569
- const textureFiltering = (textureData & 0xFF);
570
- const uAddressing = (textureData & 0xF00) >> 8;
571
- const vAddressing = (textureData & 0xF000) >> 12;
572
- const usesMipLevels = (textureData & (1 << 16)) !== 0;
573
-
574
- let textureNameSize = this.readSectionHeader().sectionSize;
575
- const textureName = this.readString(textureNameSize);
576
-
577
- // Skip various unused extensions
578
- this.skip(this.readSectionHeader().sectionSize);
579
- this.skip(this.readSectionHeader().sectionSize);
580
-
581
- return { textureFiltering, uAddressing, vAddressing, usesMipLevels, textureName };
582
- }
583
-
584
- public readAtomic(): RwAtomic {
585
- this.readSectionHeader();
586
-
587
- const frameIndex = this.readUint32();
588
- const geometryIndex = this.readUint32();
589
- const flags = this.readUint32();
590
-
591
- // Skip unused bytes
592
- this.skip(4);
593
-
594
- return { frameIndex, geometryIndex, flags };
595
- }
596
- }
1
+ import { RwFile } from '../RwFile';
2
+ import { RwSections } from '../RwSections';
3
+ import { RwParseStructureNotFoundError } from '../errors/RwParseError';
4
+ import RwVersion from '../utils/RwVersion';
5
+ import { DffModelType } from './DffModelType';
6
+ import { RwColor, RwMatrix3, RwMatrix4, RwSphere, RwTextureCoordinate, RwTriangle, RwVector3 } from '../common/types';
7
+
8
+ export interface RwDff {
9
+ modelType: DffModelType,
10
+ version: string,
11
+ versionNumber: number,
12
+ geometryList: RwGeometryList | null,
13
+ frameList: RwFrameList | null,
14
+ atomics: number[],
15
+ dummies: string[],
16
+ animNodes: RwAnimNode[],
17
+ }
18
+
19
+ export interface RwClump {
20
+ atomicCount: number,
21
+ lightCount?: number,
22
+ cameraCount?: number,
23
+ }
24
+
25
+ export interface RwAnimNode {
26
+ boneId: number,
27
+ bonesCount: number,
28
+ bones: RwBone[],
29
+ }
30
+
31
+ export interface RwBone {
32
+ boneId: number,
33
+ boneIndex: number,
34
+ flags: number,
35
+ }
36
+
37
+
38
+ export interface RwFrame {
39
+ rotationMatrix: RwMatrix3,
40
+ coordinatesOffset: RwVector3,
41
+ parentFrame: number,
42
+ }
43
+
44
+ export interface RwFrameList {
45
+ frameCount: number,
46
+ frames: RwFrame[],
47
+ }
48
+
49
+ export interface RwTexture {
50
+ textureFiltering: number,
51
+ uAddressing: number,
52
+ vAddressing: number,
53
+ usesMipLevels: boolean,
54
+ textureName: string,
55
+ }
56
+
57
+ export interface RwMaterial {
58
+ color: RwColor,
59
+ isTextured: boolean,
60
+ ambient?: number,
61
+ specular?: number,
62
+ diffuse?: number,
63
+ texture?: RwTexture,
64
+ }
65
+
66
+ export interface RwMaterialList {
67
+ materialInstanceCount: number,
68
+ materialData: RwMaterial[],
69
+ }
70
+
71
+ export interface RwGeometry {
72
+ vertexColorInformation: RwColor[],
73
+ textureCoordinatesCount: number,
74
+ textureMappingInformation: RwTextureCoordinate[][],
75
+ hasVertices: boolean,
76
+ hasNormals: boolean,
77
+ triangleInformation: RwTriangle[],
78
+ vertexInformation: RwVector3[],
79
+ normalInformation: RwVector3[],
80
+ boundingSphere?: RwSphere,
81
+ materialList: RwMaterialList,
82
+ binMesh: RwBinMesh,
83
+ skin?: RwSkin,
84
+ }
85
+
86
+ export interface RwGeometryList {
87
+ geometricObjectCount: number,
88
+ geometries: RwGeometry[],
89
+ }
90
+
91
+ export interface RwAtomic {
92
+ frameIndex: number,
93
+ geometryIndex: number,
94
+ flags: number,
95
+ }
96
+
97
+ export interface RwBinMesh {
98
+ meshCount: number,
99
+ meshes: RwMesh[],
100
+ }
101
+
102
+ export interface RwSkin {
103
+ boneCount: number,
104
+ usedBoneCount: number,
105
+ maxWeightsPerVertex: number,
106
+ boneVertexIndices: number[][],
107
+ vertexWeights: number[][],
108
+ inverseBoneMatrices: RwMatrix4[],
109
+ }
110
+
111
+ export interface RwMesh {
112
+ materialIndex: number,
113
+ indexCount: number,
114
+ indices: number[],
115
+ }
116
+
117
+ export class DffParser extends RwFile {
118
+
119
+ constructor(buffer: Buffer) {
120
+ super(buffer);
121
+ }
122
+
123
+ parse(): RwDff {
124
+ let version: string | undefined;
125
+ let versionNumber: number | undefined;
126
+ let atomics: number[] = [];
127
+ let dummies: string[] = [];
128
+ let animNodes: RwAnimNode[] = [];
129
+ let geometryList: RwGeometryList | null = null;
130
+ let frameList: RwFrameList | null = null;
131
+
132
+ while (this.getPosition() < this.getSize()) {
133
+ let header;
134
+ try {
135
+ header = this.readSectionHeader();
136
+ } catch (error) {
137
+ console.warn(`Failed to read section header at offset ${this.getPosition().toString(16)}: ${error instanceof Error ? error.message : error}. Truncating file.`);
138
+ break;
139
+ }
140
+
141
+ if (header.sectionType === 0) {
142
+ break;
143
+ }
144
+
145
+ if (header.sectionSize == 0) {
146
+ continue;
147
+ }
148
+
149
+ if (this.getPosition() + header.sectionSize > this.getSize()) {
150
+ console.warn(`Section at offset ${this.getPosition().toString(16)} claims size ${header.sectionSize} but only ${this.getSize() - this.getPosition()} bytes remaining. Truncating file.`);
151
+ break;
152
+ }
153
+
154
+ switch (header.sectionType) {
155
+ case RwSections.RwClump:
156
+ // Multiple clumps are used in SA player models, so we should eventually support it
157
+ versionNumber = RwVersion.unpackVersion(header.versionNumber);
158
+ version = RwVersion.versions[versionNumber];
159
+ break;
160
+ case RwSections.RwFrameList:
161
+ frameList = this.readFrameList();
162
+ break;
163
+ case RwSections.RwExtension:
164
+ const extensionHeader = this.readSectionHeader();
165
+ switch (extensionHeader.sectionType) {
166
+ case RwSections.RwNodeName:
167
+ dummies.push(this.readString(extensionHeader.sectionSize));
168
+ break;
169
+ case RwSections.RwAnim:
170
+ animNodes.push(this.readAnimNode());
171
+ break;
172
+ default:
173
+ console.debug(`Extension type ${extensionHeader.sectionType} (${extensionHeader.sectionType.toString(16)}) not found at offset (${this.getPosition().toString(16)}). Skipping ${extensionHeader.sectionSize} bytes.`);
174
+ this.skip(extensionHeader.sectionSize);
175
+ break;
176
+ }
177
+ break;
178
+ case RwSections.RwGeometryList:
179
+ geometryList = this.readGeometryList();
180
+ break;
181
+ case RwSections.RwAtomic:
182
+ const atomic = this.readAtomic();
183
+ atomics[atomic.geometryIndex] = atomic.frameIndex;
184
+ break;
185
+ case RwSections.RwNodeName:
186
+ // For some reason, this frame is outside RwExtension.
187
+ dummies.push(this.readString(header.sectionSize));
188
+ break;
189
+ case RwSections.RwAnim:
190
+ // For III / VC models
191
+ animNodes.push(this.readAnimNode());
192
+ break;
193
+ default:
194
+ console.debug(`Section type ${header.sectionType} (${header.sectionType.toString(16)}) not found at offset (${this.getPosition().toString(16)}). Skipping ${header.sectionSize} bytes.`);
195
+ this.skip(header.sectionSize);
196
+ break;
197
+ }
198
+ }
199
+
200
+ if (!version || !versionNumber) {
201
+ throw new RwParseStructureNotFoundError('version');
202
+ }
203
+
204
+ if (!geometryList || !geometryList.geometries || geometryList.geometries.length === 0) {
205
+ throw new RwParseStructureNotFoundError('geometry list');
206
+ }
207
+
208
+ let modelType = DffModelType.GENERIC;
209
+ if (geometryList.geometries.some(g => g.skin)) {
210
+ modelType = DffModelType.SKIN;
211
+ } else if (dummies.some(d => d.toLowerCase().includes('wheel') || d.toLowerCase().includes('chassis'))) {
212
+ modelType = DffModelType.VEHICLE;
213
+ }
214
+
215
+ return {
216
+ modelType,
217
+ version: version,
218
+ versionNumber: versionNumber,
219
+ geometryList: geometryList,
220
+ frameList: frameList,
221
+ atomics: atomics,
222
+ dummies: dummies,
223
+ animNodes: animNodes,
224
+ };
225
+ }
226
+
227
+ public readClump(): RwClump {
228
+ const { versionNumber } = this.readSectionHeader();
229
+
230
+ const atomicCount = this.readUint32();
231
+
232
+ let lightCount;
233
+ let cameraCount;
234
+ if (versionNumber > 0x33000) {
235
+ lightCount = this.readUint32();
236
+ cameraCount = this.readUint32();
237
+ }
238
+
239
+ return { atomicCount, lightCount, cameraCount };
240
+ }
241
+
242
+ public readFrameList(): RwFrameList {
243
+ this.readSectionHeader();
244
+
245
+ const frameCount = this.readUint32();
246
+
247
+ let frames: RwFrame[] = [];
248
+
249
+ for (let i = 0; i < frameCount; i++) {
250
+ // All these could probably be moved to readFrameData()
251
+
252
+ const rotationMatrix: RwMatrix3 = {
253
+ right: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() },
254
+ up: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() },
255
+ at: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() },
256
+ }
257
+
258
+ const coordinatesOffset: RwVector3 = { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() };
259
+
260
+ const parentFrame = this.readInt32();
261
+
262
+ // Skip matrix creation internal flags
263
+ // They are read by the game but are not used
264
+ this.skip(4);
265
+
266
+ frames.push({ rotationMatrix, coordinatesOffset, parentFrame });
267
+ }
268
+
269
+ return { frameCount, frames };
270
+ }
271
+
272
+ public readGeometryList(): RwGeometryList {
273
+ const header = this.readSectionHeader();
274
+
275
+ const geometricObjectCount = this.readUint32();
276
+
277
+ let geometries: RwGeometry[] = [];
278
+
279
+ for (let i = 0; i < geometricObjectCount; i++) {
280
+ this.readSectionHeader();
281
+ this.readSectionHeader();
282
+ const versionNumber = RwVersion.unpackVersion(header.versionNumber);
283
+ const geometryData = this.readGeometry(versionNumber);
284
+ geometries.push(geometryData);
285
+ }
286
+
287
+ return { geometricObjectCount, geometries };
288
+ }
289
+
290
+ public readGeometry(versionNumber: number): RwGeometry {
291
+ const flags = this.readUint16();
292
+ const textureCoordinatesCount = this.readUint8();
293
+ const _nativeGeometryFlags = this.readUint8();
294
+ const triangleCount = this.readUint32();
295
+ const vertexCount = this.readUint32();
296
+ const _morphTargetCount = this.readUint32();
297
+
298
+ // Surface properties
299
+ let _ambient;
300
+ let _specular;
301
+ let _diffuse;
302
+
303
+ if (versionNumber < 0x34000) {
304
+ _ambient = this.readFloat();
305
+ _specular = this.readFloat();
306
+ _diffuse = this.readFloat();
307
+ }
308
+
309
+ const _isTriangleStrip = (flags & (1 << 0)) !== 0;
310
+ const _vertexTranslation = (flags & (1 << 1)) !== 0;
311
+ const isTexturedUV1 = (flags & (1 << 2)) !== 0;
312
+ const isGeometryPrelit = (flags & (1 << 3)) !== 0;
313
+ const _hasNormals = (flags & (1 << 4)) !== 0;
314
+ const _isGeometryLit = (flags & (1 << 5)) !== 0;
315
+ const _shouldModulateMaterialColor = (flags & (1 << 6)) !== 0;
316
+ const isTexturedUV2 = (flags & (1 << 7)) !== 0;
317
+
318
+ const vertexColorInformation: RwColor[] = [];
319
+ const textureMappingInformation: RwTextureCoordinate[][] = [];
320
+ const triangleInformation: RwTriangle[] = [];
321
+
322
+ // Geometry is marked as prelit
323
+ if (isGeometryPrelit) {
324
+ for (let i = 0; i < vertexCount; i++) {
325
+ vertexColorInformation[i] = { r: this.readUint8(), g: this.readUint8(), b: this.readUint8(), a: this.readUint8() };
326
+ }
327
+ }
328
+
329
+ // Geometry either has first or second texture
330
+ if (isTexturedUV1 || isTexturedUV2) {
331
+ for (let textureCoordinateIndex = 0; textureCoordinateIndex < textureCoordinatesCount; textureCoordinateIndex++) {
332
+ textureMappingInformation[textureCoordinateIndex] = [];
333
+ for (let vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
334
+ textureMappingInformation[textureCoordinateIndex][vertexIndex] = { u: this.readFloat(), v: this.readFloat() };
335
+ }
336
+ }
337
+ }
338
+
339
+ for (let i = 0; i < triangleCount; i++) {
340
+ // Information is written in this order
341
+ const vertex2 = this.readUint16();
342
+ const vertex1 = this.readUint16();
343
+ const materialId = this.readUint16();
344
+ const vertex3 = this.readUint16();
345
+ triangleInformation[i] = { vector: { x: vertex1, y: vertex2, z: vertex3 }, materialId: materialId }
346
+ }
347
+
348
+ // We are sure that there's only one morph target, but if
349
+ // we are wrong, we have to loop these through morphTargetCount
350
+
351
+ const boundingSphere: RwSphere = {
352
+ vector: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() },
353
+ radius: this.readFloat(),
354
+ };
355
+
356
+ const hasVertices = !!this.readUint32();
357
+ const hasNormals = !!this.readUint32();
358
+
359
+ const vertexInformation = [];
360
+ if (hasVertices) {
361
+ for (let i = 0; i < vertexCount; i++) {
362
+ vertexInformation[i] = { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() };
363
+ }
364
+ }
365
+
366
+ const normalInformation = [];
367
+ if (hasNormals) {
368
+ for (let i = 0; i < vertexCount; i++) {
369
+ normalInformation[i] = { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() };
370
+ }
371
+ }
372
+
373
+ let materialList = this.readMaterialList();
374
+ let sectionSize = this.readSectionHeader().sectionSize;
375
+ let position = this.getPosition();
376
+ let binMesh = this.readBinMesh();
377
+ let skin = undefined;
378
+
379
+ if (this.readSectionHeader().sectionType == RwSections.RwSkin) {
380
+ skin = this.readSkin(vertexCount);
381
+ }
382
+
383
+ this.setPosition(position + sectionSize);
384
+
385
+ return {
386
+ textureCoordinatesCount,
387
+ textureMappingInformation,
388
+ boundingSphere,
389
+ hasVertices,
390
+ hasNormals,
391
+ vertexColorInformation,
392
+ vertexInformation,
393
+ normalInformation,
394
+ triangleInformation,
395
+ materialList,
396
+ binMesh,
397
+ skin,
398
+ };
399
+ }
400
+
401
+ public readBinMesh(): RwBinMesh {
402
+ this.readSectionHeader();
403
+
404
+ // Flags (0: triangle list, 1: triangle strip)
405
+ this.skip(4);
406
+
407
+ const meshCount = this.readUint32();
408
+
409
+ // Total number of indices
410
+ this.skip(4);
411
+
412
+ const meshes: RwMesh[] = [];
413
+
414
+ for (let i = 0; i < meshCount; i++) {
415
+ meshes.push(this.readMesh());
416
+ }
417
+
418
+ return {
419
+ meshCount, meshes
420
+ };
421
+ }
422
+
423
+ public readSkin(vertexCount : number): RwSkin {
424
+ const boneCount = this.readUint8();
425
+ const usedBoneCount = this.readUint8();
426
+ const maxWeightsPerVertex = this.readUint8();
427
+
428
+ this.skip(1); // Padding
429
+ this.skip(usedBoneCount); // Skipping special indices
430
+
431
+ const boneVertexIndices: number[][] = [];
432
+ const vertexWeights: number[][] = [];
433
+ const inverseBoneMatrices: RwMatrix4[] = [];
434
+
435
+ for (let i = 0; i < vertexCount; i++) {
436
+ const indices: number[] = [];
437
+ for (let j = 0; j < 4; j++) {
438
+ indices.push(this.readUint8());
439
+ }
440
+ boneVertexIndices.push(indices);
441
+ }
442
+
443
+ for (let i = 0; i < vertexCount; i++) {
444
+ const weights: number[] = [];
445
+ for (let j = 0; j < 4; j++) {
446
+ weights.push(this.readFloat());
447
+ }
448
+ vertexWeights.push(weights);
449
+ }
450
+
451
+ for (let i = 0; i < boneCount; i++) {
452
+ const matrix4x4: RwMatrix4 = {
453
+ right: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat(), t: this.readFloat() },
454
+ up: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat(), t: this.readFloat() },
455
+ at: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat(), t: this.readFloat() },
456
+ transform: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat(), t: this.readFloat() },
457
+ };
458
+
459
+ inverseBoneMatrices.push(matrix4x4);
460
+ }
461
+
462
+ return {
463
+ boneCount,
464
+ usedBoneCount,
465
+ maxWeightsPerVertex,
466
+ boneVertexIndices,
467
+ vertexWeights,
468
+ inverseBoneMatrices,
469
+ }
470
+ }
471
+
472
+ public readAnimNode() :RwAnimNode {
473
+ this.skip(4); // Skipping AnimVersion property (0x100)
474
+ const boneId = this.readInt32();
475
+ const boneCount = this.readInt32();
476
+ const bones :RwBone[] = [];
477
+
478
+ if (boneId == 0) {
479
+ this.skip(8); // Skipping flags and keyFrameSize properties
480
+ }
481
+
482
+ if (boneCount > 0) {
483
+ for (let i = 0; i < boneCount; i++){
484
+ bones.push({
485
+ boneId: this.readInt32(),
486
+ boneIndex: this.readInt32(),
487
+ flags: this.readInt32()
488
+ });
489
+ }
490
+ }
491
+
492
+ return {
493
+ boneId: boneId,
494
+ bonesCount: boneCount,
495
+ bones: bones
496
+ }
497
+ }
498
+
499
+ public readMesh(): RwMesh {
500
+ const indexCount = this.readUint32();
501
+ const materialIndex = this.readUint32();
502
+
503
+ const indices: number[] = [];
504
+
505
+ for (let i = 0; i < indexCount; i++) {
506
+ indices.push(this.readUint32());
507
+ }
508
+
509
+ return {
510
+ indexCount, materialIndex, indices
511
+ };
512
+ }
513
+
514
+ public readMaterialList(): RwMaterialList {
515
+ this.readSectionHeader();
516
+ this.readSectionHeader();
517
+
518
+ const materialInstanceCount = this.readUint32();
519
+ const materialIndices: number[] = [];
520
+
521
+ for (let i = 0; i < materialInstanceCount; i++) {
522
+ const materialIndex = this.readInt32();
523
+ materialIndices.push(materialIndex);
524
+ }
525
+
526
+ const materialData: RwMaterial[] = [];
527
+
528
+ for (let i = 0; i < materialInstanceCount; i++) {
529
+ let materialIndex = materialIndices[i];
530
+
531
+ if (materialIndex == -1) {
532
+ materialData.push(this.readMaterial());
533
+ } else {
534
+ materialData.push(materialData[materialIndex]);
535
+ }
536
+ }
537
+
538
+ return { materialInstanceCount, materialData };
539
+ }
540
+
541
+ public readMaterial(): RwMaterial {
542
+ this.readSectionHeader();
543
+ const header = this.readSectionHeader();
544
+
545
+ // Flags - not used
546
+ this.skip(4);
547
+
548
+ const color: RwColor = { r: this.readUint8(), g: this.readUint8(), b: this.readUint8(), a: this.readUint8() };
549
+
550
+ // Unknown - not used
551
+ this.skip(4);
552
+
553
+ const isTextured = this.readUint32() > 0;
554
+
555
+ // Surface properties
556
+ let ambient;
557
+ let specular;
558
+ let diffuse;
559
+
560
+ if (header.versionNumber > 0x30400) {
561
+ ambient = this.readFloat();
562
+ specular = this.readFloat();
563
+ diffuse = this.readFloat();
564
+ }
565
+
566
+ let texture;
567
+
568
+ if (isTextured) {
569
+ texture = this.readTexture();
570
+ }
571
+
572
+ // Skip various unused extensions
573
+ this.skip(this.readSectionHeader().sectionSize);
574
+
575
+ return { color, isTextured, ambient, specular, diffuse, texture };
576
+ }
577
+
578
+ public readTexture(): RwTexture {
579
+ this.readSectionHeader();
580
+ this.readSectionHeader();
581
+
582
+ const textureData = this.readUint32();
583
+
584
+ const textureFiltering = (textureData & 0xFF);
585
+ const uAddressing = (textureData & 0xF00) >> 8;
586
+ const vAddressing = (textureData & 0xF000) >> 12;
587
+ const usesMipLevels = (textureData & (1 << 16)) !== 0;
588
+
589
+ let textureNameSize = this.readSectionHeader().sectionSize;
590
+ const textureName = this.readString(textureNameSize);
591
+
592
+ // Skip various unused extensions
593
+ this.skip(this.readSectionHeader().sectionSize);
594
+ this.skip(this.readSectionHeader().sectionSize);
595
+
596
+ return { textureFiltering, uAddressing, vAddressing, usesMipLevels, textureName };
597
+ }
598
+
599
+ public readAtomic(): RwAtomic {
600
+ this.readSectionHeader();
601
+
602
+ const frameIndex = this.readUint32();
603
+ const geometryIndex = this.readUint32();
604
+ const flags = this.readUint32();
605
+
606
+ // Skip unused bytes
607
+ this.skip(4);
608
+
609
+ return { frameIndex, geometryIndex, flags };
610
+ }
611
+ }