rw-parser
Version:
Parses RenderWare DFF and TXD files into usable format!
410 lines (409 loc) • 18 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DffParser = void 0;
var RwFile_1 = require("../RwFile");
var RwSections_1 = require("../RwSections");
var RwParseError_1 = require("../errors/RwParseError");
var RwVersion_1 = __importDefault(require("../utils/RwVersion"));
var DffParser = /** @class */ (function (_super) {
__extends(DffParser, _super);
function DffParser(buffer) {
return _super.call(this, buffer) || this;
}
DffParser.prototype.parse = function () {
var version;
var versionNumber;
var atomics = [];
var dummies = [];
var animNodes = [];
var geometryList = null;
var frameList = null;
while (this.getPosition() < this.getSize()) {
var header = this.readSectionHeader();
if (header.sectionType === 0) {
break;
}
if (header.sectionSize == 0) {
continue;
}
switch (header.sectionType) {
case RwSections_1.RwSections.RwClump:
// Multiple clumps are used in SA player models, so we should eventually support it
versionNumber = RwVersion_1.default.unpackVersion(header.versionNumber);
version = RwVersion_1.default.versions[versionNumber];
break;
case RwSections_1.RwSections.RwFrameList:
frameList = this.readFrameList();
break;
case RwSections_1.RwSections.RwExtension:
var extensionHeader = this.readSectionHeader();
switch (extensionHeader.sectionType) {
case RwSections_1.RwSections.RwNodeName:
dummies.push(this.readString(extensionHeader.sectionSize));
break;
case RwSections_1.RwSections.RwAnim:
animNodes.push(this.readAnimNode());
break;
default:
console.debug("Extension type ".concat(extensionHeader.sectionType, " (").concat(extensionHeader.sectionType.toString(16), ") not found at offset (").concat(this.getPosition().toString(16), "). Skipping ").concat(extensionHeader.sectionSize, " bytes."));
this.skip(extensionHeader.sectionSize);
break;
}
break;
case RwSections_1.RwSections.RwGeometryList:
geometryList = this.readGeometryList();
break;
case RwSections_1.RwSections.RwAtomic:
var atomic = this.readAtomic();
atomics[atomic.geometryIndex] = atomic.frameIndex;
break;
case RwSections_1.RwSections.RwNodeName:
// For some reason, this frame is outside RwExtension.
dummies.push(this.readString(header.sectionSize));
break;
case RwSections_1.RwSections.RwAnim:
// For III / VC models
animNodes.push(this.readAnimNode());
break;
default:
console.debug("Section type ".concat(header.sectionType, " (").concat(header.sectionType.toString(16), ") not found at offset (").concat(this.getPosition().toString(16), "). Skipping ").concat(header.sectionSize, " bytes."));
this.skip(header.sectionSize);
break;
}
}
if (!version || !versionNumber) {
throw new RwParseError_1.RwParseStructureNotFoundError('version');
}
return {
version: version,
versionNumber: versionNumber,
geometryList: geometryList,
frameList: frameList,
atomics: atomics,
dummies: dummies,
animNodes: animNodes,
};
};
DffParser.prototype.readClump = function () {
var versionNumber = this.readSectionHeader().versionNumber;
var atomicCount = this.readUint32();
var lightCount;
var cameraCount;
if (versionNumber > 0x33000) {
lightCount = this.readUint32();
cameraCount = this.readUint32();
}
return { atomicCount: atomicCount, lightCount: lightCount, cameraCount: cameraCount };
};
DffParser.prototype.readFrameList = function () {
this.readSectionHeader();
var frameCount = this.readUint32();
var frames = [];
for (var i = 0; i < frameCount; i++) {
// All these could probably be moved to readFrameData()
var rotationMatrix = {
right: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() },
up: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() },
at: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() },
};
var coordinatesOffset = { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() };
var parentFrame = this.readInt32();
// Skip matrix creation internal flags
// They are read by the game but are not used
this.skip(4);
frames.push({ rotationMatrix: rotationMatrix, coordinatesOffset: coordinatesOffset, parentFrame: parentFrame });
}
return { frameCount: frameCount, frames: frames };
};
DffParser.prototype.readGeometryList = function () {
var header = this.readSectionHeader();
var geometricObjectCount = this.readUint32();
var geometries = [];
for (var i = 0; i < geometricObjectCount; i++) {
this.readSectionHeader();
this.readSectionHeader();
var versionNumber = RwVersion_1.default.unpackVersion(header.versionNumber);
var geometryData = this.readGeometry(versionNumber);
geometries.push(geometryData);
}
return { geometricObjectCount: geometricObjectCount, geometries: geometries };
};
DffParser.prototype.readGeometry = function (versionNumber) {
var flags = this.readUint16();
var textureCoordinatesCount = this.readUint8();
var _nativeGeometryFlags = this.readUint8();
var triangleCount = this.readUint32();
var vertexCount = this.readUint32();
var _morphTargetCount = this.readUint32();
// Surface properties
var _ambient;
var _specular;
var _diffuse;
if (versionNumber < 0x34000) {
_ambient = this.readFloat();
_specular = this.readFloat();
_diffuse = this.readFloat();
}
var _isTriangleStrip = (flags & (1 << 0)) !== 0;
var _vertexTranslation = (flags & (1 << 1)) !== 0;
var isTexturedUV1 = (flags & (1 << 2)) !== 0;
var isGeometryPrelit = (flags & (1 << 3)) !== 0;
var _hasNormals = (flags & (1 << 4)) !== 0;
var _isGeometryLit = (flags & (1 << 5)) !== 0;
var _shouldModulateMaterialColor = (flags & (1 << 6)) !== 0;
var isTexturedUV2 = (flags & (1 << 7)) !== 0;
var vertexColorInformation = [];
var textureMappingInformation = [];
var triangleInformation = [];
// Geometry is marked as prelit
if (isGeometryPrelit) {
for (var i = 0; i < vertexCount; i++) {
vertexColorInformation[i] = { r: this.readUint8(), g: this.readUint8(), b: this.readUint8(), a: this.readUint8() };
}
}
// Geometry either has first or second texture
if (isTexturedUV1 || isTexturedUV2) {
for (var textureCoordinateIndex = 0; textureCoordinateIndex < textureCoordinatesCount; textureCoordinateIndex++) {
textureMappingInformation[textureCoordinateIndex] = [];
for (var vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
textureMappingInformation[textureCoordinateIndex][vertexIndex] = { u: this.readFloat(), v: this.readFloat() };
}
}
}
for (var i = 0; i < triangleCount; i++) {
// Information is written in this order
var vertex2 = this.readUint16();
var vertex1 = this.readUint16();
var materialId = this.readUint16();
var vertex3 = this.readUint16();
triangleInformation[i] = { vector: { x: vertex1, y: vertex2, z: vertex3 }, materialId: materialId };
}
// We are sure that there's only one morph target, but if
// we are wrong, we have to loop these through morphTargetCount
var boundingSphere = {
vector: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() },
radius: this.readFloat(),
};
var hasVertices = !!this.readUint32();
var hasNormals = !!this.readUint32();
var vertexInformation = [];
if (hasVertices) {
for (var i = 0; i < vertexCount; i++) {
vertexInformation[i] = { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() };
}
}
var normalInformation = [];
if (hasNormals) {
for (var i = 0; i < vertexCount; i++) {
normalInformation[i] = { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() };
}
}
var materialList = this.readMaterialList();
var sectionSize = this.readSectionHeader().sectionSize;
var position = this.getPosition();
var binMesh = this.readBinMesh();
var skin = undefined;
if (this.readSectionHeader().sectionType == RwSections_1.RwSections.RwSkin) {
skin = this.readSkin(vertexCount);
}
this.setPosition(position + sectionSize);
return {
textureCoordinatesCount: textureCoordinatesCount,
textureMappingInformation: textureMappingInformation,
boundingSphere: boundingSphere,
hasVertices: hasVertices,
hasNormals: hasNormals,
vertexColorInformation: vertexColorInformation,
vertexInformation: vertexInformation,
normalInformation: normalInformation,
triangleInformation: triangleInformation,
materialList: materialList,
binMesh: binMesh,
skin: skin,
};
};
DffParser.prototype.readBinMesh = function () {
this.readSectionHeader();
// Flags (0: triangle list, 1: triangle strip)
this.skip(4);
var meshCount = this.readUint32();
// Total number of indices
this.skip(4);
var meshes = [];
for (var i = 0; i < meshCount; i++) {
meshes.push(this.readMesh());
}
return {
meshCount: meshCount,
meshes: meshes
};
};
DffParser.prototype.readSkin = function (vertexCount) {
var boneCount = this.readUint8();
var usedBoneCount = this.readUint8();
var maxWeightsPerVertex = this.readUint8();
this.skip(1); // Padding
this.skip(usedBoneCount); // Skipping special indices
var boneVertexIndices = [];
var vertexWeights = [];
var inverseBoneMatrices = [];
for (var i = 0; i < vertexCount; i++) {
var indices = [];
for (var j = 0; j < 4; j++) {
indices.push(this.readUint8());
}
boneVertexIndices.push(indices);
}
for (var i = 0; i < vertexCount; i++) {
var weights = [];
for (var j = 0; j < 4; j++) {
weights.push(this.readFloat());
}
vertexWeights.push(weights);
}
for (var i = 0; i < boneCount; i++) {
var matrix4x4 = {
right: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat(), t: this.readFloat() },
up: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat(), t: this.readFloat() },
at: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat(), t: this.readFloat() },
transform: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat(), t: this.readFloat() },
};
inverseBoneMatrices.push(matrix4x4);
}
return {
boneCount: boneCount,
usedBoneCount: usedBoneCount,
maxWeightsPerVertex: maxWeightsPerVertex,
boneVertexIndices: boneVertexIndices,
vertexWeights: vertexWeights,
inverseBoneMatrices: inverseBoneMatrices,
};
};
DffParser.prototype.readAnimNode = function () {
this.skip(4); // Skipping AnimVersion property (0x100)
var boneId = this.readInt32();
var boneCount = this.readInt32();
var bones = [];
if (boneId == 0) {
this.skip(8); // Skipping flags and keyFrameSize properties
}
if (boneCount > 0) {
for (var i = 0; i < boneCount; i++) {
bones.push({
boneId: this.readInt32(),
boneIndex: this.readInt32(),
flags: this.readInt32()
});
}
}
return {
boneId: boneId,
bonesCount: boneCount,
bones: bones
};
};
DffParser.prototype.readMesh = function () {
var indexCount = this.readUint32();
var materialIndex = this.readUint32();
var indices = [];
for (var i = 0; i < indexCount; i++) {
indices.push(this.readUint32());
}
return {
indexCount: indexCount,
materialIndex: materialIndex,
indices: indices
};
};
DffParser.prototype.readMaterialList = function () {
this.readSectionHeader();
this.readSectionHeader();
var materialInstanceCount = this.readUint32();
var materialIndices = [];
for (var i = 0; i < materialInstanceCount; i++) {
var materialIndex = this.readInt32();
materialIndices.push(materialIndex);
}
var materialData = [];
for (var i = 0; i < materialInstanceCount; i++) {
var materialIndex = materialIndices[i];
if (materialIndex == -1) {
materialData.push(this.readMaterial());
}
else {
materialData.push(materialData[materialIndex]);
}
}
return { materialInstanceCount: materialInstanceCount, materialData: materialData };
};
DffParser.prototype.readMaterial = function () {
this.readSectionHeader();
var header = this.readSectionHeader();
// Flags - not used
this.skip(4);
var color = { r: this.readUint8(), g: this.readUint8(), b: this.readUint8(), a: this.readUint8() };
// Unknown - not used
this.skip(4);
var isTextured = this.readUint32() > 0;
// Surface properties
var ambient;
var specular;
var diffuse;
if (header.versionNumber > 0x30400) {
ambient = this.readFloat();
specular = this.readFloat();
diffuse = this.readFloat();
}
var texture;
if (isTextured) {
texture = this.readTexture();
}
// Skip various unused extensions
this.skip(this.readSectionHeader().sectionSize);
return { color: color, isTextured: isTextured, ambient: ambient, specular: specular, diffuse: diffuse, texture: texture };
};
DffParser.prototype.readTexture = function () {
this.readSectionHeader();
this.readSectionHeader();
var textureData = this.readUint32();
var textureFiltering = (textureData & 0xFF);
var uAddressing = (textureData & 0xF00) >> 8;
var vAddressing = (textureData & 0xF000) >> 12;
var usesMipLevels = (textureData & (1 << 16)) !== 0;
var textureNameSize = this.readSectionHeader().sectionSize;
var textureName = this.readString(textureNameSize);
// Skip various unused extensions
this.skip(this.readSectionHeader().sectionSize);
this.skip(this.readSectionHeader().sectionSize);
return { textureFiltering: textureFiltering, uAddressing: uAddressing, vAddressing: vAddressing, usesMipLevels: usesMipLevels, textureName: textureName };
};
DffParser.prototype.readAtomic = function () {
this.readSectionHeader();
var frameIndex = this.readUint32();
var geometryIndex = this.readUint32();
var flags = this.readUint32();
// Skip unused bytes
this.skip(4);
return { frameIndex: frameIndex, geometryIndex: geometryIndex, flags: flags };
};
return DffParser;
}(RwFile_1.RwFile));
exports.DffParser = DffParser;