Commit 09c60db8
should have incrememnted the model format version but
didn't. Also added "since version x.x" in the model format docs.
215 lines
6.2 KiB
C++
215 lines
6.2 KiB
C++
#include <iostream>
|
|
#include <vector>
|
|
#include <zip.h>
|
|
#include <fmt/core.h>
|
|
#include <glm/vec2.hpp>
|
|
#include <tomlcpp.hpp> //dynamically link tomlcpp if it becomes common in repositories
|
|
#include <model.hpp>
|
|
#include <config.hpp>
|
|
#include <error.hpp>
|
|
|
|
#define BUFFER_SIZE_MODEL_DESC 8192 // 8 KiB
|
|
#define BUFFER_SIZE_TEXTURE 16777220 // 16 MiB
|
|
|
|
#define SUPPORTED_MODEL_MAJOR 0
|
|
#define SUPPORTED_MODEL_MINOR 4
|
|
|
|
void textureFromArchive(zip_t* archive, const char* path, ModelPart* part, size_t slot, std::string triggerName) {
|
|
zip_file_t* textureFile = zip_fopen(archive, path, 0);
|
|
if (textureFile != NULL) {
|
|
unsigned char* textureBuffer = new unsigned char[BUFFER_SIZE_TEXTURE];
|
|
size_t textureLength = zip_fread(textureFile, textureBuffer, BUFFER_SIZE_TEXTURE-1);
|
|
|
|
part->addTexture(textureBuffer, textureLength, slot, triggerName);
|
|
|
|
delete [] textureBuffer;
|
|
} else {
|
|
showError(fmt::format("Texture file \"{}\" does not exist in archive!", path), "Could not open model");
|
|
}
|
|
}
|
|
|
|
Model::Model(const char* path) {
|
|
int zipError;
|
|
zip_t* archive = zip_open(path, ZIP_RDONLY, &zipError);
|
|
|
|
if (!archive) {
|
|
showError(fmt::format("Model file {} does not exist or is corrupt!", path), "Could not open model");
|
|
return;
|
|
}
|
|
|
|
// get model description file (model.toml)
|
|
zip_file_t* modelDescFile = zip_fopen(archive, "model.toml", 0);
|
|
char modelDescBuffer[BUFFER_SIZE_MODEL_DESC];
|
|
size_t descLength = zip_fread(modelDescFile, modelDescBuffer, BUFFER_SIZE_MODEL_DESC-1);
|
|
modelDescBuffer[descLength] = 0; //null-terminate
|
|
|
|
// parse model.toml
|
|
auto modelDesc = toml::parse(std::string(modelDescBuffer));
|
|
if (!modelDesc.table) {
|
|
showError("Cannot parse model.toml:\n" + modelDesc.errmsg, "Could not open model");
|
|
}
|
|
|
|
// get format table
|
|
auto format = modelDesc.table->getTable("format");
|
|
if (!format) {
|
|
showError("Model does not have a format table!", "Could not open model");
|
|
} else {
|
|
// get format version
|
|
auto formatMajResult = format->getInt("version_major");
|
|
auto formatMinResult = format->getInt("version_minor");
|
|
|
|
if (formatMajResult.first && formatMinResult.first) {
|
|
// check format version
|
|
if (formatMajResult.second != SUPPORTED_MODEL_MAJOR ||
|
|
formatMinResult.second > SUPPORTED_MODEL_MINOR ) {
|
|
|
|
showError(fmt::format("Model format version {0}.{1} is unsupported! This version of {2} supports model file version"
|
|
" {3}.0 to version {3}.{4}.",
|
|
formatMajResult.second, formatMinResult.second,
|
|
PROJECT_NAME,
|
|
SUPPORTED_MODEL_MAJOR, SUPPORTED_MODEL_MINOR), "Could not open model");
|
|
}
|
|
} else {
|
|
showError("Model does not define a format version!", "Could not open model");
|
|
}
|
|
}
|
|
|
|
|
|
// get model info table
|
|
auto modelInfo = modelDesc.table->getTable("model_info");
|
|
|
|
if (!modelInfo) {
|
|
showError("Model does not have a model_info table!", "Could not open model");
|
|
} else {
|
|
|
|
// get name
|
|
auto nameResult = modelInfo->getString("name");
|
|
|
|
if (!nameResult.first) {
|
|
showWarning("Model does not have a name!", "Model warning");
|
|
} else {
|
|
name = nameResult.second;
|
|
}
|
|
}
|
|
|
|
// parse parts
|
|
auto partsDescArray = modelDesc.table->getArray("part");
|
|
|
|
auto partsVec = *partsDescArray->getTableVector();
|
|
|
|
for (int i = 0; i < partsVec.size(); i++) {
|
|
ModelPart newPart;
|
|
|
|
// position
|
|
auto bindResult = partsVec[i].getString("bind");
|
|
if (bindResult.first) {
|
|
newPart.setBind(bindResult.second);
|
|
}
|
|
|
|
auto parentResult = partsVec[i].getString("follow");
|
|
auto factorResult = partsVec[i].getDouble("factor");
|
|
|
|
if (parentResult.first) {
|
|
newPart.setFollowTarget(parentResult.second);
|
|
|
|
if (factorResult.first) {
|
|
newPart.factor = (float)factorResult.second;
|
|
} else {
|
|
newPart.factor = 1.0f;
|
|
}
|
|
}
|
|
|
|
// rotation and scale factor
|
|
auto rotFacResult = partsVec[i].getDouble("rot_factor");
|
|
auto scaleFacResult = partsVec[i].getDouble("scale_factor");
|
|
auto offsetFacResult = partsVec[i].getDouble("offset_factor");
|
|
|
|
if (rotFacResult.first) {
|
|
newPart.rotFactor = (float)rotFacResult.second;
|
|
}
|
|
if (scaleFacResult.first) {
|
|
newPart.scaleFactor = (float)scaleFacResult.second;
|
|
}
|
|
if (offsetFacResult.first) {
|
|
newPart.offsetFactor = (float)offsetFacResult.second;
|
|
}
|
|
|
|
|
|
// origin
|
|
auto originArray = partsVec[i].getArray("origin");
|
|
|
|
if (originArray) {
|
|
auto originVec = *originArray->getDoubleVector();
|
|
|
|
newPart.origin = glm::vec2((float)originVec[0], (float)originVec[1]);
|
|
}
|
|
|
|
// offsets
|
|
auto posOffsetArray = partsVec[i].getArray("pos_offset");
|
|
if (posOffsetArray) {
|
|
auto offsetVec = *posOffsetArray->getDoubleVector();
|
|
|
|
newPart.posOffset = glm::vec2((float)offsetVec[0], (float)offsetVec[1]);
|
|
}
|
|
|
|
auto scaleOffsetArray = partsVec[i].getArray("scale_offset");
|
|
if (scaleOffsetArray) {
|
|
auto offsetVec = *scaleOffsetArray->getDoubleVector();
|
|
|
|
newPart.scaleOffset = glm::vec2((float)offsetVec[0], (float)offsetVec[1]);
|
|
}
|
|
|
|
// offset bind
|
|
auto offsetBindResult = partsVec[i].getString("offset_bind");
|
|
if (offsetBindResult.first) {
|
|
newPart.setOffsetBind(offsetBindResult.second);
|
|
}
|
|
|
|
|
|
// texture
|
|
auto textureSingle = partsVec[i].getString("texture");
|
|
|
|
if (textureSingle.first) {
|
|
// only a single texture was defined
|
|
textureFromArchive(archive, textureSingle.second.c_str(), &newPart, 0, "null");
|
|
} else {
|
|
auto textureArray = partsVec[i].getArray("textures");
|
|
auto textureVec = *textureArray->getTableVector().get();
|
|
|
|
if (textureVec.size() < 1) {
|
|
showWarning(fmt::format("Part {} does not define any textures! Parts with no textures defined will"
|
|
" show a default \"missing texture\" pattern.", i), "Model warning");
|
|
} else {
|
|
// a list of textures with triggers were defined
|
|
for (int j = 0; j < textureVec.size(); j++) {
|
|
auto fileResult = textureVec[j].getString("file");
|
|
auto triggerResult = textureVec[j].getString("trigger");
|
|
|
|
if (fileResult.first) {
|
|
std::string trigger = triggerResult.first ? triggerResult.second : "null";
|
|
textureFromArchive(archive, fileResult.second.c_str(), &newPart, j, trigger);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
parts.push_back(newPart);
|
|
}
|
|
}
|
|
|
|
void Model::draw() {
|
|
for (size_t i = 0; i < parts.size(); i++) {
|
|
parts[i].bindAndDraw();
|
|
}
|
|
}
|
|
|
|
void Model::updateTransforms(struct FaceData faceData) {
|
|
for (size_t i = 0; i < parts.size(); i++) {
|
|
parts[i].processFaceData(faceData);
|
|
}
|
|
}
|
|
|
|
std::string Model::getName() {
|
|
return name;
|
|
}
|
|
|