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:
parent
be347e88dc
commit
6c78b0c3c5
@ -74,6 +74,7 @@ public:
|
||||
* @param path Path to the root of the game data.
|
||||
*/
|
||||
GameData(const std::string& path = "");
|
||||
~GameData();
|
||||
|
||||
GameWorld* engine;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
31
tests/test_rwbstream.cpp
Normal 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()
|
Loading…
Reference in New Issue
Block a user