irrlicht/source/Irrlicht/CColladaFileLoader.cpp

2440 lines
66 KiB
C++

// Copyright (C) 2002-2007 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#include "IrrCompileConfig.h"
#ifdef _IRR_COMPILE_WITH_COLLADA_LOADER_
#include "CColladaFileLoader.h"
#include "os.h"
#include "IXMLReader.h"
#include "IDummyTransformationSceneNode.h"
#include "SAnimatedMesh.h"
#include "fast_atof.h"
#include "quaternion.h"
#include "ILightSceneNode.h"
#include "ICameraSceneNode.h"
#include "IMeshManipulator.h"
#include "IReadFile.h"
#include "IAttributes.h"
#include "IMeshCache.h"
#include "IMeshSceneNode.h"
#include "SMeshBufferLightMap.h"
#include "irrMap.h"
#define COLLADA_READER_DEBUG
namespace irr
{
namespace scene
{
// currently supported COLLADA tag names
const core::stringc colladaSectionName = "COLLADA";
const core::stringc librarySectionName = "library";
const core::stringc libraryNodesSectionName = "library_nodes";
const core::stringc libraryGeometriesSectionName = "library_geometries";
const core::stringc libraryMaterialsSectionName = "library_materials";
const core::stringc libraryImagesSectionName = "library_images";
const core::stringc libraryVisualScenesSectionName = "library_visual_scenes";
const core::stringc libraryCamerasSectionName = "library_cameras";
const core::stringc libraryLightsSectionName = "library_lights";
const core::stringc libraryEffectsSectionName = "library_effects";
const core::stringc assetSectionName = "asset";
const core::stringc sceneSectionName = "scene";
const core::stringc visualSceneSectionName = "visual_scene";
const core::stringc lightPrefabName = "light";
const core::stringc cameraPrefabName = "camera";
const core::stringc materialSectionName = "material";
const core::stringc geometrySectionName = "geometry";
const core::stringc imageSectionName = "image";
const core::stringc textureSectionName = "texture";
const core::stringc effectSectionName = "effect";
const core::stringc meshSectionName = "mesh";
const core::stringc sourceSectionName = "source";
const core::stringc arraySectionName = "array";
const core::stringc floatArraySectionName ="float_array";
const core::stringc intArraySectionName = "int_array";
const core::stringc accessorSectionName = "accessor";
const core::stringc verticesSectionName = "vertices";
const core::stringc inputTagName = "input";
const core::stringc polylistSectionName = "polylist";
const core::stringc trianglesSectionName = "triangles";
const core::stringc polygonsSectionName = "polygons";
const core::stringc primitivesName = "p";
const core::stringc vcountName = "vcount";
const core::stringc nodeSectionName = "node";
const core::stringc lookatNodeName = "lookat";
const core::stringc matrixNodeName = "matrix";
const core::stringc perspectiveNodeName = "perspective";
const core::stringc rotateNodeName = "rotate";
const core::stringc scaleNodeName = "scale";
const core::stringc translateNodeName = "translate";
const core::stringc skewNodeName = "skew";
const core::stringc bboxNodeName = "boundingbox";
const core::stringc minNodeName = "min";
const core::stringc maxNodeName = "max";
const core::stringc instanceName = "instance";
const core::stringc instanceGeometryName = "instance_geometry";
const core::stringc instanceSceneName = "instance_visual_scene";
const core::stringc instanceEffectName = "instance_effect";
const core::stringc instanceMaterialName = "instance_material";
const core::stringc instanceLightName = "instance_light";
const core::stringc instanceNodeName = "instance_node";
const core::stringc bindMaterialName = "bind_material";
const core::stringc extraNodeName = "extra";
const core::stringc techniqueNodeName = "technique";
const core::stringc colorNodeName = "color";
const core::stringc floatNodeName = "float";
const core::stringc paramTagName = "param";
const core::stringc initFromName = "init_from";
const core::stringc textureNodeName = "texture";
const core::stringc doubleSidedName = "double_sided";
const core::stringc profileCOMMONSectionName = "profile_COMMON";
const char* const inputSemanticNames[] = {"POSITION", "VERTEX", "NORMAL", "TEXCOORD",
"UV", "TANGENT", "IMAGE", "TEXTURE", 0};
//! following class is for holding and creating instances of library
//! objects, named prefabs in this loader.
class CPrefab : public IColladaPrefab
{
public:
CPrefab(const char* id) : Id(id)
{
}
//! creates an instance of this prefab
virtual scene::ISceneNode* addInstance(scene::ISceneNode* parent,
scene::ISceneManager* mgr)
{
// empty implementation
return 0;
}
//! returns id of this prefab
virtual const c8* getId()
{
return Id.c_str();
}
protected:
core::stringc Id;
};
//! prefab for a light scene node
class CLightPrefab : public CPrefab
{
public:
CLightPrefab(const char* id) : CPrefab(id)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA: loaded light prefab:", Id.c_str());
#endif
}
video::SLight LightData; // publically accessible
//! creates an instance of this prefab
virtual scene::ISceneNode* addInstance(scene::ISceneNode* parent,
scene::ISceneManager* mgr)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA: Constructing light instance:", Id.c_str());
#endif
scene::ILightSceneNode* l = mgr->addLightSceneNode(parent);
l->setLightData ( LightData );
return l;
}
};
//! prefab for a mesh scene node
class CGeometryPrefab : public CPrefab
{
public:
CGeometryPrefab(const char* id) : CPrefab(id)
{
}
scene::IMesh* Mesh;
//! creates an instance of this prefab
virtual scene::ISceneNode* addInstance(scene::ISceneNode* parent,
scene::ISceneManager* mgr)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA: Constructing mesh instance:", Id.c_str());
#endif
scene::ISceneNode* m = mgr->addMeshSceneNode(Mesh, parent);
return m;
}
};
//! prefab for a camera scene node
class CCameraPrefab : public CPrefab
{
public:
CCameraPrefab(const char* id)
: CPrefab(id), YFov(core::PI / 2.5f), ZNear(1.0f), ZFar(3000.0f)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA: loaded camera prefab:", Id.c_str());
#endif
}
// publicly accessible data
f32 YFov;
f32 ZNear;
f32 ZFar;
//! creates an instance of this prefab
virtual scene::ISceneNode* addInstance(scene::ISceneNode* parent,
scene::ISceneManager* mgr)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA: Constructing camera instance:", Id.c_str());
#endif
scene::ICameraSceneNode* c = mgr->addCameraSceneNode(parent);
c->setFOV(YFov);
c->setNearValue(ZNear);
c->setFarValue(ZFar);
return c;
}
};
//! prefab for a container scene node
//! Collects other prefabs and instantiates them upon instantiation
//! Uses a dummy scene node to return the childs as one scene node
class CScenePrefab : public CPrefab
{
public:
CScenePrefab(const char* id) : CPrefab(id)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA: loaded scene prefab:", Id.c_str());
#endif
}
//! creates an instance of this prefab
virtual scene::ISceneNode* addInstance(scene::ISceneNode* parent,
scene::ISceneManager* mgr)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA: Constructing scene instance:", Id.c_str());
#endif
if (Childs.size()==0)
return 0;
scene::IDummyTransformationSceneNode* c = mgr->addDummyTransformationSceneNode(parent);
if (c)
{
c->getRelativeTransformationMatrix() = Transformation;
for (u32 i=0; i<Childs.size(); ++i)
Childs[i]->addInstance(c, mgr);
}
return c;
}
core::array<IColladaPrefab*> Childs;
core::matrix4 Transformation;
};
//! Constructor
CColladaFileLoader::CColladaFileLoader(video::IVideoDriver* driver,
scene::ISceneManager* smgr, io::IFileSystem* fs)
: Driver(driver), SceneManager(smgr), FileSystem(fs), DummyMesh(0),
FirstLoadedMesh(0), LoadedMeshCount(0), CreateInstances(false)
{
}
//! destructor
CColladaFileLoader::~CColladaFileLoader()
{
if (DummyMesh)
DummyMesh->drop();
if (FirstLoadedMesh)
FirstLoadedMesh->drop();
}
//! Returns true if the file maybe is able to be loaded by this class.
/** This decision should be based only on the file extension (e.g. ".cob") */
bool CColladaFileLoader::isALoadableFileExtension(const c8* fileName) const
{
return strstr(fileName, ".xml") || strstr(fileName, ".dae");
}
//! creates/loads an animated mesh from the file.
//! \return Pointer to the created mesh. Returns 0 if loading failed.
//! If you no longer need the mesh, you should call IAnimatedMesh::drop().
//! See IReferenceCounted::drop() for more information.
IAnimatedMesh* CColladaFileLoader::createMesh(io::IReadFile* file)
{
io::IXMLReaderUTF8* reader = FileSystem->createXMLReaderUTF8(file);
if (!reader)
return 0;
CurrentlyLoadingMesh = file->getFileName();
CreateInstances = SceneManager->getParameters()->getAttributeAsBool(
scene::COLLADA_CREATE_SCENE_INSTANCES);
// read until COLLADA section, skip other parts
while(reader->read())
{
if (reader->getNodeType() == io::EXN_ELEMENT)
{
if (colladaSectionName == reader->getNodeName())
readColladaSection(reader);
else
skipSection(reader, true); // unknown section
}
}
reader->drop();
scene::IAnimatedMesh* returnMesh = DummyMesh;
// because this loader loads and creates a complete scene instead of
// a single mesh, return an empty dummy mesh to make the scene manager
// know that everything went well.
if (!DummyMesh)
{
DummyMesh = new SAnimatedMesh();
returnMesh = DummyMesh;
}
// add the first loaded mesh into the mesh cache too, if more than one
// meshes have been loaded from the file
if (LoadedMeshCount>1 && FirstLoadedMesh)
{
os::Printer::log("Added COLLADA mesh", FirstLoadedMeshName.c_str());
SceneManager->getMeshCache()->addMesh(FirstLoadedMeshName.c_str(), FirstLoadedMesh);
}
// clean up temporary loaded data
clearData();
returnMesh->grab(); // store until this loader is destroyed
DummyMesh->drop();
DummyMesh = 0;
if (FirstLoadedMesh)
FirstLoadedMesh->drop();
FirstLoadedMesh = 0;
LoadedMeshCount = 0;
return returnMesh;
}
//! skips an (unknown) section in the collada document
void CColladaFileLoader::skipSection(io::IXMLReaderUTF8* reader, bool reportSkipping)
{
#ifndef COLLADA_READER_DEBUG
if (reportSkipping) // always report in COLLADA_READER_DEBUG mode
#endif
os::Printer::log("COLLADA skipping section", core::stringc(reader->getNodeName()).c_str());
// skip if this element is empty anyway.
if (reader->isEmptyElement())
return;
// read until we've reached the last element in this section
u32 tagCounter = 1;
while(tagCounter && reader->read())
{
if (reader->getNodeType() == io::EXN_ELEMENT &&
!reader->isEmptyElement())
{
#ifdef COLLADA_READER_DEBUG
if (reportSkipping)
os::Printer::log("Skipping COLLADA unknown element:", core::stringc(reader->getNodeName()).c_str());
#endif // COLLADA_READER_DEBUG
++tagCounter;
}
else
if (reader->getNodeType() == io::EXN_ELEMENT_END)
--tagCounter;
}
}
//! reads the <COLLADA> section and its content
void CColladaFileLoader::readColladaSection(io::IXMLReaderUTF8* reader)
{
if (reader->isEmptyElement())
return;
const f32 version = core::fast_atof(core::stringc(reader->getAttributeValue("version")).c_str());
Version = core::floor32(version)*10000+core::ceil32(core::fract(version)*1000.0f);
if (Version >= 10400)
os::Printer::log("Collada version with only partial support:", core::stringc(version).c_str(), ELL_WARNING);
while(reader->read())
if (reader->getNodeType() == io::EXN_ELEMENT)
{
if (assetSectionName == reader->getNodeName())
readAssetSection(reader);
else
if (librarySectionName == reader->getNodeName())
readLibrarySection(reader);
else
if (libraryNodesSectionName == reader->getNodeName())
readLibrarySection(reader);
else
if (libraryGeometriesSectionName == reader->getNodeName())
readLibrarySection(reader);
else
if (libraryMaterialsSectionName == reader->getNodeName())
readLibrarySection(reader);
else
if (libraryEffectsSectionName == reader->getNodeName())
readLibrarySection(reader);
else
if (libraryImagesSectionName == reader->getNodeName())
readLibrarySection(reader);
else
if (libraryCamerasSectionName == reader->getNodeName())
readLibrarySection(reader);
else
if (libraryLightsSectionName == reader->getNodeName())
readLibrarySection(reader);
else
if (libraryVisualScenesSectionName == reader->getNodeName())
readVisualSceneLibrary(reader);
else
if (assetSectionName == reader->getNodeName())
readAssetSection(reader);
else
if (sceneSectionName == reader->getNodeName())
readSceneSection(reader);
else
{
os::Printer::log("COLLADA loader warning: Wrong tag usage found", reader->getNodeName(), ELL_WARNING);
skipSection(reader, true); // unknown section
}
}
}
//! reads a <library> section and its content
void CColladaFileLoader::readLibrarySection(io::IXMLReaderUTF8* reader)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading library");
#endif
if (reader->isEmptyElement())
return;
while(reader->read())
{
if (reader->getNodeType() == io::EXN_ELEMENT)
{
// animation section tbd
if (cameraPrefabName == reader->getNodeName())
readCameraPrefab(reader);
else
// code section tbd
// controller section tbd
if (geometrySectionName == reader->getNodeName())
readGeometry(reader);
else
if (imageSectionName == reader->getNodeName())
readImage(reader);
else
if (lightPrefabName == reader->getNodeName())
readLightPrefab(reader);
else
if (materialSectionName == reader->getNodeName())
readMaterial(reader);
else
if (nodeSectionName == reader->getNodeName())
{
CScenePrefab p("");
readNodeSection(reader, SceneManager->getRootSceneNode(), &p);
for (u32 i=0; i<p.Childs.size(); ++i)
Prefabs.push_back(p.Childs[i]);
}
else
if (effectSectionName == reader->getNodeName())
readEffect(reader);
else
// program section tbd
if (textureSectionName == reader->getNodeName())
readTexture(reader);
else
skipSection(reader, true); // unknown section, not all allowed supported yet
}
else
if (reader->getNodeType() == io::EXN_ELEMENT_END)
{
if (librarySectionName == reader->getNodeName())
break; // end reading.
if (libraryNodesSectionName == reader->getNodeName())
break; // end reading.
if (libraryGeometriesSectionName == reader->getNodeName())
break; // end reading.
if (libraryMaterialsSectionName == reader->getNodeName())
break; // end reading.
if (libraryEffectsSectionName == reader->getNodeName())
break; // end reading.
if (libraryImagesSectionName == reader->getNodeName())
break; // end reading.
if (libraryLightsSectionName == reader->getNodeName())
break; // end reading.
if (libraryCamerasSectionName == reader->getNodeName())
break; // end reading.
}
}
}
//! reads a <visual_scene> element and stores it as a prefab
void CColladaFileLoader::readVisualSceneLibrary(io::IXMLReaderUTF8* reader)
{
CScenePrefab* p = 0;
while(reader->read())
{
if (reader->getNodeType() == io::EXN_ELEMENT)
{
if (visualSceneSectionName == reader->getNodeName())
p = new CScenePrefab(reader->getAttributeValue("id"));
else
if (p && nodeSectionName == reader->getNodeName()) // as a child of visual_scene
readNodeSection(reader, SceneManager->getRootSceneNode(), p);
else
if (assetSectionName == reader->getNodeName())
readAssetSection(reader);
else
if (extraNodeName == reader->getNodeName())
skipSection(reader, false); // ignore all other sections
else
{
os::Printer::log("COLLADA loader warning: Wrong tag usage found", reader->getNodeName(), ELL_WARNING);
skipSection(reader, true); // ignore all other sections
}
}
else
if (reader->getNodeType() == io::EXN_ELEMENT_END)
{
if (libraryVisualScenesSectionName == reader->getNodeName())
return;
else
if ((visualSceneSectionName == reader->getNodeName()) && p)
{
Prefabs.push_back(p);
p = 0;
}
}
}
}
//! reads a <scene> section and its content
void CColladaFileLoader::readSceneSection(io::IXMLReaderUTF8* reader)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading scene");
#endif
if (reader->isEmptyElement())
return;
// read the scene
core::matrix4 transform; // transformation of this node
core::aabbox3df bbox;
scene::IDummyTransformationSceneNode* node = 0;
while(reader->read())
{
if (reader->getNodeType() == io::EXN_ELEMENT)
{
if (lookatNodeName == reader->getNodeName())
transform *= readLookAtNode(reader);
else
if (matrixNodeName == reader->getNodeName())
transform *= readMatrixNode(reader);
else
if (perspectiveNodeName == reader->getNodeName())
transform *= readPerspectiveNode(reader);
else
if (rotateNodeName == reader->getNodeName())
transform *= readRotateNode(reader);
else
if (scaleNodeName == reader->getNodeName())
transform *= readScaleNode(reader);
else
if (skewNodeName == reader->getNodeName())
transform *= readSkewNode(reader);
else
if (translateNodeName == reader->getNodeName())
transform *= readTranslateNode(reader);
else
if (bboxNodeName == reader->getNodeName())
readBboxNode(reader, bbox);
else
if (nodeSectionName == reader->getNodeName())
{
// create dummy node if there is none yet.
if (!node)
node = SceneManager->addDummyTransformationSceneNode(SceneManager->getRootSceneNode());
readNodeSection(reader, node);
}
else
if ((instanceSceneName == reader->getNodeName()))
readInstanceNode(reader, SceneManager->getRootSceneNode(), 0);
else
if (extraNodeName == reader->getNodeName())
skipSection(reader, false);
else
{
os::Printer::log("COLLADA loader warning: Wrong tag usage found", reader->getNodeName(), ELL_WARNING);
skipSection(reader, true); // ignore all other sections
}
}
else
if ((reader->getNodeType() == io::EXN_ELEMENT_END) &&
(sceneSectionName == reader->getNodeName()))
return;
}
if (node)
node->getRelativeTransformationMatrix() = transform;
}
//! reads a <asset> section and its content
void CColladaFileLoader::readAssetSection(io::IXMLReaderUTF8* reader)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading asset");
#endif
// don't need asset data so far, so skip it
skipSection(reader, false);
}
//! reads a <node> section and its content
void CColladaFileLoader::readNodeSection(io::IXMLReaderUTF8* reader, scene::ISceneNode* parent, CScenePrefab* p)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading node");
#endif
if (reader->isEmptyElement())
return;
core::stringc name = reader->getAttributeValue("name"); // name of the node
if (name.size()==0)
name = reader->getAttributeValue("id");
core::matrix4 transform; // transformation of this node
core::aabbox3df bbox;
scene::ISceneNode* node = 0; // instance
CScenePrefab* nodeprefab = 0; // prefab for library_nodes usage
if (p)
{
nodeprefab = new CScenePrefab(reader->getAttributeValue("id"));
p->Childs.push_back(nodeprefab);
}
// read the node
while(reader->read())
{
if (reader->getNodeType() == io::EXN_ELEMENT)
{
if (assetSectionName == reader->getNodeName())
readAssetSection(reader);
else
if (lookatNodeName == reader->getNodeName())
transform *= readLookAtNode(reader);
else
if (matrixNodeName == reader->getNodeName())
transform *= readMatrixNode(reader);
else
if (perspectiveNodeName == reader->getNodeName())
transform *= readPerspectiveNode(reader);
else
if (rotateNodeName == reader->getNodeName())
transform *= readRotateNode(reader);
else
if (scaleNodeName == reader->getNodeName())
transform *= readScaleNode(reader);
else
if (skewNodeName == reader->getNodeName())
transform *= readSkewNode(reader);
else
if (translateNodeName == reader->getNodeName())
transform *= readTranslateNode(reader);
else
if (bboxNodeName == reader->getNodeName())
readBboxNode(reader, bbox);
else
if ((instanceName == reader->getNodeName()) ||
(instanceNodeName == reader->getNodeName()) ||
(instanceGeometryName == reader->getNodeName()) ||
(instanceLightName == reader->getNodeName()))
{
scene::ISceneNode* newnode = 0;
readInstanceNode(reader, parent, &newnode, nodeprefab);
if (node && newnode)
{
// move children from dummy to new node
core::list<ISceneNode*>::ConstIterator it = node->getChildren().begin();
for (; it != node->getChildren().end(); it = node->getChildren().begin())
(*it)->setParent(newnode);
// remove previous dummy node
node->remove();
}
node = newnode;
}
else
if (nodeSectionName == reader->getNodeName())
{
// create dummy node if there is none yet.
if (!node)
{
scene::IDummyTransformationSceneNode* dummy =
SceneManager->addDummyTransformationSceneNode(parent);
dummy->getRelativeTransformationMatrix() = transform;
node = dummy;
}
// read and add child
readNodeSection(reader, node, nodeprefab);
}
else
if (extraNodeName == reader->getNodeName())
skipSection(reader, false);
else
skipSection(reader, true); // ignore all other sections
} // end if node
else
if (reader->getNodeType() == io::EXN_ELEMENT_END)
{
if (nodeSectionName == reader->getNodeName())
break;
}
}
if (p)
p->Transformation = transform;
else
if (node)
{
// set transformation correctly into node.
node->setPosition(transform.getTranslation());
node->setRotation(transform.getRotationDegrees());
node->setScale(transform.getScale());
node->updateAbsolutePosition();
node->setName(name.c_str());
}
}
//! reads a <lookat> element and its content and creates a matrix from it
core::matrix4 CColladaFileLoader::readLookAtNode(io::IXMLReaderUTF8* reader)
{
core::matrix4 mat;
if (reader->isEmptyElement())
return mat;
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading look at node");
#endif
f32 floats[9];
readFloatsInsideElement(reader, floats, 9);
mat.buildCameraLookAtMatrixLH(
core::vector3df(floats[0], floats[1], floats[2]),
core::vector3df(floats[3], floats[4], floats[5]),
core::vector3df(floats[6], floats[7], floats[8]));
return mat;
}
//! reads a <skew> element and its content and creates a matrix from it
core::matrix4 CColladaFileLoader::readSkewNode(io::IXMLReaderUTF8* reader)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading skew node");
#endif
core::matrix4 mat;
if (reader->isEmptyElement())
return mat;
f32 floats[7]; // angle rotation-axis translation-axis
readFloatsInsideElement(reader, floats, 7);
// build skew matrix from these 7 floats
core::quaternion q;
q.fromAngleAxis(floats[0]*core::DEGTORAD, core::vector3df(floats[1], floats[2], floats[3]));
q.getMatrix(mat);
if (floats[4]==1.f) // along x-axis
{
mat[4]=0.f;
mat[6]=0.f;
mat[8]=0.f;
mat[9]=0.f;
}
else
if (floats[5]==1.f) // along y-axis
{
mat[1]=0.f;
mat[2]=0.f;
mat[8]=0.f;
mat[9]=0.f;
}
else
if (floats[6]==1.f) // along z-axis
{
mat[1]=0.f;
mat[2]=0.f;
mat[4]=0.f;
mat[6]=0.f;
}
return mat;
}
//! reads a <boundingbox> element and its content and stores it in bbox
void CColladaFileLoader::readBboxNode(io::IXMLReaderUTF8* reader,
core::aabbox3df& bbox)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading boundingbox node");
#endif
bbox.reset(core::aabbox3df());
if (reader->isEmptyElement())
return;
f32 floats[3];
while(reader->read())
{
if (reader->getNodeType() == io::EXN_ELEMENT)
{
if (minNodeName == reader->getNodeName())
{
readFloatsInsideElement(reader, floats, 3);
bbox.MinEdge.set(floats[0], floats[1], floats[2]);
}
else
if (maxNodeName == reader->getNodeName())
{
readFloatsInsideElement(reader, floats, 3);
bbox.MaxEdge.set(floats[0], floats[1], floats[2]);
}
else
skipSection(reader, true); // ignore all other sections
}
else
if (reader->getNodeType() == io::EXN_ELEMENT_END)
{
if (bboxNodeName == reader->getNodeName())
break;
}
}
}
//! reads a <matrix> element and its content and creates a matrix from it
core::matrix4 CColladaFileLoader::readMatrixNode(io::IXMLReaderUTF8* reader)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading matrix node");
#endif
core::matrix4 mat;
if (reader->isEmptyElement())
return mat;
readFloatsInsideElement(reader, mat.pointer(), 16);
// put translation into the correct place
mat.setTranslation(core::vector3df(mat[3],mat[7],mat[11]));
mat[3]=mat[7]=mat[11]=0.f;
return mat;
}
//! reads a <perspective> element and its content and creates a matrix from it
core::matrix4 CColladaFileLoader::readPerspectiveNode(io::IXMLReaderUTF8* reader)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading perspective node");
#endif
core::matrix4 mat;
if (reader->isEmptyElement())
return mat;
f32 floats[1];
readFloatsInsideElement(reader, floats, 1);
// TODO: build perspecitve matrix from this float
os::Printer::log("COLLADA loader warning: <perspective> not implemented yet.", ELL_WARNING);
return mat;
}
//! reads a <rotate> element and its content and creates a matrix from it
core::matrix4 CColladaFileLoader::readRotateNode(io::IXMLReaderUTF8* reader)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading rotate node");
#endif
core::matrix4 mat;
if (reader->isEmptyElement())
return mat;
f32 floats[4];
readFloatsInsideElement(reader, floats, 4);
if (!core::iszero(floats[3]))
{
core::quaternion q;
q.fromAngleAxis(floats[3]*core::DEGTORAD, core::vector3df(floats[0], floats[1], floats[2]));
return q.getMatrix();
}
else
return core::IdentityMatrix;
}
//! reads a <scale> element and its content and creates a matrix from it
core::matrix4 CColladaFileLoader::readScaleNode(io::IXMLReaderUTF8* reader)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading scale node");
#endif
core::matrix4 mat;
if (reader->isEmptyElement())
return mat;
f32 floats[3];
readFloatsInsideElement(reader, floats, 3);
mat.setScale(core::vector3df(floats[0], floats[1], floats[2]));
return mat;
}
//! reads a <translate> element and its content and creates a matrix from it
core::matrix4 CColladaFileLoader::readTranslateNode(io::IXMLReaderUTF8* reader)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading translate node");
#endif
core::matrix4 mat;
if (reader->isEmptyElement())
return mat;
f32 floats[3];
readFloatsInsideElement(reader, floats, 3);
mat.setTranslation(core::vector3df(floats[0], floats[1], floats[2]));
return mat;
}
//! reads any kind of <instance*> node and creates a scene node from it
void CColladaFileLoader::readInstanceNode(io::IXMLReaderUTF8* reader, scene::ISceneNode* parent,
scene::ISceneNode** outNode, CScenePrefab* p)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading instance");
#endif
// find prefab of the specified id
core::stringc url = reader->getAttributeValue("url");
uriToId(url);
if (!reader->isEmptyElement())
{
while(reader->read())
{
if (reader->getNodeType() == io::EXN_ELEMENT)
{
if (bindMaterialName == reader->getNodeName())
readBindMaterialSection(reader,url);
else
if (extraNodeName == reader->getNodeName())
skipSection(reader, false);
}
else
if (reader->getNodeType() == io::EXN_ELEMENT_END)
break;
}
}
for (u32 i=0; i<Prefabs.size(); ++i)
{
if (url == Prefabs[i]->getId())
{
if (p)
p->Childs.push_back(Prefabs[i]);
else
if (CreateInstances)
{
scene::ISceneNode * newNode
= Prefabs[i]->addInstance(parent, SceneManager);
if (outNode)
{
*outNode = newNode;
if (*outNode)
(*outNode)->setName(reader->getAttributeValue("id"));
}
}
return;
}
}
}
//! reads a <camera> element and stores it as prefab
void CColladaFileLoader::readCameraPrefab(io::IXMLReaderUTF8* reader)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading camera prefab");
#endif
CCameraPrefab* prefab = new CCameraPrefab(reader->getAttributeValue("id"));
if (!reader->isEmptyElement())
{
// read techniques optics and imager (the latter is completely ignored, though)
readColladaParameters(reader, cameraPrefabName);
SColladaParam* p;
// XFOV not yet supported
p = getColladaParameter(ECPN_YFOV);
if (p && p->Type == ECPT_FLOAT)
prefab->YFov = p->Floats[0];
p = getColladaParameter(ECPN_ZNEAR);
if (p && p->Type == ECPT_FLOAT)
prefab->ZNear = p->Floats[0];
p = getColladaParameter(ECPN_ZFAR);
if (p && p->Type == ECPT_FLOAT)
prefab->ZFar = p->Floats[0];
// orthographic camera uses LEFT, RIGHT, TOP, and BOTTOM
}
Prefabs.push_back(prefab);
}
//! reads a <image> element and stores it in the image section
void CColladaFileLoader::readImage(io::IXMLReaderUTF8* reader)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading image");
#endif
SColladaImage image;
image.Id = reader->getAttributeValue("id");
if (Version >= 10400) // start with 1.4
{
while(reader->read())
{
if (reader->getNodeType() == io::EXN_ELEMENT &&
initFromName == reader->getNodeName())
{
image.Filename = reader->getNodeData();
image.Filename.trim();
readColladaInput(reader);
}
else
if (reader->getNodeType() == io::EXN_ELEMENT_END)
{
if (initFromName == reader->getNodeName())
return;
}
} // end while reader->read();
}
else
image.Filename = reader->getAttributeValue("source");
// add image to list of loaded images.
Images.push_back(image);
}
//! reads a <texture> element and stores it in the texture section
void CColladaFileLoader::readTexture(io::IXMLReaderUTF8* reader)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading texture");
#endif
SColladaTexture texture;
texture.Id = reader->getAttributeValue("id");
if (!reader->isEmptyElement())
{
readColladaInputs(reader, textureSectionName);
SColladaInput* input = getColladaInput(ECIS_IMAGE);
if (input)
{
core::stringc imageName = input->Source;
uriToId(imageName);
for (u32 i=0; i<Images.size(); ++i)
if ((imageName == Images[i].Id) && Images[i].Filename.size())
{
texture.Texture = Driver->getTexture(Images[i].Filename.c_str());
break;
}
}
}
// add texture to list of loaded textures.
Textures.push_back(texture);
}
//! reads a <material> element and stores it in the material section
void CColladaFileLoader::readMaterial(io::IXMLReaderUTF8* reader)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading material");
#endif
SColladaMaterial material;
material.Id = reader->getAttributeValue("id");
if (Version >= 10400)
{
while(reader->read())
{
if (reader->getNodeType() == io::EXN_ELEMENT &&
instanceEffectName == reader->getNodeName())
{
material.InstanceEffectId = reader->getAttributeValue("url");
uriToId(material.InstanceEffectId);
}
else
if (reader->getNodeType() == io::EXN_ELEMENT_END &&
materialSectionName == reader->getNodeName())
{
break;
}
} // end while reader->read();
}
else
{
if (!reader->isEmptyElement())
{
readColladaInputs(reader, materialSectionName);
SColladaInput* input = getColladaInput(ECIS_TEXTURE);
if (input)
{
core::stringc textureName = input->Source;
uriToId(textureName);
for (u32 i=0; i<Textures.size(); ++i)
if (textureName == Textures[i].Id)
{
material.Mat.setTexture(0, Textures[i].Texture);
break;
}
}
//does not work because the wrong start node is chosen due to reading of inputs before
#if 0
readColladaParameters(reader, materialSectionName);
SColladaParam* p;
p = getColladaParameter(ECPN_AMBIENT);
if (p && p->Type == ECPT_FLOAT3)
material.Mat.AmbientColor = video::SColorf(p->Floats[0],p->Floats[1],p->Floats[2]).toSColor();
p = getColladaParameter(ECPN_DIFFUSE);
if (p && p->Type == ECPT_FLOAT3)
material.Mat.DiffuseColor = video::SColorf(p->Floats[0],p->Floats[1],p->Floats[2]).toSColor();
p = getColladaParameter(ECPN_SPECULAR);
if (p && p->Type == ECPT_FLOAT3)
material.Mat.DiffuseColor = video::SColorf(p->Floats[0],p->Floats[1],p->Floats[2]).toSColor();
p = getColladaParameter(ECPN_SHININESS);
if (p && p->Type == ECPT_FLOAT)
material.Mat.Shininess = p->Floats[0];
#endif
}
}
// add material to list of loaded materials.
Materials.push_back(material);
}
void CColladaFileLoader::readEffect(io::IXMLReaderUTF8* reader, SColladaEffect * effect)
{
#ifdef COLLADA_READER_DEBUG
if (!effect) os::Printer::log("COLLADA reading effect");
#endif
static const core::stringc constantNode("constant");
static const core::stringc lambertNode("lambert");
static const core::stringc phongNode("phong");
static const core::stringc blinnNode("blinn");
static const core::stringc emissionNode("emission");
static const core::stringc ambientNode("ambient");
static const core::stringc diffuseNode("diffuse");
static const core::stringc specularNode("specular");
static const core::stringc shininessNode("shininess");
static const core::stringc reflectiveNode("reflective");
static const core::stringc reflectivityNode("reflectivity");
static const core::stringc transparentNode("transparent");
static const core::stringc transparencyNode("transparency");
static const core::stringc indexOfRefractionNode("index_of_refraction");
if (!effect)
{
Effects.push_back(SColladaEffect());
effect = &Effects.getLast();
effect->Id = reader->getAttributeValue("id");
}
video::SColorf transparency;
while(reader->read())
{
if (reader->getNodeType() == io::EXN_ELEMENT)
{
if (profileCOMMONSectionName == reader->getNodeName())
readEffect(reader,effect);
else
if (techniqueNodeName == reader->getNodeName())
readEffect(reader,effect);
else
if (constantNode == reader->getNodeName() ||
lambertNode == reader->getNodeName() ||
phongNode == reader->getNodeName() ||
blinnNode == reader->getNodeName())
{
effect->Mat.setFlag(irr::video::EMF_GOURAUD_SHADING,
phongNode == reader->getNodeName() ||
blinnNode == reader->getNodeName());
while(reader->read())
{
if (reader->getNodeType() == io::EXN_ELEMENT)
{
core::stringc node = reader->getNodeName();
if (emissionNode == node || ambientNode == node ||
diffuseNode == node || specularNode == node ||
reflectiveNode == node || transparentNode == node )
{
// color or texture types
while(reader->read())
{
if (reader->getNodeType() == io::EXN_ELEMENT &&
colorNodeName == reader->getNodeName())
{
const video::SColorf colorf = readColorNode(reader);
const video::SColor color = colorf.toSColor();
if (emissionNode == node)
effect->Mat.EmissiveColor = color;
else
if (ambientNode == node)
effect->Mat.AmbientColor = color;
else
if (diffuseNode == node)
effect->Mat.DiffuseColor = color;
else
if (specularNode == node)
effect->Mat.SpecularColor = color;
else
if (transparentNode == node)
transparency = colorf;
}
else
if (reader->getNodeType() == io::EXN_ELEMENT &&
textureNodeName == reader->getNodeName())
{
const core::stringc tname = reader->getAttributeValue("texture");
for (u32 i=0; i<Images.size(); ++i)
if ((tname == Images[i].Id) && Images[i].Filename.size())
{
effect->Mat.setTexture(0, Driver->getTexture(Images[i].Filename.c_str()));
break;
}
}
else
if (reader->getNodeType() == io::EXN_ELEMENT)
skipSection(reader, false);
else
if (reader->getNodeType() == io::EXN_ELEMENT_END &&
node == reader->getNodeName())
break;
}
}
else
if (shininessNode == node || reflectivityNode == node ||
transparencyNode == node || indexOfRefractionNode == node )
{
// float or param types
while(reader->read())
{
if (reader->getNodeType() == io::EXN_ELEMENT &&
floatNodeName == reader->getNodeName())
{
f32 f = readFloatNode(reader);
if (shininessNode == node)
effect->Mat.Shininess = f;
else
if (transparencyNode == node)
{
transparency.r *= f;
transparency.g *= f;
transparency.b *= f;
}
}
else
if (reader->getNodeType() == io::EXN_ELEMENT)
skipSection(reader, false);
else
if (reader->getNodeType() == io::EXN_ELEMENT_END &&
node == reader->getNodeName())
break;
}
}
else
skipSection(reader, true); // ignore all other nodes
}
else
if (reader->getNodeType() == io::EXN_ELEMENT_END && (
constantNode == reader->getNodeName() ||
lambertNode == reader->getNodeName() ||
phongNode == reader->getNodeName() ||
blinnNode == reader->getNodeName()
))
break;
}
}
else
if (extraNodeName == reader->getNodeName())
readEffect(reader,effect);
else
if (doubleSidedName == reader->getNodeName())
{
// read the GoogleEarth extra flag for double sided polys
s32 doubleSided = 0;
readIntsInsideElement(reader,&doubleSided,1);
if (doubleSided)
{
effect->Mat.setFlag(irr::video::EMF_BACK_FACE_CULLING,false);
}
}
else
skipSection(reader, true); // ignore all other sections
}
else
if (reader->getNodeType() == io::EXN_ELEMENT_END)
{
if (effectSectionName == reader->getNodeName())
break;
else
if (profileCOMMONSectionName == reader->getNodeName())
break;
else
if (techniqueNodeName == reader->getNodeName())
break;
else
if (extraNodeName == reader->getNodeName())
break;
}
}
if (effect->Mat.AmbientColor == video::SColor(0) &&
effect->Mat.DiffuseColor != video::SColor(0))
effect->Mat.AmbientColor = effect->Mat.DiffuseColor;
if (effect->Mat.DiffuseColor == video::SColor(0) &&
effect->Mat.AmbientColor != video::SColor(0))
effect->Mat.DiffuseColor = effect->Mat.AmbientColor;
if (transparency.r > 0.0f || transparency.g > 0.0f || transparency.b > 0.0f)
{
effect->Mat.setFlag(irr::video::EMF_BACK_FACE_CULLING,false);
effect->Mat.setFlag(irr::video::EMF_ZWRITE_ENABLE,false);
effect->Mat.setFlag(irr::video::EMF_BACK_FACE_CULLING,false);
effect->Mat.MaterialType = irr::video::EMT_TRANSPARENT_ADD_COLOR;
video::SColorf ambient(effect->Mat.AmbientColor);
ambient.r *= transparency.r;
ambient.g *= transparency.g;
ambient.b *= transparency.b;
ambient.a *= transparency.a;
effect->Mat.AmbientColor = ambient.toSColor();
video::SColorf diffuse(effect->Mat.DiffuseColor);
diffuse.r *= transparency.r;
diffuse.g *= transparency.g;
diffuse.b *= transparency.b;
diffuse.a *= transparency.a;
effect->Mat.DiffuseColor = diffuse.toSColor();
}
}
const SColladaMaterial * CColladaFileLoader::findMaterial(const core::stringc & materialName)
{
// do a quick lookup in the materials
SColladaMaterial matToFind;
matToFind.Id = materialName;
s32 mat = Materials.binary_search(matToFind);
if (mat < 0)
return 0;
// instance the material effect if needed
if (Materials[mat].InstanceEffectId.size() > 0)
{
// do a quick lookup in the effects
SColladaEffect effectToFind;
effectToFind.Id = Materials[mat].InstanceEffectId;
s32 effect = Effects.binary_search(effectToFind);
if (effect >= 0)
{
// found the effect, instance by copying into the material
Materials[mat].Mat = Effects[effect].Mat;
// and indicate the material is instanced by removing the effect ref
Materials[mat].InstanceEffectId = "";
}
}
return &Materials[mat];
}
void CColladaFileLoader::readBindMaterialSection(io::IXMLReaderUTF8* reader, const core::stringc & id)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading bind material");
#endif
while(reader->read())
{
if (reader->getNodeType() == io::EXN_ELEMENT)
{
if (instanceMaterialName == reader->getNodeName())
{
// the symbol to retarget, and the target material
core::stringc materialReference = reader->getAttributeValue("symbol");
if (materialReference.size()==0)
continue;
core::stringc target = reader->getAttributeValue("target");
uriToId(target);
if (target.size()==0)
continue;
const SColladaMaterial * material = findMaterial(target);
if (!material)
continue;
// bind any pending materials for this node
materialReference = id+"/"+materialReference;
#ifdef COLLADA_READER_DEBUG
os::Printer::log((core::stringc("Material binding: ")+materialReference+" "+target).c_str());
#endif
if (MaterialsToBind.find(materialReference))
{
core::array<irr::scene::IMeshBuffer*> & toBind
= MeshesToBind[MaterialsToBind[materialReference]];
for (u32 i = 0; i < toBind.size(); ++i)
{
toBind[i]->getMaterial() = material->Mat;
}
}
}
}
else
if (reader->getNodeType() == io::EXN_ELEMENT_END &&
bindMaterialName == reader->getNodeName())
break;
}
}
//! reads a <geometry> element and stores it as mesh if possible
void CColladaFileLoader::readGeometry(io::IXMLReaderUTF8* reader)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading geometry");
#endif
core::stringc id = reader->getAttributeValue("id");
core::stringc VertexPositionSource; // each mesh has exactly one <vertex> member, containing
// a POSITION input. This string stores the source of this input.
core::array<SSource> sources;
bool okToReadArray = false;
SAnimatedMesh* amesh = new SAnimatedMesh();
scene::SMesh* mesh = new SMesh();
amesh->addMesh(mesh);
// handles geometry node and the mesh childs in this loop
// read sources with arrays and accessor for each mesh
if (!reader->isEmptyElement())
while(reader->read())
{
if (reader->getNodeType() == io::EXN_ELEMENT)
{
const char* nodeName = reader->getNodeName();
if (meshSectionName == nodeName)
{
// inside a mesh section. Don't have to do anything here.
}
else
if (sourceSectionName == nodeName)
{
// create a new source
sources.push_back(SSource());
sources.getLast().Id = reader->getAttributeValue("id");
#ifdef COLLADA_READER_DEBUG
os::Printer::log("Loaded source", sources.getLast().Id.c_str());
#endif
}
else
if (arraySectionName == nodeName || floatArraySectionName == nodeName || intArraySectionName == nodeName)
{
// create a new array and read it.
if (!sources.empty())
{
sources.getLast().Array.Name = reader->getAttributeValue("id");
int count = reader->getAttributeValueAsInt("count");
sources.getLast().Array.Data.set_used(count); // pre allocate
// check if type of array is ok
const char* type = reader->getAttributeValue("type");
okToReadArray = (type && (!strcmp("float", type) || !strcmp("int", type))) || floatArraySectionName == nodeName || intArraySectionName == nodeName;
#ifdef COLLADA_READER_DEBUG
os::Printer::log("Read array", sources.getLast().Array.Name.c_str());
#endif
}
#ifdef COLLADA_READER_DEBUG
else
os::Printer::log("Warning, array found, but no source",
reader->getAttributeValue("id"));
#endif
}
else
if (accessorSectionName == nodeName) // child of source (below a technique tag)
{
SAccessor accessor;
accessor.Count = reader->getAttributeValueAsInt("count");
accessor.Offset = reader->getAttributeValueAsInt("offset");
accessor.Stride = reader->getAttributeValueAsInt("stride");
if (accessor.Stride == 0)
accessor.Stride = 1;
// the accessor contains some information on how to access (boi!) the array,
// the info is stored in collada style parameters, so just read them.
readColladaParameters(reader, accessorSectionName);
if (!sources.empty())
{
sources.getLast().Accessors.push_back(accessor);
sources.getLast().Accessors.getLast().Parameters = Parameters;
}
}
else
if (verticesSectionName == nodeName)
{
// read vertex input position source
readColladaInputs(reader, verticesSectionName);
SColladaInput* input = getColladaInput(ECIS_POSITION);
if (input)
VertexPositionSource = input->Source;
}
else
// lines and linestrips missing
if (polygonsSectionName == nodeName ||
// polylistSectionName == nodeName ||
trianglesSectionName == nodeName)
{
// read polygons section
readPolygonSection(reader, VertexPositionSource, sources, mesh, id);
}
else
// trifans, and tristrips missing
if (extraNodeName == reader->getNodeName())
skipSection(reader, false);
else
{
// os::Printer::log("COLLADA loader warning: Wrong tag usage found", reader->getNodeName(), ELL_WARNING);
skipSection(reader, true); // ignore all other sections
}
} // end if node type is element
else
if (reader->getNodeType() == io::EXN_TEXT)
{
// read array data
if (okToReadArray && !sources.empty())
{
core::array<f32>& a = sources.getLast().Array.Data;
core::stringc data = reader->getNodeData();
data.trim();
const c8* p = &data[0];
for (u32 i=0; i<a.size(); ++i)
{
findNextNoneWhiteSpace(&p);
if (*p)
a[i] = readFloat(&p);
else
a[i] = 0.0f;
}
} // end reading array
okToReadArray = false;
} // end if node type is text
else
if (reader->getNodeType() == io::EXN_ELEMENT_END)
{
if (geometrySectionName == reader->getNodeName())
{
// end of geometry section reached, cancel out
break;
}
}
} // end while reader->read();
// add mesh as geometry
mesh->recalculateBoundingBox();
amesh->recalculateBoundingBox();
// create virtual file name
core::stringc filename = CurrentlyLoadingMesh;
filename += '#';
filename += id;
// add to scene manager
if (LoadedMeshCount)
{
SceneManager->getMeshCache()->addMesh(filename.c_str(), amesh);
os::Printer::log("Added COLLADA mesh", filename.c_str());
}
else
{
FirstLoadedMeshName = filename;
FirstLoadedMesh = amesh;
FirstLoadedMesh->grab();
}
++LoadedMeshCount;
mesh->drop();
amesh->drop();
// create geometry prefab
CGeometryPrefab* prefab = new CGeometryPrefab(id.c_str());
prefab->Mesh = mesh;
Prefabs.push_back(prefab);
// store as dummy mesh if no instances will be created
if (!CreateInstances && !DummyMesh)
{
DummyMesh = amesh;
DummyMesh->grab();
}
}
struct SInputSlot
{
f32* Data; // Pointer to source data
ECOLLADA_INPUT_SEMANTIC Semantic;
};
struct SPolygon
{
core::array<s32> Indices;
};
//! reads a polygons section and creates a mesh from it
void CColladaFileLoader::readPolygonSection(io::IXMLReaderUTF8* reader,
const core::stringc& vertexPositionSource, core::array<SSource>& sources,
scene::SMesh* mesh, const core::stringc& geometryId)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading polygon section");
#endif
core::stringc materialName = reader->getAttributeValue("material");
core::stringc polygonType = reader->getNodeName();
// int polygonCount = reader->getAttributeValueAsInt("count"); // Not useful because it only determines the number of p tags, not the number of tris
core::array<SInputSlot> slots;
core::array<SPolygon> polygons;
core::array<int> vCounts;
bool parsePolygonOK = false;
bool parseVcountOK = false;
u32 inputSemanticCount = 0;
// read all <input> and
if (!reader->isEmptyElement())
while(reader->read())
{
const char* nodeName = reader->getNodeName();
if (reader->getNodeType() == io::EXN_ELEMENT)
{
// polygon node may contain params
if (inputTagName == nodeName)
{
// read input tag
readColladaInput(reader);
// create new input slot
if (!Inputs.empty())
{
SInputSlot slot;
slot.Data=0;
slot.Semantic = Inputs.getLast().Semantic;
core::stringc sourceArrayURI;
// get input source array id, if it is a vertex input, take
// the <vertex><input>-source attribute.
if (slot.Semantic == ECIS_VERTEX)
sourceArrayURI = vertexPositionSource;
else
sourceArrayURI = Inputs.getLast().Source;
uriToId(sourceArrayURI);
// find source array (we'll ignore accessors for this implementation)
u32 s;
for (s=0; s<sources.size(); ++s)
if (sources[s].Id == sourceArrayURI)
{
// slot found
slot.Data = sources[s].Array.Data.pointer();
break;
}
if (s == sources.size())
os::Printer::log("COLLADA Warning, polygon input source not found",
sourceArrayURI.c_str());
else
slots.push_back(slot);
#ifdef COLLADA_READER_DEBUG
// print slot
core::stringc tmp = "Added slot ";
tmp += inputSemanticNames[Inputs.getLast().Semantic];
tmp += " sourceArray:";
tmp += sourceArrayURI;
os::Printer::log(tmp.c_str());
#endif
++inputSemanticCount;
}
} // end is input node
else
if (primitivesName == nodeName)
{
parsePolygonOK = true;
polygons.push_back(SPolygon());
} // end is polygon node
else
if (vcountName == nodeName)
{
parseVcountOK = true;
} // end is polygon node
} // end is element node
else
if (reader->getNodeType() == io::EXN_ELEMENT_END)
{
if (primitivesName == nodeName)
parsePolygonOK = false; // end parsing a polygon
else
if (vcountName == nodeName)
parseVcountOK = false; // end parsing vcounts
else
if (polygonType == nodeName)
break; // cancel out and create mesh
} // end is element end
else
if (reader->getNodeType() == io::EXN_TEXT)
{
if (parseVcountOK)
{
core::stringc data = reader->getNodeData();
data.trim();
const c8* p = &data[0];
while(*p)
{
findNextNoneWhiteSpace(&p);
if (*p)
vCounts.push_back(readInt(&p));
}
parseVcountOK = false;
}
else
if (parsePolygonOK && polygons.size())
{
core::stringc data = reader->getNodeData();
data.trim();
const c8* p = &data[0];
SPolygon& poly = polygons.getLast();
if (vCounts.empty())
{
while(*p)
{
findNextNoneWhiteSpace(&p);
poly.Indices.push_back(readInt(&p));
}
}
else
{
for (u32 i = 0; i < vCounts.size(); i++)
{
int polyVCount = vCounts[i];
core::array<int> polyCorners;
for (u32 j = 0; j < polyVCount * inputSemanticCount; j++)
{
if (!*p)
break;
findNextNoneWhiteSpace(&p);
polyCorners.push_back(readInt(&p));
}
while (polyCorners.size() >= 3 * inputSemanticCount)
{
// add one triangle's worth of indices
for (u32 k = 0; k < inputSemanticCount * 3; ++k)
{
poly.Indices.push_back(polyCorners[k]);
}
// remove one corner from our poly
polyCorners.erase(inputSemanticCount,inputSemanticCount);
}
polyCorners.clear();
}
vCounts.clear();
}
parsePolygonOK = false;
}
}
} // end while reader->read()
if (inputSemanticCount == 0 || inputSemanticCount != slots.size())
return; // we cannot create the mesh if one of the input semantics wasn't found.
if (!polygons.size())
return; // cancel if there are no polygons anyway.
// analyze content of slots to create a fitting mesh buffer
u32 u;
u32 textureCoordSetCount = 0;
bool normalSlotCount = false;
u32 secondTexCoordSetIndex = 0xFFFFFFFF;
for (u=0; u<slots.size(); ++u)
{
if (slots[u].Semantic == ECIS_TEXCOORD || slots[u].Semantic == ECIS_UV )
{
++textureCoordSetCount;
if (textureCoordSetCount==2)
secondTexCoordSetIndex = u;
}
else
if (slots[u].Semantic == ECIS_NORMAL)
normalSlotCount=true;
}
// if there is more than one texture coordinate set, create a lightmap mesh buffer,
// otherwise use a standard mesh buffer
scene::IMeshBuffer* buffer = 0;
if ( textureCoordSetCount <= 1 )
{
// standard mesh buffer
scene::SMeshBuffer* mbuffer = new SMeshBuffer();
buffer = mbuffer;
core::map<video::S3DVertex, int> vertMap;
for (u32 i=0; i<polygons.size(); ++i)
{
core::array<u16> indices;
// for all index/semantic groups
for (u32 v=0; v<polygons[i].Indices.size(); v+=inputSemanticCount)
{
video::S3DVertex vtx;
vtx.Color.set(255,255,255,255);
// for all input semantics
for (u32 k=0; k<slots.size(); ++k)
{
if (!slots[k].Data)
continue;
// build vertex from input semantics.
s32 idx = polygons[i].Indices[v+k];
switch(slots[k].Semantic)
{
case ECIS_POSITION:
case ECIS_VERTEX:
vtx.Pos.X = slots[k].Data[(idx*3)+0];
vtx.Pos.Y = slots[k].Data[(idx*3)+1];
vtx.Pos.Z = slots[k].Data[(idx*3)+2];
break;
case ECIS_NORMAL:
vtx.Normal.X = slots[k].Data[(idx*3)+0];
vtx.Normal.Y = slots[k].Data[(idx*3)+1];
vtx.Normal.Z = slots[k].Data[(idx*3)+2];
break;
case ECIS_TEXCOORD:
case ECIS_UV:
vtx.TCoords.X = slots[k].Data[(idx*2)+0];
vtx.TCoords.Y = slots[k].Data[(idx*2)+1];
break;
case ECIS_TANGENT:
break;
default:
break;
}
}
//first, try to find this vertex in the mesh
core::map<video::S3DVertex, int>::Node* n = vertMap.find(vtx);
if (n)
{
indices.push_back(n->getValue());
}
else
{
indices.push_back(mbuffer->getVertexCount());
mbuffer->Vertices.push_back(vtx);
vertMap.insert(vtx, mbuffer->getVertexCount()-1);
}
} // end for all vertices
if (polygonsSectionName == polygonType &&
indices.size() >= 4)
{
// need to tesselate for polygons of 4 or more vertices
// for now we naively turn interpret it as a triangle fan
// as full tesselation is problematic
for (u32 i = 0; i+2 < indices.size(); ++i)
{
mbuffer->Indices.push_back(indices[0]);
mbuffer->Indices.push_back(indices[i+1]);
mbuffer->Indices.push_back(indices[i+2]);
}
}
else
{
// it's just triangles
for (u32 i = 0; i < indices.size(); ++i)
{
mbuffer->Indices.push_back(indices[i]);
}
}
} // end for all polygons
}
else
{
// lightmap mesh buffer
scene::SMeshBufferLightMap* mbuffer = new SMeshBufferLightMap();
buffer = mbuffer;
for (u32 i=0; i<polygons.size(); ++i)
{
u32 vertexCount = polygons[i].Indices.size() / inputSemanticCount;
// for all vertices in array
for (u32 v=0; v<polygons[i].Indices.size(); v+=inputSemanticCount)
{
video::S3DVertex2TCoords vtx;
vtx.Color.set(100,255,255,255);
// for all input semantics
for (u32 k=0; k<slots.size(); ++k)
{
// build vertex from input semantics.
u32 idx = polygons[i].Indices[v+k];
switch(slots[k].Semantic)
{
case ECIS_POSITION:
case ECIS_VERTEX:
vtx.Pos.X = slots[k].Data[(idx*3)+0];
vtx.Pos.Y = slots[k].Data[(idx*3)+1];
vtx.Pos.Z = slots[k].Data[(idx*3)+2];
break;
case ECIS_NORMAL:
vtx.Normal.X = slots[k].Data[(idx*3)+0];
vtx.Normal.Y = slots[k].Data[(idx*3)+1];
vtx.Normal.Z = slots[k].Data[(idx*3)+2];
break;
case ECIS_TEXCOORD:
case ECIS_UV:
if (k==secondTexCoordSetIndex)
{
vtx.TCoords2.X = slots[k].Data[(idx*2)+0];
vtx.TCoords2.Y = slots[k].Data[(idx*2)+1];
}
else
{
vtx.TCoords.X = slots[k].Data[(idx*2)+0];
vtx.TCoords.Y = slots[k].Data[(idx*2)+1];
}
break;
case ECIS_TANGENT:
break;
default:
break;
}
}
mbuffer->Vertices.push_back(vtx);
} // end for all vertices
// add vertex indices
const u32 oldVertexCount = mbuffer->Vertices.size() - vertexCount;
for (u32 face=0; face<vertexCount-2; ++face)
{
mbuffer->Indices.push_back(oldVertexCount + 0);
mbuffer->Indices.push_back(oldVertexCount + 1 + face);
mbuffer->Indices.push_back(oldVertexCount + 2 + face);
}
} // end for all polygons
}
// add future bind reference for the material
core::stringc materialReference = geometryId+"/"+materialName;
if (!MaterialsToBind.find(materialReference))
{
MaterialsToBind[materialReference] = MeshesToBind.size();
MeshesToBind.push_back(core::array<irr::scene::IMeshBuffer*>());
}
MeshesToBind[MaterialsToBind[materialReference]].push_back(buffer);
// calculate normals if there is no slot for it
if (!normalSlotCount)
SceneManager->getMeshManipulator()->recalculateNormals(buffer);
// recalculate bounding box
buffer->recalculateBoundingBox();
// add mesh buffer
mesh->addMeshBuffer(buffer);
buffer->drop();
}
//! reads a <light> element and stores it as prefab
void CColladaFileLoader::readLightPrefab(io::IXMLReaderUTF8* reader)
{
#ifdef COLLADA_READER_DEBUG
os::Printer::log("COLLADA reading light prefab");
#endif
CLightPrefab* prefab = new CLightPrefab(reader->getAttributeValue("id"));
if (!reader->isEmptyElement())
{
readColladaParameters(reader, lightPrefabName);
SColladaParam* p = getColladaParameter(ECPN_COLOR);
if (p && p->Type == ECPT_FLOAT3)
prefab->LightData.DiffuseColor.set(p->Floats[0], p->Floats[1], p->Floats[2]);
}
Prefabs.push_back(prefab);
}
//! returns a collada parameter or none if not found
SColladaParam* CColladaFileLoader::getColladaParameter(ECOLLADA_PARAM_NAME name)
{
for (u32 i=0; i<Parameters.size(); ++i)
if (Parameters[i].Name == name)
return &Parameters[i];
return 0;
}
//! returns a collada input or none if not found
SColladaInput* CColladaFileLoader::getColladaInput(ECOLLADA_INPUT_SEMANTIC input)
{
for (u32 i=0; i<Inputs.size(); ++i)
if (Inputs[i].Semantic == input)
return &Inputs[i];
return 0;
}
//! reads a collada input tag and adds it to the input parameter
void CColladaFileLoader::readColladaInput(io::IXMLReaderUTF8* reader)
{
// parse param
SColladaInput p;
// get type
core::stringc semanticName = reader->getAttributeValue("semantic");
for (u32 i=0; inputSemanticNames[i]; ++i)
if (semanticName == inputSemanticNames[i])
{
p.Semantic = (ECOLLADA_INPUT_SEMANTIC)i;
break;
}
// get source
p.Source = reader->getAttributeValue("source");
// add input
Inputs.push_back(p);
}
//! parses all collada inputs inside an element and stores them in Inputs
void CColladaFileLoader::readColladaInputs(io::IXMLReaderUTF8* reader, const core::stringc& parentName)
{
Inputs.clear();
while(reader->read())
{
if (reader->getNodeType() == io::EXN_ELEMENT &&
inputTagName == reader->getNodeName())
{
readColladaInput(reader);
}
else
if (reader->getNodeType() == io::EXN_ELEMENT_END)
{
if (parentName == reader->getNodeName())
return; // end of parent reached
}
} // end while reader->read();
}
//! parses all collada parameters inside an element and stores them in Parameters
void CColladaFileLoader::readColladaParameters(io::IXMLReaderUTF8* reader,
const core::stringc& parentName)
{
Parameters.clear();
const char* const paramNames[] = {"COLOR", "AMBIENT", "DIFFUSE",
"SPECULAR", "SHININESS", "YFOV", "ZNEAR", "ZFAR", 0};
const char* const typeNames[] = {"float", "float2", "float3", 0};
while(reader->read())
{
const char* nodeName = reader->getNodeName();
if (reader->getNodeType() == io::EXN_ELEMENT &&
paramTagName == nodeName)
{
// parse param
SColladaParam p;
// get type
u32 i;
core::stringc typeName = reader->getAttributeValue("type");
for (i=0; typeNames[i]; ++i)
if (typeName == typeNames[i])
{
p.Type = (ECOLLADA_PARAM_TYPE)i;
break;
}
// get name
core::stringc nameName = reader->getAttributeValue("name");
for (i=0; typeNames[i]; ++i)
if (nameName == paramNames[i])
{
p.Name = (ECOLLADA_PARAM_NAME)i;
break;
}
// read parameter data inside parameter tags
switch(p.Type)
{
case ECPT_FLOAT:
case ECPT_FLOAT2:
case ECPT_FLOAT3:
case ECPT_FLOAT4:
readFloatsInsideElement(reader, p.Floats, p.Type - ECPT_FLOAT + 1);
break;
// TODO: other types of data (ints, bools or whatever)
default:
break;
}
// add param
Parameters.push_back(p);
}
else
if (reader->getNodeType() == io::EXN_ELEMENT_END)
{
if (parentName == reader->getNodeName())
return; // end of parent reached
}
} // end while reader->read();
}
//! parses a float from a char pointer and moves the pointer
//! to the end of the parsed float
inline f32 CColladaFileLoader::readFloat(const c8** p)
{
f32 ftmp;
*p = core::fast_atof_move(*p, ftmp);
return ftmp;
}
//! parses an int from a char pointer and moves the pointer to
//! the end of the parsed float
inline s32 CColladaFileLoader::readInt(const c8** p)
{
return (s32)readFloat(p);
}
//! places pointer to next begin of a token
void CColladaFileLoader::findNextNoneWhiteSpace(const c8** start)
{
const c8* p = *start;
while(*p && (*p==' ' || *p=='\n' || *p=='\r' || *p=='\t'))
++p;
// TODO: skip comments <!-- -->
*start = p;
}
//! reads floats from inside of xml element until end of xml element
void CColladaFileLoader::readFloatsInsideElement(io::IXMLReaderUTF8* reader, f32* floats, u32 count)
{
if (reader->isEmptyElement())
return;
while(reader->read())
{
// TODO: check for comments inside the element
// and ignore them.
if (reader->getNodeType() == io::EXN_TEXT)
{
// parse float data
core::stringc data = reader->getNodeData();
data.trim();
const c8* p = &data[0];
for (u32 i=0; i<count; ++i)
{
findNextNoneWhiteSpace(&p);
if (*p)
floats[i] = readFloat(&p);
else
floats[i] = 0.0f;
}
}
else
if (reader->getNodeType() == io::EXN_ELEMENT_END)
break; // end parsing text
}
}
//! reads ints from inside of xml element until end of xml element
void CColladaFileLoader::readIntsInsideElement(io::IXMLReaderUTF8* reader, s32* ints, u32 count)
{
if (reader->isEmptyElement())
return;
while(reader->read())
{
// TODO: check for comments inside the element
// and ignore them.
if (reader->getNodeType() == io::EXN_TEXT)
{
// parse float data
core::stringc data = reader->getNodeData();
data.trim();
const c8* p = &data[0];
for (u32 i=0; i<count; ++i)
{
findNextNoneWhiteSpace(&p);
if (*p)
ints[i] = readInt(&p);
else
ints[i] = 0;
}
}
else
if (reader->getNodeType() == io::EXN_ELEMENT_END)
break; // end parsing text
}
}
video::SColorf CColladaFileLoader::readColorNode(io::IXMLReaderUTF8* reader)
{
video::SColorf result;
if (reader->getNodeType() == io::EXN_ELEMENT &&
colorNodeName == reader->getNodeName())
{
f32 color[4];
readFloatsInsideElement(reader,color,4);
result.r = color[0];
result.g = color[1];
result.b = color[2];
result.a = 1.0f-color[3];
}
return result;
}
f32 CColladaFileLoader::readFloatNode(io::IXMLReaderUTF8* reader)
{
f32 result = 0.0f;
if (reader->getNodeType() == io::EXN_ELEMENT &&
floatNodeName == reader->getNodeName())
{
readFloatsInsideElement(reader,&result,1);
}
return result;
}
//! clears all loaded data
void CColladaFileLoader::clearData()
{
// delete all prefabs
for (u32 i=0; i<Prefabs.size(); ++i)
Prefabs[i]->drop();
Prefabs.clear();
// clear all parameters
Parameters.clear();
// clear all materials
Images.clear();
// clear all materials
Textures.clear();
// clear all materials
Materials.clear();
// clear all inputs
Inputs.clear();
// clear all effects
Effects.clear();
// clear all the materials to bind
MaterialsToBind.clear();
MeshesToBind.clear();
}
//! changes the XML URI into an internal id
void CColladaFileLoader::uriToId(core::stringc& str)
{
// currently, we only remove the # from the begin if there
// because we simply don't support referencing other files.
if (!str.size())
return;
if (str[0] == '#')
str.erase(0);
}
} // end namespace scene
} // end namespace irr
#endif // _IRR_COMPILE_WITH_COLLADA_LOADER_