assimp/code/AssetLib/COB/COBLoader.cpp

1180 lines
42 KiB
C++
Raw Normal View History

/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
2022-01-10 20:13:43 +00:00
Copyright (c) 2006-2022, assimp team
2018-01-28 18:42:05 +00:00
All rights reserved.
2015-05-19 03:52:10 +00:00
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.
2015-05-19 03:52:10 +00:00
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
2015-05-19 03:52:10 +00:00
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2015-05-19 03:52:10 +00:00
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2015-05-19 03:52:10 +00:00
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>
2017-02-22 16:20:26 +00:00
#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[] = {
2015-05-19 03:57:13 +00:00
1000.f,
100.f,
1.f,
0.001f,
1.f / 0.0254f,
1.f / 0.3048f,
1.f / 0.9144f,
1.f / 1609.344f
2015-05-19 03:52:10 +00:00
};
static const aiImporterDesc desc = {
2015-05-19 03:57:13 +00:00
"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
}
// ------------------------------------------------------------------------------------------------
2015-05-19 03:52:10 +00:00
// Destructor, private as well
COBImporter::~COBImporter() {
// empty
}
// ------------------------------------------------------------------------------------------------
2015-05-19 03:52:10 +00:00
// 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 {
2015-05-19 03:57:13 +00:00
return &desc;
}
// ------------------------------------------------------------------------------------------------
// Setup configuration properties for the loader
void COBImporter::SetupProperties(const Importer * /*pImp*/) {
2015-05-19 03:57:13 +00:00
// nothing to be done for the moment
}
// ------------------------------------------------------------------------------------------------
/*static*/ AI_WONT_RETURN void COBImporter::ThrowException(const std::string &msg) {
throw DeadlyImportError("COB: ", msg);
}
// ------------------------------------------------------------------------------------------------
2015-05-19 03:52:10 +00:00
// Imports the given file into the given scene structure.
void COBImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
2015-05-19 03:57:13 +00:00
COB::Scene scene;
auto file = pIOHandler->Open(pFile, "rb");
if (!file) {
ThrowException("Could not open " + pFile);
}
std::unique_ptr<StreamReaderLE> stream(new StreamReaderLE(file));
2015-05-19 03:57:13 +00:00
// check header
char head[32];
stream->CopyAndAdvance(head, 32);
if (strncmp(head, "Caligari ", 9) != 0) {
2015-05-19 03:57:13 +00:00
ThrowException("Could not found magic id: `Caligari`");
}
ASSIMP_LOG_INFO("File format tag: ", std::string(head + 9, 6));
if (head[16] != 'L') {
2015-05-19 03:57:13 +00:00
ThrowException("File is big-endian, which is not supported");
}
// load data into intermediate structures
if (head[15] == 'A') {
2015-05-19 03:57:13 +00:00
ReadAsciiFile(scene, stream.get());
} else {
2015-05-19 03:57:13 +00:00
ReadBinaryFile(scene, stream.get());
}
if (scene.nodes.empty()) {
2015-05-19 03:57:13 +00:00
ThrowException("No nodes loaded");
}
// sort faces by material indices
for (std::shared_ptr<Node> &n : scene.nodes) {
2015-05-19 03:57:13 +00:00
if (n->type == Node::TYPE_MESH) {
Mesh &mesh = (Mesh &)(*n.get());
for (Face &f : mesh.faces) {
2015-05-19 03:57:13 +00:00
mesh.temp_map[f.material].push_back(&f);
}
}
}
// count meshes
for (std::shared_ptr<Node> &n : scene.nodes) {
2015-05-19 03:57:13 +00:00
if (n->type == Node::TYPE_MESH) {
Mesh &mesh = (Mesh &)(*n.get());
2015-05-19 03:57:13 +00:00
if (mesh.vertex_positions.size() && mesh.texture_coords.size()) {
pScene->mNumMeshes += static_cast<unsigned int>(mesh.temp_map.size());
2015-05-19 03:57:13 +00:00
}
}
}
pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]();
pScene->mMaterials = new aiMaterial *[pScene->mNumMeshes]();
2015-05-19 03:57:13 +00:00
pScene->mNumMeshes = 0;
// count lights and cameras
for (std::shared_ptr<Node> &n : scene.nodes) {
2015-05-19 03:57:13 +00:00
if (n->type == Node::TYPE_LIGHT) {
++pScene->mNumLights;
} else if (n->type == Node::TYPE_CAMERA) {
2015-05-19 03:57:13 +00:00
++pScene->mNumCameras;
}
}
if (pScene->mNumLights) {
pScene->mLights = new aiLight *[pScene->mNumLights]();
2015-05-19 03:57:13 +00:00
}
if (pScene->mNumCameras) {
pScene->mCameras = new aiCamera *[pScene->mNumCameras]();
2015-05-19 03:57:13 +00:00
}
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) {
2015-05-19 03:57:13 +00:00
root->temp_children.push_back(&nn);
}
for (size_t m = n; m < scene.nodes.size(); ++m) {
const Node &mm = *scene.nodes[m].get();
2015-05-19 03:57:13 +00:00
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();
2015-05-19 03:57:13 +00:00
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);
2015-05-19 03:57:13 +00:00
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
2015-05-19 03:57:13 +00:00
size_t n = 0;
for (Face *f : reflist.second) {
2015-05-19 03:57:13 +00:00
n += f->indices.size();
}
if (!n) {
continue;
}
aiMesh *outmesh = fill->mMeshes[fill->mNumMeshes++] = new aiMesh();
2015-05-19 03:57:13 +00:00
++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) {
2015-05-19 03:57:13 +00:00
if (f->indices.empty()) {
continue;
}
aiFace &fout = outmesh->mFaces[outmesh->mNumFaces++];
2015-05-19 03:57:13 +00:00
fout.mIndices = new unsigned int[f->indices.size()];
for (VertexIndex &v : f->indices) {
2015-05-19 03:57:13 +00:00
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];
2015-05-19 03:57:13 +00:00
outmesh->mTextureCoords[0][outmesh->mNumVertices] = aiVector3D(
ndmesh.texture_coords[v.uv_idx].x,
ndmesh.texture_coords[v.uv_idx].y,
0.f);
2015-05-19 03:57:13 +00:00
fout.mIndices[fout.mNumIndices++] = outmesh->mNumVertices++;
}
}
outmesh->mMaterialIndex = fill->mNumMaterials;
}
{ // create material
const Material *min = nullptr;
for (const Material &m : scin.materials) {
2015-05-19 03:57:13 +00:00
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");
2015-05-19 03:57:13 +00:00
defmat.reset(min = new Material());
2015-05-19 03:57:13 +00:00
}
aiMaterial *mat = new aiMaterial();
2015-05-19 03:57:13 +00:00
fill->mMaterials[fill->mNumMaterials++] = mat;
const aiString s(format("#mat_") << fill->mNumMeshes << "_" << min->matnum);
mat->AddProperty(&s, AI_MATKEY_NAME);
2015-05-19 03:57:13 +00:00
if (int tmp = ndmesh.draw_flags & Mesh::WIRED ? 1 : 0) {
mat->AddProperty(&tmp, 1, AI_MATKEY_ENABLE_WIREFRAME);
2015-05-19 03:57:13 +00:00
}
{
int shader;
switch (min->shader) {
2015-05-19 03:57:13 +00:00
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.");
2015-05-19 03:57:13 +00:00
ai_assert(false); // shouldn't be here
break;
2015-05-19 03:57:13 +00:00
}
mat->AddProperty(&shader, 1, AI_MATKEY_SHADING_MODEL);
if (shader != aiShadingMode_Gouraud) {
mat->AddProperty(&min->exp, 1, AI_MATKEY_SHININESS);
2015-05-19 03:57:13 +00:00
}
}
mat->AddProperty(&min->ior, 1, AI_MATKEY_REFRACTI);
mat->AddProperty(&min->rgb, 1, AI_MATKEY_COLOR_DIFFUSE);
2015-05-19 03:57:13 +00:00
aiColor3D c = aiColor3D(min->rgb) * min->ks;
mat->AddProperty(&c, 1, AI_MATKEY_COLOR_SPECULAR);
2015-05-19 03:57:13 +00:00
c = aiColor3D(min->rgb) * min->ka;
mat->AddProperty(&c, 1, AI_MATKEY_COLOR_AMBIENT);
2015-05-19 03:57:13 +00:00
// convert textures if some exist.
if (min->tex_color) {
ConvertTexture(min->tex_color, mat, aiTextureType_DIFFUSE);
2015-05-19 03:57:13 +00:00
}
if (min->tex_env) {
ConvertTexture(min->tex_env, mat, aiTextureType_UNKNOWN);
2015-05-19 03:57:13 +00:00
}
if (min->tex_bump) {
ConvertTexture(min->tex_bump, mat, aiTextureType_HEIGHT);
2015-05-19 03:57:13 +00:00
}
}
}
}
} else if (Node::TYPE_LIGHT == root.type) {
const Light &ndlight = (const Light &)(root);
aiLight *outlight = fill->mLights[fill->mNumLights++] = new aiLight();
2015-05-19 03:57:13 +00:00
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();
2015-05-19 03:57:13 +00:00
outcam->mName.Set(ndcam.name);
}
// add meshes
if (nd->mNumMeshes) { // mMeshes must be nullptr if count is 0
2015-05-19 03:57:13 +00:00
nd->mMeshes = new unsigned int[nd->mNumMeshes];
for (unsigned int i = 0; i < nd->mNumMeshes; ++i) {
nd->mMeshes[i] = fill->mNumMeshes - i - 1;
2015-05-19 03:57:13 +00:00
}
}
// 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;
2015-05-19 03:57:13 +00:00
}
return nd;
}
// ------------------------------------------------------------------------------------------------
// Read an ASCII file into the given scene data structure
void COBImporter::ReadAsciiFile(Scene &out, StreamReaderLE *stream) {
2015-05-19 03:57:13 +00:00
ChunkInfo ci;
for (LineSplitter splitter(*stream); splitter; ++splitter) {
2015-05-19 03:57:13 +00:00
// add all chunks to be recognized here. /else ../ omitted intentionally.
if (splitter.match_start("PolH ")) {
ReadChunkInfo_Ascii(ci, splitter);
ReadPolH_Ascii(out, splitter, ci);
2015-05-19 03:57:13 +00:00
}
if (splitter.match_start("BitM ")) {
ReadChunkInfo_Ascii(ci, splitter);
ReadBitM_Ascii(out, splitter, ci);
2015-05-19 03:57:13 +00:00
}
if (splitter.match_start("Mat1 ")) {
ReadChunkInfo_Ascii(ci, splitter);
ReadMat1_Ascii(out, splitter, ci);
2015-05-19 03:57:13 +00:00
}
if (splitter.match_start("Grou ")) {
ReadChunkInfo_Ascii(ci, splitter);
ReadGrou_Ascii(out, splitter, ci);
2015-05-19 03:57:13 +00:00
}
if (splitter.match_start("Lght ")) {
ReadChunkInfo_Ascii(ci, splitter);
ReadLght_Ascii(out, splitter, ci);
2015-05-19 03:57:13 +00:00
}
if (splitter.match_start("Came ")) {
ReadChunkInfo_Ascii(ci, splitter);
ReadCame_Ascii(out, splitter, ci);
2015-05-19 03:57:13 +00:00
}
if (splitter.match_start("Bone ")) {
ReadChunkInfo_Ascii(ci, splitter);
ReadBone_Ascii(out, splitter, ci);
2015-05-19 03:57:13 +00:00
}
if (splitter.match_start("Chan ")) {
ReadChunkInfo_Ascii(ci, splitter);
ReadChan_Ascii(out, splitter, ci);
2015-05-19 03:57:13 +00:00
}
if (splitter.match_start("Unit ")) {
ReadChunkInfo_Ascii(ci, splitter);
ReadUnit_Ascii(out, splitter, ci);
2015-05-19 03:57:13 +00:00
}
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];
2015-05-19 03:57:13 +00:00
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]);
2015-05-19 03:57:13 +00:00
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 << "]";
2015-05-19 03:57:13 +00:00
// we can recover if the chunk size was specified.
if (nfo.size != static_cast<unsigned int>(-1)) {
2018-04-19 14:48:43 +00:00
ASSIMP_LOG_ERROR(error);
2015-05-19 03:57:13 +00:00
// (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) {
2015-05-19 03:57:13 +00:00
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) {
2015-05-19 03:57:13 +00:00
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) {
2015-05-19 03:57:13 +00:00
SkipSpaces(&rgb);
if (*rgb == ',') ++rgb;
2015-05-19 03:57:13 +00:00
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");
2015-05-19 03:57:13 +00:00
}
++splitter;
if (!splitter.match_start("mat# ")) {
ASSIMP_LOG_WARN("Expected `mat#` line in `Mat1` chunk ", nfo.id);
2015-05-19 03:57:13 +00:00
return;
}
out.materials.push_back(Material());
Material &mat = out.materials.back();
2015-05-19 03:57:13 +00:00
mat = nfo;
mat.matnum = strtoul10(splitter[1]);
++splitter;
if (!splitter.match_start("shader: ")) {
ASSIMP_LOG_WARN("Expected `mat#` line in `Mat1` chunk ", nfo.id);
2015-05-19 03:57:13 +00:00
return;
}
std::string shader = std::string(splitter[1]);
shader = shader.substr(0, shader.find_first_of(" \t"));
2015-05-19 03:57:13 +00:00
if (shader == "metal") {
mat.shader = Material::METAL;
} else if (shader == "phong") {
2015-05-19 03:57:13 +00:00
mat.shader = Material::PHONG;
} else if (shader != "flat") {
ASSIMP_LOG_WARN("Unknown value for `shader` in `Mat1` chunk ", nfo.id);
2015-05-19 03:57:13 +00:00
}
++splitter;
if (!splitter.match_start("rgb ")) {
ASSIMP_LOG_WARN("Expected `rgb` line in `Mat1` chunk ", nfo.id);
2015-05-19 03:57:13 +00:00
}
const char *rgb = splitter[1];
ReadFloat3Tuple_Ascii(mat.rgb, &rgb);
2015-05-19 03:57:13 +00:00
++splitter;
if (!splitter.match_start("alpha ")) {
ASSIMP_LOG_WARN("Expected `alpha` line in `Mat1` chunk ", nfo.id);
2015-05-19 03:57:13 +00:00
}
const char *tokens[10];
2015-05-19 03:57:13 +00:00
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");
2015-05-19 03:57:13 +00:00
}
++splitter;
if (!splitter.match_start("Units ")) {
ASSIMP_LOG_WARN("Expected `Units` line in `Unit` chunk ", nfo.id);
2015-05-19 03:57:13 +00:00
return;
}
2021-10-05 08:59:43 +00:00
// parent chunks preceede their children, so we should have the
2015-05-19 03:57:13 +00:00
// corresponding chunk already.
for (std::shared_ptr<Node> &nd : out.nodes) {
2015-05-19 03:57:13 +00:00
if (nd->id == nfo.parent_id) {
const unsigned int t = strtoul10(splitter[1]);
2015-05-19 03:57:13 +00:00
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];
2015-05-19 03:57:13 +00:00
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");
2015-05-19 03:57:13 +00:00
}
}
// ------------------------------------------------------------------------------------------------
void COBImporter::ReadLght_Ascii(Scene &out, LineSplitter &splitter, const ChunkInfo &nfo) {
if (nfo.version > 8) {
return UnsupportedChunk_Ascii(splitter, nfo, "Lght");
2015-05-19 03:57:13 +00:00
}
out.nodes.push_back(std::shared_ptr<Light>(new Light()));
Light &msh = (Light &)(*out.nodes.back().get());
2015-05-19 03:57:13 +00:00
msh = nfo;
ReadBasicNodeInfo_Ascii(msh, ++splitter, nfo);
2015-05-19 03:57:13 +00:00
if (splitter.match_start("Infinite ")) {
msh.ltype = Light::INFINITE;
} else if (splitter.match_start("Local ")) {
2015-05-19 03:57:13 +00:00
msh.ltype = Light::LOCAL;
} else if (splitter.match_start("Spot ")) {
2015-05-19 03:57:13 +00:00
msh.ltype = Light::SPOT;
} else {
ASSIMP_LOG_WARN("Unknown kind of light source in `Lght` chunk ", nfo.id, " : ", *splitter);
2015-05-19 03:57:13 +00:00
msh.ltype = Light::SPOT;
}
++splitter;
if (!splitter.match_start("color ")) {
ASSIMP_LOG_WARN("Expected `color` line in `Lght` chunk ", nfo.id);
2015-05-19 03:57:13 +00:00
}
const char *rgb = splitter[1];
ReadFloat3Tuple_Ascii(msh.color, &rgb);
2015-05-19 03:57:13 +00:00
SkipSpaces(&rgb);
if (strncmp(rgb, "cone angle", 10) != 0) {
ASSIMP_LOG_WARN("Expected `cone angle` entity in `color` line in `Lght` chunk ", nfo.id);
2015-05-19 03:57:13 +00:00
}
SkipSpaces(rgb + 10, &rgb);
2015-05-19 03:57:13 +00:00
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);
2015-05-19 03:57:13 +00:00
}
SkipSpaces(rgb + 11, &rgb);
2015-05-19 03:57:13 +00:00
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");
2015-05-19 03:57:13 +00:00
}
out.nodes.push_back(std::shared_ptr<Camera>(new Camera()));
Camera &msh = (Camera &)(*out.nodes.back().get());
2015-05-19 03:57:13 +00:00
msh = nfo;
ReadBasicNodeInfo_Ascii(msh, ++splitter, nfo);
2021-10-05 08:59:43 +00:00
// skip the next line, we don't know this differentiation between a
2015-05-19 03:57:13 +00:00
// 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");
2015-05-19 03:57:13 +00:00
}
out.nodes.push_back(std::shared_ptr<Bone>(new Bone()));
Bone &msh = (Bone &)(*out.nodes.back().get());
2015-05-19 03:57:13 +00:00
msh = nfo;
ReadBasicNodeInfo_Ascii(msh, ++splitter, nfo);
2015-05-19 03:57:13 +00:00
// TODO
}
// ------------------------------------------------------------------------------------------------
void COBImporter::ReadGrou_Ascii(Scene &out, LineSplitter &splitter, const ChunkInfo &nfo) {
if (nfo.version > 1) {
return UnsupportedChunk_Ascii(splitter, nfo, "Grou");
2015-05-19 03:57:13 +00:00
}
out.nodes.push_back(std::shared_ptr<Group>(new Group()));
Group &msh = (Group &)(*out.nodes.back().get());
2015-05-19 03:57:13 +00:00
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");
2015-05-19 03:57:13 +00:00
}
out.nodes.push_back(std::shared_ptr<Mesh>(new Mesh()));
Mesh &msh = (Mesh &)(*out.nodes.back().get());
2015-05-19 03:57:13 +00:00
msh = nfo;
ReadBasicNodeInfo_Ascii(msh, ++splitter, nfo);
2015-05-19 03:57:13 +00:00
// 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) {
2015-05-19 03:57:13 +00:00
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();
2015-05-19 03:57:13 +00:00
aiVector3D &v = msh.vertex_positions[cur];
2015-05-19 03:57:13 +00:00
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")) {
2015-05-19 03:57:13 +00:00
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();
2015-05-19 03:57:13 +00:00
aiVector2D &v = msh.texture_coords[cur];
2015-05-19 03:57:13 +00:00
SkipSpaces(&s);
v.x = fast_atof(&s);
SkipSpaces(&s);
v.y = fast_atof(&s);
}
} else if (splitter.match_start("Faces")) {
2015-05-19 03:57:13 +00:00
const unsigned int cnt = strtoul10(splitter[1]);
msh.faces.reserve(cnt);
for (unsigned int cur = 0; cur < cnt && ++splitter; ++cur) {
2015-05-19 03:57:13 +00:00
if (splitter.match_start("Hole")) {
ASSIMP_LOG_WARN("Skipping unsupported `Hole` line");
2015-05-19 03:57:13 +00:00
continue;
}
if (!splitter.match_start("Face")) {
ThrowException("Expected Face line");
}
msh.faces.emplace_back();
Face &face = msh.faces.back();
2015-05-19 03:57:13 +00:00
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)) {
2015-05-19 03:57:13 +00:00
ThrowException("Expected EOL token in Face entry");
}
if ('<' != *s++) {
ThrowException("Expected < token in Face entry");
}
face.indices[i].pos_idx = strtoul10(s, &s);
2015-05-19 03:57:13 +00:00
if (',' != *s++) {
ThrowException("Expected , token in Face entry");
}
face.indices[i].uv_idx = strtoul10(s, &s);
2015-05-19 03:57:13 +00:00
if ('>' != *s++) {
ThrowException("Expected < token in Face entry");
}
}
}
if (nfo.version <= 4) {
break;
}
} else if (splitter.match_start("DrawFlags")) {
2015-05-19 03:57:13 +00:00
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");
2015-05-19 03:57:13 +00:00
}
2015-05-19 03:57:13 +00:00
const unsigned int head = strtoul10((++splitter)[1]);
if (head != sizeof(Bitmap::BitmapHeader)) {
ASSIMP_LOG_WARN("Unexpected ThumbNailHdrSize, skipping this chunk");
2015-05-19 03:57:13 +00:00
return;
}
}
// ------------------------------------------------------------------------------------------------
void COBImporter::ReadString_Binary(std::string &out, StreamReaderLE &reader) {
out.resize(reader.GetI2());
for (char &c : out) {
2015-05-19 03:57:13 +00:00
c = reader.GetI1();
}
}
// ------------------------------------------------------------------------------------------------
void COBImporter::ReadBasicNodeInfo_Binary(Node &msh, StreamReaderLE &reader, const ChunkInfo & /*nfo*/) {
2015-05-19 03:57:13 +00:00
const unsigned int dupes = reader.GetI2();
ReadString_Binary(msh.name, reader);
msh.name = format(msh.name) << '_' << dupes;
2015-05-19 03:57:13 +00:00
// skip local axes for the moment
reader.IncPtr(48);
2015-05-19 03:57:13 +00:00
msh.transform = aiMatrix4x4();
for (unsigned int y = 0; y < 3; ++y) {
for (unsigned int x = 0; x < 4; ++x) {
2015-05-19 03:57:13 +00:00
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 << "]";
2015-05-19 03:57:13 +00:00
// we can recover if the chunk size was specified.
if (nfo.size != static_cast<unsigned int>(-1)) {
2018-04-19 14:48:43 +00:00
ASSIMP_LOG_ERROR(error);
2015-05-19 03:57:13 +00:00
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()) {
2019-07-12 14:08:51 +00:00
// empty
2015-05-19 03:57:13 +00:00
}
2015-05-19 03:57:13 +00:00
~chunk_guard() {
// don't do anything if the size is not given
if (nfo.size != static_cast<unsigned int>(-1)) {
2017-05-09 18:15:30 +00:00
try {
reader.IncPtr(static_cast<int>(nfo.size) - reader.GetCurrentPos() + cur);
} catch (const DeadlyImportError &) {
2017-05-09 18:15:30 +00:00
// out of limit so correct the value
reader.IncPtr(reader.GetReadLimit());
2017-05-09 18:15:30 +00:00
}
2015-05-19 03:57:13 +00:00
}
}
private:
const COB::ChunkInfo &nfo;
StreamReaderLE &reader;
2015-05-19 03:57:13 +00:00
long cur;
};
// ------------------------------------------------------------------------------------------------
void COBImporter::ReadBinaryFile(Scene &out, StreamReaderLE *reader) {
2019-07-12 14:08:51 +00:00
if (nullptr == reader) {
return;
}
while (1) {
2015-05-19 03:57:13 +00:00
std::string type;
type += reader->GetI1();
type += reader->GetI1();
type += reader->GetI1();
type += reader->GetI1();
2015-05-19 03:57:13 +00:00
ChunkInfo nfo;
nfo.version = reader->GetI2() * 10;
nfo.version += reader->GetI2();
2015-05-19 03:57:13 +00:00
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") {
2015-05-19 03:57:13 +00:00
// ignore layer index silently.
if (nfo.size != static_cast<unsigned int>(-1)) {
2015-05-19 03:57:13 +00:00
reader->IncPtr(nfo.size);
} else
return UnsupportedChunk_Binary(*reader, nfo, type.c_str());
} else if (type == "END ") {
2015-05-19 03:57:13 +00:00
return;
} else {
UnsupportedChunk_Binary(*reader, nfo, type.c_str());
}
2015-05-19 03:57:13 +00:00
}
}
// ------------------------------------------------------------------------------------------------
void COBImporter::ReadPolH_Binary(COB::Scene &out, StreamReaderLE &reader, const ChunkInfo &nfo) {
if (nfo.version > 8) {
return UnsupportedChunk_Binary(reader, nfo, "PolH");
2015-05-19 03:57:13 +00:00
}
const chunk_guard cn(nfo, reader);
2015-05-19 03:57:13 +00:00
out.nodes.push_back(std::shared_ptr<Mesh>(new Mesh()));
Mesh &msh = (Mesh &)(*out.nodes.back().get());
2015-05-19 03:57:13 +00:00
msh = nfo;
ReadBasicNodeInfo_Binary(msh, reader, nfo);
2015-05-19 03:57:13 +00:00
msh.vertex_positions.resize(reader.GetI4());
for (aiVector3D &v : msh.vertex_positions) {
2015-05-19 03:57:13 +00:00
v.x = reader.GetF4();
v.y = reader.GetF4();
v.z = reader.GetF4();
}
msh.texture_coords.resize(reader.GetI4());
for (aiVector2D &v : msh.texture_coords) {
2015-05-19 03:57:13 +00:00
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) {
2015-05-19 03:57:13 +00:00
// XXX backface culling flag is 0x10 in flags
// hole?
2020-03-01 12:15:45 +00:00
bool hole = (reader.GetI1() & 0x08) != 0;
if (hole) {
2015-05-19 03:57:13 +00:00
// 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.emplace_back();
Face &f = msh.faces.back();
2015-05-19 03:57:13 +00:00
const size_t num = reader.GetI2();
f.indices.reserve(f.indices.size() + num);
if (!hole) {
2015-05-19 03:57:13 +00:00
f.material = reader.GetI2();
f.flags = 0;
}
for (size_t x = 0; x < num; ++x) {
f.indices.emplace_back();
2015-05-19 03:57:13 +00:00
VertexIndex &v = f.indices.back();
2015-05-19 03:57:13 +00:00
v.pos_idx = reader.GetI4();
v.uv_idx = reader.GetI4();
}
if (hole) {
std::reverse(f.indices.rbegin(), f.indices.rbegin() + num);
2015-05-19 03:57:13 +00:00
}
}
if (nfo.version > 4) {
2015-05-19 03:57:13 +00:00
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");
2015-05-19 03:57:13 +00:00
}
const chunk_guard cn(nfo, reader);
2015-05-19 03:57:13 +00:00
const uint32_t len = reader.GetI4();
reader.IncPtr(len);
2015-05-19 03:57:13 +00:00
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");
2015-05-19 03:57:13 +00:00
}
const chunk_guard cn(nfo, reader);
2015-05-19 03:57:13 +00:00
out.materials.push_back(Material());
Material &mat = out.materials.back();
2015-05-19 03:57:13 +00:00
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;
2015-05-19 03:57:13 +00:00
}
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();
2015-05-19 03:57:13 +00:00
char id[2];
id[0] = reader.GetI1(), id[1] = reader.GetI1();
2015-05-19 03:57:13 +00:00
if (id[0] == 'e' && id[1] == ':') {
mat.tex_env.reset(new Texture());
reader.GetI1();
ReadString_Binary(mat.tex_env->path, reader);
2015-05-19 03:57:13 +00:00
// advance to next texture-id
id[0] = reader.GetI1(), id[1] = reader.GetI1();
2015-05-19 03:57:13 +00:00
}
if (id[0] == 't' && id[1] == ':') {
mat.tex_color.reset(new Texture());
reader.GetI1();
ReadString_Binary(mat.tex_color->path, reader);
2015-05-19 03:57:13 +00:00
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();
2015-05-19 03:57:13 +00:00
}
if (id[0] == 'b' && id[1] == ':') {
mat.tex_bump.reset(new Texture());
reader.GetI1();
ReadString_Binary(mat.tex_bump->path, reader);
2015-05-19 03:57:13 +00:00
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");
2015-05-19 03:57:13 +00:00
}
const chunk_guard cn(nfo, reader);
out.nodes.push_back(std::shared_ptr<Camera>(new Camera()));
Camera &msh = (Camera &)(*out.nodes.back().get());
2015-05-19 03:57:13 +00:00
msh = nfo;
ReadBasicNodeInfo_Binary(msh, reader, nfo);
2015-05-19 03:57:13 +00:00
// the rest is not interesting for us, so we skip over it.
if (nfo.version > 1) {
if (reader.GetI2() == 512) {
2015-05-19 03:57:13 +00:00
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");
2015-05-19 03:57:13 +00:00
}
const chunk_guard cn(nfo, reader);
out.nodes.push_back(std::shared_ptr<Light>(new Light()));
Light &msh = (Light &)(*out.nodes.back().get());
2015-05-19 03:57:13 +00:00
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");
2015-05-19 03:57:13 +00:00
}
const chunk_guard cn(nfo, reader);
out.nodes.push_back(std::make_shared<Group>());
Group &msh = (Group &)(*out.nodes.back().get());
2015-05-19 03:57:13 +00:00
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");
2015-05-19 03:57:13 +00:00
}
const chunk_guard cn(nfo, reader);
2015-05-19 03:57:13 +00:00
2021-10-05 08:59:43 +00:00
// parent chunks preceede their children, so we should have the
2015-05-19 03:57:13 +00:00
// corresponding chunk already.
for (std::shared_ptr<Node> &nd : out.nodes) {
2015-05-19 03:57:13 +00:00
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];
2015-05-19 03:57:13 +00:00
return;
}
}
ASSIMP_LOG_WARN("`Unit` chunk ", nfo.id, " is a child of ", nfo.parent_id, " which does not exist");
}
2018-04-26 12:10:18 +00:00
#endif // ASSIMP_BUILD_NO_COB_IMPORTER