1/*
2Open Asset Import Library (assimp)
3----------------------------------------------------------------------
4
5Copyright (c) 2006-2012, 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 C4DImporter.cpp
42 * @brief Implementation of the Cinema4D importer class.
43 */
44#ifndef ASSIMP_BUILD_NO_C4D_IMPORTER
45
46// no #ifdefing here, Cinema4D support is carried out in a branch of assimp
47// where it is turned on in the CMake settings.
48
49#ifndef _MSC_VER
50# error C4D support is currently MSVC only
51#endif
52
53#include "C4DImporter.h"
54#include "TinyFormatter.h"
55#include <memory>
56#include <assimp/IOSystem.hpp>
57#include <assimp/scene.h>
58#include <assimp/ai_assert.h>
59
60#if defined(_M_X64) || defined(__amd64__)
61# define __C4D_64BIT
62#endif
63
64#define __PC
65#include "c4d_file.h"
66#include "default_alien_overloads.h"
67
68using namespace melange;
69
70// overload this function and fill in your own unique data
71void GetWriterInfo(int &id, String &appname)
72{
73 id = 2424226;
74 appname = "Open Asset Import Library";
75}
76
77using namespace Assimp;
78using namespace Assimp::Formatter;
79
80namespace Assimp {
81 template<> const std::string LogFunctions<C4DImporter>::log_prefix = "C4D: ";
82}
83
84static const aiImporterDesc desc = {
85 "Cinema4D Importer",
86 "",
87 "",
88 "",
89 aiImporterFlags_SupportBinaryFlavour,
90 0,
91 0,
92 0,
93 0,
94 "c4d"
95};
96
97
98// ------------------------------------------------------------------------------------------------
99C4DImporter::C4DImporter()
100{}
101
102// ------------------------------------------------------------------------------------------------
103C4DImporter::~C4DImporter()
104{}
105
106// ------------------------------------------------------------------------------------------------
107bool C4DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
108{
109 const std::string& extension = GetExtension(pFile);
110 if (extension == "c4d") {
111 return true;
112 }
113
114 else if ((!extension.length() || checkSig) && pIOHandler) {
115 // TODO
116 }
117 return false;
118}
119
120// ------------------------------------------------------------------------------------------------
121const aiImporterDesc* C4DImporter::GetInfo () const
122{
123 return &desc;
124}
125
126// ------------------------------------------------------------------------------------------------
127void C4DImporter::SetupProperties(const Importer* /*pImp*/)
128{
129 // nothing to be done for the moment
130}
131
132
133// ------------------------------------------------------------------------------------------------
134// Imports the given file into the given scene structure.
135void C4DImporter::InternReadFile( const std::string& pFile,
136 aiScene* pScene, IOSystem* pIOHandler)
137{
138 std::unique_ptr<IOStream> file( pIOHandler->Open( pFile));
139
140 if( file.get() == NULL) {
141 ThrowException("failed to open file " + pFile);
142 }
143
144 const size_t file_size = file->FileSize();
145
146 std::vector<uint8_t> mBuffer(file_size);
147 file->Read(&mBuffer[0], 1, file_size);
148
149 Filename f;
150 f.SetMemoryReadMode(&mBuffer[0], file_size);
151
152 // open document first
153 BaseDocument* doc = LoadDocument(f, SCENEFILTER_OBJECTS | SCENEFILTER_MATERIALS);
154 if(doc == NULL) {
155 ThrowException("failed to read document " + pFile);
156 }
157
158 pScene->mRootNode = new aiNode("<C4DRoot>");
159
160 // first convert all materials
161 ReadMaterials(doc->GetFirstMaterial());
162
163 // process C4D scenegraph recursively
164 try {
165 RecurseHierarchy(doc->GetFirstObject(), pScene->mRootNode);
166 }
167 catch(...) {
168 for(aiMesh* mesh : meshes) {
169 delete mesh;
170 }
171 BaseDocument::Free(doc);
172 throw;
173 }
174 BaseDocument::Free(doc);
175
176 // copy meshes over
177 pScene->mNumMeshes = static_cast<unsigned int>(meshes.size());
178 pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]();
179 std::copy(meshes.begin(), meshes.end(), pScene->mMeshes);
180
181 // copy materials over, adding a default material if necessary
182 unsigned int mat_count = static_cast<unsigned int>(materials.size());
183 for(aiMesh* mesh : meshes) {
184 ai_assert(mesh->mMaterialIndex <= mat_count);
185 if(mesh->mMaterialIndex >= mat_count) {
186 ++mat_count;
187
188 std::unique_ptr<aiMaterial> def_material(new aiMaterial());
189 const aiString name(AI_DEFAULT_MATERIAL_NAME);
190 def_material->AddProperty(&name, AI_MATKEY_NAME);
191
192 materials.push_back(def_material.release());
193 break;
194 }
195 }
196
197 pScene->mNumMaterials = static_cast<unsigned int>(materials.size());
198 pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials]();
199 std::copy(materials.begin(), materials.end(), pScene->mMaterials);
200}
201
202
203// ------------------------------------------------------------------------------------------------
204bool C4DImporter::ReadShader(aiMaterial* out, melange::BaseShader* shader)
205{
206 // based on Melange sample code (C4DImportExport.cpp)
207 while(shader) {
208 if(shader->GetType() == Xlayer) {
209 BaseContainer* container = shader->GetDataInstance();
210 GeData blend = container->GetData(SLA_LAYER_BLEND);
211 iBlendDataType* blend_list = reinterpret_cast<iBlendDataType*>(blend.GetCustomDataType(CUSTOMDATA_BLEND_LIST));
212 if (!blend_list)
213 {
214 LogWarn("ignoring XLayer shader: no blend list given");
215 continue;
216 }
217
218 LayerShaderLayer *lsl = dynamic_cast<LayerShaderLayer*>(blend_list->m_BlendLayers.GetObject(0));
219
220 // Ignore the actual layer blending - models for real-time rendering should not
221 // use them in a non-trivial way. Just try to find textures that we can apply
222 // to the model.
223 while (lsl)
224 {
225 if (lsl->GetType() == TypeFolder)
226 {
227 BlendFolder* const folder = dynamic_cast<BlendFolder*>(lsl);
228 LayerShaderLayer *subLsl = dynamic_cast<LayerShaderLayer*>(folder->m_Children.GetObject(0));
229
230 while (subLsl)
231 {
232 if (subLsl->GetType() == TypeShader) {
233 BlendShader* const shader = dynamic_cast<BlendShader*>(subLsl);
234 if(ReadShader(out, static_cast<BaseShader*>(shader->m_pLink->GetLink()))) {
235 return true;
236 }
237 }
238
239 subLsl = subLsl->GetNext();
240 }
241 }
242 else if (lsl->GetType() == TypeShader) {
243 BlendShader* const shader = dynamic_cast<BlendShader*>(lsl);
244 if(ReadShader(out, static_cast<BaseShader*>(shader->m_pLink->GetLink()))) {
245 return true;
246 }
247 }
248
249 lsl = lsl->GetNext();
250 }
251 }
252 else if ( shader->GetType() == Xbitmap )
253 {
254 aiString path;
255 shader->GetFileName().GetString().GetCString(path.data, MAXLEN-1);
256 path.length = ::strlen(path.data);
257 out->AddProperty(&path, AI_MATKEY_TEXTURE_DIFFUSE(0));
258 return true;
259 }
260 else {
261 LogWarn("ignoring shader type: " + std::string(GetObjectTypeName(shader->GetType())));
262 }
263 shader = shader->GetNext();
264 }
265 return false;
266}
267
268
269// ------------------------------------------------------------------------------------------------
270void C4DImporter::ReadMaterials(melange::BaseMaterial* mat)
271{
272 // based on Melange sample code
273 while (mat)
274 {
275 const String& name = mat->GetName();
276 if (mat->GetType() == Mmaterial)
277 {
278 aiMaterial* out = new aiMaterial();
279 material_mapping[mat] = static_cast<unsigned int>(materials.size());
280 materials.push_back(out);
281
282 aiString ai_name;
283 name.GetCString(ai_name.data, MAXLEN-1);
284 ai_name.length = ::strlen(ai_name.data);
285 out->AddProperty(&ai_name, AI_MATKEY_NAME);
286
287 Material& m = dynamic_cast<Material&>(*mat);
288
289 if (m.GetChannelState(CHANNEL_COLOR))
290 {
291 GeData data;
292 mat->GetParameter(MATERIAL_COLOR_COLOR, data);
293 Vector color = data.GetVector();
294 mat->GetParameter(MATERIAL_COLOR_BRIGHTNESS, data);
295 const Float brightness = data.GetFloat();
296
297 color *= brightness;
298
299 aiVector3D v;
300 v.x = color.x;
301 v.y = color.y;
302 v.z = color.z;
303 out->AddProperty(&v, 1, AI_MATKEY_COLOR_DIFFUSE);
304 }
305
306 BaseShader* const shader = m.GetShader(MATERIAL_COLOR_SHADER);
307 if(shader) {
308 ReadShader(out, shader);
309 }
310 }
311 else
312 {
313 LogWarn("ignoring plugin material: " + std::string(GetObjectTypeName(mat->GetType())));
314 }
315 mat = mat->GetNext();
316 }
317}
318
319// ------------------------------------------------------------------------------------------------
320void C4DImporter::RecurseHierarchy(BaseObject* object, aiNode* parent)
321{
322 ai_assert(parent != NULL);
323 std::vector<aiNode*> nodes;
324
325 // based on Melange sample code
326 while (object)
327 {
328 const String& name = object->GetName();
329 const LONG type = object->GetType();
330 const Matrix& ml = object->GetMl();
331
332 aiString string;
333 name.GetCString(string.data, MAXLEN-1);
334 string.length = ::strlen(string.data);
335 aiNode* const nd = new aiNode();
336
337 nd->mParent = parent;
338 nd->mName = string;
339
340 nd->mTransformation.a1 = ml.v1.x;
341 nd->mTransformation.b1 = ml.v1.y;
342 nd->mTransformation.c1 = ml.v1.z;
343
344 nd->mTransformation.a2 = ml.v2.x;
345 nd->mTransformation.b2 = ml.v2.y;
346 nd->mTransformation.c2 = ml.v2.z;
347
348 nd->mTransformation.a3 = ml.v3.x;
349 nd->mTransformation.b3 = ml.v3.y;
350 nd->mTransformation.c3 = ml.v3.z;
351
352 nd->mTransformation.a4 = ml.off.x;
353 nd->mTransformation.b4 = ml.off.y;
354 nd->mTransformation.c4 = ml.off.z;
355
356 nodes.push_back(nd);
357
358 GeData data;
359 if (type == Ocamera)
360 {
361 object->GetParameter(CAMERAOBJECT_FOV, data);
362 // TODO: read camera
363 }
364 else if (type == Olight)
365 {
366 // TODO: read light
367 }
368 else if (type == Opolygon)
369 {
370 aiMesh* const mesh = ReadMesh(object);
371 if(mesh != NULL) {
372 nd->mNumMeshes = 1;
373 nd->mMeshes = new unsigned int[1];
374 nd->mMeshes[0] = static_cast<unsigned int>(meshes.size());
375 meshes.push_back(mesh);
376 }
377 }
378 else {
379 LogWarn("ignoring object: " + std::string(GetObjectTypeName(type)));
380 }
381
382 RecurseHierarchy(object->GetDown(), nd);
383 object = object->GetNext();
384 }
385
386 // copy nodes over to parent
387 parent->mNumChildren = static_cast<unsigned int>(nodes.size());
388 parent->mChildren = new aiNode*[parent->mNumChildren]();
389 std::copy(nodes.begin(), nodes.end(), parent->mChildren);
390}
391
392
393// ------------------------------------------------------------------------------------------------
394aiMesh* C4DImporter::ReadMesh(BaseObject* object)
395{
396 ai_assert(object != NULL && object->GetType() == Opolygon);
397
398 // based on Melange sample code
399 PolygonObject* const polyObject = dynamic_cast<PolygonObject*>(object);
400 ai_assert(polyObject != NULL);
401
402 const LONG pointCount = polyObject->GetPointCount();
403 const LONG polyCount = polyObject->GetPolygonCount();
404 if(!polyObject || !pointCount) {
405 LogWarn("ignoring mesh with zero vertices or faces");
406 return NULL;
407 }
408
409 const Vector* points = polyObject->GetPointR();
410 ai_assert(points != NULL);
411
412 const CPolygon* polys = polyObject->GetPolygonR();
413 ai_assert(polys != NULL);
414
415 std::unique_ptr<aiMesh> mesh(new aiMesh());
416 mesh->mNumFaces = static_cast<unsigned int>(polyCount);
417 aiFace* face = mesh->mFaces = new aiFace[mesh->mNumFaces]();
418
419 mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
420 mesh->mMaterialIndex = 0;
421
422 unsigned int vcount = 0;
423
424 // first count vertices
425 for (LONG i = 0; i < polyCount; i++)
426 {
427 vcount += 3;
428
429 // TODO: do we also need to handle lines or points with similar checks?
430 if (polys[i].c != polys[i].d)
431 {
432 mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
433 ++vcount;
434 }
435 }
436
437 ai_assert(vcount > 0);
438
439 mesh->mNumVertices = vcount;
440 aiVector3D* verts = mesh->mVertices = new aiVector3D[mesh->mNumVertices];
441 aiVector3D* normals, *uvs, *tangents, *bitangents;
442 unsigned int n = 0;
443
444 // check if there are normals, tangents or UVW coordinates
445 BaseTag* tag = object->GetTag(Tnormal);
446 NormalTag* normals_src = NULL;
447 if(tag) {
448 normals_src = dynamic_cast<NormalTag*>(tag);
449 normals = mesh->mNormals = new aiVector3D[mesh->mNumVertices]();
450 }
451
452 tag = object->GetTag(Ttangent);
453 TangentTag* tangents_src = NULL;
454 if(tag) {
455 tangents_src = dynamic_cast<TangentTag*>(tag);
456 tangents = mesh->mTangents = new aiVector3D[mesh->mNumVertices]();
457 bitangents = mesh->mBitangents = new aiVector3D[mesh->mNumVertices]();
458 }
459
460 tag = object->GetTag(Tuvw);
461 UVWTag* uvs_src = NULL;
462 if(tag) {
463 uvs_src = dynamic_cast<UVWTag*>(tag);
464 uvs = mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices]();
465 }
466
467 // copy vertices and extra channels over and populate faces
468 for (LONG i = 0; i < polyCount; ++i, ++face)
469 {
470 ai_assert(polys[i].a < pointCount && polys[i].a >= 0);
471 const Vector& pointA = points[polys[i].a];
472 verts->x = pointA.x;
473 verts->y = pointA.y;
474 verts->z = pointA.z;
475 ++verts;
476
477 ai_assert(polys[i].b < pointCount && polys[i].b >= 0);
478 const Vector& pointB = points[polys[i].b];
479 verts->x = pointB.x;
480 verts->y = pointB.y;
481 verts->z = pointB.z;
482 ++verts;
483
484 ai_assert(polys[i].c < pointCount && polys[i].c >= 0);
485 const Vector& pointC = points[polys[i].c];
486 verts->x = pointC.x;
487 verts->y = pointC.y;
488 verts->z = pointC.z;
489 ++verts;
490
491 // TODO: do we also need to handle lines or points with similar checks?
492 if (polys[i].c != polys[i].d)
493 {
494 ai_assert(polys[i].d < pointCount && polys[i].d >= 0);
495
496 face->mNumIndices = 4;
497 mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
498 const Vector& pointD = points[polys[i].d];
499 verts->x = pointD.x;
500 verts->y = pointD.y;
501 verts->z = pointD.z;
502 ++verts;
503 }
504 else {
505 face->mNumIndices = 3;
506 }
507 face->mIndices = new unsigned int[face->mNumIndices];
508 for(unsigned int j = 0; j < face->mNumIndices; ++j) {
509 face->mIndices[j] = n++;
510 }
511
512 // copy normals
513 if (normals_src) {
514 if(i >= normals_src->GetDataCount()) {
515 LogError("unexpected number of normals, ignoring");
516 }
517 else {
518 ConstNormalHandle normal_handle = normals_src->GetDataAddressR();
519 NormalStruct nor;
520 NormalTag::Get(normal_handle, i, nor);
521 normals->x = nor.a.x;
522 normals->y = nor.a.y;
523 normals->z = nor.a.z;
524 ++normals;
525
526 normals->x = nor.b.x;
527 normals->y = nor.b.y;
528 normals->z = nor.b.z;
529 ++normals;
530
531 normals->x = nor.c.x;
532 normals->y = nor.c.y;
533 normals->z = nor.c.z;
534 ++normals;
535
536 if(face->mNumIndices == 4) {
537 normals->x = nor.d.x;
538 normals->y = nor.d.y;
539 normals->z = nor.d.z;
540 ++normals;
541 }
542 }
543 }
544
545 // copy tangents and bitangents
546 if (tangents_src) {
547
548 for(unsigned int k = 0; k < face->mNumIndices; ++k) {
549 LONG l;
550 switch(k) {
551 case 0:
552 l = polys[i].a;
553 break;
554 case 1:
555 l = polys[i].b;
556 break;
557 case 2:
558 l = polys[i].c;
559 break;
560 case 3:
561 l = polys[i].d;
562 break;
563 default:
564 ai_assert(false);
565 }
566 if(l >= tangents_src->GetDataCount()) {
567 LogError("unexpected number of tangents, ignoring");
568 break;
569 }
570
571 Tangent tan = tangents_src->GetDataR()[l];
572 tangents->x = tan.vl.x;
573 tangents->y = tan.vl.y;
574 tangents->z = tan.vl.z;
575 ++tangents;
576
577 bitangents->x = tan.vr.x;
578 bitangents->y = tan.vr.y;
579 bitangents->z = tan.vr.z;
580 ++bitangents;
581 }
582 }
583
584 // copy UVs
585 if (uvs_src) {
586 if(i >= uvs_src->GetDataCount()) {
587 LogError("unexpected number of UV coordinates, ignoring");
588 }
589 else {
590 UVWStruct uvw;
591 uvs_src->Get(uvs_src->GetDataAddressR(),i,uvw);
592
593 uvs->x = uvw.a.x;
594 uvs->y = 1.0f-uvw.a.y;
595 uvs->z = uvw.a.z;
596 ++uvs;
597
598 uvs->x = uvw.b.x;
599 uvs->y = 1.0f-uvw.b.y;
600 uvs->z = uvw.b.z;
601 ++uvs;
602
603 uvs->x = uvw.c.x;
604 uvs->y = 1.0f-uvw.c.y;
605 uvs->z = uvw.c.z;
606 ++uvs;
607
608 if(face->mNumIndices == 4) {
609 uvs->x = uvw.d.x;
610 uvs->y = 1.0f-uvw.d.y;
611 uvs->z = uvw.d.z;
612 ++uvs;
613 }
614 }
615 }
616 }
617
618 mesh->mMaterialIndex = ResolveMaterial(polyObject);
619 return mesh.release();
620}
621
622
623// ------------------------------------------------------------------------------------------------
624unsigned int C4DImporter::ResolveMaterial(PolygonObject* obj)
625{
626 ai_assert(obj != NULL);
627
628 const unsigned int mat_count = static_cast<unsigned int>(materials.size());
629
630 BaseTag* tag = obj->GetTag(Ttexture);
631 if(tag == NULL) {
632 return mat_count;
633 }
634
635 TextureTag& ttag = dynamic_cast<TextureTag&>(*tag);
636
637 BaseMaterial* const mat = ttag.GetMaterial();
638 ai_assert(mat != NULL);
639
640 const MaterialMap::const_iterator it = material_mapping.find(mat);
641 if(it == material_mapping.end()) {
642 return mat_count;
643 }
644
645 ai_assert((*it).second < mat_count);
646 return (*it).second;
647}
648
649#endif // ASSIMP_BUILD_NO_C4D_IMPORTER
650
651