1/*
2Open Asset Import Library (assimp)
3----------------------------------------------------------------------
4
5Copyright (c) 2006-2017, assimp team
6All rights reserved.
7
8Redistribution and use of this software in source and binary forms,
9with or without modification, are permitted provided that the
10following conditions are met:
11
12* Redistributions of source code must retain the above
13 copyright notice, this list of conditions and the
14 following disclaimer.
15
16* Redistributions in binary form must reproduce the above
17 copyright notice, this list of conditions and the
18 following disclaimer in the documentation and/or other
19 materials provided with the distribution.
20
21* Neither the name of the assimp team, nor the names of its
22 contributors may be used to endorse or promote products
23 derived from this software without specific prior
24 written permission of the assimp team.
25
26THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37
38----------------------------------------------------------------------
39*/
40
41/** @file COBLoader.cpp
42 * @brief Implementation of the TrueSpace COB/SCN importer class.
43 */
44
45
46#ifndef ASSIMP_BUILD_NO_COB_IMPORTER
47#include "COBLoader.h"
48#include "COBScene.h"
49
50#include "StreamReader.h"
51#include "ParsingUtils.h"
52#include "fast_atof.h"
53
54#include "LineSplitter.h"
55#include "TinyFormatter.h"
56#include <memory>
57#include <assimp/IOSystem.hpp>
58#include <assimp/DefaultLogger.hpp>
59#include <assimp/scene.h>
60#include <assimp/importerdesc.h>
61
62using namespace Assimp;
63using namespace Assimp::COB;
64using namespace Assimp::Formatter;
65
66
67static const float units[] = {
68 1000.f,
69 100.f,
70 1.f,
71 0.001f,
72 1.f/0.0254f,
73 1.f/0.3048f,
74 1.f/0.9144f,
75 1.f/1609.344f
76};
77
78static const aiImporterDesc desc = {
79 "TrueSpace Object Importer",
80 "",
81 "",
82 "little-endian files only",
83 aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour,
84 0,
85 0,
86 0,
87 0,
88 "cob scn"
89};
90
91
92// ------------------------------------------------------------------------------------------------
93// Constructor to be privately used by Importer
94COBImporter::COBImporter()
95{}
96
97// ------------------------------------------------------------------------------------------------
98// Destructor, private as well
99COBImporter::~COBImporter()
100{}
101
102// ------------------------------------------------------------------------------------------------
103// Returns whether the class can handle the format of the given file.
104bool COBImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
105{
106 const std::string& extension = GetExtension(pFile);
107 if (extension == "cob" || extension == "scn") {
108 return true;
109 }
110
111 else if ((!extension.length() || checkSig) && pIOHandler) {
112 const char* tokens[] = {"Caligary"};
113 return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
114 }
115 return false;
116}
117
118// ------------------------------------------------------------------------------------------------
119// Loader meta information
120const aiImporterDesc* COBImporter::GetInfo () const
121{
122 return &desc;
123}
124
125// ------------------------------------------------------------------------------------------------
126// Setup configuration properties for the loader
127void COBImporter::SetupProperties(const Importer* /*pImp*/)
128{
129 // nothing to be done for the moment
130}
131
132// ------------------------------------------------------------------------------------------------
133/*static*/ AI_WONT_RETURN void COBImporter::ThrowException(const std::string& msg)
134{
135 throw DeadlyImportError("COB: "+msg);
136}
137
138// ------------------------------------------------------------------------------------------------
139// Imports the given file into the given scene structure.
140void COBImporter::InternReadFile( const std::string& pFile,
141 aiScene* pScene, IOSystem* pIOHandler)
142{
143 COB::Scene scene;
144 std::unique_ptr<StreamReaderLE> stream(new StreamReaderLE( pIOHandler->Open(pFile,"rb")) );
145
146 // check header
147 char head[32];
148 stream->CopyAndAdvance(head,32);
149 if (strncmp(head,"Caligari ",9)) {
150 ThrowException("Could not found magic id: `Caligari`");
151 }
152
153 DefaultLogger::get()->info("File format tag: "+std::string(head+9,6));
154 if (head[16]!='L') {
155 ThrowException("File is big-endian, which is not supported");
156 }
157
158 // load data into intermediate structures
159 if (head[15]=='A') {
160 ReadAsciiFile(scene, stream.get());
161 }
162 else {
163 ReadBinaryFile(scene, stream.get());
164 }
165 if(scene.nodes.empty()) {
166 ThrowException("No nodes loaded");
167 }
168
169 // sort faces by material indices
170 for(std::shared_ptr< Node >& n : scene.nodes) {
171 if (n->type == Node::TYPE_MESH) {
172 Mesh& mesh = (Mesh&)(*n.get());
173 for(Face& f : mesh.faces) {
174 mesh.temp_map[f.material].push_back(&f);
175 }
176 }
177 }
178
179 // count meshes
180 for(std::shared_ptr< Node >& n : scene.nodes) {
181 if (n->type == Node::TYPE_MESH) {
182 Mesh& mesh = (Mesh&)(*n.get());
183 if (mesh.vertex_positions.size() && mesh.texture_coords.size()) {
184 pScene->mNumMeshes += static_cast<unsigned int>(mesh.temp_map.size());
185 }
186 }
187 }
188 pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]();
189 pScene->mMaterials = new aiMaterial*[pScene->mNumMeshes]();
190 pScene->mNumMeshes = 0;
191
192 // count lights and cameras
193 for(std::shared_ptr< Node >& n : scene.nodes) {
194 if (n->type == Node::TYPE_LIGHT) {
195 ++pScene->mNumLights;
196 }
197 else if (n->type == Node::TYPE_CAMERA) {
198 ++pScene->mNumCameras;
199 }
200 }
201
202 if (pScene->mNumLights) {
203 pScene->mLights = new aiLight*[pScene->mNumLights]();
204 }
205 if (pScene->mNumCameras) {
206 pScene->mCameras = new aiCamera*[pScene->mNumCameras]();
207 }
208 pScene->mNumLights = pScene->mNumCameras = 0;
209
210 // resolve parents by their IDs and build the output graph
211 std::unique_ptr<Node> root(new Group());
212 for(size_t n = 0; n < scene.nodes.size(); ++n) {
213 const Node& nn = *scene.nodes[n].get();
214 if(nn.parent_id==0) {
215 root->temp_children.push_back(&nn);
216 }
217
218 for(size_t m = n; m < scene.nodes.size(); ++m) {
219 const Node& mm = *scene.nodes[m].get();
220 if (mm.parent_id == nn.id) {
221 nn.temp_children.push_back(&mm);
222 }
223 }
224 }
225
226 pScene->mRootNode = BuildNodes(*root.get(),scene,pScene);
227}
228
229// ------------------------------------------------------------------------------------------------
230void ConvertTexture(std::shared_ptr< Texture > tex, aiMaterial* out, aiTextureType type)
231{
232 const aiString path( tex->path );
233 out->AddProperty(&path,AI_MATKEY_TEXTURE(type,0));
234 out->AddProperty(&tex->transform,1,AI_MATKEY_UVTRANSFORM(type,0));
235}
236
237// ------------------------------------------------------------------------------------------------
238aiNode* COBImporter::BuildNodes(const Node& root,const Scene& scin,aiScene* fill)
239{
240 aiNode* nd = new aiNode();
241 nd->mName.Set(root.name);
242 nd->mTransformation = root.transform;
243
244 // Note to everybody believing Voodoo is appropriate here:
245 // I know polymorphism, run as fast as you can ;-)
246 if (Node::TYPE_MESH == root.type) {
247 const Mesh& ndmesh = (const Mesh&)(root);
248 if (ndmesh.vertex_positions.size() && ndmesh.texture_coords.size()) {
249
250 typedef std::pair<unsigned int,Mesh::FaceRefList> Entry;
251 for(const Entry& reflist : ndmesh.temp_map) {
252 { // create mesh
253 size_t n = 0;
254 for(Face* f : reflist.second) {
255 n += f->indices.size();
256 }
257 if (!n) {
258 continue;
259 }
260 aiMesh* outmesh = fill->mMeshes[fill->mNumMeshes++] = new aiMesh();
261 ++nd->mNumMeshes;
262
263 outmesh->mVertices = new aiVector3D[n];
264 outmesh->mTextureCoords[0] = new aiVector3D[n];
265
266 outmesh->mFaces = new aiFace[reflist.second.size()]();
267 for(Face* f : reflist.second) {
268 if (f->indices.empty()) {
269 continue;
270 }
271
272 aiFace& fout = outmesh->mFaces[outmesh->mNumFaces++];
273 fout.mIndices = new unsigned int[f->indices.size()];
274
275 for(VertexIndex& v : f->indices) {
276 if (v.pos_idx >= ndmesh.vertex_positions.size()) {
277 ThrowException("Position index out of range");
278 }
279 if (v.uv_idx >= ndmesh.texture_coords.size()) {
280 ThrowException("UV index out of range");
281 }
282 outmesh->mVertices[outmesh->mNumVertices] = ndmesh.vertex_positions[ v.pos_idx ];
283 outmesh->mTextureCoords[0][outmesh->mNumVertices] = aiVector3D(
284 ndmesh.texture_coords[ v.uv_idx ].x,
285 ndmesh.texture_coords[ v.uv_idx ].y,
286 0.f
287 );
288
289 fout.mIndices[fout.mNumIndices++] = outmesh->mNumVertices++;
290 }
291 }
292 outmesh->mMaterialIndex = fill->mNumMaterials;
293 }{ // create material
294 const Material* min = NULL;
295 for(const Material& m : scin.materials) {
296 if (m.parent_id == ndmesh.id && m.matnum == reflist.first) {
297 min = &m;
298 break;
299 }
300 }
301 std::unique_ptr<const Material> defmat;
302 if(!min) {
303 DefaultLogger::get()->debug(format()<<"Could not resolve material index "
304 <<reflist.first<<" - creating default material for this slot");
305
306 defmat.reset(min=new Material());
307 }
308
309 aiMaterial* mat = new aiMaterial();
310 fill->mMaterials[fill->mNumMaterials++] = mat;
311
312 const aiString s(format("#mat_")<<fill->mNumMeshes<<"_"<<min->matnum);
313 mat->AddProperty(&s,AI_MATKEY_NAME);
314
315 if(int tmp = ndmesh.draw_flags & Mesh::WIRED ? 1 : 0) {
316 mat->AddProperty(&tmp,1,AI_MATKEY_ENABLE_WIREFRAME);
317 }
318
319 { int shader;
320 switch(min->shader)
321 {
322 case Material::FLAT:
323 shader = aiShadingMode_Gouraud;
324 break;
325
326 case Material::PHONG:
327 shader = aiShadingMode_Phong;
328 break;
329
330 case Material::METAL:
331 shader = aiShadingMode_CookTorrance;
332 break;
333
334 default:
335 ai_assert(false); // shouldn't be here
336 }
337 mat->AddProperty(&shader,1,AI_MATKEY_SHADING_MODEL);
338 if(shader != aiShadingMode_Gouraud) {
339 mat->AddProperty(&min->exp,1,AI_MATKEY_SHININESS);
340 }
341 }
342
343 mat->AddProperty(&min->ior,1,AI_MATKEY_REFRACTI);
344 mat->AddProperty(&min->rgb,1,AI_MATKEY_COLOR_DIFFUSE);
345
346 aiColor3D c = aiColor3D(min->rgb)*min->ks;
347 mat->AddProperty(&c,1,AI_MATKEY_COLOR_SPECULAR);
348
349 c = aiColor3D(min->rgb)*min->ka;
350 mat->AddProperty(&c,1,AI_MATKEY_COLOR_AMBIENT);
351
352 // convert textures if some exist.
353 if(min->tex_color) {
354 ConvertTexture(min->tex_color,mat,aiTextureType_DIFFUSE);
355 }
356 if(min->tex_env) {
357 ConvertTexture(min->tex_env ,mat,aiTextureType_UNKNOWN);
358 }
359 if(min->tex_bump) {
360 ConvertTexture(min->tex_bump ,mat,aiTextureType_HEIGHT);
361 }
362 }
363 }
364 }
365 }
366 else if (Node::TYPE_LIGHT == root.type) {
367 const Light& ndlight = (const Light&)(root);
368 aiLight* outlight = fill->mLights[fill->mNumLights++] = new aiLight();
369
370 outlight->mName.Set(ndlight.name);
371 outlight->mColorDiffuse = outlight->mColorAmbient = outlight->mColorSpecular = ndlight.color;
372
373 outlight->mAngleOuterCone = AI_DEG_TO_RAD(ndlight.angle);
374 outlight->mAngleInnerCone = AI_DEG_TO_RAD(ndlight.inner_angle);
375
376 // XXX
377 outlight->mType = ndlight.ltype==Light::SPOT ? aiLightSource_SPOT : aiLightSource_DIRECTIONAL;
378 }
379 else if (Node::TYPE_CAMERA == root.type) {
380 const Camera& ndcam = (const Camera&)(root);
381 aiCamera* outcam = fill->mCameras[fill->mNumCameras++] = new aiCamera();
382
383 outcam->mName.Set(ndcam.name);
384 }
385
386 // add meshes
387 if (nd->mNumMeshes) { // mMeshes must be NULL if count is 0
388 nd->mMeshes = new unsigned int[nd->mNumMeshes];
389 for(unsigned int i = 0; i < nd->mNumMeshes;++i) {
390 nd->mMeshes[i] = fill->mNumMeshes-i-1;
391 }
392 }
393
394 // add children recursively
395 nd->mChildren = new aiNode*[root.temp_children.size()]();
396 for(const Node* n : root.temp_children) {
397 (nd->mChildren[nd->mNumChildren++] = BuildNodes(*n,scin,fill))->mParent = nd;
398 }
399
400 return nd;
401}
402
403// ------------------------------------------------------------------------------------------------
404// Read an ASCII file into the given scene data structure
405void COBImporter::ReadAsciiFile(Scene& out, StreamReaderLE* stream)
406{
407 ChunkInfo ci;
408 for(LineSplitter splitter(*stream);splitter;++splitter) {
409
410 // add all chunks to be recognized here. /else ../ omitted intentionally.
411 if (splitter.match_start("PolH ")) {
412 ReadChunkInfo_Ascii(ci,splitter);
413 ReadPolH_Ascii(out,splitter,ci);
414 }
415 if (splitter.match_start("BitM ")) {
416 ReadChunkInfo_Ascii(ci,splitter);
417 ReadBitM_Ascii(out,splitter,ci);
418 }
419 if (splitter.match_start("Mat1 ")) {
420 ReadChunkInfo_Ascii(ci,splitter);
421 ReadMat1_Ascii(out,splitter,ci);
422 }
423 if (splitter.match_start("Grou ")) {
424 ReadChunkInfo_Ascii(ci,splitter);
425 ReadGrou_Ascii(out,splitter,ci);
426 }
427 if (splitter.match_start("Lght ")) {
428 ReadChunkInfo_Ascii(ci,splitter);
429 ReadLght_Ascii(out,splitter,ci);
430 }
431 if (splitter.match_start("Came ")) {
432 ReadChunkInfo_Ascii(ci,splitter);
433 ReadCame_Ascii(out,splitter,ci);
434 }
435 if (splitter.match_start("Bone ")) {
436 ReadChunkInfo_Ascii(ci,splitter);
437 ReadBone_Ascii(out,splitter,ci);
438 }
439 if (splitter.match_start("Chan ")) {
440 ReadChunkInfo_Ascii(ci,splitter);
441 ReadChan_Ascii(out,splitter,ci);
442 }
443 if (splitter.match_start("Unit ")) {
444 ReadChunkInfo_Ascii(ci,splitter);
445 ReadUnit_Ascii(out,splitter,ci);
446 }
447 if (splitter.match_start("END ")) {
448 // we don't need this, but I guess there is a reason this
449 // chunk has been implemented into COB for.
450 return;
451 }
452 }
453}
454
455// ------------------------------------------------------------------------------------------------
456void COBImporter::ReadChunkInfo_Ascii(ChunkInfo& out, const LineSplitter& splitter)
457{
458 const char* all_tokens[8];
459 splitter.get_tokens(all_tokens);
460
461 out.version = (all_tokens[1][1]-'0')*100+(all_tokens[1][3]-'0')*10+(all_tokens[1][4]-'0');
462 out.id = strtoul10(all_tokens[3]);
463 out.parent_id = strtoul10(all_tokens[5]);
464 out.size = strtol10(all_tokens[7]);
465}
466
467// ------------------------------------------------------------------------------------------------
468void COBImporter::UnsupportedChunk_Ascii(LineSplitter& splitter, const ChunkInfo& nfo, const char* name)
469{
470 const std::string error = format("Encountered unsupported chunk: ") << name <<
471 " [version: "<<nfo.version<<", size: "<<nfo.size<<"]";
472
473 // we can recover if the chunk size was specified.
474 if(nfo.size != static_cast<unsigned int>(-1)) {
475 DefaultLogger::get()->error(error);
476
477 // (HACK) - our current position in the stream is the beginning of the
478 // head line of the next chunk. That's fine, but the caller is going
479 // to call ++ on `splitter`, which we need to swallow to avoid
480 // missing the next line.
481 splitter.get_stream().IncPtr(nfo.size);
482 splitter.swallow_next_increment();
483 }
484 else ThrowException(error);
485}
486
487// ------------------------------------------------------------------------------------------------
488void COBImporter::LogWarn_Ascii(const LineSplitter& splitter, const format& message) {
489 LogWarn_Ascii(message << " [at line "<< splitter.get_index()<<"]");
490}
491
492// ------------------------------------------------------------------------------------------------
493void COBImporter::LogError_Ascii(const LineSplitter& splitter, const format& message) {
494 LogError_Ascii(message << " [at line "<< splitter.get_index()<<"]");
495}
496
497// ------------------------------------------------------------------------------------------------
498void COBImporter::LogInfo_Ascii(const LineSplitter& splitter, const format& message) {
499 LogInfo_Ascii(message << " [at line "<< splitter.get_index()<<"]");
500}
501
502// ------------------------------------------------------------------------------------------------
503void COBImporter::LogDebug_Ascii(const LineSplitter& splitter, const format& message) {
504 LogDebug_Ascii(message << " [at line "<< splitter.get_index()<<"]");
505}
506
507// ------------------------------------------------------------------------------------------------
508void COBImporter::LogWarn_Ascii(const Formatter::format& message) {
509 DefaultLogger::get()->warn(std::string("COB: ")+=message);
510}
511
512// ------------------------------------------------------------------------------------------------
513void COBImporter::LogError_Ascii(const Formatter::format& message) {
514 DefaultLogger::get()->error(std::string("COB: ")+=message);
515}
516
517// ------------------------------------------------------------------------------------------------
518void COBImporter::LogInfo_Ascii(const Formatter::format& message) {
519 DefaultLogger::get()->info(std::string("COB: ")+=message);
520}
521
522// ------------------------------------------------------------------------------------------------
523void COBImporter::LogDebug_Ascii(const Formatter::format& message) {
524 DefaultLogger::get()->debug(std::string("COB: ")+=message);
525}
526
527// ------------------------------------------------------------------------------------------------
528void COBImporter::ReadBasicNodeInfo_Ascii(Node& msh, LineSplitter& splitter, const ChunkInfo& /*nfo*/)
529{
530 for(;splitter;++splitter) {
531 if (splitter.match_start("Name")) {
532 msh.name = std::string(splitter[1]);
533
534 // make nice names by merging the dupe count
535 std::replace(msh.name.begin(),msh.name.end(),
536 ',','_');
537 }
538 else if (splitter.match_start("Transform")) {
539 for(unsigned int y = 0; y < 4 && ++splitter; ++y) {
540 const char* s = splitter->c_str();
541 for(unsigned int x = 0; x < 4; ++x) {
542 SkipSpaces(&s);
543 msh.transform[y][x] = fast_atof(&s);
544 }
545 }
546 // we need the transform chunk, so we won't return until we have it.
547 return;
548 }
549 }
550}
551
552// ------------------------------------------------------------------------------------------------
553template <typename T>
554void COBImporter::ReadFloat3Tuple_Ascii(T& fill, const char** in)
555{
556 const char* rgb = *in;
557 for(unsigned int i = 0; i < 3; ++i) {
558 SkipSpaces(&rgb);
559 if (*rgb == ',')++rgb;
560 SkipSpaces(&rgb);
561
562 fill[i] = fast_atof(&rgb);
563 }
564 *in = rgb;
565}
566
567// ------------------------------------------------------------------------------------------------
568void COBImporter::ReadMat1_Ascii(Scene& out, LineSplitter& splitter, const ChunkInfo& nfo)
569{
570 if(nfo.version > 8) {
571 return UnsupportedChunk_Ascii(splitter,nfo,"Mat1");
572 }
573
574 ++splitter;
575 if (!splitter.match_start("mat# ")) {
576 LogWarn_Ascii(splitter,format()<<
577 "Expected `mat#` line in `Mat1` chunk "<<nfo.id);
578 return;
579 }
580
581 out.materials.push_back(Material());
582 Material& mat = out.materials.back();
583 mat = nfo;
584
585 mat.matnum = strtoul10(splitter[1]);
586 ++splitter;
587
588 if (!splitter.match_start("shader: ")) {
589 LogWarn_Ascii(splitter,format()<<
590 "Expected `mat#` line in `Mat1` chunk "<<nfo.id);
591 return;
592 }
593 std::string shader = std::string(splitter[1]);
594 shader = shader.substr(0,shader.find_first_of(" \t"));
595
596 if (shader == "metal") {
597 mat.shader = Material::METAL;
598 }
599 else if (shader == "phong") {
600 mat.shader = Material::PHONG;
601 }
602 else if (shader != "flat") {
603 LogWarn_Ascii(splitter,format()<<
604 "Unknown value for `shader` in `Mat1` chunk "<<nfo.id);
605 }
606
607 ++splitter;
608 if (!splitter.match_start("rgb ")) {
609 LogWarn_Ascii(splitter,format()<<
610 "Expected `rgb` line in `Mat1` chunk "<<nfo.id);
611 }
612
613 const char* rgb = splitter[1];
614 ReadFloat3Tuple_Ascii(mat.rgb,&rgb);
615
616 ++splitter;
617 if (!splitter.match_start("alpha ")) {
618 LogWarn_Ascii(splitter,format()<<
619 "Expected `alpha` line in `Mat1` chunk "<<nfo.id);
620 }
621
622 const char* tokens[10];
623 splitter.get_tokens(tokens);
624
625 mat.alpha = fast_atof( tokens[1] );
626 mat.ka = fast_atof( tokens[3] );
627 mat.ks = fast_atof( tokens[5] );
628 mat.exp = fast_atof( tokens[7] );
629 mat.ior = fast_atof( tokens[9] );
630}
631
632// ------------------------------------------------------------------------------------------------
633void COBImporter::ReadUnit_Ascii(Scene& out, LineSplitter& splitter, const ChunkInfo& nfo)
634{
635 if(nfo.version > 1) {
636 return UnsupportedChunk_Ascii(splitter,nfo,"Unit");
637 }
638 ++splitter;
639 if (!splitter.match_start("Units ")) {
640 LogWarn_Ascii(splitter,format()<<
641 "Expected `Units` line in `Unit` chunk "<<nfo.id);
642 return;
643 }
644
645 // parent chunks preceede their childs, so we should have the
646 // corresponding chunk already.
647 for(std::shared_ptr< Node >& nd : out.nodes) {
648 if (nd->id == nfo.parent_id) {
649 const unsigned int t=strtoul10(splitter[1]);
650
651 nd->unit_scale = t>=sizeof(units)/sizeof(units[0])?(
652 LogWarn_Ascii(splitter,format()<<t<<" is not a valid value for `Units` attribute in `Unit chunk` "<<nfo.id)
653 ,1.f):units[t];
654 return;
655 }
656 }
657 LogWarn_Ascii(splitter,format()<<"`Unit` chunk "<<nfo.id<<" is a child of "
658 <<nfo.parent_id<<" which does not exist");
659}
660
661// ------------------------------------------------------------------------------------------------
662void COBImporter::ReadChan_Ascii(Scene& /*out*/, LineSplitter& splitter, const ChunkInfo& nfo)
663{
664 if(nfo.version > 8) {
665 return UnsupportedChunk_Ascii(splitter,nfo,"Chan");
666 }
667}
668
669// ------------------------------------------------------------------------------------------------
670void COBImporter::ReadLght_Ascii(Scene& out, LineSplitter& splitter, const ChunkInfo& nfo)
671{
672 if(nfo.version > 8) {
673 return UnsupportedChunk_Ascii(splitter,nfo,"Lght");
674 }
675
676 out.nodes.push_back(std::shared_ptr<Light>(new Light()));
677 Light& msh = (Light&)(*out.nodes.back().get());
678 msh = nfo;
679
680 ReadBasicNodeInfo_Ascii(msh,++splitter,nfo);
681
682 if (splitter.match_start("Infinite ")) {
683 msh.ltype = Light::INFINITE;
684 }
685 else if (splitter.match_start("Local ")) {
686 msh.ltype = Light::LOCAL;
687 }
688 else if (splitter.match_start("Spot ")) {
689 msh.ltype = Light::SPOT;
690 }
691 else {
692 LogWarn_Ascii(splitter,format()<<
693 "Unknown kind of light source in `Lght` chunk "<<nfo.id<<" : "<<*splitter);
694 msh.ltype = Light::SPOT;
695 }
696
697 ++splitter;
698 if (!splitter.match_start("color ")) {
699 LogWarn_Ascii(splitter,format()<<
700 "Expected `color` line in `Lght` chunk "<<nfo.id);
701 }
702
703 const char* rgb = splitter[1];
704 ReadFloat3Tuple_Ascii(msh.color ,&rgb);
705
706 SkipSpaces(&rgb);
707 if (strncmp(rgb,"cone angle",10)) {
708 LogWarn_Ascii(splitter,format()<<
709 "Expected `cone angle` entity in `color` line in `Lght` chunk "<<nfo.id);
710 }
711 SkipSpaces(rgb+10,&rgb);
712 msh.angle = fast_atof(&rgb);
713
714 SkipSpaces(&rgb);
715 if (strncmp(rgb,"inner angle",11)) {
716 LogWarn_Ascii(splitter,format()<<
717 "Expected `inner angle` entity in `color` line in `Lght` chunk "<<nfo.id);
718 }
719 SkipSpaces(rgb+11,&rgb);
720 msh.inner_angle = fast_atof(&rgb);
721
722 // skip the rest for we can't handle this kind of physically-based lighting information.
723}
724
725// ------------------------------------------------------------------------------------------------
726void COBImporter::ReadCame_Ascii(Scene& out, LineSplitter& splitter, const ChunkInfo& nfo)
727{
728 if(nfo.version > 2) {
729 return UnsupportedChunk_Ascii(splitter,nfo,"Came");
730 }
731
732 out.nodes.push_back(std::shared_ptr<Camera>(new Camera()));
733 Camera& msh = (Camera&)(*out.nodes.back().get());
734 msh = nfo;
735
736 ReadBasicNodeInfo_Ascii(msh,++splitter,nfo);
737
738 // skip the next line, we don't know this differenciation between a
739 // standard camera and a panoramic camera.
740 ++splitter;
741}
742
743// ------------------------------------------------------------------------------------------------
744void COBImporter::ReadBone_Ascii(Scene& out, LineSplitter& splitter, const ChunkInfo& nfo)
745{
746 if(nfo.version > 5) {
747 return UnsupportedChunk_Ascii(splitter,nfo,"Bone");
748 }
749
750 out.nodes.push_back(std::shared_ptr<Bone>(new Bone()));
751 Bone& msh = (Bone&)(*out.nodes.back().get());
752 msh = nfo;
753
754 ReadBasicNodeInfo_Ascii(msh,++splitter,nfo);
755
756 // TODO
757}
758
759// ------------------------------------------------------------------------------------------------
760void COBImporter::ReadGrou_Ascii(Scene& out, LineSplitter& splitter, const ChunkInfo& nfo)
761{
762 if(nfo.version > 1) {
763 return UnsupportedChunk_Ascii(splitter,nfo,"Grou");
764 }
765
766 out.nodes.push_back(std::shared_ptr<Group>(new Group()));
767 Group& msh = (Group&)(*out.nodes.back().get());
768 msh = nfo;
769
770 ReadBasicNodeInfo_Ascii(msh,++splitter,nfo);
771}
772
773// ------------------------------------------------------------------------------------------------
774void COBImporter::ReadPolH_Ascii(Scene& out, LineSplitter& splitter, const ChunkInfo& nfo)
775{
776 if(nfo.version > 8) {
777 return UnsupportedChunk_Ascii(splitter,nfo,"PolH");
778 }
779
780 out.nodes.push_back(std::shared_ptr<Mesh>(new Mesh()));
781 Mesh& msh = (Mesh&)(*out.nodes.back().get());
782 msh = nfo;
783
784 ReadBasicNodeInfo_Ascii(msh,++splitter,nfo);
785
786 // the chunk has a fixed order of components, but some are not interesting of us so
787 // we're just looking for keywords in arbitrary order. The end of the chunk is
788 // either the last `Face` or the `DrawFlags` attribute, depending on the format ver.
789 for(;splitter;++splitter) {
790 if (splitter.match_start("World Vertices")) {
791 const unsigned int cnt = strtoul10(splitter[2]);
792 msh.vertex_positions.resize(cnt);
793
794 for(unsigned int cur = 0;cur < cnt && ++splitter;++cur) {
795 const char* s = splitter->c_str();
796
797 aiVector3D& v = msh.vertex_positions[cur];
798
799 SkipSpaces(&s);
800 v.x = fast_atof(&s);
801 SkipSpaces(&s);
802 v.y = fast_atof(&s);
803 SkipSpaces(&s);
804 v.z = fast_atof(&s);
805 }
806 }
807 else if (splitter.match_start("Texture Vertices")) {
808 const unsigned int cnt = strtoul10(splitter[2]);
809 msh.texture_coords.resize(cnt);
810
811 for(unsigned int cur = 0;cur < cnt && ++splitter;++cur) {
812 const char* s = splitter->c_str();
813
814 aiVector2D& v = msh.texture_coords[cur];
815
816 SkipSpaces(&s);
817 v.x = fast_atof(&s);
818 SkipSpaces(&s);
819 v.y = fast_atof(&s);
820 }
821 }
822 else if (splitter.match_start("Faces")) {
823 const unsigned int cnt = strtoul10(splitter[1]);
824 msh.faces.reserve(cnt);
825
826 for(unsigned int cur = 0; cur < cnt && ++splitter ;++cur) {
827 if (splitter.match_start("Hole")) {
828 LogWarn_Ascii(splitter,"Skipping unsupported `Hole` line");
829 continue;
830 }
831
832 if (!splitter.match_start("Face")) {
833 ThrowException("Expected Face line");
834 }
835
836 msh.faces.push_back(Face());
837 Face& face = msh.faces.back();
838
839 face.indices.resize(strtoul10(splitter[2]));
840 face.flags = strtoul10(splitter[4]);
841 face.material = strtoul10(splitter[6]);
842
843 const char* s = (++splitter)->c_str();
844 for(size_t i = 0; i < face.indices.size(); ++i) {
845 if(!SkipSpaces(&s)) {
846 ThrowException("Expected EOL token in Face entry");
847 }
848 if ('<' != *s++) {
849 ThrowException("Expected < token in Face entry");
850 }
851 face.indices[i].pos_idx = strtoul10(s,&s);
852 if (',' != *s++) {
853 ThrowException("Expected , token in Face entry");
854 }
855 face.indices[i].uv_idx = strtoul10(s,&s);
856 if ('>' != *s++) {
857 ThrowException("Expected < token in Face entry");
858 }
859 }
860 }
861 if (nfo.version <= 4) {
862 break;
863 }
864 }
865 else if (splitter.match_start("DrawFlags")) {
866 msh.draw_flags = strtoul10(splitter[1]);
867 break;
868 }
869 }
870}
871
872// ------------------------------------------------------------------------------------------------
873void COBImporter::ReadBitM_Ascii(Scene& /*out*/, LineSplitter& splitter, const ChunkInfo& nfo)
874{
875 if(nfo.version > 1) {
876 return UnsupportedChunk_Ascii(splitter,nfo,"BitM");
877 }
878/*
879 "\nThumbNailHdrSize %ld"
880 "\nThumbHeader: %02hx 02hx %02hx "
881 "\nColorBufSize %ld"
882 "\nColorBufZipSize %ld"
883 "\nZippedThumbnail: %02hx 02hx %02hx "
884*/
885
886 const unsigned int head = strtoul10((++splitter)[1]);
887 if (head != sizeof(Bitmap::BitmapHeader)) {
888 LogWarn_Ascii(splitter,"Unexpected ThumbNailHdrSize, skipping this chunk");
889 return;
890 }
891
892 /*union {
893 Bitmap::BitmapHeader data;
894 char opaq[sizeof Bitmap::BitmapHeader()];
895 };*/
896// ReadHexOctets(opaq,head,(++splitter)[1]);
897}
898
899// ------------------------------------------------------------------------------------------------
900void COBImporter::ReadString_Binary(std::string& out, StreamReaderLE& reader)
901{
902 out.resize( reader.GetI2());
903 for(char& c : out) {
904 c = reader.GetI1();
905 }
906}
907
908// ------------------------------------------------------------------------------------------------
909void COBImporter::ReadBasicNodeInfo_Binary(Node& msh, StreamReaderLE& reader, const ChunkInfo& /*nfo*/)
910{
911 const unsigned int dupes = reader.GetI2();
912 ReadString_Binary(msh.name,reader);
913
914 msh.name = format(msh.name)<<'_'<<dupes;
915
916 // skip local axes for the moment
917 reader.IncPtr(48);
918
919 msh.transform = aiMatrix4x4();
920 for(unsigned int y = 0; y < 3; ++y) {
921 for(unsigned int x =0; x < 4; ++x) {
922 msh.transform[y][x] = reader.GetF4();
923 }
924 }
925}
926
927// ------------------------------------------------------------------------------------------------
928void COBImporter::UnsupportedChunk_Binary( StreamReaderLE& reader, const ChunkInfo& nfo, const char* name)
929{
930 const std::string error = format("Encountered unsupported chunk: ") << name <<
931 " [version: "<<nfo.version<<", size: "<<nfo.size<<"]";
932
933 // we can recover if the chunk size was specified.
934 if(nfo.size != static_cast<unsigned int>(-1)) {
935 DefaultLogger::get()->error(error);
936 reader.IncPtr(nfo.size);
937 }
938 else ThrowException(error);
939}
940
941// ------------------------------------------------------------------------------------------------
942// tiny utility guard to aid me at staying within chunk boundaries.
943class chunk_guard {
944public:
945 chunk_guard(const COB::ChunkInfo& nfo, StreamReaderLE& reader)
946 : nfo(nfo)
947 , reader(reader)
948 , cur(reader.GetCurrentPos()) {
949 }
950
951 ~chunk_guard() {
952 // don't do anything if the size is not given
953 if(nfo.size != static_cast<unsigned int>(-1)) {
954 try {
955 reader.IncPtr( static_cast< int >( nfo.size ) - reader.GetCurrentPos() + cur );
956 } catch ( DeadlyImportError e ) {
957 // out of limit so correct the value
958 reader.IncPtr( reader.GetReadLimit() );
959 }
960 }
961 }
962
963private:
964
965 const COB::ChunkInfo& nfo;
966 StreamReaderLE& reader;
967 long cur;
968};
969
970// ------------------------------------------------------------------------------------------------
971void COBImporter::ReadBinaryFile(Scene& out, StreamReaderLE* reader)
972{
973 while(1) {
974 std::string type;
975 type += reader -> GetI1()
976 ,type += reader -> GetI1()
977 ,type += reader -> GetI1()
978 ,type += reader -> GetI1()
979 ;
980
981 ChunkInfo nfo;
982 nfo.version = reader -> GetI2()*10;
983 nfo.version += reader -> GetI2();
984
985 nfo.id = reader->GetI4();
986 nfo.parent_id = reader->GetI4();
987 nfo.size = reader->GetI4();
988
989 if (type == "PolH") {
990 ReadPolH_Binary(out,*reader,nfo);
991 }
992 else if (type == "BitM") {
993 ReadBitM_Binary(out,*reader,nfo);
994 }
995 else if (type == "Grou") {
996 ReadGrou_Binary(out,*reader,nfo);
997 }
998 else if (type == "Lght") {
999 ReadLght_Binary(out,*reader,nfo);
1000 }
1001 else if (type == "Came") {
1002 ReadCame_Binary(out,*reader,nfo);
1003 }
1004 else if (type == "Mat1") {
1005 ReadMat1_Binary(out,*reader,nfo);
1006 }
1007 /* else if (type == "Bone") {
1008 ReadBone_Binary(out,*reader,nfo);
1009 }
1010 else if (type == "Chan") {
1011 ReadChan_Binary(out,*reader,nfo);
1012 }*/
1013 else if (type == "Unit") {
1014 ReadUnit_Binary(out,*reader,nfo);
1015 }
1016 else if (type == "OLay") {
1017 // ignore layer index silently.
1018 if(nfo.size != static_cast<unsigned int>(-1) ) {
1019 reader->IncPtr(nfo.size);
1020 }
1021 else return UnsupportedChunk_Binary(*reader,nfo,type.c_str());
1022 }
1023 else if (type == "END ") {
1024 return;
1025 }
1026 else UnsupportedChunk_Binary(*reader,nfo,type.c_str());
1027 }
1028}
1029
1030// ------------------------------------------------------------------------------------------------
1031void COBImporter::ReadPolH_Binary(COB::Scene& out, StreamReaderLE& reader, const ChunkInfo& nfo)
1032{
1033 if(nfo.version > 8) {
1034 return UnsupportedChunk_Binary(reader,nfo,"PolH");
1035 }
1036 const chunk_guard cn(nfo,reader);
1037
1038 out.nodes.push_back(std::shared_ptr<Mesh>(new Mesh()));
1039 Mesh& msh = (Mesh&)(*out.nodes.back().get());
1040 msh = nfo;
1041
1042 ReadBasicNodeInfo_Binary(msh,reader,nfo);
1043
1044 msh.vertex_positions.resize(reader.GetI4());
1045 for(aiVector3D& v : msh.vertex_positions) {
1046 v.x = reader.GetF4();
1047 v.y = reader.GetF4();
1048 v.z = reader.GetF4();
1049 }
1050
1051 msh.texture_coords.resize(reader.GetI4());
1052 for(aiVector2D& v : msh.texture_coords) {
1053 v.x = reader.GetF4();
1054 v.y = reader.GetF4();
1055 }
1056
1057 const size_t numf = reader.GetI4();
1058 msh.faces.reserve(numf);
1059 for(size_t i = 0; i < numf; ++i) {
1060 // XXX backface culling flag is 0x10 in flags
1061
1062 // hole?
1063 bool hole;
1064 if ((hole = (reader.GetI1() & 0x08) != 0)) {
1065 // XXX Basically this should just work fine - then triangulator
1066 // should output properly triangulated data even for polygons
1067 // with holes. Test data specific to COB is needed to confirm it.
1068 if (msh.faces.empty()) {
1069 ThrowException(format("A hole is the first entity in the `PolH` chunk with id ") << nfo.id);
1070 }
1071 }
1072 else msh.faces.push_back(Face());
1073 Face& f = msh.faces.back();
1074
1075 const size_t num = reader.GetI2();
1076 f.indices.reserve(f.indices.size() + num);
1077
1078 if(!hole) {
1079 f.material = reader.GetI2();
1080 f.flags = 0;
1081 }
1082
1083 for(size_t x = 0; x < num; ++x) {
1084 f.indices.push_back(VertexIndex());
1085
1086 VertexIndex& v = f.indices.back();
1087 v.pos_idx = reader.GetI4();
1088 v.uv_idx = reader.GetI4();
1089 }
1090
1091 if(hole) {
1092 std::reverse(f.indices.rbegin(),f.indices.rbegin()+num);
1093 }
1094 }
1095 if (nfo.version>4) {
1096 msh.draw_flags = reader.GetI4();
1097 }
1098 nfo.version>5 && nfo.version<8 ? reader.GetI4() : 0;
1099}
1100
1101// ------------------------------------------------------------------------------------------------
1102void COBImporter::ReadBitM_Binary(COB::Scene& /*out*/, StreamReaderLE& reader, const ChunkInfo& nfo)
1103{
1104 if(nfo.version > 1) {
1105 return UnsupportedChunk_Binary(reader,nfo,"BitM");
1106 }
1107
1108 const chunk_guard cn(nfo,reader);
1109
1110 const uint32_t len = reader.GetI4();
1111 reader.IncPtr(len);
1112
1113 reader.GetI4();
1114 reader.IncPtr(reader.GetI4());
1115}
1116
1117// ------------------------------------------------------------------------------------------------
1118void COBImporter::ReadMat1_Binary(COB::Scene& out, StreamReaderLE& reader, const ChunkInfo& nfo)
1119{
1120 if(nfo.version > 8) {
1121 return UnsupportedChunk_Binary(reader,nfo,"Mat1");
1122 }
1123
1124 const chunk_guard cn(nfo,reader);
1125
1126 out.materials.push_back(Material());
1127 Material& mat = out.materials.back();
1128 mat = nfo;
1129
1130 mat.matnum = reader.GetI2();
1131 switch(reader.GetI1()) {
1132 case 'f':
1133 mat.type = Material::FLAT;
1134 break;
1135 case 'p':
1136 mat.type = Material::PHONG;
1137 break;
1138 case 'm':
1139 mat.type = Material::METAL;
1140 break;
1141 default:
1142 LogError_Ascii(format("Unrecognized shader type in `Mat1` chunk with id ")<<nfo.id);
1143 mat.type = Material::FLAT;
1144 }
1145
1146 switch(reader.GetI1()) {
1147 case 'f':
1148 mat.autofacet = Material::FACETED;
1149 break;
1150 case 'a':
1151 mat.autofacet = Material::AUTOFACETED;
1152 break;
1153 case 's':
1154 mat.autofacet = Material::SMOOTH;
1155 break;
1156 default:
1157 LogError_Ascii(format("Unrecognized faceting mode in `Mat1` chunk with id ")<<nfo.id);
1158 mat.autofacet = Material::FACETED;
1159 }
1160 mat.autofacet_angle = static_cast<float>(reader.GetI1());
1161
1162 mat.rgb.r = reader.GetF4();
1163 mat.rgb.g = reader.GetF4();
1164 mat.rgb.b = reader.GetF4();
1165
1166 mat.alpha = reader.GetF4();
1167 mat.ka = reader.GetF4();
1168 mat.ks = reader.GetF4();
1169 mat.exp = reader.GetF4();
1170 mat.ior = reader.GetF4();
1171
1172 char id[2];
1173 id[0] = reader.GetI1(),id[1] = reader.GetI1();
1174
1175 if (id[0] == 'e' && id[1] == ':') {
1176 mat.tex_env.reset(new Texture());
1177
1178 reader.GetI1();
1179 ReadString_Binary(mat.tex_env->path,reader);
1180
1181 // advance to next texture-id
1182 id[0] = reader.GetI1(),id[1] = reader.GetI1();
1183 }
1184
1185 if (id[0] == 't' && id[1] == ':') {
1186 mat.tex_color.reset(new Texture());
1187
1188 reader.GetI1();
1189 ReadString_Binary(mat.tex_color->path,reader);
1190
1191 mat.tex_color->transform.mTranslation.x = reader.GetF4();
1192 mat.tex_color->transform.mTranslation.y = reader.GetF4();
1193
1194 mat.tex_color->transform.mScaling.x = reader.GetF4();
1195 mat.tex_color->transform.mScaling.y = reader.GetF4();
1196
1197 // advance to next texture-id
1198 id[0] = reader.GetI1(),id[1] = reader.GetI1();
1199 }
1200
1201 if (id[0] == 'b' && id[1] == ':') {
1202 mat.tex_bump.reset(new Texture());
1203
1204 reader.GetI1();
1205 ReadString_Binary(mat.tex_bump->path,reader);
1206
1207 mat.tex_bump->transform.mTranslation.x = reader.GetF4();
1208 mat.tex_bump->transform.mTranslation.y = reader.GetF4();
1209
1210 mat.tex_bump->transform.mScaling.x = reader.GetF4();
1211 mat.tex_bump->transform.mScaling.y = reader.GetF4();
1212
1213 // skip amplitude for I don't know its purpose.
1214 reader.GetF4();
1215 }
1216 reader.IncPtr(-2);
1217}
1218
1219// ------------------------------------------------------------------------------------------------
1220void COBImporter::ReadCame_Binary(COB::Scene& out, StreamReaderLE& reader, const ChunkInfo& nfo)
1221{
1222 if(nfo.version > 2) {
1223 return UnsupportedChunk_Binary(reader,nfo,"Came");
1224 }
1225
1226 const chunk_guard cn(nfo,reader);
1227
1228 out.nodes.push_back(std::shared_ptr<Camera>(new Camera()));
1229 Camera& msh = (Camera&)(*out.nodes.back().get());
1230 msh = nfo;
1231
1232 ReadBasicNodeInfo_Binary(msh,reader,nfo);
1233
1234 // the rest is not interesting for us, so we skip over it.
1235 if(nfo.version > 1) {
1236 if (reader.GetI2()==512) {
1237 reader.IncPtr(42);
1238 }
1239 }
1240}
1241
1242// ------------------------------------------------------------------------------------------------
1243void COBImporter::ReadLght_Binary(COB::Scene& out, StreamReaderLE& reader, const ChunkInfo& nfo)
1244{
1245 if(nfo.version > 2) {
1246 return UnsupportedChunk_Binary(reader,nfo,"Lght");
1247 }
1248
1249 const chunk_guard cn(nfo,reader);
1250
1251 out.nodes.push_back(std::shared_ptr<Light>(new Light()));
1252 Light& msh = (Light&)(*out.nodes.back().get());
1253 msh = nfo;
1254
1255 ReadBasicNodeInfo_Binary(msh,reader,nfo);
1256}
1257
1258// ------------------------------------------------------------------------------------------------
1259void COBImporter::ReadGrou_Binary(COB::Scene& out, StreamReaderLE& reader, const ChunkInfo& nfo)
1260{
1261 if(nfo.version > 2) {
1262 return UnsupportedChunk_Binary(reader,nfo,"Grou");
1263 }
1264
1265 const chunk_guard cn(nfo,reader);
1266
1267 out.nodes.push_back(std::shared_ptr<Group>(new Group()));
1268 Group& msh = (Group&)(*out.nodes.back().get());
1269 msh = nfo;
1270
1271 ReadBasicNodeInfo_Binary(msh,reader,nfo);
1272}
1273
1274// ------------------------------------------------------------------------------------------------
1275void COBImporter::ReadUnit_Binary(COB::Scene& out, StreamReaderLE& reader, const ChunkInfo& nfo)
1276{
1277 if(nfo.version > 1) {
1278 return UnsupportedChunk_Binary(reader,nfo,"Unit");
1279 }
1280
1281 const chunk_guard cn(nfo,reader);
1282
1283 // parent chunks preceede their childs, so we should have the
1284 // corresponding chunk already.
1285 for(std::shared_ptr< Node >& nd : out.nodes) {
1286 if (nd->id == nfo.parent_id) {
1287 const unsigned int t=reader.GetI2();
1288 nd->unit_scale = t>=sizeof(units)/sizeof(units[0])?(
1289 LogWarn_Ascii(format()<<t<<" is not a valid value for `Units` attribute in `Unit chunk` "<<nfo.id)
1290 ,1.f):units[t];
1291
1292 return;
1293 }
1294 }
1295 LogWarn_Ascii(format()<<"`Unit` chunk "<<nfo.id<<" is a child of "
1296 <<nfo.parent_id<<" which does not exist");
1297}
1298
1299
1300#endif
1301