This repository has been archived on 2023-10-28. You can view files and clone it, but cannot push or open issues or pull requests.
frostbite/snowlib/WorldReader.cpp
2021-04-01 23:49:11 +01:00

219 lines
7.5 KiB
C++

#include "WorldReader.h"
#include "World.h"
#include "LmpRepository.h"
#include "DataUtil.h"
#include "VifDecoder.h"
World* WorldReader::readWorld(LmpRepository* lmpRepository, const char* name)
{
World* world = new World();
std::string worldLmpName = std::string(name) + ".lmp";
LmpFile* world_lmp = lmpRepository->getLmp(worldLmpName);
LmpEntry* world_entry = world_lmp->findEntry(world_lmp->findFilenamesByExtension("world")[0].c_str());
decodeWorldFile(world, world_entry->data, world_entry->length);
return world;
}
void WorldReader::decodeWorldFile(World* world, const unsigned char* data, int dataLength)
{
int numElements = DataUtil::getLEInt(data, 0);
int offset4 = DataUtil::getLEInt(data, 4);
decodeTopography(world, data, dataLength);
int elementBase = DataUtil::getLEInt(data, 0x24);
int i28 = DataUtil::getLEInt(data, 0x28);
int i2C = DataUtil::getLEInt(data, 0x2C);
int cols_38 = DataUtil::getLEInt(data, 0x30);
int rows_38 = DataUtil::getLEInt(data, 0x34);
int offset_38 = DataUtil::getLEInt(data, 0x38);
int i3C = DataUtil::getLEInt(data, 0x3C);
int i40 = DataUtil::getLEInt(data, 0x40);
int i44 = DataUtil::getLEInt(data, 0x44);
int i48 = DataUtil::getLEInt(data, 0x48);
int offset4C = DataUtil::getLEInt(data, 0x4C);
int len50 = DataUtil::getLEInt(data, 0x50);
int offset54 = DataUtil::getLEInt(data, 0x54);
int texMinxy = DataUtil::getLEInt(data, 0x58);
int texMaxxy = DataUtil::getLEInt(data, 0x5C);
int texMinx = texMinxy % 100;
int texMiny = texMinxy / 100;
int texMaxx = texMaxxy % 100;
int texMaxy = texMaxxy / 100;
int offset60 = DataUtil::getLEInt(data, 0x60);
int textureArrayOffset = DataUtil::getLEInt(data, 0x64);
int offset68 = DataUtil::getLEInt(data, 0x68);
int worldTexOffsetsOffset = DataUtil::getLEInt(data, 0x6C);
readTextureChunkOffsets(world, data, dataLength, worldTexOffsetsOffset, texMinx, texMiny, texMaxx+1, texMaxy);
readElements(world, data, dataLength, elementBase, numElements, texMinx, texMiny);
}
void WorldReader::readElements(World* world, const unsigned char* data, int dataLength, int elementBase, int numElements, int texMinx, int texMiny)
{
for (int idx = 0; idx < numElements; ++idx) {
auto element = new WorldElement();
int elementSize = 0x38;
if (GameType::CHAMPIONS_RTA == gameType || GameType::JL_HEROES == gameType) {
elementSize = 0x3C;
}
int elementOffset = elementBase + idx * elementSize;
DataUtil reader(data, elementOffset);
int vifDataOffset = reader.getLEInt();
if (GameType::DARK_ALLIANCE == gameType) {
int Tex2 = reader.getLEInt();
}
int vifLen = reader.getLEInt();
float x1 = reader.getLEFloat();
float y1 = reader.getLEFloat();
float z1 = reader.getLEFloat();
float x2 = reader.getLEFloat();
float y2 = reader.getLEFloat();
float z2 = reader.getLEFloat();
element->boundingBox = FloatBox(x1, y1, z1, x2 - x1, y2 - y1, z2 - z1);
int textureNum = reader.getLEInt() / 0x40;
int texCellxy = reader.getLEUShort();
int y = texCellxy / 100;
int x = texCellxy % 100;
if (GameType::CHAMPIONS_RTA == gameType || GameType::JL_HEROES == gameType) {
x += texMinx;
y += texMiny;
}
if (textureNum != 0) {
// TODO
}
int texWidth = 100;
int texHeight = 100;
/*
if (element.Texture != null)
{
texWidth = element.Texture.PixelWidth;
texHeight = element.Texture.PixelHeight;
}
*/
unsigned char nregs = data[vifDataOffset + 0x10];
int vifStartOffset = (nregs + 2) * 0x10;
int vifDataLength = vifLen * 0x10 - vifStartOffset;
element->mesh.meshList = VifDecoder().decode(data, vifDataOffset + vifStartOffset);
world->elements.push_back(element);
}
}
void WorldReader::readTextureChunkOffsets(World* world, const unsigned char* data, int dataLength, int worldTexOffsetsOffset, int texMinx, int texMiny, int texMaxx, int texMaxy)
{
for (int y = texMiny; y <= texMaxy; ++y)
{
for (int x = texMinx; x <= texMaxx; ++x)
{
int cellOffset = ((y - texMiny) * 100 + x - texMinx) * 8;
// This test is needed to deal with town.world in BGDA which addresses textures outside of the maximum x range.
if (dataLength >= cellOffset + 4)
{
int addr;
if (GameType::CHAMPIONS_RTA == gameType || GameType::JL_HEROES == gameType)
{
// TODO: Figure out what this should really be
addr = 0x800;
}
else
{
addr = DataUtil::getLEInt(data, cellOffset);
}
world->setTextureChunkOffset(x, y, addr);
}
}
}
}
void WorldReader::decodeTopography(World* world, const unsigned char* data, int dataLength)
{
world->topoStartCol = DataUtil::getLEInt(data, 8);
world->topoStartRow = DataUtil::getLEInt(data, 0x0C);
world->numTopoCols = DataUtil::getLEInt(data, 0x10);
world->numTopoRows = DataUtil::getLEInt(data, 0x14);
int topoElementsPerCellOffset = DataUtil::getLEInt(data, 0x18);
int numTopoElements = DataUtil::getLEInt(data, 0x1C);
int topoArrayOffset = DataUtil::getLEInt(data, 0x20);
// Allows us to quickly look up patches from the offsets stored in the file.
std::unordered_map<int, std::shared_ptr<TopoPatch>> patchAddressMap;
world->topoElements.resize(numTopoElements);
for (int el = 0; el < numTopoElements; ++el){
int topoElementOffset = topoArrayOffset + el * 0x1C;
TopoElement* element = new TopoElement();
element->llx = DataUtil::getLEShort(data, topoElementOffset);
element->lly = DataUtil::getLEShort(data, topoElementOffset + 2);
element->urx = DataUtil::getLEShort(data, topoElementOffset + 4);
element->ury = DataUtil::getLEShort(data, topoElementOffset + 6);
element->int8 = DataUtil::getLEInt(data, topoElementOffset + 8);
int patchOffset = DataUtil::getLEInt(data, topoElementOffset + 0xc);
if (patchAddressMap.find(patchOffset) == patchAddressMap.end()){
patchAddressMap[patchOffset] = readTopoPatch(data, patchOffset);
}
element->patch = patchAddressMap[patchOffset];
element->flags = DataUtil::getLEShort(data, topoElementOffset + 0x10);
element->x0 = DataUtil::getLEShort(data, topoElementOffset + 0x12);
element->y0 = DataUtil::getLEShort(data, topoElementOffset + 0x14);
element->baseHeight = DataUtil::getLEShort(data, topoElementOffset + 0x16);
element->cos_alpha = DataUtil::getLEShort(data, topoElementOffset + 0x18) / 32767.0;
element->sin_alpha = DataUtil::getLEShort(data, topoElementOffset + 0x1A) / 32767.0;
world->topoElements.push_back(element);
}
world->topoElementsPerCell.resize(world->numTopoCols * world->numTopoRows);
int index = 0;
for (int r = 0; r < world->numTopoRows; ++r){
for (int c = 0; c < world->numTopoCols; ++c){
int elementsInCellListOffset = DataUtil::getLEInt(data, topoElementsPerCellOffset + index*4);
auto& elementList = world->topoElementsPerCell[index];
int elementId = DataUtil::getLEShort(data, elementsInCellListOffset);
while (elementId >= 0){
elementsInCellListOffset += 2;
elementList.push_back(world->topoElements[elementId]);
elementId = DataUtil::getLEShort(data, elementsInCellListOffset);
}
index++;
}
}
}
std::shared_ptr<TopoPatch> WorldReader::readTopoPatch(const unsigned char* data, int offset)
{
int w = DataUtil::getLEInt(data, offset + 8);
int h = DataUtil::getLEInt(data, offset + 0x0c);
auto patch = std::make_shared<TopoPatch>(w, h);
patch->x0 = DataUtil::getLEInt(data, offset);
patch->y0 = DataUtil::getLEInt(data, offset + 4);
patch->minHeight = DataUtil::getLEShort(data, offset + 0x10);
patch->maxHeight = DataUtil::getLEShort(data, offset + 0x12);
int j = 0;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
int i = y * w + x;
patch->heights[j++] = DataUtil::getLEShort(data, offset + 0x14 + i * 2);
}
}
return patch;
}