1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-11-07 03:12:36 +01:00

Refactor DFF Loader, underlying stream handling

+ Fix some model related memory leaks
This commit is contained in:
Daniel Evans 2014-08-04 22:21:01 +01:00
parent be347e88dc
commit 6c78b0c3c5
12 changed files with 631 additions and 311 deletions

View File

@ -74,6 +74,7 @@ public:
* @param path Path to the root of the game data.
*/
GameData(const std::string& path = "");
~GameData();
GameWorld* engine;

View File

@ -10,19 +10,51 @@
#include <vector>
#include <string>
#include <WorkContext.hpp>
#include <engine/RWTypes.hpp>
class Model;
class GameData;
class DFFLoaderException
{
std::string _message;
public:
DFFLoaderException(const std::string& message)
: _message(message)
{}
const std::string& which() { return _message; }
};
class LoaderDFF
{
private:
template<class T> T readStructure(char *data, size_t &dataI);
RW::BSSectionHeader readHeader(char *data, size_t &dataI);
/**
* @brief loads a Frame List chunk from stream into model.
* @param model
* @param stream
*/
void readFrameList(Model* model, const RWBStream &stream);
void readGeometryList(Model* model, const RWBStream& stream);
void readGeometry(Model* model, const RWBStream& stream);
void readMaterialList(Model* model, const RWBStream& stream);
void readMaterial(Model* model, const RWBStream& stream);
void readTexture(Model* model, const RWBStream& stream);
void readGeometryExtension(Model* model, const RWBStream& stream);
void readBinMeshPLG(Model* model, const RWBStream& stream);
void readAtomic(Model* model, const RWBStream& stream);
public:
Model* loadFromMemory(char *data, GameData* gameData);
Model* loadFromMemory(FileHandle file, GameData* gameData);
};
#include <functional>
@ -36,7 +68,7 @@ private:
GameData* _gameData;
std::string _file;
ModelCallback _callback;
char* _data;
FileHandle _data;
public:
LoadModelJob(WorkContext* context, GameData* gd, const std::string& file, ModelCallback cb);

View File

@ -6,8 +6,80 @@
#include <cstddef>
#include <cassert>
/**
* @brief Class for working with RenderWare binary streams.
*
* Stream files are split into chunks, each of which may have numerous
* child chunks (in particular, the "struct" chunk which is used to store
* data relating to the parent chunk).
*/
class RWBStream
{
char* _data;
std::ptrdiff_t _size;
char* _dataCur;
char* _nextChunk;
std::uint32_t _chunkVersion;
std::ptrdiff_t _currChunkSz;
public:
typedef std::uint32_t ChunkID;
RWBStream(char* data, size_t size)
: _data(data), _size(size), _dataCur(data), _nextChunk(data)
{}
/**
* Moves the stream to the next chunk and returns it's ID
*/
ChunkID getNextChunk()
{
// Check that there's any data left
if( (unsigned)(_dataCur - _data) >= _size ) return 0;
// _nextChunk is initally = to _data, making this a non-op
_dataCur = _nextChunk;
ChunkID id = *(ChunkID*)(_dataCur);
_dataCur += sizeof(ChunkID);
_currChunkSz = *(std::uint32_t*)(_dataCur);
_dataCur += sizeof(std::uint32_t);
_chunkVersion = *(std::uint32_t*)(_dataCur);
_dataCur += sizeof(std::uint32_t);
_nextChunk = _dataCur + _currChunkSz;
return id;
}
char* getCursor() const
{
return _dataCur;
}
size_t getCurrentChunkSize() const
{
return _currChunkSz;
}
std::uint32_t getChunkVersion() const
{
return _chunkVersion;
}
/**
* @brief Returns a new stream for the data inside this one.
*/
RWBStream getInnerStream() const
{
return RWBStream(_dataCur, _currChunkSz);
}
};
/**
* @file rwbinarystream.h
* Deprecated RenderWare binary stream interface.
* Contains the structs for the shared Render Ware binary stream data.
* Many thanks to http://www.gtamodding.com/index.php?title=RenderWare_binary_stream_file
*/
@ -39,17 +111,11 @@ namespace RW
SID_NodeName = 0x0253F2FE
};
/**
* Vector data
*/
typedef glm::vec3 BSTVector3;
/**
* Rotation Matrix
*/
typedef glm::mat3 BSTMatrix;
struct BSSectionHeader
{
uint32_t id;
@ -66,14 +132,7 @@ namespace RW
{
uint32_t numframes;
};
struct BSFrameListFrame //??????
{
BSTMatrix rotation;
BSTVector3 position;
int32_t index;
uint32_t matrixflags; // UNUSED BY ANYTHING.
};
struct BSClump
{

View File

@ -20,9 +20,17 @@ enum AttributeSemantic {
struct AttributeIndex {
AttributeSemantic sem;
GLsizei size;
/*GLenum type*/
GLsizei stride;
GLsizei offset;
GLenum type;
AttributeIndex(AttributeSemantic s,
GLsizei sz,
GLsizei strd,
GLsizei offs,
GLenum type = GL_FLOAT)
: sem(s), size(sz), stride(strd), offset(offs), type(type)
{}
};
typedef std::vector<AttributeIndex> AttributeList;

View File

@ -27,7 +27,6 @@ class ModelFrame {
public:
ModelFrame(ModelFrame* parent, glm::mat3 dR, glm::vec3 dT);
~ModelFrame();
void reset();
void setTransform(const glm::mat4& m);
@ -72,7 +71,7 @@ public:
TriangleStrip = 1
};
RW::BSClump clump;
std::uint32_t numAtomics;
struct Texture {
std::string name;
@ -105,7 +104,7 @@ public:
glm::vec3 position; /* 0 */
glm::vec3 normal; /* 24 */
glm::vec2 texcoord; /* 48 */
glm::vec4 colour; /* 64 */
glm::u8vec4 colour; /* 64 */
/** @see GeometryBuffer */
static const AttributeList vertex_attributes() {
@ -113,7 +112,7 @@ public:
{ATRS_Position, 3, sizeof(GeometryVertex), 0ul},
{ATRS_Normal, 3, sizeof(GeometryVertex), sizeof(float)*3},
{ATRS_TexCoord, 2, sizeof(GeometryVertex), sizeof(float)*6},
{ATRS_Colour, 4, sizeof(GeometryVertex), sizeof(float)*8}
{ATRS_Colour, 4, sizeof(GeometryVertex), sizeof(float)*8, GL_UNSIGNED_BYTE}
};
}
};
@ -157,6 +156,8 @@ public:
[&](ModelFrame* f) { return f->getName() == name; });
return fit != frames.end() ? *fit : nullptr;
}
~Model();
};
#endif

View File

@ -8,6 +8,7 @@
#include <data/ObjectData.hpp>
#include <data/WeaponData.hpp>
#include <script/SCMFile.hpp>
#include <render/Model.hpp>
#include <loaders/LoaderGXT.hpp>
@ -81,6 +82,16 @@ GameData::GameData(const std::string& path)
}
GameData::~GameData()
{
for(auto& m : models) {
if(m.second->model) {
delete m.second->model;
}
delete m.second;
}
}
void GameData::load()
{
parseDAT(datpath+"/data/default.dat");

View File

@ -7,304 +7,455 @@
#include <set>
#include <cstring>
Model* LoaderDFF::loadFromMemory(char *data, GameData *gameData)
enum DFFChunks
{
auto model = new Model;
RW::BinaryStreamSection root(data);
CHUNK_STRUCT = 0x0001,
CHUNK_EXTENSION = 0x0003,
CHUNK_TEXTURE = 0x0006,
CHUNK_MATERIAL = 0x0007,
CHUNK_MATERIALLIST = 0x0008,
CHUNK_FRAMELIST = 0x000E,
CHUNK_GEOMETRY = 0x000F,
CHUNK_CLUMP = 0x0010,
model->clump = root.readStructure<RW::BSClump>();
CHUNK_ATOMIC = 0x0014,
size_t dataI = 0, clumpID = 0;
while (root.hasMoreData(dataI)) {
auto sec = root.getNextChildSection(dataI);
CHUNK_GEOMETRYLIST = 0x001A,
switch (sec.header.id) {
case RW::SID_FrameList: {
auto list = sec.readStructure<RW::BSFrameList>();
size_t fdataI = sizeof(RW::BSFrameList) + sizeof(RW::BSSectionHeader);
model->frames.reserve(list.numframes);
for(size_t f = 0; f < list.numframes; ++f) {
RW::BSFrameListFrame& rawframe = sec.readSubStructure<RW::BSFrameListFrame>(fdataI);
fdataI += sizeof(RW::BSFrameListFrame);
ModelFrame* parent = nullptr;
if(rawframe.index != -1) {
parent = model->frames[rawframe.index];
}
else {
model->rootFrameIdx = f;
}
model->frames.push_back(
new ModelFrame(parent, rawframe.rotation, rawframe.position)
);
}
size_t fldataI = 0;
size_t fn = 0;
while( sec.hasMoreData(fldataI)) {
auto listsec = sec.getNextChildSection(fldataI);
if( listsec.header.id == RW::SID_Extension) {
size_t extI = 0;
while( listsec.hasMoreData(extI)) {
auto extSec = listsec.getNextChildSection(extI);
if( extSec.header.id == RW::SID_NodeName) {
std::string framename(extSec.raw(), extSec.header.size);
std::transform(framename.begin(), framename.end(), framename.begin(), ::tolower );
if( fn < model->frames.size() ) {
model->frames[fn]->setName(framename);
fn++;
}
}
CHUNK_BINMESHPLG = 0x050E,
CHUNK_NODENAME = 0x0253F2FE,
};
// These structs are used to interpret raw bytes from the stream.
/// @todo worry about endianness.
typedef glm::vec3 BSTVector3;
typedef glm::mat3 BSTMatrix;
typedef glm::i8vec4 BSTColour;
struct RWBSFrame
{
BSTMatrix rotation;
BSTVector3 position;
int32_t index;
uint32_t matrixflags; // Not used
};
void LoaderDFF::readFrameList(Model *model, const RWBStream &stream)
{
auto listStream = stream.getInnerStream();
auto listStructID = listStream.getNextChunk();
if( listStructID != CHUNK_STRUCT ) {
throw DFFLoaderException("Frame List missing struct chunk");
}
char* headerPtr = listStream.getCursor();
unsigned int numFrames = *(std::uint32_t*)headerPtr;
headerPtr += sizeof(std::uint32_t);
model->frames.reserve(numFrames);
for( size_t f = 0; f < numFrames; ++f ) {
auto data = (RWBSFrame*)headerPtr;
headerPtr += sizeof(RWBSFrame);
ModelFrame* parent = nullptr;
if( data->index != -1 ) {
parent = model->frames[data->index];
}
else {
model->rootFrameIdx = f;
}
auto frame = new ModelFrame(parent, data->rotation, data->position);
model->frames.push_back(frame);
}
size_t namedFrames = 0;
/// @todo perhaps flatten this out a little
for( auto chunkID = listStream.getNextChunk(); chunkID != 0; chunkID = listStream.getNextChunk() )
{
switch(chunkID) {
case CHUNK_EXTENSION: {
auto extStream = listStream.getInnerStream();
for( auto chunkID = extStream.getNextChunk(); chunkID != 0; chunkID = extStream.getNextChunk() )
{
switch( chunkID ) {
case CHUNK_NODENAME: {
std::string fname(extStream.getCursor(), extStream.getCurrentChunkSize());
std::transform(fname.begin(), fname.end(), fname.begin(), ::tolower );
if( namedFrames < model->frames.size() ) {
model->frames[namedFrames++]->setName(fname);
}
}
break;
default:
break;
}
}
}
break;
default:
break;
}
case RW::SID_GeometryList: {
/*auto list =*/ sec.readStructure<RW::BSGeometryList>();
size_t gdataI = 0;
while (sec.hasMoreData(gdataI)) {
std::shared_ptr<Model::Geometry> geom(new Model::Geometry);
auto item = sec.getNextChildSection(gdataI);
}
}
if (item.header.id == RW::SID_Geometry) {
size_t dataI = 0, secI = 0;
auto geometry = item.readStructure<RW::BSGeometry>();
// std::cout << " verts(" << geometry.numverts << ") tris(" << geometry.numtris << ")" << std::endl;
geom->flags = geometry.flags;
void LoaderDFF::readGeometryList(Model *model, const RWBStream &stream)
{
auto listStream = stream.getInnerStream();
item.getNextChildSection(secI);
char *data = item.raw() + sizeof(RW::BSSectionHeader) + sizeof(RW::BSGeometry);
auto listStructID = listStream.getNextChunk();
if( listStructID != CHUNK_STRUCT ) {
throw DFFLoaderException("Geometry List missing struct chunk");
}
if (item.header.versionid < 0x1003FFFF)
/*auto colors =*/ readStructure<RW::BSGeometryColor>(data, dataI);
std::vector<Model::GeometryVertex> vertices;
vertices.resize(geometry.numverts);
if ((geometry.flags & RW::BSGeometry::VertexColors) == RW::BSGeometry::VertexColors) {
for (size_t v = 0; v < geometry.numverts; ++v) {
auto s = readStructure<RW::BSColor>(data, dataI);
vertices[v].colour = glm::vec4(s.r/255.f, s.g/255.f, s.b/255.f, s.a/255.f);
}
}
else {
// To save needing another shader, just insert a white colour for each vertex
for (size_t v = 0; v < geometry.numverts; ++v) {
vertices[v].colour = glm::vec4(1.f, 1.f, 1.f, 1.f);
}
}
/** TEX COORDS **/
if ((geometry.flags & RW::BSGeometry::TexCoords1) == RW::BSGeometry::TexCoords1 ||
(geometry.flags & RW::BSGeometry::TexCoords2) == RW::BSGeometry::TexCoords1) {
for (size_t v = 0; v < geometry.numverts; ++v) {
vertices[v].texcoord = readStructure<glm::vec2>(data, dataI);
}
}
char* headerPtr = listStream.getCursor();
for (size_t j = 0; j < geometry.numtris; ++j) {
readStructure<RW::BSGeometryTriangle>(data, dataI);
}
unsigned int numGeometries = *(std::uint32_t*)headerPtr;
headerPtr += sizeof(std::uint32_t);
/** GEOMETRY BOUNDS **/
geom->geometryBounds = readStructure<RW::BSGeometryBounds>(data, dataI);
model->geometries.reserve(numGeometries);
/** VERTICES **/
for (size_t v = 0; v < geometry.numverts; ++v) {
vertices[v].position = readStructure<glm::vec3>(data, dataI);
}
/** NORMALS **/
if ((geometry.flags & RW::BSGeometry::StoreNormals) == RW::BSGeometry::StoreNormals) {
for (size_t v = 0; v < geometry.numverts; ++v) {
vertices[v].normal = readStructure<glm::vec3>(data, dataI);
}
}
else {
for (size_t v = 0; v < geometry.numverts; ++v) {
vertices[v].normal = glm::vec3(0.f, 0.f, 1.f);
}
// Generate normals.
/*geometryStruct.normals.resize(geometry.numverts);
for (auto &subgeom : geometryStruct.subgeom) {
glm::vec3 normal{0, 0, 0};
for (size_t i = 0; i+2 < subgeom.indices.size(); i += 3) {
glm::vec3 p1 = geometryStruct.vertices[subgeom.indices[i]];
glm::vec3 p2 = geometryStruct.vertices[subgeom.indices[i+1]];
glm::vec3 p3 = geometryStruct.vertices[subgeom.indices[i+2]];
glm::vec3 U = p2 - p1;
glm::vec3 V = p3 - p1;
normal.x = (U.y * V.z) - (U.z * V.y);
normal.y = (U.z * V.x) - (U.x * V.z);
normal.z = (U.x * V.y) - (U.y * V.x);
if (glm::length(normal) > 0.0000001)
normal = glm::normalize(normal);
geometryStruct.normals[subgeom.indices[i]] = normal;
geometryStruct.normals[subgeom.indices[i+1]] = normal;
geometryStruct.normals[subgeom.indices[i+2]] = normal;
}
}*/
}
/** TEXTURES **/
auto materiallistsec = item.getNextChildSection(secI);
auto materialList = materiallistsec.readStructure<RW::BSMaterialList>();
// Skip over the per-material byte values that I don't know what do.
dataI += sizeof(uint32_t) * materialList.nummaterials;
size_t matI = 0;
materiallistsec.getNextChildSection(matI);
geom->materials.resize(materialList.nummaterials);
for (size_t m = 0; m < materialList.nummaterials; ++m) {
auto materialsec = materiallistsec.getNextChildSection(matI);
if (materialsec.header.id != RW::SID_Material)
continue;
auto material = materialsec.readStructure<RW::BSMaterial>();
geom->materials[m].textures.resize(material.numtextures);
geom->materials[m].colour = material.color;
geom->materials[m].diffuseIntensity = material.diffuse;
geom->materials[m].ambientIntensity = material.ambient;
geom->materials[m].flags = 0;
if( material.color.r == 60 && material.color.g == 255 && material.color.b == 0 ) {
geom->materials[m].flags |= Model::MTF_PrimaryColour;
}
else if( material.color.r == 255 && material.color.g == 0 && material.color.b == 175 ) {
geom->materials[m].flags |= Model::MTF_SecondaryColour;
}
size_t texI = 0;
materialsec.getNextChildSection(texI);
for (size_t t = 0; t < material.numtextures; ++t) {
auto texsec = materialsec.getNextChildSection(texI);
/*auto texture =*/ texsec.readStructure<RW::BSTexture>();
std::string textureName, alphaName;
size_t yetAnotherI = 0;
texsec.getNextChildSection(yetAnotherI);
auto namesec = texsec.getNextChildSection(yetAnotherI);
auto alphasec = texsec.getNextChildSection(yetAnotherI);
// The data is null terminated anyway.
textureName = namesec.raw();
alphaName = alphasec.raw();
std::transform(textureName.begin(), textureName.end(), textureName.begin(), ::tolower );
std::transform(alphaName.begin(), alphaName.end(), alphaName.begin(), ::tolower );
geom->materials[m].textures[t] = {textureName, alphaName};
}
if(geom->materials[m].textures.size() < 1) continue;
}
if(item.hasMoreData(secI))
{
auto extensions = item.getNextChildSection(secI);
size_t extI = 0;
while(extensions.hasMoreData(extI))
{
auto extsec = extensions.getNextChildSection(extI);
if(extsec.header.id == RW::SID_BinMeshPLG)
{
auto meshplg = extsec.readSubStructure<RW::BSBinMeshPLG>(0);
geom->subgeom.resize(meshplg.numsplits);
geom->facetype = static_cast<Model::FaceType>(meshplg.facetype);
size_t meshplgI = sizeof(RW::BSBinMeshPLG);
size_t sgstart = 0;
for(size_t i = 0; i < meshplg.numsplits; ++i)
{
auto plgHeader = extsec.readSubStructure<RW::BSMaterialSplit>(meshplgI);
meshplgI += sizeof(RW::BSMaterialSplit);
geom->subgeom[i].material = plgHeader.index;
geom->subgeom[i].indices = new uint32_t[plgHeader.numverts];
geom->subgeom[i].numIndices = plgHeader.numverts;
geom->subgeom[i].start = sgstart;
sgstart += plgHeader.numverts;
for (size_t j = 0; j < plgHeader.numverts; ++j) {
geom->subgeom[i].indices[j] = extsec.readSubStructure<uint32_t>(meshplgI);
meshplgI += sizeof(uint32_t);
}
}
}
}
}
/* Upload Vertex Data */
geom->dbuff.setFaceType(geom->facetype == Model::Triangles ?
GL_TRIANGLES : GL_TRIANGLE_STRIP);
/* uploadVertices uses magic to determine what is inside vertices */
geom->gbuff.uploadVertices(vertices);
/* dbuff asks gbuff for it's contents */
geom->dbuff.addGeometry(&geom->gbuff);
/* TODO: Migrate indicies to new framework */
glGenBuffers(1, &geom->EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, geom->EBO);
size_t icount = std::accumulate(
geom->subgeom.begin(),
geom->subgeom.end(), 0u,
[](size_t a, const Model::SubGeometry& b) {return a + b.numIndices;});
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32_t) * icount, 0, GL_STATIC_DRAW);
for(auto& sg : geom->subgeom) {
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER,
sg.start * sizeof(uint32_t),
sizeof(uint32_t) * sg.numIndices,
sg.indices);
}
geom->clumpNum = clumpID;
// Add it
model->geometries.push_back(geom);
}
}
clumpID++;
for( auto chunkID = listStream.getNextChunk(); chunkID != 0; chunkID = listStream.getNextChunk() )
{
switch(chunkID) {
case CHUNK_GEOMETRY:
readGeometry(model, listStream);
break;
default:
break;
}
case RW::SID_Atomic: {
const Model::Atomic& at = sec.readStructure<Model::Atomic>();
model->frames[at.frame]->addGeometry(at.geometry);
model->atomics.push_back(at);
}
}
void LoaderDFF::readGeometry(Model *model, const RWBStream &stream)
{
auto geomStream = stream.getInnerStream();
auto geomStructID = geomStream.getNextChunk();
if( geomStructID != CHUNK_STRUCT ) {
throw DFFLoaderException("Geometry missing struct chunk");
}
std::shared_ptr<Model::Geometry> geom(new Model::Geometry);
char* headerPtr = geomStream.getCursor();
geom->flags = *(std::uint16_t*)headerPtr;
headerPtr += sizeof(std::uint16_t);
unsigned short numUVs = *(std::uint8_t*)headerPtr;
headerPtr += sizeof(std::uint8_t);
unsigned short moreFlags = *(std::uint8_t*)headerPtr;
headerPtr += sizeof(std::uint8_t);
unsigned int numTris = *(std::uint32_t*)headerPtr;
headerPtr += sizeof(std::uint32_t);
unsigned int numVerts = *(std::uint32_t*)headerPtr;
headerPtr += sizeof(std::uint32_t);
unsigned int numFrames = *(std::uint32_t*)headerPtr;
headerPtr += sizeof(std::uint32_t);
std::vector<Model::GeometryVertex> verts;
verts.resize(numVerts);
if( geomStream.getChunkVersion() < 0x1003FFFF ) {
headerPtr += sizeof(RW::BSGeometryColor);
}
/// @todo extract magic numbers.
if( (geom->flags & 8) == 8 ) {
for(size_t v = 0; v < numVerts; ++v) {
verts[v].colour = *(glm::u8vec4*)headerPtr;
headerPtr += sizeof(glm::u8vec4);
}
}
else {
for(size_t v = 0; v < numVerts; ++v) {
verts[v].colour = {255, 255, 255, 255};
}
}
if( (geom->flags & 4) == 4 || (geom->flags & 128) == 128) {
for(size_t v = 0; v < numVerts; ++v) {
verts[v].texcoord = *(glm::vec2*)headerPtr;
headerPtr += sizeof(glm::vec2);
}
}
// Skip indicies data for now.
headerPtr += sizeof(RW::BSGeometryTriangle) * numTris;
geom->geometryBounds = *(RW::BSGeometryBounds*)headerPtr;
geom->geometryBounds.radius = std::abs(geom->geometryBounds.radius);
headerPtr += sizeof(RW::BSGeometryBounds);
for(size_t v = 0; v < numVerts; ++v) {
verts[v].position = *(glm::vec3*)headerPtr;
headerPtr += sizeof(glm::vec3);
}
if( (geom->flags & 16) == 16 ) {
for(size_t v = 0; v < numVerts; ++v) {
verts[v].normal = *(glm::vec3*)headerPtr;
headerPtr += sizeof(glm::vec3);
}
}
// Add the geometry to the model now so that it can be accessed.
model->geometries.push_back(geom);
// Process the geometry child sections
for(auto chunkID = geomStream.getNextChunk(); chunkID != 0; chunkID = geomStream.getNextChunk())
{
switch( chunkID ) {
case CHUNK_MATERIALLIST:
readMaterialList(model, geomStream);
break;
case CHUNK_EXTENSION:
readGeometryExtension(model, geomStream);
break;
default:
break;
}
}
geom->dbuff.setFaceType(geom->facetype == Model::Triangles ?
GL_TRIANGLES : GL_TRIANGLE_STRIP);
geom->gbuff.uploadVertices(verts);
geom->dbuff.addGeometry(&geom->gbuff);
glGenBuffers(1, &geom->EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, geom->EBO);
size_t icount = std::accumulate(geom->subgeom.begin(), geom->subgeom.end(),
0u,
[](size_t a, const Model::SubGeometry& b) {return a + b.numIndices;});
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32_t) * icount, 0, GL_STATIC_DRAW);
for(auto& sg : geom->subgeom) {
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER,
sg.start * sizeof(uint32_t),
sizeof(uint32_t) * sg.numIndices,
sg.indices);
}
}
void LoaderDFF::readMaterialList(Model *model, const RWBStream &stream)
{
auto listStream = stream.getInnerStream();
auto listStructID = listStream.getNextChunk();
if( listStructID != CHUNK_STRUCT ) {
throw DFFLoaderException("MaterialList missing struct chunk");
}
unsigned int numMaterials = *(std::uint32_t*)listStream.getCursor();
model->geometries.back()->materials.reserve(numMaterials);
RWBStream::ChunkID chunkID;
while( (chunkID = listStream.getNextChunk()) ) {
switch( chunkID ) {
case CHUNK_MATERIAL:
readMaterial(model, listStream);
break;
default:
break;
}
}
}
void LoaderDFF::readMaterial(Model *model, const RWBStream &stream)
{
auto materialStream = stream.getInnerStream();
auto matStructID = materialStream.getNextChunk();
if( matStructID != CHUNK_STRUCT ) {
throw DFFLoaderException("Material missing struct chunk");
}
char* matData = materialStream.getCursor();
Model::Material material;
// Unkown
matData += sizeof(std::uint32_t);
material.colour = *(glm::u8vec4*)matData;
matData += sizeof(std::uint32_t);
// Unkown
matData += sizeof(std::uint32_t);
// uses texture
bool usesTexture = *(std::uint32_t*)matData;
matData += sizeof(std::uint32_t);
material.ambientIntensity = *(float*)matData;
matData += sizeof(float);
/*float specular = *(float*)matData;*/
matData += sizeof(float);
material.diffuseIntensity = *(float*)matData;
matData += sizeof(float);
material.flags = 0;
model->geometries.back()->materials.push_back(material);
RWBStream::ChunkID chunkID;
while( chunkID = materialStream.getNextChunk() ) {
switch( chunkID ) {
case CHUNK_TEXTURE:
readTexture(model, materialStream);
break;
default:
break;
}
}
}
void LoaderDFF::readTexture(Model *model, const RWBStream &stream)
{
auto texStream = stream.getInnerStream();
auto texStructID = texStream.getNextChunk();
if( texStructID != CHUNK_STRUCT ) {
throw DFFLoaderException("Texture missing struct chunk");
}
// There's some data in the Texture's struct, but we don't know what it is.
/// @todo improve how these strings are read.
std::string name, alpha;
texStream.getNextChunk();
name = texStream.getCursor();
texStream.getNextChunk();
alpha = texStream.getCursor();
std::transform(name.begin(), name.end(), name.begin(), ::tolower );
std::transform(alpha.begin(), alpha.end(), alpha.begin(), ::tolower );
model->geometries.back()->materials.back().textures.push_back({name, alpha});
}
void LoaderDFF::readGeometryExtension(Model *model, const RWBStream &stream)
{
auto extStream = stream.getInnerStream();
RWBStream::ChunkID chunkID;
while( (chunkID = extStream.getNextChunk()) ) {
switch( chunkID ) {
case CHUNK_BINMESHPLG:
readBinMeshPLG(model, extStream);
break;
default:
break;
}
}
}
void LoaderDFF::readBinMeshPLG(Model *model, const RWBStream &stream)
{
auto data = stream.getCursor();
model->geometries.back()->facetype = static_cast<Model::FaceType>(
*(std::uint32_t*)data);
data += sizeof(std::uint32_t);
unsigned int numSplits = *(std::uint32_t*)data;
data += sizeof(std::uint32_t);
// Number of triangles.
data += sizeof(std::uint32_t);
model->geometries.back()->subgeom.reserve(numSplits);
size_t start = 0;
for(size_t s = 0; s < numSplits; ++s) {
Model::SubGeometry sg;
sg.numIndices = *(std::uint32_t*)data;
data += sizeof(std::uint32_t);
sg.material = *(std::uint32_t*)data;
data += sizeof(std::uint32_t);
sg.start = start;
start += sg.numIndices;
sg.indices = new std::uint32_t[sg.numIndices];
for(size_t i = 0; i < sg.numIndices; ++i) {
sg.indices[i] = *(std::uint32_t*)data;
data += sizeof(std::uint32_t);
}
model->geometries.back()->subgeom.push_back(sg);
}
}
void LoaderDFF::readAtomic(Model *model, const RWBStream &stream)
{
auto atomicStream = stream.getInnerStream();
auto atomicStructID = atomicStream.getNextChunk();
if( atomicStructID != CHUNK_STRUCT ) {
throw DFFLoaderException("Atomic missing struct chunk");
}
Model::Atomic atom;
auto data = atomicStream.getCursor();
atom.frame = *(std::uint32_t*)data;
data += sizeof(std::uint32_t);
atom.geometry = *(std::uint32_t*)data;
model->frames[atom.frame]->addGeometry(atom.geometry);
model->atomics.push_back(atom);
/// @todo are any atomic extensions important?
}
Model* LoaderDFF::loadFromMemory(FileHandle file, GameData *gameData)
{
auto model = new Model;
RWBStream rootStream(file->data, file->length);
auto rootID = rootStream.getNextChunk();
if( rootID != CHUNK_CLUMP ) {
throw DFFLoaderException("Invalid root section ID " + std::to_string(rootID));
}
RWBStream modelStream = rootStream.getInnerStream();
auto rootStructID = modelStream.getNextChunk();
if( rootStructID != CHUNK_STRUCT ) {
throw DFFLoaderException("Clump missing struct chunk");
}
// There is only one value in the struct section.
model->numAtomics = *(std::uint32_t*)rootStream.getCursor();
// Process everything inside the clump stream.
RWBStream::ChunkID chunkID;
while( chunkID = modelStream.getNextChunk() ) {
switch( chunkID ) {
case CHUNK_FRAMELIST:
readFrameList(model, modelStream);
break;
case CHUNK_GEOMETRYLIST:
readGeometryList(model, modelStream);
break;
case CHUNK_ATOMIC:
readAtomic(model, modelStream);
break;
default:
break;
}
}
return model;
}
template<class T> T LoaderDFF::readStructure(char *data, size_t &dataI)
{
size_t originalOffset = dataI;
dataI += sizeof(T);
return *reinterpret_cast<T*>(data + originalOffset);
}
RW::BSSectionHeader LoaderDFF::readHeader(char *data, size_t &dataI)
{
return readStructure<RW::BSSectionHeader>(data, dataI);
}
LoadModelJob::LoadModelJob(WorkContext *context, GameData* gd, const std::string &file, ModelCallback cb)
: WorkJob(context), _gameData(gd), _file(file), _callback(cb), _data(nullptr)
{
@ -313,7 +464,7 @@ LoadModelJob::LoadModelJob(WorkContext *context, GameData* gd, const std::string
void LoadModelJob::work()
{
_data = _gameData->openFile(_file);
_data = _gameData->openFile2(_file);
}
void LoadModelJob::complete()
@ -329,6 +480,4 @@ void LoadModelJob::complete()
}
_callback(m);
delete[] _data;
}

View File

@ -35,6 +35,6 @@ void DrawBuffer::addGeometry(GeometryBuffer* gbuff)
for(const AttributeIndex& at : gbuff->getDataAttributes()) {
GLuint vaoindex = semantic_to_attrib_array[at.sem];
glEnableVertexAttribArray(vaoindex);
glVertexAttribPointer(vaoindex, at.size, GL_FLOAT, GL_FALSE, at.stride, reinterpret_cast<GLvoid*>(at.offset));
glVertexAttribPointer(vaoindex, at.size, at.type, GL_TRUE, at.stride, reinterpret_cast<GLvoid*>(at.offset));
}
}

View File

@ -1027,13 +1027,14 @@ bool GameRenderer::renderSubgeometry(Model* model, size_t g, size_t sg, const gl
if( (model->geometries[g]->flags & RW::BSGeometry::ModuleMaterialColor) == RW::BSGeometry::ModuleMaterialColor) {
auto col = mat.colour;
if(col.a < 255 && queueTransparent) return false;
if( object && object->type() == GameObject::Vehicle ) {
auto vehicle = static_cast<VehicleObject*>(object);
if( (mat.flags&Model::MTF_PrimaryColour) != 0 ) {
if( col.r == 60 && col.g == 255 && col.b == 0 ) {
oudata.colour = glm::vec4(vehicle->colourPrimary, 1.f);
}
else if( (mat.flags&Model::MTF_SecondaryColour) != 0 ) {
else if( col.r == 255 && col.g == 0 && col.b == 175 ) {
oudata.colour = glm::vec4(vehicle->colourSecondary, 1.f);
}
else {
@ -1055,7 +1056,6 @@ bool GameRenderer::renderSubgeometry(Model* model, size_t g, size_t sg, const gl
glDrawElements(model->geometries[g]->dbuff.getFaceType(),
subgeom.numIndices, GL_UNSIGNED_INT, (void*)(sizeof(uint32_t) * subgeom.start));
return true;
}

View File

@ -13,6 +13,11 @@ Model::Geometry::Geometry()
Model::Geometry::~Geometry()
{
for(auto& sg : subgeom) {
if( sg.indices ) {
delete[] sg.indices;
}
}
}
ModelFrame::ModelFrame(ModelFrame* parent, glm::mat3 dR, glm::vec3 dT)
@ -33,3 +38,11 @@ void ModelFrame::addGeometry(size_t idx)
{
geometries.push_back(idx);
}
Model::~Model()
{
for(auto mf : frames) {
delete mf;
}
}

View File

@ -5,10 +5,10 @@
BOOST_AUTO_TEST_SUITE(LoaderDFFTests)
BOOST_AUTO_TEST_CASE(test_open_dff)
BOOST_AUTO_TEST_CASE(test_load_dff)
{
{
auto d = Global::get().e->gameData.openFile("landstal.dff");
auto d = Global::get().e->gameData.openFile2("landstal.dff");
LoaderDFF loader;
@ -16,7 +16,22 @@ BOOST_AUTO_TEST_CASE(test_open_dff)
BOOST_REQUIRE( m != nullptr );
BOOST_CHECK( m->frames.size() > 0 );
BOOST_REQUIRE_EQUAL( m->frames.size(), 40 );
BOOST_REQUIRE_EQUAL( m->geometries.size(), 16 );
BOOST_REQUIRE_EQUAL( m->geometries[0]->subgeom.size(), 5 );
for(auto& g : m->geometries) {
BOOST_CHECK_GT( g->geometryBounds.radius, 0.f );
}
BOOST_REQUIRE( m->atomics.size() > 0 );
for(Model::Atomic& a : m->atomics) {
BOOST_CHECK( a.frame < m->frames.size() );
BOOST_CHECK( a.geometry < m->geometries.size() );
}
delete m;
}

31
tests/test_rwbstream.cpp Normal file
View File

@ -0,0 +1,31 @@
#include <boost/test/unit_test.hpp>
#include "test_globals.hpp"
#include <loaders/rwbinarystream.h>
BOOST_AUTO_TEST_SUITE(RWBStreamTests)
BOOST_AUTO_TEST_CASE(iterate_stream_test)
{
{
auto d = Global::get().e->gameData.openFile2("landstal.dff");
RWBStream stream(d->data, d->length);
RWBStream::ChunkID id = stream.getNextChunk();
BOOST_REQUIRE_EQUAL( id, 0x0010 );
auto inner = stream.getInnerStream();
auto inner1 = inner.getNextChunk();
BOOST_REQUIRE_EQUAL( inner1, 0x0001 );
auto innerCursor = inner.getCursor();
// This is a value inside in the Clump's struct header section.
BOOST_CHECK_EQUAL( *(std::uint32_t*)innerCursor, 0x10 );
}
}
BOOST_AUTO_TEST_SUITE_END()