diff --git a/Makefile b/Makefile index f2a1123..b35560b 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,8 @@ CXX=g++ CXXOPTS=-Og -ggdb -fvar-tracking-assignments -Wall -Wextra -Wno-unused-function -pedantic -std=c++14 -march=native -fstack-protector-strong --param=ssp-buffer-size=4 -flto -I. LDOPTS=-Wl,--sort-common,--as-needed -render_CXXSRCS ::= render/Renderer.cc render/GlResource.cc render/ProgramProvider.cc render/renderutil.cc render/Overlay.cc render/VBOManager.cc -game_CXXSRCS ::= game/GSMvePlay.cc +render_CXXSRCS ::= render/Renderer.cc render/GlResource.cc render/ProgramProvider.cc render/renderutil.cc render/Overlay.cc render/VBOManager.cc render/Object.cc +game_CXXSRCS ::= game/GSMvePlay.cc game/GSShowObject.cc iffexplore_CXXSRCS ::= iffexplore.cc IffFile.cc util.cc exceptions.cc iffexplore_LIBS ::= @@ -17,7 +17,10 @@ font2png_LIBS ::= -lpng mvedecode_CXXSRCS ::= mvedecode.cc TreFile.cc IffFile.cc util.cc MveDecoder.cc exceptions.cc decompress.cc $(render_CXXSRCS) $(game_CXXSRCS) mvedecode_LIBS ::= -lSDL2 -lglbinding -progs ::= iffexplore treexplore mvedecode +objdecode_CXXSRCS ::= objdecode.cc TreFile.cc IffFile.cc util.cc ObjDecoder.cc exceptions.cc decompress.cc $(render_CXXSRCS) $(game_CXXSRCS) +objdecode_LIBS ::= -lSDL2 -lglbinding + +progs ::= iffexplore treexplore mvedecode objdecode all: $(progs) diff --git a/ObjDecoder.cc b/ObjDecoder.cc new file mode 100644 index 0000000..6d008c7 --- /dev/null +++ b/ObjDecoder.cc @@ -0,0 +1,203 @@ +#include + +#include "ObjDecoder.hh" +#include "exceptions.hh" +#include "util.hh" + +ObjDecoder::ObjDecoder(uint8_t const* base, size_t length) + : iff_(base, length) +{ + auto& root = iff_.getRoot(); + if (!root.isForm()) + throw FormatException{"Root node not FORM"}; + + auto& rootForm = dynamic_cast(root); + if (rootForm.getSubtype() != "REAL") + throw FormatException{"Root form not REAL"}; + + for (auto realIt = rootForm.childrenBegin();realIt != rootForm.childrenEnd();++realIt) { + if (realIt->getType() == "INFO") { + name_ = *realIt; + printf("Name: %s\n", name_.c_str()); + } else if (realIt->isForm()) { + auto& itForm = dynamic_cast(*realIt); + if (itForm.getSubtype() == "OBJT") + parseOBJT_(itForm); + else if (itForm.getSubtype() == "APPR") + parseAPPR_(itForm); + else + throw FormatException{"Unknown FORM type " + itForm.getSubtype()}; + } else + throw FormatException{"Unknown chunk type " + realIt->getType()}; + } +} + +void ObjDecoder::parseOBJT_(IffFile::Form const& form) +{ +} + +void ObjDecoder::parseAPPR_(IffFile::Form const& form) +{ + if (form.getChildCount() != 1) + throw FormatException{"Unexpected child count in APPR"}; + + auto& child = *form.childrenBegin(); + if (!child.isForm()) + throw FormatException{"APPR child not FORM"}; + + auto& childForm = dynamic_cast(child); + if (childForm.getSubtype() != "POLY") + throw FormatException{"APPR child form not POLY"}; + + parsePOLY_(childForm); +} + +void ObjDecoder::parsePOLY_(IffFile::Form const& form) +{ + for (auto it = form.childrenBegin();it != form.childrenEnd();++it) { + if (it->getType() == "INFO") { + } else if (it->getType() == "VERT") { + if (it->getSize()%12 != 0) + throw FormatException{"Unexpected VERT size"}; + for (size_t i = 0;i < it->getSize();i+=12) { + std::array vertex; + for (size_t j = 0;j < 3;++j) + vertex[j] = readU32LE(it->begin()+i+j*4); + printf("V: %d %d %d\n", vertex[0], vertex[1], vertex[2]); + vertices_.push_back(vertex); + } + } else if (it->isForm()) { + auto& itForm = dynamic_cast(*it); + if (itForm.getSubtype() == "TRIS") { + parseTRIS_(itForm); + } else if (itForm.getSubtype() == "QUAD") { + parseQUAD_(itForm); + } else if (itForm.getSubtype() == "DETA") { + // NYI + } else if (itForm.getSubtype() == "TXMS") { + parseTXMS_(itForm); + } else if (itForm.getSubtype() == "GRUP") { + // NYI + } else + throw FormatException{"Unknown POLY subform " + itForm.getSubtype()}; + } else if (it->getType() == "ATTR") { + // NYI + } else if (it->getType() == "XATR") { + // NYI + } else + throw FormatException{"Unknown POLY child " + it->getType()}; + } +} + +void ObjDecoder::parseTRIS_(IffFile::Form const& form) +{ + if (form.getChildCount() != 2) + throw FormatException{"Unexpected child count in TRIS"}; + + auto vertCount = vertices_.size(); + printf("VC: %lu\n", vertCount); + + IffFile::Object const *face = nullptr, *maps = nullptr; + for (auto it = form.childrenBegin();it != form.childrenEnd();++it) { + if (it->getType() == "FACE") { + if (face) + throw FormatException{"Multiple FACE in TRIS"}; + face = &*it; + } else if (it->getType() == "MAPS") { + if (maps) + throw FormatException{"Multiple MAPS in TRIS"}; + maps = &*it; + } else + throw FormatException{"Unknown TRIS child " + it->getType()}; + } + + assert(face && maps); + + if (face->getSize()%8 != 0) + throw FormatException{"Unexpected FACE size"}; + auto numTriangles = face->getSize()/8; + if (maps->getSize() != numTriangles*16) + throw FormatException{"Unexpected MAPS size"}; + + for (unsigned i = 0;i < numTriangles;++i) { + Triangle tri; + for (unsigned j = 0;j < 3;++j) + tri.vertIdx[j] = readU16LE(face->begin()+i*8+2+j*2); + tri.id = readU16LE(maps->begin()+i*16); + tri.tex = readU16LE(maps->begin()+i*16+2); + for (unsigned j = 0;j < 3;++j) { + tri.texCoords[j][0] = readU16LE(maps->begin()+i*16+4+j*4); + tri.texCoords[j][1] = readU16LE(maps->begin()+i*16+4+j*4+2); + } + printf("T%u: (%hu (%hu %hu)), (%hu (%hu %hu)), (%hu (%hu %hu))\n", tri.id, + tri.vertIdx[0], tri.texCoords[0][0], tri.texCoords[0][1], + tri.vertIdx[1], tri.texCoords[1][0], tri.texCoords[1][1], + tri.vertIdx[2], tri.texCoords[2][0], tri.texCoords[2][1]); + triangles_.push_back(tri); + } +} + +void ObjDecoder::parseQUAD_(IffFile::Form const& form) +{ + if (form.getChildCount() != 2) + throw FormatException{"Unexpected child count in QUAD"}; + + IffFile::Object const *face = nullptr, *maps = nullptr; + for (auto it = form.childrenBegin();it != form.childrenEnd();++it) { + if (it->getType() == "FACE") { + if (face) + throw FormatException{"Multiple FACE in QUAD"}; + face = &*it; + } else if (it->getType() == "MAPS") { + if (maps) + throw FormatException{"Multiple MAPS in QUAD"}; + maps = &*it; + } else + throw FormatException{"Unknown TRIS child " + it->getType()}; + } + + assert(face && maps); + + if (face->getSize()%10 != 0) + throw FormatException{"Unexpected FACE size"}; + auto numQuads = face->getSize()/10; + if (maps->getSize() != numQuads*20) + throw FormatException{"Unexpected MAPS size"}; + + for (unsigned i = 0;i < numQuads;++i) { + Quad quad; + for (unsigned j = 0;j < 4;++j) + quad.vertIdx[j] = readU16LE(face->begin()+i*10+2+j*2); + quad.id = readU16LE(maps->begin()+i*20); + quad.tex = readU16LE(maps->begin()+i*20+2); + for (unsigned j = 0;j < 4;++j) { + quad.texCoords[j][0] = readU16LE(maps->begin()+i*20+4+j*4); + quad.texCoords[j][1] = readU16LE(maps->begin()+i*20+4+j*4+2); + } + printf("Q%u: (%hu (%hu %hu)), (%hu (%hu %hu)), (%hu (%hu %hu)), (%hu (%hu %hu))\n", quad.id, + quad.vertIdx[0], quad.texCoords[0][0], quad.texCoords[0][1], + quad.vertIdx[1], quad.texCoords[1][0], quad.texCoords[1][1], + quad.vertIdx[2], quad.texCoords[2][0], quad.texCoords[2][1], + quad.vertIdx[3], quad.texCoords[3][0], quad.texCoords[3][1]); + quads_.push_back(quad); + } +} + +void ObjDecoder::parseTXMS_(IffFile::Form const& form) +{ + if (form.getChildCount() < 1) + throw FormatException{"Unexpected child count in TXMS"}; + + auto it = form.childrenBegin(); + auto& info = *it; + if (info.getType() != "INFO") + throw FormatException{"Missing INFO in TXMS"}; + if (info.getSize() != 4) + throw FormatException{"Unexpected INFO size in TXMS"}; + + uint16_t numTXMP, numTXMA; + numTXMP = readU16LE(info.begin()); + numTXMA = readU16LE(info.begin()+2); + if (form.getChildCount() != 1+numTXMP+numTXMA) + throw FormatException{"Unexpected child count in TXMS"}; +} diff --git a/ObjDecoder.hh b/ObjDecoder.hh new file mode 100644 index 0000000..3be57a8 --- /dev/null +++ b/ObjDecoder.hh @@ -0,0 +1,62 @@ +#ifndef WC3RE_OBJDECODER_HH__ +#define WC3RE_OBJDECODER_HH__ + +#include +#include +#include +#include + +#include "IffFile.hh" + +class ObjDecoder { +public: + ObjDecoder(uint8_t const* base, size_t length); + + using Vertex = std::array; + using Vertices = std::vector; + using TexCoord = std::array; + + struct Triangle { + uint16_t id; + uint16_t tex; + std::array vertIdx; + std::array texCoords; + }; + using Triangles = std::vector; + + struct Quad { + uint16_t id; + uint16_t tex; + std::array vertIdx; + std::array texCoords; + }; + using Quads = std::vector; + + Vertices const& getVertices() const { + return vertices_; + } + + Triangles const& getTriangles() const { + return triangles_; + } + + Quads const& getQuads() const { + return quads_; + } +private: + IffFile iff_; + std::string name_; + + void parseOBJT_(IffFile::Form const& form); + void parseAPPR_(IffFile::Form const& form); + void parsePOLY_(IffFile::Form const& form); + void parseTRIS_(IffFile::Form const& form); + void parseQUAD_(IffFile::Form const& form); + void parseTXMS_(IffFile::Form const& form); + + Vertices vertices_; + Triangles triangles_; + Quads quads_; +}; + +#endif diff --git a/game/GSMvePlay.hh b/game/GSMvePlay.hh index 3446a72..63c2895 100644 --- a/game/GSMvePlay.hh +++ b/game/GSMvePlay.hh @@ -2,7 +2,6 @@ #define WC3RE_GAME_GSMVEPLAY_HH__ #include "GameState.hh" -#include "render/sdlutil.hh" class MveDecoder; namespace render { diff --git a/game/GSShowObject.cc b/game/GSShowObject.cc new file mode 100644 index 0000000..a5066de --- /dev/null +++ b/game/GSShowObject.cc @@ -0,0 +1,22 @@ +#include "ObjDecoder.hh" + +#include "GSShowObject.hh" +#include "render/Object.hh" + +namespace game { + GSShowObject::GSShowObject(render::Renderer& renderer, ObjDecoder& obj) + : GameState(renderer), obj_(obj), + object_(nullptr), delta_(0) + { + object_ = std::make_unique(renderer, obj); + } + + GSShowObject::~GSShowObject() + { + } + + void GSShowObject::draw(unsigned delta_ms) + { + object_->draw(); + } +} diff --git a/game/GSShowObject.hh b/game/GSShowObject.hh new file mode 100644 index 0000000..194444f --- /dev/null +++ b/game/GSShowObject.hh @@ -0,0 +1,27 @@ +#ifndef WC3RE_GAME_GSSHOWOBJECT_HH__ +#define WC3RE_GAME_GSSHOWOBJECT_HH__ + +#include "GameState.hh" + +class ObjDecoder; +namespace render { + class Object; +} + +namespace game { + class GSShowObject : public GameState { + public: + GSShowObject(render::Renderer& renderer, ObjDecoder& obj); + + ~GSShowObject() override; + + void draw(unsigned delta_ms) override; + + private: + ObjDecoder& obj_; + std::unique_ptr object_; + unsigned delta_; + }; +} + +#endif diff --git a/objdecode.cc b/objdecode.cc new file mode 100644 index 0000000..ed5f0f4 --- /dev/null +++ b/objdecode.cc @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "common.hh" +#include "util.hh" +#include "TreFile.hh" +#include "IffFile.hh" +#include "ObjDecoder.hh" +#include "render/Renderer.hh" +#include "game/GSShowObject.hh" + +void usage(char *argv0) { + fprintf(stderr, "Usage: %s [-h] (tre-file name/crc)/iff-file\n", argv0); + fprintf(stderr, "\tAttempt to decode the object stored in iff-file, or in the\n\tiff-object \"name\"/\"crc\" contained in tre-file\n"); + fprintf(stderr, "\t-h\tPrint this help\n"); +} + +int main(int argc, char *argv[]) { + std::string inFile, objectSpec; + bool useTre = false; + { + int opt; + while ((opt = getopt(argc, argv, "h")) != -1) { + switch (opt) { + case 'h': + usage(argv[0]); + return 0; + default: + usage(argv[0]); + return 1; + } + } + + if (optind >= argc) { + usage(argv[0]); + return 1; + } + + inFile = argv[optind++]; + + if (optind < argc) { + useTre = true; + objectSpec = argv[optind]; + } + } + + try { + MmapFile mmap{inFile}; + + std::unique_ptr tre; + TreFile::Object object; + std::unique_ptr obj; + if (useTre) { + tre = std::make_unique(mmap.data(), mmap.size()); + + // Try to parse objectSpec as CRC + try { + unsigned long CRC = std::stoul(objectSpec, nullptr, 16); + if (CRC <= std::numeric_limits::max()) { + object = tre->openCRC(CRC); + } + } catch (std::invalid_argument &ex) { + } catch (std::out_of_range &ex) { + } + + + if (!object) // Wasn't a CRC, try as name + object = tre->openName(objectSpec); + + obj = std::make_unique(object.data(), object.size()); + } else + obj = std::make_unique(mmap.data(), mmap.size()); + + render::Renderer renderer; + renderer.pushGS(std::make_unique(renderer, *obj)); + + renderer.run(); + } catch (POSIXException &ex) { + fflush(stdout); + fprintf(stderr, "%s\n", ex.toString().c_str()); + return 2; + } catch (Exception &ex) { + fflush(stdout); + fprintf(stderr, "%s\n", ex.toString().c_str()); + return 3; + } + + return 0; +} diff --git a/render/Object.cc b/render/Object.cc new file mode 100644 index 0000000..dbd5fd8 --- /dev/null +++ b/render/Object.cc @@ -0,0 +1,107 @@ +#include + +#include +#include + +#include "Object.hh" +#include "ProgramProvider.hh" +#include "Renderer.hh" +#include "ObjDecoder.hh" + +using namespace gl; + +namespace render { + namespace { + struct VertexAttribs { + int32_t vertex[3]; + uint16_t texCoords[2]; + } __attribute__((__packed__)); + } + + Object::Object(Renderer& renderer, ObjDecoder& obj) + : Drawable(renderer), + vbo_() + { + program_ = ProgramProvider::getInstance().getProgram("object", "object"); + auto& tris = obj.getTriangles(); + auto& quads = obj.getQuads(); + numVertices_ = (tris.size()*3+quads.size()*6); + vbo_ = VBOManager::getInstance().alloc(sizeof(VertexAttribs)*numVertices_); + + glGenVertexArrays(1, &vertexArray_.get()); + std::vector vertexAttribs; + vertexAttribs.reserve(numVertices_); + + auto& vertices = obj.getVertices(); + for (auto& tri : tris) { + for (unsigned i = 0;i < 3;++i) { + VertexAttribs attrs; + std::copy(vertices.at(tri.vertIdx[i]).begin(), vertices.at(tri.vertIdx[i]).end(), + attrs.vertex); + std::copy(tri.texCoords[i].begin(), tri.texCoords[i].end(), + attrs.texCoords); + + vertexAttribs.push_back(attrs); + } + } + + for (auto& quad : quads) { + for (unsigned i = 0;i < 3;++i) { + VertexAttribs attrs; + std::copy(vertices.at(quad.vertIdx[i]).begin(), vertices.at(quad.vertIdx[i]).end(), + attrs.vertex); + std::copy(quad.texCoords[i].begin(), quad.texCoords[i].end(), + attrs.texCoords); + + vertexAttribs.push_back(attrs); + } + for (unsigned i = 0;i < 3;++i) { + VertexAttribs attrs; + std::copy(vertices.at(quad.vertIdx[i+1]).begin(), vertices.at(quad.vertIdx[i+1]).end(), + attrs.vertex); + std::copy(quad.texCoords[i+1].begin(), quad.texCoords[i+1].end(), + attrs.texCoords); + + vertexAttribs.push_back(attrs); + } + } + + // std::vector vertexAttribs = { + // {{-1500, -1500, 0}, {0, 0}}, + // {{-1500, 1500, 0}, {0, 0}}, + // {{1500, -1500, 0}, {0, 0}}, + // {{1500, 1500, 0}, {0, 0}}}; + + glBindBuffer(GL_ARRAY_BUFFER, vbo_.getVBOId()); + glBindVertexArray(vertexArray_); + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_INT, GL_FALSE, sizeof(VertexAttribs), + vbo_.getOfs(offsetof(VertexAttribs, vertex))); + + glBufferSubData(GL_ARRAY_BUFFER, vbo_.getBase(), + sizeof(VertexAttribs)*numVertices_, + vertexAttribs.data()); + + mvp_ = glm::perspectiveFov(75.0f, static_cast(renderer.getWidth()), + static_cast(renderer.getHeight()), + 100.f, 20000.0f) * + glm::lookAt(glm::vec3(0.0f, 0.0f, 10000), + glm::vec3(0.0f, 0.0f, 0), + glm::vec3(0, 1, 0)); + + } + + void Object::draw() + { + glDisable(GL_CULL_FACE); + + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + glUseProgram(program_); + glUniformMatrix4fv(0, 1, GL_FALSE, glm::value_ptr(mvp_)); + glBindVertexArray(vertexArray_); + + glDrawArrays(GL_TRIANGLES, 0, numVertices_); + } +} + diff --git a/render/Object.hh b/render/Object.hh new file mode 100644 index 0000000..3ff13da --- /dev/null +++ b/render/Object.hh @@ -0,0 +1,29 @@ +#ifndef WC3RE_RENDER_OBJECT_HH__ +#define WC3RE_RENDER_OBJECT_HH__ + +#define GLM_FORCE_RADIANS +#include + +#include "Drawable.hh" +#include "GlResource.hh" +#include "VBOManager.hh" + +class ObjDecoder; + +namespace render { + class Object : public Drawable { + public: + Object(Renderer& renderer, ObjDecoder& obj); + + void draw() override; + + private: + VertexArrayResource vertexArray_; + gl::GLuint program_; + VBOManager::VBOAlloc vbo_; + glm::mat4 mvp_; + unsigned numVertices_; + }; +} + +#endif diff --git a/shaders/object.fs b/shaders/object.fs new file mode 100644 index 0000000..4c75f78 --- /dev/null +++ b/shaders/object.fs @@ -0,0 +1,8 @@ +#version 330 core +#extension GL_ARB_explicit_uniform_location : enable + +out vec4 color; + +void main(void) { + color = vec4(1.0, 1.0, 1.0, 1.0); +} \ No newline at end of file diff --git a/shaders/object.vs b/shaders/object.vs new file mode 100644 index 0000000..0cc157f --- /dev/null +++ b/shaders/object.vs @@ -0,0 +1,16 @@ +#version 330 core +#extension GL_ARB_shading_language_420pack : enable +#extension GL_ARB_explicit_uniform_location : enable + +layout(location = 0) uniform mat4 mvp_matrix; + +layout(location = 0) in vec3 vertex; +layout(location = 1) in vec2 vertexTC; + +out vec2 fragTC; + +void main(void) { + gl_Position = mvp_matrix * vec4(vertex, 1.0); + + fragTC = vertexTC; +}