diff options
Diffstat (limited to 'src/mesh/assimp-master/code/AssetLib/COB/COBLoader.cpp')
-rw-r--r-- | src/mesh/assimp-master/code/AssetLib/COB/COBLoader.cpp | 1179 |
1 files changed, 1179 insertions, 0 deletions
diff --git a/src/mesh/assimp-master/code/AssetLib/COB/COBLoader.cpp b/src/mesh/assimp-master/code/AssetLib/COB/COBLoader.cpp new file mode 100644 index 0000000..1c83100 --- /dev/null +++ b/src/mesh/assimp-master/code/AssetLib/COB/COBLoader.cpp @@ -0,0 +1,1179 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file COBLoader.cpp + * @brief Implementation of the TrueSpace COB/SCN importer class. + */ + +#ifndef ASSIMP_BUILD_NO_COB_IMPORTER + +#include "AssetLib/COB/COBLoader.h" +#include "AssetLib/COB/COBScene.h" +#include "PostProcessing/ConvertToLHProcess.h" + +#include <assimp/LineSplitter.h> +#include <assimp/ParsingUtils.h> +#include <assimp/StreamReader.h> +#include <assimp/TinyFormatter.h> +#include <assimp/fast_atof.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> + +#include <memory> + +using namespace Assimp; +using namespace Assimp::COB; +using namespace Assimp::Formatter; + +static const float units[] = { + 1000.f, + 100.f, + 1.f, + 0.001f, + 1.f / 0.0254f, + 1.f / 0.3048f, + 1.f / 0.9144f, + 1.f / 1609.344f +}; + +static const aiImporterDesc desc = { + "TrueSpace Object Importer", + "", + "", + "little-endian files only", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "cob scn" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +COBImporter::COBImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +COBImporter::~COBImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool COBImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const char *tokens[] = { "Caligary" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +// Loader meta information +const aiImporterDesc *COBImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties for the loader +void COBImporter::SetupProperties(const Importer * /*pImp*/) { + // nothing to be done for the moment +} + +// ------------------------------------------------------------------------------------------------ +/*static*/ AI_WONT_RETURN void COBImporter::ThrowException(const std::string &msg) { + throw DeadlyImportError("COB: ", msg); +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void COBImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + COB::Scene scene; + + auto file = pIOHandler->Open(pFile, "rb"); + if (!file) { + ThrowException("Could not open " + pFile); + } + + std::unique_ptr<StreamReaderLE> stream(new StreamReaderLE(file)); + + // check header + char head[32]; + stream->CopyAndAdvance(head, 32); + if (strncmp(head, "Caligari ", 9) != 0) { + ThrowException("Could not found magic id: `Caligari`"); + } + + ASSIMP_LOG_INFO("File format tag: ", std::string(head + 9, 6)); + if (head[16] != 'L') { + ThrowException("File is big-endian, which is not supported"); + } + + // load data into intermediate structures + if (head[15] == 'A') { + ReadAsciiFile(scene, stream.get()); + } else { + ReadBinaryFile(scene, stream.get()); + } + if (scene.nodes.empty()) { + ThrowException("No nodes loaded"); + } + + // sort faces by material indices + for (std::shared_ptr<Node> &n : scene.nodes) { + if (n->type == Node::TYPE_MESH) { + Mesh &mesh = (Mesh &)(*n.get()); + for (Face &f : mesh.faces) { + mesh.temp_map[f.material].push_back(&f); + } + } + } + + // count meshes + for (std::shared_ptr<Node> &n : scene.nodes) { + if (n->type == Node::TYPE_MESH) { + Mesh &mesh = (Mesh &)(*n.get()); + if (mesh.vertex_positions.size() && mesh.texture_coords.size()) { + pScene->mNumMeshes += static_cast<unsigned int>(mesh.temp_map.size()); + } + } + } + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes](); + pScene->mMaterials = new aiMaterial *[pScene->mNumMeshes](); + pScene->mNumMeshes = 0; + + // count lights and cameras + for (std::shared_ptr<Node> &n : scene.nodes) { + if (n->type == Node::TYPE_LIGHT) { + ++pScene->mNumLights; + } else if (n->type == Node::TYPE_CAMERA) { + ++pScene->mNumCameras; + } + } + + if (pScene->mNumLights) { + pScene->mLights = new aiLight *[pScene->mNumLights](); + } + if (pScene->mNumCameras) { + pScene->mCameras = new aiCamera *[pScene->mNumCameras](); + } + pScene->mNumLights = pScene->mNumCameras = 0; + + // resolve parents by their IDs and build the output graph + std::unique_ptr<Node> root(new Group()); + for (size_t n = 0; n < scene.nodes.size(); ++n) { + const Node &nn = *scene.nodes[n].get(); + if (nn.parent_id == 0) { + root->temp_children.push_back(&nn); + } + + for (size_t m = n; m < scene.nodes.size(); ++m) { + const Node &mm = *scene.nodes[m].get(); + if (mm.parent_id == nn.id) { + nn.temp_children.push_back(&mm); + } + } + } + + pScene->mRootNode = BuildNodes(*root.get(), scene, pScene); + //flip normals after import + FlipWindingOrderProcess flip; + flip.Execute(pScene); +} + +// ------------------------------------------------------------------------------------------------ +void ConvertTexture(const std::shared_ptr<Texture> &tex, aiMaterial *out, aiTextureType type) { + const aiString path(tex->path); + out->AddProperty(&path, AI_MATKEY_TEXTURE(type, 0)); + out->AddProperty(&tex->transform, 1, AI_MATKEY_UVTRANSFORM(type, 0)); +} + +// ------------------------------------------------------------------------------------------------ +aiNode *COBImporter::BuildNodes(const Node &root, const Scene &scin, aiScene *fill) { + aiNode *nd = new aiNode(); + nd->mName.Set(root.name); + nd->mTransformation = root.transform; + + // Note to everybody believing Voodoo is appropriate here: + // I know polymorphism, run as fast as you can ;-) + if (Node::TYPE_MESH == root.type) { + const Mesh &ndmesh = (const Mesh &)(root); + if (ndmesh.vertex_positions.size() && ndmesh.texture_coords.size()) { + + typedef std::pair<const unsigned int, Mesh::FaceRefList> Entry; + for (const Entry &reflist : ndmesh.temp_map) { + { // create mesh + size_t n = 0; + for (Face *f : reflist.second) { + n += f->indices.size(); + } + if (!n) { + continue; + } + aiMesh *outmesh = fill->mMeshes[fill->mNumMeshes++] = new aiMesh(); + ++nd->mNumMeshes; + + outmesh->mVertices = new aiVector3D[n]; + outmesh->mTextureCoords[0] = new aiVector3D[n]; + + outmesh->mFaces = new aiFace[reflist.second.size()](); + for (Face *f : reflist.second) { + if (f->indices.empty()) { + continue; + } + + aiFace &fout = outmesh->mFaces[outmesh->mNumFaces++]; + fout.mIndices = new unsigned int[f->indices.size()]; + + for (VertexIndex &v : f->indices) { + if (v.pos_idx >= ndmesh.vertex_positions.size()) { + ThrowException("Position index out of range"); + } + if (v.uv_idx >= ndmesh.texture_coords.size()) { + ThrowException("UV index out of range"); + } + outmesh->mVertices[outmesh->mNumVertices] = ndmesh.vertex_positions[v.pos_idx]; + outmesh->mTextureCoords[0][outmesh->mNumVertices] = aiVector3D( + ndmesh.texture_coords[v.uv_idx].x, + ndmesh.texture_coords[v.uv_idx].y, + 0.f); + + fout.mIndices[fout.mNumIndices++] = outmesh->mNumVertices++; + } + } + outmesh->mMaterialIndex = fill->mNumMaterials; + } + { // create material + const Material *min = nullptr; + for (const Material &m : scin.materials) { + if (m.parent_id == ndmesh.id && m.matnum == reflist.first) { + min = &m; + break; + } + } + std::unique_ptr<const Material> defmat; + if (!min) { + ASSIMP_LOG_VERBOSE_DEBUG("Could not resolve material index ", reflist.first, " - creating default material for this slot"); + + defmat.reset(min = new Material()); + } + + aiMaterial *mat = new aiMaterial(); + fill->mMaterials[fill->mNumMaterials++] = mat; + + const aiString s(format("#mat_") << fill->mNumMeshes << "_" << min->matnum); + mat->AddProperty(&s, AI_MATKEY_NAME); + + if (int tmp = ndmesh.draw_flags & Mesh::WIRED ? 1 : 0) { + mat->AddProperty(&tmp, 1, AI_MATKEY_ENABLE_WIREFRAME); + } + + { + int shader; + switch (min->shader) { + case Material::FLAT: + shader = aiShadingMode_Gouraud; + break; + + case Material::PHONG: + shader = aiShadingMode_Phong; + break; + + case Material::METAL: + shader = aiShadingMode_CookTorrance; + break; + + default: + ASSIMP_LOG_ERROR("Unknown option."); + ai_assert(false); // shouldn't be here + break; + } + mat->AddProperty(&shader, 1, AI_MATKEY_SHADING_MODEL); + if (shader != aiShadingMode_Gouraud) { + mat->AddProperty(&min->exp, 1, AI_MATKEY_SHININESS); + } + } + + mat->AddProperty(&min->ior, 1, AI_MATKEY_REFRACTI); + mat->AddProperty(&min->rgb, 1, AI_MATKEY_COLOR_DIFFUSE); + + aiColor3D c = aiColor3D(min->rgb) * min->ks; + mat->AddProperty(&c, 1, AI_MATKEY_COLOR_SPECULAR); + + c = aiColor3D(min->rgb) * min->ka; + mat->AddProperty(&c, 1, AI_MATKEY_COLOR_AMBIENT); + + // convert textures if some exist. + if (min->tex_color) { + ConvertTexture(min->tex_color, mat, aiTextureType_DIFFUSE); + } + if (min->tex_env) { + ConvertTexture(min->tex_env, mat, aiTextureType_UNKNOWN); + } + if (min->tex_bump) { + ConvertTexture(min->tex_bump, mat, aiTextureType_HEIGHT); + } + } + } + } + } else if (Node::TYPE_LIGHT == root.type) { + const Light &ndlight = (const Light &)(root); + aiLight *outlight = fill->mLights[fill->mNumLights++] = new aiLight(); + + outlight->mName.Set(ndlight.name); + outlight->mColorDiffuse = outlight->mColorAmbient = outlight->mColorSpecular = ndlight.color; + + outlight->mAngleOuterCone = AI_DEG_TO_RAD(ndlight.angle); + outlight->mAngleInnerCone = AI_DEG_TO_RAD(ndlight.inner_angle); + + // XXX + outlight->mType = ndlight.ltype == Light::SPOT ? aiLightSource_SPOT : aiLightSource_DIRECTIONAL; + } else if (Node::TYPE_CAMERA == root.type) { + const Camera &ndcam = (const Camera &)(root); + aiCamera *outcam = fill->mCameras[fill->mNumCameras++] = new aiCamera(); + + outcam->mName.Set(ndcam.name); + } + + // add meshes + if (nd->mNumMeshes) { // mMeshes must be nullptr if count is 0 + nd->mMeshes = new unsigned int[nd->mNumMeshes]; + for (unsigned int i = 0; i < nd->mNumMeshes; ++i) { + nd->mMeshes[i] = fill->mNumMeshes - i - 1; + } + } + + // add children recursively + nd->mChildren = new aiNode *[root.temp_children.size()](); + for (const Node *n : root.temp_children) { + (nd->mChildren[nd->mNumChildren++] = BuildNodes(*n, scin, fill))->mParent = nd; + } + + return nd; +} + +// ------------------------------------------------------------------------------------------------ +// Read an ASCII file into the given scene data structure +void COBImporter::ReadAsciiFile(Scene &out, StreamReaderLE *stream) { + ChunkInfo ci; + for (LineSplitter splitter(*stream); splitter; ++splitter) { + + // add all chunks to be recognized here. /else ../ omitted intentionally. + if (splitter.match_start("PolH ")) { + ReadChunkInfo_Ascii(ci, splitter); + ReadPolH_Ascii(out, splitter, ci); + } + if (splitter.match_start("BitM ")) { + ReadChunkInfo_Ascii(ci, splitter); + ReadBitM_Ascii(out, splitter, ci); + } + if (splitter.match_start("Mat1 ")) { + ReadChunkInfo_Ascii(ci, splitter); + ReadMat1_Ascii(out, splitter, ci); + } + if (splitter.match_start("Grou ")) { + ReadChunkInfo_Ascii(ci, splitter); + ReadGrou_Ascii(out, splitter, ci); + } + if (splitter.match_start("Lght ")) { + ReadChunkInfo_Ascii(ci, splitter); + ReadLght_Ascii(out, splitter, ci); + } + if (splitter.match_start("Came ")) { + ReadChunkInfo_Ascii(ci, splitter); + ReadCame_Ascii(out, splitter, ci); + } + if (splitter.match_start("Bone ")) { + ReadChunkInfo_Ascii(ci, splitter); + ReadBone_Ascii(out, splitter, ci); + } + if (splitter.match_start("Chan ")) { + ReadChunkInfo_Ascii(ci, splitter); + ReadChan_Ascii(out, splitter, ci); + } + if (splitter.match_start("Unit ")) { + ReadChunkInfo_Ascii(ci, splitter); + ReadUnit_Ascii(out, splitter, ci); + } + if (splitter.match_start("END ")) { + // we don't need this, but I guess there is a reason this + // chunk has been implemented into COB for. + return; + } + } +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadChunkInfo_Ascii(ChunkInfo &out, const LineSplitter &splitter) { + const char *all_tokens[8]; + splitter.get_tokens(all_tokens); + + out.version = (all_tokens[1][1] - '0') * 100 + (all_tokens[1][3] - '0') * 10 + (all_tokens[1][4] - '0'); + out.id = strtoul10(all_tokens[3]); + out.parent_id = strtoul10(all_tokens[5]); + out.size = strtol10(all_tokens[7]); +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::UnsupportedChunk_Ascii(LineSplitter &splitter, const ChunkInfo &nfo, const char *name) { + const std::string error = format("Encountered unsupported chunk: ") << name << " [version: " << nfo.version << ", size: " << nfo.size << "]"; + + // we can recover if the chunk size was specified. + if (nfo.size != static_cast<unsigned int>(-1)) { + ASSIMP_LOG_ERROR(error); + + // (HACK) - our current position in the stream is the beginning of the + // head line of the next chunk. That's fine, but the caller is going + // to call ++ on `splitter`, which we need to swallow to avoid + // missing the next line. + splitter.get_stream().IncPtr(nfo.size); + splitter.swallow_next_increment(); + } else { + ThrowException(error); + } +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadBasicNodeInfo_Ascii(Node &msh, LineSplitter &splitter, const ChunkInfo & /*nfo*/) { + for (; splitter; ++splitter) { + if (splitter.match_start("Name")) { + msh.name = std::string(splitter[1]); + + // make nice names by merging the dupe count + std::replace(msh.name.begin(), msh.name.end(), + ',', '_'); + } else if (splitter.match_start("Transform")) { + for (unsigned int y = 0; y < 4 && ++splitter; ++y) { + const char *s = splitter->c_str(); + for (unsigned int x = 0; x < 4; ++x) { + SkipSpaces(&s); + msh.transform[y][x] = fast_atof(&s); + } + } + // we need the transform chunk, so we won't return until we have it. + return; + } + } +} + +// ------------------------------------------------------------------------------------------------ +template <typename T> +void COBImporter::ReadFloat3Tuple_Ascii(T &fill, const char **in) { + const char *rgb = *in; + for (unsigned int i = 0; i < 3; ++i) { + SkipSpaces(&rgb); + if (*rgb == ',') ++rgb; + SkipSpaces(&rgb); + + fill[i] = fast_atof(&rgb); + } + *in = rgb; +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadMat1_Ascii(Scene &out, LineSplitter &splitter, const ChunkInfo &nfo) { + if (nfo.version > 8) { + return UnsupportedChunk_Ascii(splitter, nfo, "Mat1"); + } + + ++splitter; + if (!splitter.match_start("mat# ")) { + ASSIMP_LOG_WARN("Expected `mat#` line in `Mat1` chunk ", nfo.id); + return; + } + + out.materials.push_back(Material()); + Material &mat = out.materials.back(); + mat = nfo; + + mat.matnum = strtoul10(splitter[1]); + ++splitter; + + if (!splitter.match_start("shader: ")) { + ASSIMP_LOG_WARN("Expected `mat#` line in `Mat1` chunk ", nfo.id); + return; + } + std::string shader = std::string(splitter[1]); + shader = shader.substr(0, shader.find_first_of(" \t")); + + if (shader == "metal") { + mat.shader = Material::METAL; + } else if (shader == "phong") { + mat.shader = Material::PHONG; + } else if (shader != "flat") { + ASSIMP_LOG_WARN("Unknown value for `shader` in `Mat1` chunk ", nfo.id); + } + + ++splitter; + if (!splitter.match_start("rgb ")) { + ASSIMP_LOG_WARN("Expected `rgb` line in `Mat1` chunk ", nfo.id); + } + + const char *rgb = splitter[1]; + ReadFloat3Tuple_Ascii(mat.rgb, &rgb); + + ++splitter; + if (!splitter.match_start("alpha ")) { + ASSIMP_LOG_WARN("Expected `alpha` line in `Mat1` chunk ", nfo.id); + } + + const char *tokens[10]; + splitter.get_tokens(tokens); + + mat.alpha = fast_atof(tokens[1]); + mat.ka = fast_atof(tokens[3]); + mat.ks = fast_atof(tokens[5]); + mat.exp = fast_atof(tokens[7]); + mat.ior = fast_atof(tokens[9]); +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadUnit_Ascii(Scene &out, LineSplitter &splitter, const ChunkInfo &nfo) { + if (nfo.version > 1) { + return UnsupportedChunk_Ascii(splitter, nfo, "Unit"); + } + ++splitter; + if (!splitter.match_start("Units ")) { + ASSIMP_LOG_WARN("Expected `Units` line in `Unit` chunk ", nfo.id); + return; + } + + // parent chunks preceede their children, so we should have the + // corresponding chunk already. + for (std::shared_ptr<Node> &nd : out.nodes) { + if (nd->id == nfo.parent_id) { + const unsigned int t = strtoul10(splitter[1]); + + nd->unit_scale = t >= sizeof(units) / sizeof(units[0]) ? ( + ASSIMP_LOG_WARN(t, " is not a valid value for `Units` attribute in `Unit chunk` ", nfo.id), 1.f) : + units[t]; + return; + } + } + ASSIMP_LOG_WARN("`Unit` chunk ", nfo.id, " is a child of ", nfo.parent_id, " which does not exist"); +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadChan_Ascii(Scene & /*out*/, LineSplitter &splitter, const ChunkInfo &nfo) { + if (nfo.version > 8) { + return UnsupportedChunk_Ascii(splitter, nfo, "Chan"); + } +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadLght_Ascii(Scene &out, LineSplitter &splitter, const ChunkInfo &nfo) { + if (nfo.version > 8) { + return UnsupportedChunk_Ascii(splitter, nfo, "Lght"); + } + + out.nodes.push_back(std::shared_ptr<Light>(new Light())); + Light &msh = (Light &)(*out.nodes.back().get()); + msh = nfo; + + ReadBasicNodeInfo_Ascii(msh, ++splitter, nfo); + + if (splitter.match_start("Infinite ")) { + msh.ltype = Light::INFINITE; + } else if (splitter.match_start("Local ")) { + msh.ltype = Light::LOCAL; + } else if (splitter.match_start("Spot ")) { + msh.ltype = Light::SPOT; + } else { + ASSIMP_LOG_WARN("Unknown kind of light source in `Lght` chunk ", nfo.id, " : ", *splitter); + msh.ltype = Light::SPOT; + } + + ++splitter; + if (!splitter.match_start("color ")) { + ASSIMP_LOG_WARN("Expected `color` line in `Lght` chunk ", nfo.id); + } + + const char *rgb = splitter[1]; + ReadFloat3Tuple_Ascii(msh.color, &rgb); + + SkipSpaces(&rgb); + if (strncmp(rgb, "cone angle", 10) != 0) { + ASSIMP_LOG_WARN("Expected `cone angle` entity in `color` line in `Lght` chunk ", nfo.id); + } + SkipSpaces(rgb + 10, &rgb); + msh.angle = fast_atof(&rgb); + + SkipSpaces(&rgb); + if (strncmp(rgb, "inner angle", 11) != 0) { + ASSIMP_LOG_WARN("Expected `inner angle` entity in `color` line in `Lght` chunk ", nfo.id); + } + SkipSpaces(rgb + 11, &rgb); + msh.inner_angle = fast_atof(&rgb); + + // skip the rest for we can't handle this kind of physically-based lighting information. +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadCame_Ascii(Scene &out, LineSplitter &splitter, const ChunkInfo &nfo) { + if (nfo.version > 2) { + return UnsupportedChunk_Ascii(splitter, nfo, "Came"); + } + + out.nodes.push_back(std::shared_ptr<Camera>(new Camera())); + Camera &msh = (Camera &)(*out.nodes.back().get()); + msh = nfo; + + ReadBasicNodeInfo_Ascii(msh, ++splitter, nfo); + + // skip the next line, we don't know this differentiation between a + // standard camera and a panoramic camera. + ++splitter; +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadBone_Ascii(Scene &out, LineSplitter &splitter, const ChunkInfo &nfo) { + if (nfo.version > 5) { + return UnsupportedChunk_Ascii(splitter, nfo, "Bone"); + } + + out.nodes.push_back(std::shared_ptr<Bone>(new Bone())); + Bone &msh = (Bone &)(*out.nodes.back().get()); + msh = nfo; + + ReadBasicNodeInfo_Ascii(msh, ++splitter, nfo); + + // TODO +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadGrou_Ascii(Scene &out, LineSplitter &splitter, const ChunkInfo &nfo) { + if (nfo.version > 1) { + return UnsupportedChunk_Ascii(splitter, nfo, "Grou"); + } + + out.nodes.push_back(std::shared_ptr<Group>(new Group())); + Group &msh = (Group &)(*out.nodes.back().get()); + msh = nfo; + + ReadBasicNodeInfo_Ascii(msh, ++splitter, nfo); +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadPolH_Ascii(Scene &out, LineSplitter &splitter, const ChunkInfo &nfo) { + if (nfo.version > 8) { + return UnsupportedChunk_Ascii(splitter, nfo, "PolH"); + } + + out.nodes.push_back(std::shared_ptr<Mesh>(new Mesh())); + Mesh &msh = (Mesh &)(*out.nodes.back().get()); + msh = nfo; + + ReadBasicNodeInfo_Ascii(msh, ++splitter, nfo); + + // the chunk has a fixed order of components, but some are not interesting of us so + // we're just looking for keywords in arbitrary order. The end of the chunk is + // either the last `Face` or the `DrawFlags` attribute, depending on the format ver. + for (; splitter; ++splitter) { + if (splitter.match_start("World Vertices")) { + const unsigned int cnt = strtoul10(splitter[2]); + msh.vertex_positions.resize(cnt); + + for (unsigned int cur = 0; cur < cnt && ++splitter; ++cur) { + const char *s = splitter->c_str(); + + aiVector3D &v = msh.vertex_positions[cur]; + + SkipSpaces(&s); + v.x = fast_atof(&s); + SkipSpaces(&s); + v.y = fast_atof(&s); + SkipSpaces(&s); + v.z = fast_atof(&s); + } + } else if (splitter.match_start("Texture Vertices")) { + const unsigned int cnt = strtoul10(splitter[2]); + msh.texture_coords.resize(cnt); + + for (unsigned int cur = 0; cur < cnt && ++splitter; ++cur) { + const char *s = splitter->c_str(); + + aiVector2D &v = msh.texture_coords[cur]; + + SkipSpaces(&s); + v.x = fast_atof(&s); + SkipSpaces(&s); + v.y = fast_atof(&s); + } + } else if (splitter.match_start("Faces")) { + const unsigned int cnt = strtoul10(splitter[1]); + msh.faces.reserve(cnt); + + for (unsigned int cur = 0; cur < cnt && ++splitter; ++cur) { + if (splitter.match_start("Hole")) { + ASSIMP_LOG_WARN("Skipping unsupported `Hole` line"); + continue; + } + + if (!splitter.match_start("Face")) { + ThrowException("Expected Face line"); + } + + msh.faces.push_back(Face()); + Face &face = msh.faces.back(); + + face.indices.resize(strtoul10(splitter[2])); + face.flags = strtoul10(splitter[4]); + face.material = strtoul10(splitter[6]); + + const char *s = (++splitter)->c_str(); + for (size_t i = 0; i < face.indices.size(); ++i) { + if (!SkipSpaces(&s)) { + ThrowException("Expected EOL token in Face entry"); + } + if ('<' != *s++) { + ThrowException("Expected < token in Face entry"); + } + face.indices[i].pos_idx = strtoul10(s, &s); + if (',' != *s++) { + ThrowException("Expected , token in Face entry"); + } + face.indices[i].uv_idx = strtoul10(s, &s); + if ('>' != *s++) { + ThrowException("Expected < token in Face entry"); + } + } + } + if (nfo.version <= 4) { + break; + } + } else if (splitter.match_start("DrawFlags")) { + msh.draw_flags = strtoul10(splitter[1]); + break; + } + } +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadBitM_Ascii(Scene & /*out*/, LineSplitter &splitter, const ChunkInfo &nfo) { + if (nfo.version > 1) { + return UnsupportedChunk_Ascii(splitter, nfo, "BitM"); + } + + const unsigned int head = strtoul10((++splitter)[1]); + if (head != sizeof(Bitmap::BitmapHeader)) { + ASSIMP_LOG_WARN("Unexpected ThumbNailHdrSize, skipping this chunk"); + return; + } +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadString_Binary(std::string &out, StreamReaderLE &reader) { + out.resize(reader.GetI2()); + for (char &c : out) { + c = reader.GetI1(); + } +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadBasicNodeInfo_Binary(Node &msh, StreamReaderLE &reader, const ChunkInfo & /*nfo*/) { + const unsigned int dupes = reader.GetI2(); + ReadString_Binary(msh.name, reader); + + msh.name = format(msh.name) << '_' << dupes; + + // skip local axes for the moment + reader.IncPtr(48); + + msh.transform = aiMatrix4x4(); + for (unsigned int y = 0; y < 3; ++y) { + for (unsigned int x = 0; x < 4; ++x) { + msh.transform[y][x] = reader.GetF4(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::UnsupportedChunk_Binary(StreamReaderLE &reader, const ChunkInfo &nfo, const char *name) { + const std::string error = format("Encountered unsupported chunk: ") << name << " [version: " << nfo.version << ", size: " << nfo.size << "]"; + + // we can recover if the chunk size was specified. + if (nfo.size != static_cast<unsigned int>(-1)) { + ASSIMP_LOG_ERROR(error); + reader.IncPtr(nfo.size); + } else + ThrowException(error); +} + +// ------------------------------------------------------------------------------------------------ +// tiny utility guard to aid me at staying within chunk boundaries. +class chunk_guard { +public: + chunk_guard(const COB::ChunkInfo &nfo, StreamReaderLE &reader) : + nfo(nfo), reader(reader), cur(reader.GetCurrentPos()) { + // empty + } + + ~chunk_guard() { + // don't do anything if the size is not given + if (nfo.size != static_cast<unsigned int>(-1)) { + try { + reader.IncPtr(static_cast<int>(nfo.size) - reader.GetCurrentPos() + cur); + } catch (const DeadlyImportError &) { + // out of limit so correct the value + reader.IncPtr(reader.GetReadLimit()); + } + } + } + +private: + const COB::ChunkInfo &nfo; + StreamReaderLE &reader; + long cur; +}; + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadBinaryFile(Scene &out, StreamReaderLE *reader) { + if (nullptr == reader) { + return; + } + + while (1) { + std::string type; + type += reader->GetI1(); + type += reader->GetI1(); + type += reader->GetI1(); + type += reader->GetI1(); + + ChunkInfo nfo; + nfo.version = reader->GetI2() * 10; + nfo.version += reader->GetI2(); + + nfo.id = reader->GetI4(); + nfo.parent_id = reader->GetI4(); + nfo.size = reader->GetI4(); + + if (type == "PolH") { + ReadPolH_Binary(out, *reader, nfo); + } else if (type == "BitM") { + ReadBitM_Binary(out, *reader, nfo); + } else if (type == "Grou") { + ReadGrou_Binary(out, *reader, nfo); + } else if (type == "Lght") { + ReadLght_Binary(out, *reader, nfo); + } else if (type == "Came") { + ReadCame_Binary(out, *reader, nfo); + } else if (type == "Mat1") { + ReadMat1_Binary(out, *reader, nfo); + } else if (type == "Unit") { + ReadUnit_Binary(out, *reader, nfo); + } else if (type == "OLay") { + // ignore layer index silently. + if (nfo.size != static_cast<unsigned int>(-1)) { + reader->IncPtr(nfo.size); + } else + return UnsupportedChunk_Binary(*reader, nfo, type.c_str()); + } else if (type == "END ") { + return; + } else { + UnsupportedChunk_Binary(*reader, nfo, type.c_str()); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadPolH_Binary(COB::Scene &out, StreamReaderLE &reader, const ChunkInfo &nfo) { + if (nfo.version > 8) { + return UnsupportedChunk_Binary(reader, nfo, "PolH"); + } + const chunk_guard cn(nfo, reader); + + out.nodes.push_back(std::shared_ptr<Mesh>(new Mesh())); + Mesh &msh = (Mesh &)(*out.nodes.back().get()); + msh = nfo; + + ReadBasicNodeInfo_Binary(msh, reader, nfo); + + msh.vertex_positions.resize(reader.GetI4()); + for (aiVector3D &v : msh.vertex_positions) { + v.x = reader.GetF4(); + v.y = reader.GetF4(); + v.z = reader.GetF4(); + } + + msh.texture_coords.resize(reader.GetI4()); + for (aiVector2D &v : msh.texture_coords) { + v.x = reader.GetF4(); + v.y = reader.GetF4(); + } + + const size_t numf = reader.GetI4(); + msh.faces.reserve(numf); + for (size_t i = 0; i < numf; ++i) { + // XXX backface culling flag is 0x10 in flags + + // hole? + bool hole = (reader.GetI1() & 0x08) != 0; + if (hole) { + // XXX Basically this should just work fine - then triangulator + // should output properly triangulated data even for polygons + // with holes. Test data specific to COB is needed to confirm it. + if (msh.faces.empty()) { + ThrowException(format("A hole is the first entity in the `PolH` chunk with id ") << nfo.id); + } + } else + msh.faces.push_back(Face()); + Face &f = msh.faces.back(); + + const size_t num = reader.GetI2(); + f.indices.reserve(f.indices.size() + num); + + if (!hole) { + f.material = reader.GetI2(); + f.flags = 0; + } + + for (size_t x = 0; x < num; ++x) { + f.indices.push_back(VertexIndex()); + + VertexIndex &v = f.indices.back(); + v.pos_idx = reader.GetI4(); + v.uv_idx = reader.GetI4(); + } + + if (hole) { + std::reverse(f.indices.rbegin(), f.indices.rbegin() + num); + } + } + if (nfo.version > 4) { + msh.draw_flags = reader.GetI4(); + } + nfo.version > 5 && nfo.version < 8 ? reader.GetI4() : 0; +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadBitM_Binary(COB::Scene & /*out*/, StreamReaderLE &reader, const ChunkInfo &nfo) { + if (nfo.version > 1) { + return UnsupportedChunk_Binary(reader, nfo, "BitM"); + } + + const chunk_guard cn(nfo, reader); + + const uint32_t len = reader.GetI4(); + reader.IncPtr(len); + + reader.GetI4(); + reader.IncPtr(reader.GetI4()); +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadMat1_Binary(COB::Scene &out, StreamReaderLE &reader, const ChunkInfo &nfo) { + if (nfo.version > 8) { + return UnsupportedChunk_Binary(reader, nfo, "Mat1"); + } + + const chunk_guard cn(nfo, reader); + + out.materials.push_back(Material()); + Material &mat = out.materials.back(); + mat = nfo; + + mat.matnum = reader.GetI2(); + switch (reader.GetI1()) { + case 'f': + mat.type = Material::FLAT; + break; + case 'p': + mat.type = Material::PHONG; + break; + case 'm': + mat.type = Material::METAL; + break; + default: + ASSIMP_LOG_ERROR("Unrecognized shader type in `Mat1` chunk with id ", nfo.id); + mat.type = Material::FLAT; + } + + switch (reader.GetI1()) { + case 'f': + mat.autofacet = Material::FACETED; + break; + case 'a': + mat.autofacet = Material::AUTOFACETED; + break; + case 's': + mat.autofacet = Material::SMOOTH; + break; + default: + ASSIMP_LOG_ERROR("Unrecognized faceting mode in `Mat1` chunk with id ", nfo.id); + mat.autofacet = Material::FACETED; + } + mat.autofacet_angle = static_cast<float>(reader.GetI1()); + + mat.rgb.r = reader.GetF4(); + mat.rgb.g = reader.GetF4(); + mat.rgb.b = reader.GetF4(); + + mat.alpha = reader.GetF4(); + mat.ka = reader.GetF4(); + mat.ks = reader.GetF4(); + mat.exp = reader.GetF4(); + mat.ior = reader.GetF4(); + + char id[2]; + id[0] = reader.GetI1(), id[1] = reader.GetI1(); + + if (id[0] == 'e' && id[1] == ':') { + mat.tex_env.reset(new Texture()); + + reader.GetI1(); + ReadString_Binary(mat.tex_env->path, reader); + + // advance to next texture-id + id[0] = reader.GetI1(), id[1] = reader.GetI1(); + } + + if (id[0] == 't' && id[1] == ':') { + mat.tex_color.reset(new Texture()); + + reader.GetI1(); + ReadString_Binary(mat.tex_color->path, reader); + + mat.tex_color->transform.mTranslation.x = reader.GetF4(); + mat.tex_color->transform.mTranslation.y = reader.GetF4(); + + mat.tex_color->transform.mScaling.x = reader.GetF4(); + mat.tex_color->transform.mScaling.y = reader.GetF4(); + + // advance to next texture-id + id[0] = reader.GetI1(), id[1] = reader.GetI1(); + } + + if (id[0] == 'b' && id[1] == ':') { + mat.tex_bump.reset(new Texture()); + + reader.GetI1(); + ReadString_Binary(mat.tex_bump->path, reader); + + mat.tex_bump->transform.mTranslation.x = reader.GetF4(); + mat.tex_bump->transform.mTranslation.y = reader.GetF4(); + + mat.tex_bump->transform.mScaling.x = reader.GetF4(); + mat.tex_bump->transform.mScaling.y = reader.GetF4(); + + // skip amplitude for I don't know its purpose. + reader.GetF4(); + } + reader.IncPtr(-2); +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadCame_Binary(COB::Scene &out, StreamReaderLE &reader, const ChunkInfo &nfo) { + if (nfo.version > 2) { + return UnsupportedChunk_Binary(reader, nfo, "Came"); + } + + const chunk_guard cn(nfo, reader); + + out.nodes.push_back(std::shared_ptr<Camera>(new Camera())); + Camera &msh = (Camera &)(*out.nodes.back().get()); + msh = nfo; + + ReadBasicNodeInfo_Binary(msh, reader, nfo); + + // the rest is not interesting for us, so we skip over it. + if (nfo.version > 1) { + if (reader.GetI2() == 512) { + reader.IncPtr(42); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadLght_Binary(COB::Scene &out, StreamReaderLE &reader, const ChunkInfo &nfo) { + if (nfo.version > 2) { + return UnsupportedChunk_Binary(reader, nfo, "Lght"); + } + + const chunk_guard cn(nfo, reader); + + out.nodes.push_back(std::shared_ptr<Light>(new Light())); + Light &msh = (Light &)(*out.nodes.back().get()); + msh = nfo; + + ReadBasicNodeInfo_Binary(msh, reader, nfo); +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadGrou_Binary(COB::Scene &out, StreamReaderLE &reader, const ChunkInfo &nfo) { + if (nfo.version > 2) { + return UnsupportedChunk_Binary(reader, nfo, "Grou"); + } + + const chunk_guard cn(nfo, reader); + + out.nodes.push_back(std::make_shared<Group>()); + Group &msh = (Group &)(*out.nodes.back().get()); + msh = nfo; + + ReadBasicNodeInfo_Binary(msh, reader, nfo); +} + +// ------------------------------------------------------------------------------------------------ +void COBImporter::ReadUnit_Binary(COB::Scene &out, StreamReaderLE &reader, const ChunkInfo &nfo) { + if (nfo.version > 1) { + return UnsupportedChunk_Binary(reader, nfo, "Unit"); + } + + const chunk_guard cn(nfo, reader); + + // parent chunks preceede their children, so we should have the + // corresponding chunk already. + for (std::shared_ptr<Node> &nd : out.nodes) { + if (nd->id == nfo.parent_id) { + const unsigned int t = reader.GetI2(); + nd->unit_scale = t >= sizeof(units) / sizeof(units[0]) ? ( + ASSIMP_LOG_WARN(t, " is not a valid value for `Units` attribute in `Unit chunk` ", nfo.id), 1.f) : + units[t]; + + return; + } + } + ASSIMP_LOG_WARN("`Unit` chunk ", nfo.id, " is a child of ", nfo.parent_id, " which does not exist"); +} + +#endif // ASSIMP_BUILD_NO_COB_IMPORTER |