celestia/src/celmodel/modelfile.cpp

1960 lines
53 KiB
C++

// modelfile.cpp
//
// Copyright (C) 2004-2010, Celestia Development Team
// Original version by Chris Laurel <claurel@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
#include <cassert>
#include <cstring>
#include <iostream>
#include <fmt/format.h>
#include <fmt/ostream.h>
#include <celutil/binaryread.h>
#include <celutil/binarywrite.h>
#include <celutil/tokenizer.h>
#include "mesh.h"
#include "model.h"
#include "modelfile.h"
namespace celutil = celestia::util;
namespace cmod
{
namespace
{
constexpr std::size_t CEL_MODEL_HEADER_LENGTH = 16;
constexpr const char CEL_MODEL_HEADER_ASCII[] = "#celmodel__ascii";
constexpr const char CEL_MODEL_HEADER_BINARY[] = "#celmodel_binary";
// Material default values
constexpr Material::Color DefaultDiffuse(0.0f, 0.0f, 0.0f);
constexpr Material::Color DefaultSpecular(0.0f, 0.0f, 0.0f);
constexpr Material::Color DefaultEmissive(0.0f, 0.0f, 0.0f);
constexpr float DefaultSpecularPower = 1.0f;
constexpr float DefaultOpacity = 1.0f;
constexpr Material::BlendMode DefaultBlend = Material::NormalBlend;
// Standard tokens for ASCII model loader
constexpr const char MeshToken[] = "mesh";
constexpr const char EndMeshToken[] = "end_mesh";
constexpr const char VertexDescToken[] = "vertexdesc";
constexpr const char EndVertexDescToken[] = "end_vertexdesc";
constexpr const char VerticesToken[] = "vertices";
constexpr const char MaterialToken[] = "material";
constexpr const char EndMaterialToken[] = "end_material";
// Binary file tokens
enum ModelFileToken
{
CMOD_Material = 1001,
CMOD_EndMaterial = 1002,
CMOD_Diffuse = 1003,
CMOD_Specular = 1004,
CMOD_SpecularPower = 1005,
CMOD_Opacity = 1006,
CMOD_Texture = 1007,
CMOD_Mesh = 1009,
CMOD_EndMesh = 1010,
CMOD_VertexDesc = 1011,
CMOD_EndVertexDesc = 1012,
CMOD_Vertices = 1013,
CMOD_Emissive = 1014,
CMOD_Blend = 1015,
};
enum ModelFileType
{
CMOD_Float1 = 1,
CMOD_Float2 = 2,
CMOD_Float3 = 3,
CMOD_Float4 = 4,
CMOD_String = 5,
CMOD_Uint32 = 6,
CMOD_Color = 7,
};
class ModelLoader
{
public:
ModelLoader() = default;
virtual ~ModelLoader() = default;
virtual Model* load() = 0;
const std::string& getErrorMessage() const { return errorMessage; }
TextureLoader* getTextureLoader() const { return textureLoader; }
void setTextureLoader(TextureLoader* _textureLoader) { textureLoader = _textureLoader; }
protected:
virtual void reportError(const std::string& msg) { errorMessage = msg; }
private:
std::string errorMessage;
TextureLoader* textureLoader{ nullptr };
};
class ModelWriter
{
public:
virtual ~ModelWriter() = default;
virtual bool write(const Model&) = 0;
};
/***** ASCII loader *****/
/*!
This is an approximate Backus Naur form for the contents of ASCII cmod
files. For brevity, the categories &lt;unsigned_int&gt; and &lt;float&gt; aren't
defined here--they have the obvious definitions.
\code
<modelfile> ::= <header> <model>
<header> ::= #celmodel__ascii
<model> ::= { <material_definition> } { <mesh_definition> }
<material_definition> ::= material
{ <material_attribute> }
end_material
<texture_semantic> ::= texture0 |
normalmap |
specularmap |
emissivemap
<texture> ::= <texture_semantic> <string>
<material_attribute> ::= diffuse <color> |
specular <color> |
emissive <color> |
specpower <float> |
opacity <float> |
blend <blendmode> |
<texture>
<color> ::= <float> <float> <float>
<string> ::= """ { letter } """
<blendmode> ::= normal | add | premultiplied
<mesh_definition> ::= mesh
<vertex_description>
<vertex_pool>
{ <prim_group> }
end_mesh
<vertex_description> ::= vertexdesc
{ <vertex_attribute> }
end_vertexdesc
<vertex_attribute> ::= <vertex_semantic> <vertex_format>
<vertex_semantic> ::= position | normal | color0 | color1 | tangent |
texcoord0 | texcoord1 | texcoord2 | texcoord3 |
pointsize
<vertex_format> ::= f1 | f2 | f3 | f4 | ub4
<vertex_pool> ::= vertices <count>
{ <float> }
<count> ::= <unsigned_int>
<prim_group> ::= <prim_group_type> <material_index> <count>
{ <unsigned_int> }
<prim_group_type> ::= trilist | tristrip | trifan |
linelist | linestrip | points |
sprites
<material_index> :: <unsigned_int> | -1
\endcode
*/
class AsciiModelLoader : public ModelLoader
{
public:
explicit AsciiModelLoader(std::istream& _in) : tok(&_in) {}
~AsciiModelLoader() override = default;
Model* load() override;
void reportError(const std::string& /*msg*/) override;
Material* loadMaterial();
Mesh::VertexDescription* loadVertexDescription();
Mesh* loadMesh();
char* loadVertices(const Mesh::VertexDescription& vertexDesc,
unsigned int& vertexCount);
private:
Tokenizer tok;
};
void
AsciiModelLoader::reportError(const std::string& msg)
{
std::string s = fmt::format("{} (line {})", msg, tok.getLineNumber());
ModelLoader::reportError(s);
}
Material*
AsciiModelLoader::loadMaterial()
{
if (tok.nextToken() != Tokenizer::TokenName || tok.getNameValue() != MaterialToken)
{
reportError("Material definition expected");
return nullptr;
}
auto* material = new Material();
material->diffuse = DefaultDiffuse;
material->specular = DefaultSpecular;
material->emissive = DefaultEmissive;
material->specularPower = DefaultSpecularPower;
material->opacity = DefaultOpacity;
while (tok.nextToken() == Tokenizer::TokenName && tok.getNameValue() != EndMaterialToken)
{
std::string property = tok.getStringValue();
Material::TextureSemantic texType = Mesh::parseTextureSemantic(property);
if (texType != Material::InvalidTextureSemantic)
{
if (tok.nextToken() != Tokenizer::TokenString)
{
reportError("Texture name expected");
delete material;
return nullptr;
}
std::string textureName = tok.getStringValue();
Material::TextureResource* tex = nullptr;
if (getTextureLoader())
{
tex = getTextureLoader()->loadTexture(textureName);
}
else
{
tex = new Material::DefaultTextureResource(textureName);
}
material->maps[texType] = tex;
}
else if (property == "blend")
{
Material::BlendMode blendMode = Material::InvalidBlend;
if (tok.nextToken() == Tokenizer::TokenName)
{
std::string blendModeName = tok.getStringValue();
if (blendModeName == "normal")
blendMode = Material::NormalBlend;
else if (blendModeName == "add")
blendMode = Material::AdditiveBlend;
else if (blendModeName == "premultiplied")
blendMode = Material::PremultipliedAlphaBlend;
}
if (blendMode == Material::InvalidBlend)
{
reportError("Bad blend mode in material");
delete material;
return nullptr;
}
material->blend = blendMode;
}
else
{
// All non-texture material properties are 3-vectors except
// specular power and opacity
double data[3];
int nValues = 3;
if (property == "specpower" || property == "opacity")
{
nValues = 1;
}
for (int i = 0; i < nValues; i++)
{
if (tok.nextToken() != Tokenizer::TokenNumber)
{
reportError("Bad property value in material");
delete material;
return nullptr;
}
data[i] = tok.getNumberValue();
}
Material::Color colorVal;
if (nValues == 3)
{
colorVal = Material::Color((float) data[0], (float) data[1], (float) data[2]);
}
if (property == "diffuse")
{
material->diffuse = colorVal;
}
else if (property == "specular")
{
material->specular = colorVal;
}
else if (property == "emissive")
{
material->emissive = colorVal;
}
else if (property == "opacity")
{
material->opacity = (float) data[0];
}
else if (property == "specpower")
{
material->specularPower = (float) data[0];
}
}
}
if (tok.getTokenType() != Tokenizer::TokenName)
{
delete material;
return nullptr;
}
return material;
}
Mesh::VertexDescription*
AsciiModelLoader::loadVertexDescription()
{
if (tok.nextToken() != Tokenizer::TokenName || tok.getNameValue() != VertexDescToken)
{
reportError("Vertex description expected");
return nullptr;
}
int maxAttributes = 16;
int nAttributes = 0;
unsigned int offset = 0;
auto* attributes = new Mesh::VertexAttribute[maxAttributes];
while (tok.nextToken() == Tokenizer::TokenName && tok.getNameValue() != EndVertexDescToken)
{
std::string semanticName;
std::string formatName;
bool valid = false;
if (nAttributes == maxAttributes)
{
// TODO: Should eliminate the attribute limit, though no real vertex
// will ever exceed it.
reportError("Attribute limit exceeded in vertex description");
delete[] attributes;
return nullptr;
}
semanticName = tok.getStringValue();
if (tok.nextToken() == Tokenizer::TokenName)
{
formatName = tok.getStringValue();
valid = true;
}
if (!valid)
{
reportError("Invalid vertex description");
delete[] attributes;
return nullptr;
}
Mesh::VertexAttributeSemantic semantic =
Mesh::parseVertexAttributeSemantic(semanticName);
if (semantic == Mesh::InvalidSemantic)
{
reportError(fmt::format("Invalid vertex attribute semantic '{}'", semanticName));
delete[] attributes;
return nullptr;
}
Mesh::VertexAttributeFormat format =
Mesh::parseVertexAttributeFormat(formatName);
if (format == Mesh::InvalidFormat)
{
reportError(fmt::format("Invalid vertex attribute format '{}'", formatName));
delete[] attributes;
return nullptr;
}
attributes[nAttributes].semantic = semantic;
attributes[nAttributes].format = format;
attributes[nAttributes].offset = offset;
offset += Mesh::getVertexAttributeSize(format);
nAttributes++;
}
if (tok.getTokenType() != Tokenizer::TokenName)
{
reportError("Invalid vertex description");
delete[] attributes;
return nullptr;
}
if (nAttributes == 0)
{
reportError("Vertex definitition cannot be empty");
delete[] attributes;
return nullptr;
}
auto *vertexDesc =
new Mesh::VertexDescription(offset, nAttributes, attributes);
delete[] attributes;
return vertexDesc;
}
char*
AsciiModelLoader::loadVertices(const Mesh::VertexDescription& vertexDesc,
unsigned int& vertexCount)
{
if (tok.nextToken() != Tokenizer::TokenName && tok.getNameValue() != VerticesToken)
{
reportError("Vertex data expected");
return nullptr;
}
if (tok.nextToken() != Tokenizer::TokenNumber || !tok.isInteger())
{
reportError("Vertex count expected");
return nullptr;
}
std::int32_t num = tok.getIntegerValue();
if (num <= 0)
{
reportError("Bad vertex count for mesh");
return nullptr;
}
vertexCount = (unsigned int) num;
unsigned int vertexDataSize = vertexDesc.stride * vertexCount;
auto* vertexData = new char[vertexDataSize];
unsigned int offset = 0;
double data[4];
for (unsigned int i = 0; i < vertexCount; i++, offset += vertexDesc.stride)
{
assert(offset < vertexDataSize);
for (unsigned int attr = 0; attr < vertexDesc.nAttributes; attr++)
{
Mesh::VertexAttributeFormat fmt = vertexDesc.attributes[attr].format;
/*unsigned int nBytes = Mesh::getVertexAttributeSize(fmt); Unused*/
int readCount = 0;
switch (fmt)
{
case Mesh::Float1:
readCount = 1;
break;
case Mesh::Float2:
readCount = 2;
break;
case Mesh::Float3:
readCount = 3;
break;
case Mesh::Float4:
case Mesh::UByte4:
readCount = 4;
break;
default:
assert(0);
delete[] vertexData;
return nullptr;
}
for (int j = 0; j < readCount; j++)
{
if (tok.nextToken() != Tokenizer::TokenNumber)
{
reportError("Error in vertex data");
data[j] = 0.0;
}
else
{
data[j] = tok.getNumberValue();
}
// TODO: range check unsigned byte values
}
unsigned int base = offset + vertexDesc.attributes[attr].offset;
if (fmt == Mesh::UByte4)
{
for (int k = 0; k < readCount; k++)
{
*(vertexData + base + k) = static_cast<char>(static_cast<unsigned char>(data[k]));
}
}
else
{
for (int k = 0; k < readCount; k++)
{
float value = static_cast<float>(data[k]);
std::memcpy(vertexData + base + sizeof(float) * k, &value, sizeof(float));
}
}
}
}
return vertexData;
}
Mesh*
AsciiModelLoader::loadMesh()
{
if (tok.nextToken() != Tokenizer::TokenName && tok.getNameValue() != MeshToken)
{
reportError("Mesh definition expected");
return nullptr;
}
Mesh::VertexDescription* vertexDesc = loadVertexDescription();
if (vertexDesc == nullptr)
return nullptr;
unsigned int vertexCount = 0;
char* vertexData = loadVertices(*vertexDesc, vertexCount);
if (vertexData == nullptr)
{
delete vertexDesc;
return nullptr;
}
auto* mesh = new Mesh();
mesh->setVertexDescription(*vertexDesc);
mesh->setVertices(vertexCount, vertexData);
delete vertexDesc;
while (tok.nextToken() == Tokenizer::TokenName && tok.getNameValue() != EndMeshToken)
{
Mesh::PrimitiveGroupType type =
Mesh::parsePrimitiveGroupType(tok.getStringValue());
if (type == Mesh::InvalidPrimitiveGroupType)
{
reportError("Bad primitive group type: " + tok.getStringValue());
delete mesh;
return nullptr;
}
if (tok.nextToken() != Tokenizer::TokenNumber || !tok.isInteger())
{
reportError("Material index expected in primitive group");
delete mesh;
return nullptr;
}
unsigned int materialIndex;
if (tok.getIntegerValue() == -1)
{
materialIndex = ~0u;
}
else
{
materialIndex = (unsigned int) tok.getIntegerValue();
}
if (tok.nextToken() != Tokenizer::TokenNumber || !tok.isInteger())
{
reportError("Index count expected in primitive group");
delete mesh;
return nullptr;
}
unsigned int indexCount = (unsigned int) tok.getIntegerValue();
auto* indices = new Mesh::index32[indexCount];
for (unsigned int i = 0; i < indexCount; i++)
{
if (tok.nextToken() != Tokenizer::TokenNumber || !tok.isInteger())
{
reportError("Incomplete index list in primitive group");
delete[] indices;
delete mesh;
return nullptr;
}
unsigned int index = (unsigned int) tok.getIntegerValue();
if (index >= vertexCount)
{
reportError("Index out of range");
delete[] indices;
delete mesh;
return nullptr;
}
indices[i] = index;
}
mesh->addGroup(type, materialIndex, indexCount, indices);
}
return mesh;
}
Model*
AsciiModelLoader::load()
{
auto* model = new Model();
bool seenMeshes = false;
// Parse material and mesh definitions
for (Tokenizer::TokenType token = tok.nextToken(); token != Tokenizer::TokenEnd; token = tok.nextToken())
{
if (token == Tokenizer::TokenName)
{
std::string name = tok.getStringValue();
tok.pushBack();
if (name == "material")
{
if (seenMeshes)
{
reportError("Materials must be defined before meshes");
delete model;
return nullptr;
}
Material* material = loadMaterial();
if (material == nullptr)
{
delete model;
return nullptr;
}
model->addMaterial(material);
}
else if (name == "mesh")
{
seenMeshes = true;
Mesh* mesh = loadMesh();
if (mesh == nullptr)
{
delete model;
return nullptr;
}
model->addMesh(mesh);
}
else
{
reportError(fmt::format("Error: Unknown block type {}", name));
delete model;
return nullptr;
}
}
else
{
reportError("Block name expected");
delete model;
return nullptr;
}
}
return model;
}
/***** ASCII writer *****/
class AsciiModelWriter : public ModelWriter
{
public:
explicit AsciiModelWriter(std::ostream& _out) : out(_out) {}
~AsciiModelWriter() override = default;
bool write(const Model& /*model*/) override;
private:
bool writeMesh(const Mesh& /*mesh*/);
bool writeMaterial(const Material& /*material*/);
bool writeGroup(const Mesh::PrimitiveGroup& /*group*/);
bool writeVertexDescription(const Mesh::VertexDescription& /*desc*/);
bool writeVertices(const void* vertexData,
unsigned int nVertices,
unsigned int stride,
const Mesh::VertexDescription& desc);
std::ostream& out;
};
bool
AsciiModelWriter::write(const Model& model)
{
fmt::print(out, "{}\n\n", CEL_MODEL_HEADER_ASCII);
if (!out.good()) { return false; }
for (unsigned int matIndex = 0; model.getMaterial(matIndex) != nullptr; matIndex++)
{
if (!writeMaterial(*model.getMaterial(matIndex))) { return false; }
if (!(out << '\n').good()) { return false; }
}
for (unsigned int meshIndex = 0; model.getMesh(meshIndex) != nullptr; meshIndex++)
{
if (!writeMesh(*model.getMesh(meshIndex))) { return false; }
if (!(out << '\n').good()) { return false; }
}
return true;
}
bool
AsciiModelWriter::writeGroup(const Mesh::PrimitiveGroup& group)
{
switch (group.prim)
{
case Mesh::TriList:
fmt::print(out, "trilist"); break;
case Mesh::TriStrip:
fmt::print(out, "tristrip"); break;
case Mesh::TriFan:
fmt::print(out, "trifan"); break;
case Mesh::LineList:
fmt::print(out, "linelist"); break;
case Mesh::LineStrip:
fmt::print(out, "linestrip"); break;
case Mesh::PointList:
fmt::print(out, "points"); break;
case Mesh::SpriteList:
fmt::print(out, "sprites"); break;
default:
return false;
}
if (!out.good()) { return false; }
if (group.materialIndex == ~0u)
out << " -1";
else
fmt::print(out, " {}", group.materialIndex);
if (!out.good()) { return false; }
fmt::print(out, " {}\n", group.nIndices);
if (!out.good()) { return false;}
// Print the indices, twelve per line
for (unsigned int i = 0; i < group.nIndices; i++)
{
fmt::print(out, "{}{}",
group.indices[i],
i % 12 == 11 || i == group.nIndices - 1 ? '\n' : ' ');
if (!out.good()) { return false; }
}
return true;
}
bool
AsciiModelWriter::writeMesh(const Mesh& mesh)
{
if (!(out << "mesh\n").good()) { return false; }
if (!mesh.getName().empty())
{
fmt::print(out, "# {}\n", mesh.getName());
if (!out.good()) { return false; }
}
if (!writeVertexDescription(mesh.getVertexDescription())) { return false; }
if (!(out << '\n').good()) { return false; }
if (!writeVertices(mesh.getVertexData(),
mesh.getVertexCount(),
mesh.getVertexStride(),
mesh.getVertexDescription()))
{
return false;
}
if (!(out << '\n').good()) { return false; }
for (unsigned int groupIndex = 0; mesh.getGroup(groupIndex) != nullptr; groupIndex++)
{
if (!writeGroup(*mesh.getGroup(groupIndex))
|| !(out << '\n').good())
{
return false;
}
}
return (out << "end_mesh\n").good();
}
bool
AsciiModelWriter::writeVertices(const void* vertexData,
unsigned int nVertices,
unsigned int stride,
const Mesh::VertexDescription& desc)
{
const auto* vertex = reinterpret_cast<const unsigned char*>(vertexData);
fmt::print(out, "vertices {}\n", nVertices);
if (!out.good()) { return false; }
for (unsigned int i = 0; i < nVertices; i++, vertex += stride)
{
for (unsigned int attr = 0; attr < desc.nAttributes; attr++)
{
const unsigned char* ubdata = vertex + desc.attributes[attr].offset;
//const auto* fdata = reinterpret_cast<const float*>(ubdata);
float fdata[4];
switch (desc.attributes[attr].format)
{
case Mesh::Float1:
std::memcpy(fdata, ubdata, sizeof(float));
fmt::print(out, "{}", fdata[0]);
break;
case Mesh::Float2:
std::memcpy(fdata, ubdata, sizeof(float) * 2);
fmt::print(out, "{} {}", fdata[0], fdata[1]);
break;
case Mesh::Float3:
std::memcpy(fdata, ubdata, sizeof(float) * 3);
fmt::print(out, "{} {} {}", fdata[0], fdata[1], fdata[2]);
break;
case Mesh::Float4:
std::memcpy(fdata, ubdata, sizeof(float) * 4);
fmt::print(out, "{} {} {} {}", fdata[0], fdata[1], fdata[2], fdata[3]);
break;
case Mesh::UByte4:
fmt::print(out, "{} {} {} {}", +ubdata[0], +ubdata[1], +ubdata[2], +ubdata[3]);
break;
default:
assert(0);
break;
}
if (!out.good() || !(out << ' ').good()) { return false; }
}
if (!(out << '\n').good()) { return false; }
}
return true;
}
bool
AsciiModelWriter::writeVertexDescription(const Mesh::VertexDescription& desc)
{
if (!(out << "vertexdesc\n").good()) { return false; }
for (unsigned int attr = 0; attr < desc.nAttributes; attr++)
{
// We should never have a vertex description with invalid
// fields . . .
switch (desc.attributes[attr].semantic)
{
case Mesh::Position:
out << "position";
break;
case Mesh::Color0:
out << "color0";
break;
case Mesh::Color1:
out << "color1";
break;
case Mesh::Normal:
out << "normal";
break;
case Mesh::Tangent:
out << "tangent";
break;
case Mesh::Texture0:
out << "texcoord0";
break;
case Mesh::Texture1:
out << "texcoord1";
break;
case Mesh::Texture2:
out << "texcoord2";
break;
case Mesh::Texture3:
out << "texcoord3";
break;
case Mesh::PointSize:
out << "pointsize";
break;
default:
assert(0);
break;
}
if (!out.good() || !(out << ' ').good()) { return false; }
switch (desc.attributes[attr].format)
{
case Mesh::Float1:
out << "f1";
break;
case Mesh::Float2:
out << "f2";
break;
case Mesh::Float3:
out << "f3";
break;
case Mesh::Float4:
out << "f4";
break;
case Mesh::UByte4:
out << "ub4";
break;
default:
assert(0);
break;
}
if (!out.good() || !(out << '\n').good()) { return false; }
}
return (out << "end_vertexdesc\n").good();
}
bool
AsciiModelWriter::writeMaterial(const Material& material)
{
if (!(out << "material\n").good()) { return false; }
if (material.diffuse != DefaultDiffuse)
{
fmt::print(out, "diffuse {} {} {}\n",
material.diffuse.red(),
material.diffuse.green(),
material.diffuse.blue());
if (!out.good()) { return false; }
}
if (material.emissive != DefaultEmissive)
{
fmt::print(out, "emissive {} {} {}\n",
material.emissive.red(),
material.emissive.green(),
material.emissive.blue());
if (!out.good()) { return false; }
}
if (material.specular != DefaultSpecular)
{
fmt::print(out, "specular {} {} {}\n",
material.specular.red(),
material.specular.green(),
material.specular.blue());
if (!out.good()) { return false; }
}
if (material.specularPower != DefaultSpecularPower)
{
fmt::print(out, "specpower {}\n", material.specularPower);
if (!out.good()) { return false; }
}
if (material.opacity != DefaultOpacity)
{
fmt::print(out, "opacity {}\n", material.opacity);
if (!out.good()) { return false; }
}
if (material.blend != DefaultBlend)
{
if (!(out << "blend ").good()) { return false; }
switch (material.blend)
{
case Material::NormalBlend:
out << "normal";
break;
case Material::AdditiveBlend:
out << "add";
break;
case Material::PremultipliedAlphaBlend:
out << "premultiplied";
break;
default:
assert(0);
break;
}
if (!out.good() || !(out << '\n').good()) { return false; }
}
for (int i = 0; i < Material::TextureSemanticMax; i++)
{
fs::path texSource;
if (material.maps[i] != nullptr)
{
texSource = material.maps[i]->source();
}
if (!texSource.empty())
{
switch (Material::TextureSemantic(i))
{
case Material::DiffuseMap:
out << "texture0";
break;
case Material::NormalMap:
out << "normalmap";
break;
case Material::SpecularMap:
out << "specularmap";
break;
case Material::EmissiveMap:
out << "emissivemap";
break;
default:
assert(0);
}
if (!out.good()) { return false; }
fmt::print(out, " \"{}\"\n", texSource.string());
if (!out.good()) { return false; }
}
}
return (out << "end_material\n").good();
}
/***** Binary loader *****/
bool readToken(std::istream& in, ModelFileToken& value)
{
std::int16_t num;
if (!celutil::readLE<std::int16_t>(in, num)) { return false; }
value = static_cast<ModelFileToken>(num);
return true;
}
bool readType(std::istream& in, ModelFileType& value)
{
std::int16_t num;
if (!celutil::readLE<std::int16_t>(in, num)) { return false; }
value = static_cast<ModelFileType>(num);
return true;
}
bool readTypeFloat1(std::istream& in, float& f)
{
ModelFileType cmodType;
return readType(in, cmodType)
&& cmodType == CMOD_Float1
&& celutil::readLE<float>(in, f);
}
bool readTypeColor(std::istream& in, Material::Color& c)
{
ModelFileType cmodType;
float r, g, b;
if (!readType(in, cmodType)
|| cmodType != CMOD_Color
|| !celutil::readLE<float>(in, r)
|| !celutil::readLE<float>(in, g)
|| !celutil::readLE<float>(in, b))
{
return false;
}
c = Material::Color(r, g, b);
return true;
}
bool readTypeString(std::istream& in, std::string& s)
{
ModelFileType cmodType;
uint16_t len;
if (!readType(in, cmodType)
|| cmodType != CMOD_String
|| !celutil::readLE<std::uint16_t>(in, len))
{
return false;
}
if (len == 0)
{
s = "";
}
else
{
std::vector<char> buf(len);
if (!in.read(buf.data(), len).good()) { return false; }
s = std::string(buf.cbegin(), buf.cend());
}
return true;
}
bool ignoreValue(std::istream& in)
{
ModelFileType type;
if (!readType(in, type)) { return false; }
std::streamsize size = 0;
switch (type)
{
case CMOD_Float1:
size = 4;
break;
case CMOD_Float2:
size = 8;
break;
case CMOD_Float3:
size = 12;
break;
case CMOD_Float4:
size = 16;
break;
case CMOD_Uint32:
size = 4;
break;
case CMOD_Color:
size = 12;
break;
case CMOD_String:
{
std::uint16_t len;
if (!celutil::readLE<std::uint16_t>(in, len)) { return false; }
size = len;
}
break;
default:
return false;
}
return in.ignore(size).good();
}
class BinaryModelLoader : public ModelLoader
{
public:
explicit BinaryModelLoader(std::istream& _in) : in(_in) {}
~BinaryModelLoader() override = default;
Model* load() override;
void reportError(const std::string& /*msg*/) override;
Material* loadMaterial();
Mesh::VertexDescription* loadVertexDescription();
Mesh* loadMesh();
char* loadVertices(const Mesh::VertexDescription& vertexDesc,
unsigned int& vertexCount);
private:
std::istream& in;
};
void
BinaryModelLoader::reportError(const std::string& msg)
{
std::string s = fmt::format("{} (offset {})", msg, 0);
ModelLoader::reportError(s);
}
Model*
BinaryModelLoader::load()
{
auto* model = new Model();
bool seenMeshes = false;
// Parse material and mesh definitions
for (;;)
{
ModelFileToken tok;
if (!readToken(in, tok))
{
if (in.eof()) { break; }
reportError("Failed to read token");
delete model;
return nullptr;
}
if (tok == CMOD_Material)
{
if (seenMeshes)
{
reportError("Materials must be defined before meshes");
delete model;
return nullptr;
}
Material* material = loadMaterial();
if (material == nullptr)
{
delete model;
return nullptr;
}
model->addMaterial(material);
}
else if (tok == CMOD_Mesh)
{
seenMeshes = true;
Mesh* mesh = loadMesh();
if (mesh == nullptr)
{
delete model;
return nullptr;
}
model->addMesh(mesh);
}
else
{
reportError("Error: Unknown block type in model");
delete model;
return nullptr;
}
}
return model;
}
Material*
BinaryModelLoader::loadMaterial()
{
auto* material = new Material();
material->diffuse = DefaultDiffuse;
material->specular = DefaultSpecular;
material->emissive = DefaultEmissive;
material->specularPower = DefaultSpecularPower;
material->opacity = DefaultOpacity;
for (;;)
{
ModelFileToken tok;
if (!readToken(in, tok))
{
reportError("Error reading token type");
delete material;
return nullptr;
}
switch (tok)
{
case CMOD_Diffuse:
if (!readTypeColor(in, material->diffuse))
{
reportError("Incorrect type for diffuse color");
delete material;
return nullptr;
}
break;
case CMOD_Specular:
if (!readTypeColor(in, material->specular))
{
reportError("Incorrect type for specular color");
delete material;
return nullptr;
}
break;
case CMOD_Emissive:
if (!readTypeColor(in, material->emissive))
{
reportError("Incorrect type for emissive color");
delete material;
return nullptr;
}
break;
case CMOD_SpecularPower:
if (!readTypeFloat1(in, material->specularPower))
{
reportError("Float expected for specularPower");
delete material;
return nullptr;
}
break;
case CMOD_Opacity:
if (!readTypeFloat1(in, material->opacity))
{
reportError("Float expected for opacity");
delete material;
return nullptr;
}
break;
case CMOD_Blend:
{
std::int16_t blendMode;
if (!celutil::readLE<std::int16_t>(in, blendMode)
|| blendMode < 0 || blendMode >= Material::BlendMax)
{
reportError("Bad blend mode");
delete material;
return nullptr;
}
material->blend = (Material::BlendMode) blendMode;
}
break;
case CMOD_Texture:
{
std::int16_t texType;
if (!celutil::readLE<std::int16_t>(in, texType)
|| texType < 0 || texType >= Material::TextureSemanticMax)
{
reportError("Bad texture type");
delete material;
return nullptr;
}
std::string texfile;
if (!readTypeString(in, texfile))
{
reportError("String expected for texture filename");
delete material;
return nullptr;
}
if (texfile.empty())
{
reportError("Zero length texture name in material definition");
delete material;
return nullptr;
}
Material::TextureResource* tex = nullptr;
if (getTextureLoader() != nullptr)
{
tex = getTextureLoader()->loadTexture(texfile);
}
else
{
tex = new Material::DefaultTextureResource(texfile);
}
material->maps[texType] = tex;
}
break;
case CMOD_EndMaterial:
return material;
default:
// Skip unrecognized tokens
if (!ignoreValue(in))
{
delete material;
return nullptr;
}
} // switch
} // for
}
Mesh::VertexDescription*
BinaryModelLoader::loadVertexDescription()
{
ModelFileToken tok;
if (!readToken(in, tok) || tok != CMOD_VertexDesc)
{
reportError("Vertex description expected");
return nullptr;
}
int maxAttributes = 16;
int nAttributes = 0;
unsigned int offset = 0;
auto* attributes = new Mesh::VertexAttribute[maxAttributes];
for (;;)
{
std::int16_t tok;
if (!celutil::readLE<std::int16_t>(in, tok))
{
reportError("Could not read token");
delete[] attributes;
return nullptr;
}
if (tok == CMOD_EndVertexDesc)
{
break;
}
if (tok >= 0 && tok < Mesh::SemanticMax)
{
std::int16_t fmt;
if (!celutil::readLE<std::int16_t>(in, fmt)
|| fmt < 0 || fmt >= Mesh::FormatMax)
{
reportError("Invalid vertex attribute type");
delete[] attributes;
return nullptr;
}
if (nAttributes == maxAttributes)
{
reportError("Too many attributes in vertex description");
delete[] attributes;
return nullptr;
}
attributes[nAttributes].semantic =
static_cast<Mesh::VertexAttributeSemantic>(tok);
attributes[nAttributes].format =
static_cast<Mesh::VertexAttributeFormat>(fmt);
attributes[nAttributes].offset = offset;
offset += Mesh::getVertexAttributeSize(attributes[nAttributes].format);
nAttributes++;
}
else
{
reportError("Invalid semantic in vertex description");
delete[] attributes;
return nullptr;
}
}
if (nAttributes == 0)
{
reportError("Vertex definitition cannot be empty");
delete[] attributes;
return nullptr;
}
auto *vertexDesc =
new Mesh::VertexDescription(offset, nAttributes, attributes);
delete[] attributes;
return vertexDesc;
}
Mesh*
BinaryModelLoader::loadMesh()
{
Mesh::VertexDescription* vertexDesc = loadVertexDescription();
if (vertexDesc == nullptr)
return nullptr;
unsigned int vertexCount = 0;
char* vertexData = loadVertices(*vertexDesc, vertexCount);
if (vertexData == nullptr)
{
delete vertexDesc;
return nullptr;
}
auto* mesh = new Mesh();
mesh->setVertexDescription(*vertexDesc);
mesh->setVertices(vertexCount, vertexData);
delete vertexDesc;
for (;;)
{
std::int16_t tok;
if (!celutil::readLE<std::int16_t>(in, tok))
{
reportError("Failed to read token type");
delete mesh;
return nullptr;
}
if (tok == CMOD_EndMesh)
{
break;
}
if (tok < 0 || tok >= Mesh::PrimitiveTypeMax)
{
reportError("Bad primitive group type");
delete mesh;
return nullptr;
}
Mesh::PrimitiveGroupType type =
static_cast<Mesh::PrimitiveGroupType>(tok);
std::uint32_t materialIndex, indexCount;
if (!celutil::readLE<std::uint32_t>(in, materialIndex)
|| !celutil::readLE<std::uint32_t>(in, indexCount))
{
reportError("Could not read primitive indices");
delete mesh;
return nullptr;
}
auto* indices = new uint32_t[indexCount];
for (unsigned int i = 0; i < indexCount; i++)
{
std::uint32_t index;
if (!celutil::readLE<std::uint32_t>(in, index) || index >= vertexCount)
{
reportError("Index out of range");
delete[] indices;
delete mesh;
return nullptr;
}
indices[i] = index;
}
mesh->addGroup(type, materialIndex, indexCount, indices);
}
return mesh;
}
char*
BinaryModelLoader::loadVertices(const Mesh::VertexDescription& vertexDesc,
unsigned int& vertexCount)
{
ModelFileToken tok;
if (!readToken(in, tok) || tok != CMOD_Vertices)
{
reportError("Vertex data expected");
return nullptr;
}
if (!celutil::readLE<std::uint32_t>(in, vertexCount))
{
reportError("Vertex count expected");
return nullptr;
}
unsigned int vertexDataSize = vertexDesc.stride * vertexCount;
auto* vertexData = new char[vertexDataSize];
unsigned int offset = 0;
for (unsigned int i = 0; i < vertexCount; i++, offset += vertexDesc.stride)
{
assert(offset < vertexDataSize);
for (unsigned int attr = 0; attr < vertexDesc.nAttributes; attr++)
{
unsigned int base = offset + vertexDesc.attributes[attr].offset;
Mesh::VertexAttributeFormat fmt = vertexDesc.attributes[attr].format;
float f[4];
/*int readCount = 0; Unused*/
switch (fmt)
{
case Mesh::Float1:
if (!celutil::readLE<float>(in, f[0]))
{
reportError("Failed to read Float1");
delete[] vertexData;
return nullptr;
}
std::memcpy(vertexData + base, f, sizeof(float));
break;
case Mesh::Float2:
if (!celutil::readLE<float>(in, f[0])
|| !celutil::readLE<float>(in, f[1]))
{
reportError("Failed to read Float2");
delete[] vertexData;
return nullptr;
}
std::memcpy(vertexData + base, f, sizeof(float) * 2);
break;
case Mesh::Float3:
if (!celutil::readLE<float>(in, f[0])
|| !celutil::readLE<float>(in, f[1])
|| !celutil::readLE<float>(in, f[2]))
{
reportError("Failed to read Float3");
delete[] vertexData;
return nullptr;
}
std::memcpy(vertexData + base, f, sizeof(float) * 3);
break;
case Mesh::Float4:
if (!celutil::readLE<float>(in, f[0])
|| !celutil::readLE<float>(in, f[1])
|| !celutil::readLE<float>(in, f[2])
|| !celutil::readLE<float>(in, f[3]))
{
reportError("Failed to read Float4");
delete[] vertexData;
return nullptr;
}
std::memcpy(vertexData + base, f, sizeof(float) * 4);
break;
case Mesh::UByte4:
if (!in.get(reinterpret_cast<char*>(vertexData + base), 4).good())
{
reportError("failed to read UByte4");
delete[] vertexData;
return nullptr;
}
break;
default:
assert(0);
delete[] vertexData;
return nullptr;
}
}
}
return vertexData;
}
/***** Binary writer *****/
bool writeToken(std::ostream& out, ModelFileToken val)
{
return celutil::writeLE<std::int16_t>(out, static_cast<std::int16_t>(val));
}
bool writeType(std::ostream& out, ModelFileType val)
{
return celutil::writeLE<std::int16_t>(out, static_cast<std::int16_t>(val));
}
bool writeTypeFloat1(std::ostream& out, float f)
{
return writeType(out, CMOD_Float1) && celutil::writeLE<float>(out, f);
}
bool writeTypeColor(std::ostream& out, const Material::Color& c)
{
return writeType(out, CMOD_Color)
&& celutil::writeLE<float>(out, c.red())
&& celutil::writeLE<float>(out, c.green())
&& celutil::writeLE<float>(out, c.blue());
}
bool writeTypeString(std::ostream& out, const std::string& s)
{
return s.length() <= INT16_MAX
&& writeType(out, CMOD_String)
&& celutil::writeLE<std::int16_t>(out, s.length())
&& out.write(s.c_str(), s.length()).good();
}
class BinaryModelWriter : public ModelWriter
{
public:
explicit BinaryModelWriter(std::ostream& _out) : out(_out) {}
~BinaryModelWriter() override = default;
bool write(const Model& /*model*/) override;
private:
bool writeMesh(const Mesh& /*mesh*/);
bool writeMaterial(const Material& /*material*/);
bool writeGroup(const Mesh::PrimitiveGroup& /*group*/);
bool writeVertexDescription(const Mesh::VertexDescription& /*desc*/);
bool writeVertices(const void* vertexData,
unsigned int nVertices,
unsigned int stride,
const Mesh::VertexDescription& desc);
std::ostream& out;
};
bool
BinaryModelWriter::write(const Model& model)
{
if (!out.write(CEL_MODEL_HEADER_BINARY, CEL_MODEL_HEADER_LENGTH).good()) { return false; }
for (unsigned int matIndex = 0; model.getMaterial(matIndex) != nullptr; matIndex++)
{
if (!writeMaterial(*model.getMaterial(matIndex))) { return false; }
}
for (unsigned int meshIndex = 0; model.getMesh(meshIndex) != nullptr; meshIndex++)
{
if (!writeMesh(*model.getMesh(meshIndex))) { return false; }
}
return true;
}
bool
BinaryModelWriter::writeGroup(const Mesh::PrimitiveGroup& group)
{
if (!celutil::writeLE<std::int16_t>(out, group.prim)
|| !celutil::writeLE<std::uint32_t>(out, group.materialIndex)
|| !celutil::writeLE<std::uint32_t>(out, group.nIndices))
{
return false;
}
for (unsigned int i = 0; i < group.nIndices; i++)
{
if (!celutil::writeLE<std::uint32_t>(out, group.indices[i])) { return false; }
}
return true;
}
bool
BinaryModelWriter::writeMesh(const Mesh& mesh)
{
if (!writeToken(out, CMOD_Mesh)
|| !writeVertexDescription(mesh.getVertexDescription())
|| !writeVertices(mesh.getVertexData(),
mesh.getVertexCount(),
mesh.getVertexStride(),
mesh.getVertexDescription()))
{
return false;
}
for (unsigned int groupIndex = 0; mesh.getGroup(groupIndex) != nullptr; groupIndex++)
{
if (!writeGroup(*mesh.getGroup(groupIndex))) { return false; }
}
return writeToken(out, CMOD_EndMesh);
}
bool
BinaryModelWriter::writeVertices(const void* vertexData,
unsigned int nVertices,
unsigned int stride,
const Mesh::VertexDescription& desc)
{
const auto* vertex = reinterpret_cast<const char*>(vertexData);
if (!writeToken(out, CMOD_Vertices) || !celutil::writeLE<std::uint32_t>(out, nVertices))
{
return false;
}
for (unsigned int i = 0; i < nVertices; i++, vertex += stride)
{
for (unsigned int attr = 0; attr < desc.nAttributes; attr++)
{
const char* cdata = vertex + desc.attributes[attr].offset;
float fdata[4];
bool result;
switch (desc.attributes[attr].format)
{
case Mesh::Float1:
std::memcpy(fdata, cdata, sizeof(float));
result = celutil::writeLE<float>(out, fdata[0]);
break;
case Mesh::Float2:
std::memcpy(fdata, cdata, sizeof(float) * 2);
result = celutil::writeLE<float>(out, fdata[0])
&& celutil::writeLE<float>(out, fdata[1]);
break;
case Mesh::Float3:
std::memcpy(fdata, cdata, sizeof(float) * 3);
result = celutil::writeLE<float>(out, fdata[0])
&& celutil::writeLE<float>(out, fdata[1])
&& celutil::writeLE<float>(out, fdata[2]);
break;
case Mesh::Float4:
std::memcpy(fdata, cdata, sizeof(float) * 4);
result = celutil::writeLE<float>(out, fdata[0])
&& celutil::writeLE<float>(out, fdata[1])
&& celutil::writeLE<float>(out, fdata[2])
&& celutil::writeLE<float>(out, fdata[3]);
break;
case Mesh::UByte4:
result = out.write(cdata, 4).good();
break;
default:
assert(0);
break;
}
if (!result) { return false; }
}
}
return true;
}
bool
BinaryModelWriter::writeVertexDescription(const Mesh::VertexDescription& desc)
{
if (!writeToken(out, CMOD_VertexDesc)) { return false; }
for (unsigned int attr = 0; attr < desc.nAttributes; attr++)
{
if (!celutil::writeLE<std::int16_t>(out, desc.attributes[attr].semantic)
|| !celutil::writeLE<std::int16_t>(out, desc.attributes[attr].format))
{
return false;
}
}
return writeToken(out, CMOD_EndVertexDesc);
}
bool
BinaryModelWriter::writeMaterial(const Material& material)
{
if (!writeToken(out, CMOD_Material)) { return false; }
if (material.diffuse != DefaultDiffuse
&& (!writeToken(out, CMOD_Diffuse) || !writeTypeColor(out, material.diffuse)))
{
return false;
}
if (material.emissive != DefaultEmissive
&& (!writeToken(out, CMOD_Emissive) || !writeTypeColor(out, material.emissive)))
{
return false;
}
if (material.specular != DefaultSpecular
&& (!writeToken(out, CMOD_Specular) || !writeTypeColor(out, material.specular)))
{
return false;
}
if (material.specularPower != DefaultSpecularPower
&& (!writeToken(out, CMOD_SpecularPower) || !writeTypeFloat1(out, material.specularPower)))
{
return false;
}
if (material.opacity != DefaultOpacity
&& (!writeToken(out, CMOD_Opacity) || !writeTypeFloat1(out, material.opacity)))
{
return false;
}
if (material.blend != DefaultBlend
&& (!writeToken(out, CMOD_Blend) || !celutil::writeLE<std::int16_t>(out, material.blend)))
{
return false;
}
for (int i = 0; i < Material::TextureSemanticMax; i++)
{
if (material.maps[i])
{
fs::path texSource = material.maps[i]->source();
if (!texSource.empty()
&& (!writeToken(out, CMOD_Texture)
|| !celutil::writeLE<std::int16_t>(out, i)
|| !writeTypeString(out, texSource.string())))
{
return false;
}
}
}
return writeToken(out, CMOD_EndMaterial);
}
ModelLoader* OpenModel(std::istream& in)
{
char header[CEL_MODEL_HEADER_LENGTH];
if (!in.read(header, CEL_MODEL_HEADER_LENGTH).good())
{
std::cerr << "Could not read model header\n";
return nullptr;
}
if (std::strncmp(header, CEL_MODEL_HEADER_ASCII, CEL_MODEL_HEADER_LENGTH) == 0)
{
return new AsciiModelLoader(in);
}
if (std::strncmp(header, CEL_MODEL_HEADER_BINARY, CEL_MODEL_HEADER_LENGTH) == 0)
{
return new BinaryModelLoader(in);
}
else
{
std::cerr << "Model file has invalid header.\n";
return nullptr;
}
}
} // end unnamed namespace
Model* LoadModel(std::istream& in, TextureLoader* textureLoader)
{
ModelLoader* loader = OpenModel(in);
if (loader == nullptr)
return nullptr;
loader->setTextureLoader(textureLoader);
Model* model = loader->load();
if (model == nullptr)
{
std::cerr << "Error in model file: " << loader->getErrorMessage() << '\n';
}
delete loader;
return model;
}
bool
SaveModelAscii(const Model* model, std::ostream& out)
{
if (model == nullptr) { return false; }
return AsciiModelWriter(out).write(*model);
}
bool
SaveModelBinary(const Model* model, std::ostream& out)
{
if (model == nullptr) { return false; }
return BinaryModelWriter(out).write(*model);
}
} // end namespace cmod