irrlicht/source/Irrlicht/CColladaFileLoader.cpp

1412 lines
36 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 "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"
//#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 assetSectionName = "asset";
const core::stringc sceneSectionName = "scene";
const core::stringc lightPrefabName = "light";
const core::stringc cameraPrefabName = "camera";
const core::stringc materialSectionName = "material";
const core::stringc geometrySectionName = "geometry";
const core::stringc meshSectionName = "mesh";
const core::stringc sourceSectionName = "source";
const core::stringc arraySectionName = "array";
const core::stringc accessorSectionName = "accessor";
const core::stringc verticesSectionName = "vertices";
const core::stringc inputTagName = "input";
const core::stringc polygonsSectionName = "polygons";
const core::stringc polygonName = "p";
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 instanceNodeName = "instance";
const core::stringc paramTagName = "param";
const char* const inputSemanticNames[] = {"POSITION", "VERTEX", "NORMAL", "TEXCOORD",
"UV", "TANGENT", 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; // public 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 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
}
// public 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;
}
};
//! 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)
{
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 IUnknown::drop() for more information.
IAnimatedMesh* CColladaFileLoader::createMesh(irr::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
int tagCounter = 1;
while(tagCounter && reader->read())
{
if (reader->getNodeType() == io::EXN_ELEMENT &&
!reader->isEmptyElement())
{
#ifdef COLLADA_READER_DEBUG
if (reportSkipping)
os::Printer::log("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;
// I ignore version information here. Keep on reading content:
while(reader->read())
if (reader->getNodeType() == io::EXN_ELEMENT)
{
if (librarySectionName == reader->getNodeName())
readLibrarySection(reader);
else
if (assetSectionName == reader->getNodeName())
readAssetSection(reader);
else
if (sceneSectionName == reader->getNodeName())
readSceneSection(reader);
else
skipSection(reader, true); // unknown section
}
}
//! reads a <library> section and its content
void CColladaFileLoader::readLibrarySection(io::IXMLReaderUTF8* reader)
{
while(reader->read())
if (reader->getNodeType() == io::EXN_ELEMENT)
{
if (lightPrefabName == reader->getNodeName())
readLightPrefab(reader);
else
if (materialSectionName == reader->getNodeName())
readMaterial(reader);
else
if (cameraPrefabName == reader->getNodeName())
readCameraPrefab(reader);
else
if (geometrySectionName == reader->getNodeName())
readGeometry(reader);
else
skipSection(reader, true); // unknown section
}
else
if (reader->getNodeType() == io::EXN_ELEMENT_END)
{
if (librarySectionName == reader->getNodeName())
break; // end reading.
}
}
//! 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
while(reader->read())
if (reader->getNodeType() == io::EXN_ELEMENT)
{
if (nodeSectionName == reader->getNodeName())
readNodeSection(reader, SceneManager->getRootSceneNode());
else
skipSection(reader, true); // ignore all other sections
}
}
//! 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)
{
#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
core::matrix4 transform; // transformation of this node
scene::ISceneNode* node = 0; // instance
// read the node
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 (translateNodeName == reader->getNodeName())
transform *= readTranslateNode(reader);
else
if (skewNodeName == reader->getNodeName())
transform *= readSkewNode(reader);
else
if (instanceNodeName == reader->getNodeName())
{
scene::ISceneNode* newnode = 0;
readInstanceNode(reader, parent, &newnode);
if (node && newnode)
{
// move children from dummy to new node
core::list<ISceneNode*>::Iterator 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);
}
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 (node)
{
// TODO: set transformation correctly into node.
// currently this isn't done correctly. Need to get transformation,
// rotation and scale from the matrix.
core::vector3df trans = transform.getTranslation();
core::vector3df rot = transform.getRotationDegrees();
node->setPosition(trans);
node->setRotation(rot);
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)
{
core::matrix4 mat;
if (reader->isEmptyElement())
return mat;
f32 floats[7];
readFloatsInsideElement(reader, floats, 7);
// TODO: build skew matrix from these 7 floats
os::Printer::log("COLLADA loader warning: <skew> not implemented yet.", ELL_WARNING);
return mat;
}
//! reads a <matrix> element and its content and creates a matrix from it
core::matrix4 CColladaFileLoader::readMatrixNode(io::IXMLReaderUTF8* reader)
{
core::matrix4 mat;
if (reader->isEmptyElement())
return mat;
readFloatsInsideElement(reader, mat.pointer(), 16);
return mat;
}
//! reads a <perspective> element and its content and creates a matrix from it
core::matrix4 CColladaFileLoader::readPerspectiveNode(io::IXMLReaderUTF8* reader)
{
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)
{
core::matrix4 mat;
if (reader->isEmptyElement())
return mat;
f32 floats[4];
readFloatsInsideElement(reader, floats, 4);
core::quaternion q(floats[0], floats[1], floats[2], floats[3]);
mat = q.getMatrix();
return mat;
}
//! reads a <scale> element and its content and creates a matrix from it
core::matrix4 CColladaFileLoader::readScaleNode(io::IXMLReaderUTF8* reader)
{
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)
{
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 a <instance> node and creates a scene node from it
void CColladaFileLoader::readInstanceNode(io::IXMLReaderUTF8* reader, scene::ISceneNode* parent,
scene::ISceneNode** outNode)
{
#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 (CreateInstances)
for (int i=0; i<(int)Prefabs.size(); ++i)
if (url == Prefabs[i]->getId())
{
*outNode = Prefabs[i]->addInstance(parent, SceneManager);
if (*outNode)
(*outNode)->setName(reader->getAttributeValue("id"));
return;
}
}
//! reads a <camera> element and stores it as prefab
void CColladaFileLoader::readCameraPrefab(io::IXMLReaderUTF8* reader)
{
CCameraPrefab* prefab = new CCameraPrefab(reader->getAttributeValue("id"));
if (!reader->isEmptyElement())
{
readColladaParameters(reader, cameraPrefabName);
SColladaParam* p;
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];
}
Prefabs.push_back(prefab);
}
//! reads a <material> element and stores it in the material section
void CColladaFileLoader::readMaterial(io::IXMLReaderUTF8* reader)
{
SColladaMaterial material;
material.Id = reader->getAttributeValue("id");
if (!reader->isEmptyElement())
{
// TODO: implement material parsing later.
// for now we only read and discard then all <param> tags.
readColladaParameters(reader, materialSectionName);
}
// add material to list of loaded materials.
Materials.push_back(material);
}
//! reads a <geometry> element and stores it as mesh if possible
void CColladaFileLoader::readGeometry(io::IXMLReaderUTF8* reader)
{
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);
// 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)
{
// 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 = (!strcmp("float", type) || !strcmp("int", type));
#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)
{
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
if (polygonsSectionName == nodeName)
{
// read polygons section
readPolygonSection(reader, VertexPositionSource, sources, mesh);
}
} // 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();
const c8* p = &data[0];
for (int i=0; i<(int)a.size(); ++i)
{
findNextNoneWhiteSpace(&p);
a[i] = readFloat(&p);
}
} // 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();
// 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<int> Indices;
};
//! reads a polygons section and creates a mesh from it
void CColladaFileLoader::readPolygonSection(io::IXMLReaderUTF8* reader,
core::stringc vertexPositionSource, core::array<SSource>& sources,
scene::SMesh* mesh)
{
core::stringc material = reader->getAttributeValue("material");
// int polygonCount = reader->getAttributeValueAsInt("count");
core::array<SInputSlot> slots;
core::array<SPolygon> polygons;
bool parsePolygonOK = false;
int inputSemanticCount = 0;
// read all <input> and
if (!reader->isEmptyElement())
while(reader->read())
{
const char* nodeName = reader->getNodeName();
if (reader->getNodeType() == io::EXN_ELEMENT)
{
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 acessors for this implementation)
int s;
for (s=0; s<(int)sources.size(); ++s)
if (sources[s].Id == sourceArrayURI)
{
// slot found
slot.Data = sources[s].Array.Data.pointer();
break;
}
if (s == (int)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 (polygonName == nodeName)
{
parsePolygonOK = true;
polygons.push_back(SPolygon());
} // end is polygon node
} // end is element node
else
if (reader->getNodeType() == io::EXN_ELEMENT_END)
{
if (polygonName == nodeName)
parsePolygonOK = false; // end parsing a polygon
else
if (polygonsSectionName == nodeName)
break; // cancel out and create mesh
} // end is element end
else
if (reader->getNodeType() == io::EXN_TEXT)
{
if (parsePolygonOK && polygons.size())
{
// parse this text and add it to the last polygon data
core::stringc data = reader->getNodeData();
const c8* p = &data[0];
SPolygon& poly = polygons.getLast();
while(*p)
{
findNextNoneWhiteSpace(&p);
poly.Indices.push_back(readInt(&p));
}
parsePolygonOK = false;
}
}
} // end while reader->read()
if (inputSemanticCount == 0 || inputSemanticCount != (int)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
int i;
int textureCoordSetCount = 0;
int normalSlotCount = 0;
int secondTexCoordSetIndex = -1;
for (i=0; i<(int)slots.size(); ++i)
{
if (slots[i].Semantic == ECIS_TEXCOORD || slots[i].Semantic == ECIS_UV )
{
++textureCoordSetCount;
if (textureCoordSetCount==2)
secondTexCoordSetIndex = i;
}
else
if (slots[i].Semantic == ECIS_NORMAL)
++normalSlotCount;
}
// 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;
for (i=0; i<(int)polygons.size(); ++i)
{
int vertexCount = polygons[i].Indices.size() / inputSemanticCount;
// for all vertices in array
for (int v=0; v<(int)polygons[i].Indices.size(); v+=inputSemanticCount)
{
video::S3DVertex vtx;
vtx.Color.set(100,255,255,255);
// for all input semantics
for (int k=0; k<(int)slots.size(); ++k)
{
// build vertex from input semantics.
int 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;
}
}
mbuffer->Vertices.push_back(vtx);
} // end for all vertices
// add vertex indices
int currentVertexCount = mbuffer->Vertices.size();
int oldVertexCount = currentVertexCount - vertexCount;
for (int 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
}
else
{
// lightmap mesh buffer
scene::SMeshBufferLightMap* mbuffer = new SMeshBufferLightMap();
buffer = mbuffer;
for (i=0; i<(int)polygons.size(); ++i)
{
int vertexCount = polygons[i].Indices.size() / inputSemanticCount;
// for all vertices in array
for (int v=0; v<(int)polygons[i].Indices.size(); v+=inputSemanticCount)
{
video::S3DVertex2TCoords vtx;
vtx.Color.set(100,255,255,255);
// for all input semantics
for (int k=0; k<(int)slots.size(); ++k)
{
// build vertex from input semantics.
int 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
int currentVertexCount = mbuffer->Vertices.size();
int oldVertexCount = currentVertexCount - vertexCount;
for (int 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
}
// calculate normals if there is no slot for it
if (!normalSlotCount)
SceneManager->getMeshManipulator()->recalculateNormals(buffer);
// recalculate bounding box
SceneManager->getMeshManipulator()->recalculateBoundingBox(buffer);
// add mesh buffer
mesh->addMeshBuffer(buffer);
buffer->drop();
}
//! reads a <light> element and stores it as prefab
void CColladaFileLoader::readLightPrefab(io::IXMLReaderUTF8* reader)
{
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 (int i=0; i<(int)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 (int i=0; i<(int)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
int i;
core::stringc semanticName = reader->getAttributeValue("semantic");
for (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 inuts inside an element and stores them in Parameters
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
int 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, s32 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();
const c8* p = &data[0];
for (int i=0; i<count; ++i)
{
findNextNoneWhiteSpace(&p);
floats[i] = readFloat(&p);
}
}
else
if (reader->getNodeType() == io::EXN_ELEMENT_END)
break; // end parsing text
}
}
//! clears all loaded data
void CColladaFileLoader::clearData()
{
// delete all prefabs
for (int i=0; i<(int)Prefabs.size(); ++i)
Prefabs[i]->drop();
Prefabs.clear();
// clear all parameters
Parameters.clear();
// clear all materials
Materials.clear();
// clear all inputs
Inputs.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