1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-09-15 06:52:34 +02:00
openrw/rwcore/loaders/LoaderDFF.cpp
Daniel Evans 4fd92a1549 Rename rwlib library to "core" to fit its new role
Also move up source files into the root directory, as there's nothing else in this directory
2018-08-09 20:28:24 +01:00

515 lines
16 KiB
C++

#include "loaders/LoaderDFF.hpp"
#include <algorithm>
#include <cctype>
#include <cstdint>
#include <cstring>
#include <cstdlib>
#include <memory>
#include <numeric>
#include <glm/glm.hpp>
#include "data/Clump.hpp"
#include "gl/gl_core_3_3.h"
#include "loaders/RWBinaryStream.hpp"
#include "platform/FileHandle.hpp"
#include "rw/debug.hpp"
enum DFFChunks {
CHUNK_STRUCT = 0x0001,
CHUNK_EXTENSION = 0x0003,
CHUNK_TEXTURE = 0x0006,
CHUNK_MATERIAL = 0x0007,
CHUNK_MATERIALLIST = 0x0008,
CHUNK_FRAMELIST = 0x000E,
CHUNK_GEOMETRY = 0x000F,
CHUNK_CLUMP = 0x0010,
CHUNK_ATOMIC = 0x0014,
CHUNK_GEOMETRYLIST = 0x001A,
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
};
LoaderDFF::FrameList LoaderDFF::readFrameList(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 = *reinterpret_cast<std::uint32_t *>(headerPtr);
headerPtr += sizeof(std::uint32_t);
FrameList framelist;
framelist.reserve(numFrames);
for (size_t f = 0; f < numFrames; ++f) {
auto data = reinterpret_cast<RWBSFrame *>(headerPtr);
headerPtr += sizeof(RWBSFrame);
auto frame =
std::make_shared<ModelFrame>(f, data->rotation, data->position);
RW_CHECK(data->index < static_cast<int>(framelist.size()),
"Frame parent out of bounds");
if (data->index != -1 &&
data->index < static_cast<int>(framelist.size())) {
framelist[data->index]->addChild(frame);
}
framelist.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 < framelist.size()) {
framelist[namedFrames++]->setName(fname);
}
} break;
default:
break;
}
}
} break;
default:
break;
}
}
return framelist;
}
LoaderDFF::GeometryList LoaderDFF::readGeometryList(const RWBStream &stream) {
auto listStream = stream.getInnerStream();
auto listStructID = listStream.getNextChunk();
if (listStructID != CHUNK_STRUCT) {
throw DFFLoaderException("Geometry List missing struct chunk");
}
char *headerPtr = listStream.getCursor();
unsigned int numGeometries = bit_cast<std::uint32_t>(*headerPtr);
headerPtr += sizeof(std::uint32_t);
std::vector<GeometryPtr> geometrylist;
geometrylist.reserve(numGeometries);
for (auto chunkID = listStream.getNextChunk(); chunkID != 0;
chunkID = listStream.getNextChunk()) {
switch (chunkID) {
case CHUNK_GEOMETRY: {
geometrylist.push_back(readGeometry(listStream));
} break;
default:
break;
}
}
return geometrylist;
}
GeometryPtr LoaderDFF::readGeometry(const RWBStream &stream) {
auto geomStream = stream.getInnerStream();
auto geomStructID = geomStream.getNextChunk();
if (geomStructID != CHUNK_STRUCT) {
throw DFFLoaderException("Geometry missing struct chunk");
}
auto geom = std::make_shared<Geometry>();
char *headerPtr = geomStream.getCursor();
geom->flags = bit_cast<std::uint16_t>(*headerPtr);
headerPtr += sizeof(std::uint16_t);
/*unsigned short numUVs = bit_cast<std::uint8_t>(*headerPtr);*/
headerPtr += sizeof(std::uint8_t);
/*unsigned short moreFlags = bit_cast<std::uint8_t>(*headerPtr);*/
headerPtr += sizeof(std::uint8_t);
unsigned int numTris = bit_cast<std::uint32_t>(*headerPtr);
headerPtr += sizeof(std::uint32_t);
unsigned int numVerts = bit_cast<std::uint32_t>(*headerPtr);
headerPtr += sizeof(std::uint32_t);
/*unsigned int numFrames = bit_cast<std::uint32_t>(*headerPtr);*/
headerPtr += sizeof(std::uint32_t);
std::vector<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 = bit_cast<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 = bit_cast<glm::vec2>(*headerPtr);
headerPtr += sizeof(glm::vec2);
}
}
// Grab indicies data to generate normals (if applicable).
auto triangles = std::make_unique<RW::BSGeometryTriangle[]>(numTris);
memcpy(triangles.get(), headerPtr, sizeof(RW::BSGeometryTriangle) * numTris);
headerPtr += sizeof(RW::BSGeometryTriangle) * numTris;
geom->geometryBounds = bit_cast<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 = bit_cast<glm::vec3>(*headerPtr);
headerPtr += sizeof(glm::vec3);
}
if ((geom->flags & 16) == 16) {
for (size_t v = 0; v < numVerts; ++v) {
verts[v].normal = bit_cast<glm::vec3>(*headerPtr);
headerPtr += sizeof(glm::vec3);
}
} else {
// Use triangle data to calculate normals for each vert.
for (size_t t = 0; t < numTris; ++t) {
auto &triangle = triangles[t];
auto &A = verts[triangle.first];
auto &B = verts[triangle.second];
auto &C = verts[triangle.third];
auto normal = glm::normalize(
glm::cross(C.position - A.position, B.position - A.position));
A.normal = normal;
B.normal = normal;
C.normal = normal;
}
}
// Process the geometry child sections
for (auto chunkID = geomStream.getNextChunk(); chunkID != 0;
chunkID = geomStream.getNextChunk()) {
switch (chunkID) {
case CHUNK_MATERIALLIST:
readMaterialList(geom, geomStream);
break;
case CHUNK_EXTENSION:
readGeometryExtension(geom, geomStream);
break;
default:
break;
}
}
geom->dbuff.setFaceType(geom->facetype == Geometry::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 SubGeometry &b) { return a + b.numIndices; });
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32_t) * icount, nullptr,
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.data());
}
return geom;
}
void LoaderDFF::readMaterialList(const GeometryPtr &geom, const RWBStream &stream) {
auto listStream = stream.getInnerStream();
auto listStructID = listStream.getNextChunk();
if (listStructID != CHUNK_STRUCT) {
throw DFFLoaderException("MaterialList missing struct chunk");
}
unsigned int numMaterials = bit_cast<std::uint32_t>(*listStream.getCursor());
geom->materials.reserve(numMaterials);
RWBStream::ChunkID chunkID;
while ((chunkID = listStream.getNextChunk())) {
switch (chunkID) {
case CHUNK_MATERIAL:
readMaterial(geom, listStream);
break;
default:
break;
}
}
}
void LoaderDFF::readMaterial(const GeometryPtr &geom, 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();
Geometry::Material material;
// Unkown
matData += sizeof(std::uint32_t);
material.colour = bit_cast<glm::u8vec4>(*matData);
matData += sizeof(std::uint32_t);
// Unkown
matData += sizeof(std::uint32_t);
/*bool usesTexture = bit_cast<std::uint32_t>(*matData);*/
matData += sizeof(std::uint32_t);
material.ambientIntensity = bit_cast<float>(*matData);
matData += sizeof(float);
/*float specular = bit_cast<float>(*matData);*/
matData += sizeof(float);
material.diffuseIntensity = bit_cast<float>(*matData);
matData += sizeof(float);
material.flags = 0;
RWBStream::ChunkID chunkID;
while ((chunkID = materialStream.getNextChunk())) {
switch (chunkID) {
case CHUNK_TEXTURE:
readTexture(material, materialStream);
break;
default:
break;
}
}
geom->materials.push_back(material);
}
void LoaderDFF::readTexture(Geometry::Material &material,
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);
TextureData::Handle textureinst =
texturelookup ? texturelookup(name, alpha) : nullptr;
material.textures.emplace_back(std::move(name), std::move(alpha), textureinst);
}
void LoaderDFF::readGeometryExtension(const GeometryPtr &geom,
const RWBStream &stream) {
auto extStream = stream.getInnerStream();
RWBStream::ChunkID chunkID;
while ((chunkID = extStream.getNextChunk())) {
switch (chunkID) {
case CHUNK_BINMESHPLG:
readBinMeshPLG(geom, extStream);
break;
default:
break;
}
}
}
void LoaderDFF::readBinMeshPLG(const GeometryPtr &geom, const RWBStream &stream) {
auto data = stream.getCursor();
geom->facetype = static_cast<Geometry::FaceType>(bit_cast<std::uint32_t>(*data));
data += sizeof(std::uint32_t);
unsigned int numSplits = bit_cast<std::uint32_t>(*data);
data += sizeof(std::uint32_t);
// Number of triangles.
data += sizeof(std::uint32_t);
geom->subgeom.reserve(numSplits);
size_t start = 0;
for (size_t s = 0; s < numSplits; ++s) {
SubGeometry sg;
sg.numIndices = bit_cast<std::uint32_t>(*data);
data += sizeof(std::uint32_t);
sg.material = bit_cast<std::uint32_t>(*data);
data += sizeof(std::uint32_t);
sg.start = start;
start += sg.numIndices;
sg.indices.resize(sg.numIndices);
std::memcpy(sg.indices.data(), data,
sizeof(std::uint32_t) * sg.numIndices);
data += sizeof(std::uint32_t) * sg.numIndices;
geom->subgeom.push_back(std::move(sg));
}
}
AtomicPtr LoaderDFF::readAtomic(FrameList &framelist,
GeometryList &geometrylist,
const RWBStream &stream) {
auto atomicStream = stream.getInnerStream();
auto atomicStructID = atomicStream.getNextChunk();
if (atomicStructID != CHUNK_STRUCT) {
throw DFFLoaderException("Atomic missing struct chunk");
}
auto data = atomicStream.getCursor();
std::uint32_t frame = bit_cast<std::uint32_t>(*data);
data += sizeof(std::uint32_t);
std::uint32_t geometry = bit_cast<std::uint32_t>(*data);
data += sizeof(std::uint32_t);
std::uint32_t flags = bit_cast<std::uint32_t>(*data);
// Verify the atomic's particulars
RW_CHECK(frame < framelist.size(), "atomic frame " << frame
<< " out of bounds");
RW_CHECK(geometry < geometrylist.size(),
"atomic geometry " << geometry << " out of bounds");
auto atomic = std::make_shared<Atomic>();
if (geometry < geometrylist.size()) {
atomic->setGeometry(geometrylist[geometry]);
}
if (frame < framelist.size()) {
atomic->setFrame(framelist[frame]);
}
atomic->setFlags(flags);
return atomic;
}
ClumpPtr LoaderDFF::loadFromMemory(const FileContentsInfo& file) {
auto model = std::make_shared<Clump>();
RWBStream rootStream(file.data.get(), 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.
std::uint32_t numAtomics = bit_cast<std::uint32_t>(*rootStream.getCursor());
RW_UNUSED(numAtomics);
GeometryList geometrylist;
FrameList framelist;
// Process everything inside the clump stream.
RWBStream::ChunkID chunkID;
while ((chunkID = modelStream.getNextChunk())) {
switch (chunkID) {
case CHUNK_FRAMELIST:
framelist = readFrameList(modelStream);
break;
case CHUNK_GEOMETRYLIST:
geometrylist = readGeometryList(modelStream);
break;
case CHUNK_ATOMIC: {
auto atomic = readAtomic(framelist, geometrylist, modelStream);
RW_CHECK(atomic, "Failed to read atomic");
if (!atomic) {
// Abort reading the rest of the clump
return nullptr;
}
model->addAtomic(atomic);
} break;
default:
break;
}
}
if (!framelist.empty()) {
model->setFrame(framelist[0]);
}
// Ensure the model has cached metrics
model->recalculateMetrics();
return model;
}