vcglib/wrap/io_trimesh/export_idtf.h

576 lines
16 KiB
C
Raw Normal View History

2007-10-16 16:42:30 +02:00
#ifndef __VCGLIB_EXPORTERIDTF
#define __VCGLIB_EXPORTERIDTF
#include <sstream>
#include <fstream>
#include <ostream>
2007-12-05 15:12:13 +01:00
#include <vcg/complex/trimesh/update/bounding.h>
2007-12-06 00:08:13 +01:00
#include <wrap/io_trimesh/io_mask.h>
#include <QString>
#include <QFile>
2007-10-16 16:42:30 +02:00
2007-10-18 17:00:51 +02:00
2007-10-16 16:42:30 +02:00
class TextUtility
{
public:
template<typename NUMERICTYPE>
static std::string nmbToStr(NUMERICTYPE n)
{
std::stringstream ss;
ss << n;
return ss.str();
}
};
2007-10-18 17:00:51 +02:00
class Output_File
2007-10-16 16:42:30 +02:00
{
public:
2007-10-18 17:00:51 +02:00
Output_File(const std::string& file)
2007-10-16 16:42:30 +02:00
:_file()
{
_file.open(file.c_str(),std::ios::out);
}
void write(unsigned int tabl,const std::string& st)
{
std::string tmp;
for(unsigned int ii = 0;ii < tabl;++ii)
tmp += '\t';
_file << tmp << st << std::endl;
}
2007-10-18 17:00:51 +02:00
~Output_File()
2007-10-16 16:42:30 +02:00
{
_file.close();
}
private:
std::ofstream _file;
std::string _tab;
};
2007-12-14 00:15:45 +01:00
#include <QString>
#include <QtGlobal>
#include <fstream>
2007-12-18 18:50:13 +01:00
#include <QImage>
2007-12-14 00:15:45 +01:00
2007-10-18 17:00:51 +02:00
namespace vcg {
namespace tri {
namespace io {
namespace QtUtilityFunctions
{
static void splitFilePath(const QString& filepath,QStringList& trim_path)
{
QString file_uniformed = filepath;
file_uniformed.replace(QString("\\"),QString("/"));
trim_path = file_uniformed.split("/");
}
static QString fileNameFromTrimmedPath(const QStringList& file_path)
{
if (file_path.size() > 0)
return file_path.at(file_path.size() - 1);
else
return QString();
}
static QString fileNameFromPath(const QString& filepath)
{
QStringList list;
splitFilePath(filepath,list);
return fileNameFromTrimmedPath(list);
}
static QString pathWithoutFileName(const QString& filepath)
{
QString tmp(filepath);
tmp.remove(fileNameFromPath(filepath));
return tmp;
}
static QString fileExtension(const QString& filepath)
{
QStringList trim_list;
splitFilePath(filepath,trim_list);
QString file = fileNameFromTrimmedPath(trim_list);
trim_list = file.split(".");
return trim_list.at(trim_list.size() - 1);
}
}
class TGA_Exporter
{
public:
struct TGAHeader
{
unsigned char identsize;
unsigned char colourmaptype;
unsigned char imagetype;
unsigned char colormapspecs[5];
short xstart;
short ystart;
short width;
short height;
unsigned char bits;
unsigned char descriptor;
};
static void convert(const QString& outfile,const QImage& im)
{
TGAHeader tga;
tga.identsize = 0;
tga.colourmaptype = 0;
tga.imagetype = 2;
memset(tga.colormapspecs,0,5);
tga.xstart = (short) im.offset().x();
tga.ystart = (short) im.offset().y();
tga.height = (short) im.height();
tga.width = (short) im.width();
//QString moutfile = QString("C:/Users/Guido/AppData/Local/Temp/duckCM.tga");
QFile file(qPrintable(outfile));
file.setPermissions(QFile::WriteOther);
file.open(QIODevice::WriteOnly);
QString err = file.errorString();
//bool val = file.failbit;
unsigned char* tmpchan;
int totbyte;
if (im.hasAlphaChannel())
{
//is a 8-digits binary number code
// always 0 0 | mirroring | bits
//(future uses)| image | for alpha-channel
//--------------------------------------------
// 7 6 | 5 4 | 3 2 1 0
//--------------------------------------------
// 0 0 | 1 0 | 1 0 0 0
tga.descriptor = (char) 40;
tga.bits = (char) 32;
}
else
{
//is a 8-digits binary number code
// always 0 0 | mirroring | bits
//(future uses)| image | for alpha-channel
//--------------------------------------------
// 7 6 | 5 4 | 3 2 1 0
//--------------------------------------------
// 0 0 | 1 0 | 0 0 0 0
tga.descriptor = (char) 32;
tga.bits = (char) 24;
}
totbyte = tga.height * tga.width * (tga.bits / 8);
if (im.hasAlphaChannel())
tmpchan = const_cast<unsigned char*>(im.bits());
else
{
tmpchan = new unsigned char[totbyte];
int ii = 0;
while(ii < totbyte)
{
tmpchan[ii] = const_cast<unsigned char*>(im.bits())[ii + (ii/3)];
++ii;
}
}
file.write((char *) &tga,qint64(sizeof(tga)));
file.write(reinterpret_cast<const char*>(tmpchan),qint64(totbyte));
file.close();
}
template<typename SaveMeshType>
static void convertTexturesFiles(SaveMeshType& m,const QString& file_path,QStringList& conv_file)
{
for(unsigned int ii = 0; ii < m.textures.size(); ++ii)
{
QString qtmp(m.textures[ii].c_str());
QString ext = QtUtilityFunctions::fileExtension(qtmp);
QString filename = QtUtilityFunctions::fileNameFromPath(qtmp);
if (ext.toLower() != "tga")
{
QImage img(qtmp);
QString stmp;
if ((file_path.at(file_path.length() - 1) != '/') || (file_path.at(file_path.length() - 1) != '\\'))
stmp = file_path + QString("/");
else
stmp = file_path;
filename = stmp + filename.remove(ext) + "tga";
m.textures[ii] = filename.toStdString();
TGA_Exporter::convert(filename,img);
conv_file.push_back(filename);
}
}
}
static void removeConvertedTexturesFiles(const QStringList& conv_file)
{
for(unsigned int ii = 0;ii < conv_file.size();++ii)
{
QDir dir(QtUtilityFunctions::pathWithoutFileName(conv_file[ii]));
dir.remove(QtUtilityFunctions::fileNameFromPath(conv_file[ii]));
}
}
};
2007-10-16 16:42:30 +02:00
template<typename SaveMeshType>
class ExporterIDTF
{
2007-10-16 16:42:30 +02:00
public:
2007-11-26 21:38:54 +01:00
typedef typename SaveMeshType::VertexPointer VertexPointer;
typedef typename SaveMeshType::ScalarType ScalarType;
typedef typename SaveMeshType::VertexType VertexType;
typedef typename SaveMeshType::FaceType FaceType;
typedef typename SaveMeshType::ConstVertexIterator ConstVertexIterator;
typedef typename SaveMeshType::VertexIterator VertexIterator;
typedef typename SaveMeshType::FaceIterator FaceIterator;
typedef typename SaveMeshType::ConstFaceIterator ConstFaceIterator;
typedef typename SaveMeshType::CoordType CoordType;
2007-11-21 11:25:45 +01:00
enum IDTFError
{
E_NOERROR // 0
};
static const char *ErrorMsg(int error)
{
static const char * dae_error_msg[] =
{
"No errors"
};
if(error>0 || error<0) return "Unknown error";
else return dae_error_msg[error];
};
static QStringList convertInTGATextures(SaveMeshType& m,const QString& path,QStringList& textures_to_be_restored)
{
//if there are textures file that aren't in tga format I have to convert them
//I maintain the converted file name (i.e. file_path + originalname without extension + tga) in mesh.textures but I have to revert to the original ones
//before the function return.
for(unsigned int ii = 0; ii < m.textures.size();++ii)
textures_to_be_restored.push_back(m.textures[ii].c_str());
//tmp vector to save the tga created files that should be deleted.
QStringList convfile;
vcg::tri::io::TGA_Exporter::convertTexturesFiles(m,path,convfile);
return convfile;
}
static void removeConvertedTGATextures(const QStringList& convfile)
{
//if some tga files have been created I have to delete them
vcg::tri::io::TGA_Exporter::removeConvertedTexturesFiles(convfile);
}
static void restoreConvertedTextures(SaveMeshType& mesh_with_textures_to_be_restored,const QStringList& textures_to_be_restored)
{
mesh_with_textures_to_be_restored.textures.clear();
for(QStringList::ConstIterator it = textures_to_be_restored.begin();it != textures_to_be_restored.end();++it)
mesh_with_textures_to_be_restored.textures.push_back(it->toStdString());
}
2007-11-21 11:25:45 +01:00
static int Save(SaveMeshType& m,const char* file,const int mask)
2007-10-16 16:42:30 +02:00
{
2007-10-18 17:00:51 +02:00
Output_File idtf(file);
2007-10-16 16:42:30 +02:00
idtf.write(0,"FILE_FORMAT \"IDTF\"");
idtf.write(0,"FORMAT_VERSION 100\n");
idtf.write(0,"NODE \"MODEL\" {");
idtf.write(1,"NODE_NAME \"VcgMesh01\"");
idtf.write(1,"PARENT_LIST {");
idtf.write(2,"PARENT_COUNT 1");
idtf.write(2,"PARENT 0 {");
idtf.write(3,"PARENT_NAME \"<NULL>\"");
idtf.write(3,"PARENT_TM {");
idtf.write(4,"1.000000 0.000000 0.000000 0.000000");
idtf.write(4,"0.000000 1.000000 0.000000 0.000000");
idtf.write(4,"0.000000 0.000000 1.000000 0.000000");
idtf.write(4,"0.000000 0.000000 0.000000 1.000000");
idtf.write(3,"}");
idtf.write(2,"}");
idtf.write(1,"}");
2007-12-14 00:15:45 +01:00
idtf.write(1,"RESOURCE_NAME \"MyVcgMesh01\"");
2007-10-16 16:42:30 +02:00
idtf.write(0,"}");
2007-12-06 00:08:13 +01:00
if (mask & Mask::IOM_WEDGTEXCOORD)
2007-10-16 16:42:30 +02:00
{
2007-12-14 00:15:45 +01:00
idtf.write(0,"");
idtf.write(0,"RESOURCE_LIST \"SHADER\" {");
idtf.write(1,"RESOURCE_COUNT " + TextUtility::nmbToStr(m.textures.size()));
for(unsigned int ii = 0; ii < m.textures.size(); ++ii)
{
idtf.write(1,"RESOURCE " + TextUtility::nmbToStr(ii) + " {");
idtf.write(2,"RESOURCE_NAME \"ModelShader" + TextUtility::nmbToStr(ii) +"\"");
idtf.write(2,"SHADER_MATERIAL_NAME \"Material1\"");
idtf.write(2,"SHADER_ACTIVE_TEXTURE_COUNT 1");
idtf.write(2,"SHADER_TEXTURE_LAYER_LIST {");
idtf.write(3,"TEXTURE_LAYER 0 {");
idtf.write(4,"TEXTURE_NAME \"Texture" + TextUtility::nmbToStr(ii) +"\"");
idtf.write(3,"}");
idtf.write(2,"}");
}
idtf.write(1,"}");
idtf.write(0,"}");
idtf.write(0,"");
idtf.write(0,"RESOURCE_LIST \"MATERIAL\" {");
2007-10-16 16:42:30 +02:00
idtf.write(1,"RESOURCE_COUNT 1");
idtf.write(1,"RESOURCE 0 {");
2007-12-14 00:15:45 +01:00
idtf.write(2,"RESOURCE_NAME \"Material1\"");
idtf.write(2,"MATERIAL_AMBIENT 0.2 0.2 0.2");
idtf.write(2,"MATERIAL_DIFFUSE 0.8 0.8 0.8");
idtf.write(2,"MATERIAL_SPECULAR 0.0 0.0 0.0");
idtf.write(2,"MATERIAL_EMISSIVE 0.0 0.0 0.0");
2007-12-14 00:15:45 +01:00
idtf.write(2,"MATERIAL_REFLECTIVITY 0.100000");
idtf.write(2,"MATERIAL_OPACITY 1.000000");
idtf.write(1,"}");
idtf.write(0,"}");
idtf.write(0,"");
idtf.write(0,"RESOURCE_LIST \"TEXTURE\" {");
idtf.write(1,"RESOURCE_COUNT " + TextUtility::nmbToStr(m.textures.size()));
for(unsigned int ii = 0; ii < m.textures.size();++ii)
{
idtf.write(1,"RESOURCE " + TextUtility::nmbToStr(ii) + " {");
idtf.write(2,"RESOURCE_NAME \"Texture" + TextUtility::nmbToStr(ii) + "\"");
idtf.write(2,"TEXTURE_PATH \"" + m.textures[ii] + "\"");
2007-12-14 13:03:44 +01:00
idtf.write(1,"}");
2007-12-14 00:15:45 +01:00
}
2007-10-16 16:42:30 +02:00
idtf.write(0,"}");
}
2007-12-14 00:15:45 +01:00
idtf.write(0,"");
2007-10-16 16:42:30 +02:00
idtf.write(0,"RESOURCE_LIST \"MODEL\" {");
idtf.write(1,"RESOURCE_COUNT 1");
idtf.write(1,"RESOURCE 0 {");
2007-12-14 00:15:45 +01:00
idtf.write(2,"RESOURCE_NAME \"MyVcgMesh01\"");
2007-10-16 16:42:30 +02:00
idtf.write(2,"MODEL_TYPE \"MESH\"");
idtf.write(2,"MESH {");
idtf.write(3,"FACE_COUNT " + TextUtility::nmbToStr(m.face.size()));
idtf.write(3,"MODEL_POSITION_COUNT " + TextUtility::nmbToStr(m.vert.size()));
idtf.write(3,"MODEL_NORMAL_COUNT " + TextUtility::nmbToStr(m.face.size() * 3));
if (mask & vcg::tri::io::Mask::IOM_VERTCOLOR)
idtf.write(3,"MODEL_DIFFUSE_COLOR_COUNT " + TextUtility::nmbToStr(m.face.size() * 3));
else
idtf.write(3,"MODEL_DIFFUSE_COLOR_COUNT 0");
2007-10-16 16:42:30 +02:00
idtf.write(3,"MODEL_SPECULAR_COLOR_COUNT 0");
2007-12-14 00:15:45 +01:00
if (mask & vcg::tri::io::Mask::IOM_WEDGTEXCOORD) idtf.write(3,"MODEL_TEXTURE_COORD_COUNT " + TextUtility::nmbToStr(m.face.size() * 3));
2007-10-16 16:42:30 +02:00
else idtf.write(3,"MODEL_TEXTURE_COORD_COUNT 0");
idtf.write(3,"MODEL_BONE_COUNT 0");
2007-12-14 13:03:44 +01:00
unsigned int mod_sha;
if (m.textures.size() == 0)
mod_sha = 1;
else
mod_sha = m.textures.size();
idtf.write(3,"MODEL_SHADING_COUNT " + TextUtility::nmbToStr(mod_sha));
2007-10-16 16:42:30 +02:00
idtf.write(3,"MODEL_SHADING_DESCRIPTION_LIST {");
2007-12-14 00:15:45 +01:00
unsigned int hh = 0;
do
2007-10-16 16:42:30 +02:00
{
2007-12-14 00:15:45 +01:00
idtf.write(4,"SHADING_DESCRIPTION " + TextUtility::nmbToStr(hh) + " {");
if (mask & vcg::tri::io::Mask::IOM_WEDGTEXCOORD)
{
idtf.write(5,"TEXTURE_LAYER_COUNT 1");
idtf.write(5,"TEXTURE_COORD_DIMENSION_LIST {");
idtf.write(6,"TEXTURE_LAYER 0 DIMENSION: 2");
idtf.write(5,"}");
idtf.write(5,"SHADER_ID 0");
}
else
{
idtf.write(5,"TEXTURE_LAYER_COUNT 0");
idtf.write(5,"SHADER_ID 0");
}
idtf.write(4,"}");
++hh;
2007-10-16 16:42:30 +02:00
}
2007-12-14 00:15:45 +01:00
while(hh < m.textures.size());
2007-10-16 16:42:30 +02:00
idtf.write(3,"}");
idtf.write(3,"MESH_FACE_POSITION_LIST {");
2007-11-26 21:38:54 +01:00
for(ConstFaceIterator fit = m.face.begin();fit != m.face.end();++fit)
2007-10-16 16:42:30 +02:00
{
idtf.write(4,TextUtility::nmbToStr(fit->V(0) - &(*m.vert.begin())) + " " +
2007-12-14 13:03:44 +01:00
TextUtility::nmbToStr(fit->V(1) - &(*m.vert.begin())) + " " +
TextUtility::nmbToStr(fit->V(2) - &(*m.vert.begin())));
2007-10-16 16:42:30 +02:00
}
idtf.write(3,"}");
idtf.write(3,"MESH_FACE_NORMAL_LIST {");
unsigned int nn = 0;
2007-11-26 21:38:54 +01:00
for(ConstFaceIterator fit = m.face.begin();fit != m.face.end();++fit)
2007-10-16 16:42:30 +02:00
{
idtf.write(4,TextUtility::nmbToStr(nn) + " " +
TextUtility::nmbToStr(nn + 1) + " " +
TextUtility::nmbToStr(nn + 2));
nn += 3;
}
idtf.write(3,"}");
idtf.write(3,"MESH_FACE_SHADING_LIST {");
for(FaceIterator fit = m.face.begin();fit != m.face.end();++fit)
{
unsigned int texind = 0;
if (mask & vcg::tri::io::Mask::IOM_WEDGTEXCOORD)
texind = fit->WT(0).N();
idtf.write(4,TextUtility::nmbToStr(texind));
}
idtf.write(3,"}");
if (mask & vcg::tri::io::Mask::IOM_VERTCOLOR)
{
idtf.write(3,"MESH_FACE_DIFFUSE_COLOR_LIST {");
nn = 0;
2007-12-14 13:03:44 +01:00
for(FaceIterator fit = m.face.begin();fit != m.face.end();++fit)
{
idtf.write(4,TextUtility::nmbToStr(nn) + " " +
TextUtility::nmbToStr(nn + 2) + " " +
TextUtility::nmbToStr(nn + 1));
nn += 3;
2007-12-14 13:03:44 +01:00
}
idtf.write(3,"}");
}
2007-12-14 00:15:45 +01:00
if (mask & vcg::tri::io::Mask::IOM_WEDGTEXCOORD)
{
idtf.write(3,"MESH_FACE_TEXTURE_COORD_LIST {");
for(unsigned int ii = 0; ii < m.face.size();++ii)
{
idtf.write(4,"FACE " + TextUtility::nmbToStr(ii) + " {");
2007-12-14 13:03:44 +01:00
idtf.write(5,"TEXTURE_LAYER 0 TEX_COORD: " + TextUtility::nmbToStr(ii * 3) + " " + TextUtility::nmbToStr(ii * 3 + 1) + " " + TextUtility::nmbToStr(ii * 3 + 2));
2007-12-14 00:15:45 +01:00
idtf.write(4,"}");
}
idtf.write(3,"}");
}
2007-10-16 16:42:30 +02:00
idtf.write(3,"MODEL_POSITION_LIST {");
//vcg::tri::UpdateBounding<SaveMeshType>::Box(m);
2007-12-12 01:24:57 +01:00
//ScalarType diag = m.bbox.Diag();
2007-12-19 15:06:06 +01:00
//CoordType center = m.bbox.Center();
2007-11-26 21:38:54 +01:00
for(ConstVertexIterator vit = m.vert.begin();vit != m.vert.end();++vit)
2007-10-16 16:42:30 +02:00
{
2007-12-11 15:51:34 +01:00
CoordType tmp = vit->P();// - center);// /diag;
2007-12-14 13:03:44 +01:00
idtf.write(4,TextUtility::nmbToStr(-tmp.X()) + " " +
2007-12-05 15:12:13 +01:00
TextUtility::nmbToStr(tmp.Z()) + " " +
TextUtility::nmbToStr(tmp.Y()));
2007-10-16 16:42:30 +02:00
}
idtf.write(3,"}");
idtf.write(3,"MODEL_NORMAL_LIST {");
2007-11-26 21:38:54 +01:00
for(FaceIterator fitn = m.face.begin();fitn != m.face.end();++fitn)
2007-10-16 16:42:30 +02:00
{
for(unsigned int ii = 0;ii < 3;++ii)
{
2007-12-10 17:52:38 +01:00
fitn->N().Normalize();
2007-12-19 15:06:06 +01:00
idtf.write(4,TextUtility::nmbToStr(-fitn->N().X()) + " " +
2007-12-05 15:12:13 +01:00
TextUtility::nmbToStr(fitn->N().Z()) + " " +
TextUtility::nmbToStr(fitn->N().Y()));
2007-10-16 16:42:30 +02:00
}
}
idtf.write(3,"}");
2007-10-16 16:42:30 +02:00
if (mask & vcg::tri::io::Mask::IOM_WEDGTEXCOORD)
{
idtf.write(3,"MODEL_TEXTURE_COORD_LIST {");
2007-11-26 21:38:54 +01:00
for(FaceIterator fitn = m.face.begin();fitn != m.face.end();++fitn)
2007-10-16 16:42:30 +02:00
{
for(unsigned int ii = 0;ii < 3;++ii)
{
idtf.write(4,TextUtility::nmbToStr(fitn->WT(ii).U()) + " " +
2007-12-14 13:03:44 +01:00
TextUtility::nmbToStr(-fitn->WT(ii).V()) + " " + TextUtility::nmbToStr(0.0f) + " " + TextUtility::nmbToStr(0.0f));
2007-10-16 16:42:30 +02:00
}
}
idtf.write(3,"}");
}
if (mask & vcg::tri::io::Mask::IOM_VERTCOLOR)
{
idtf.write(3,"MODEL_DIFFUSE_COLOR_LIST {");
//ScalarType diag = m.bbox.Diag();
//CoordType center = m.bbox.Center();
for(FaceIterator vit = m.face.begin();vit != m.face.end();++vit)
{
for (unsigned int ii =0; ii <3;++ii)
idtf.write(4,TextUtility::nmbToStr((int) vit->V(ii)->C().X()) + " " +
TextUtility::nmbToStr((int) vit->V(ii)->C().Y()) + " " +
TextUtility::nmbToStr((int) vit->V(ii)->C().Z()) + " 0");
}
idtf.write(3,"}");
}
2007-10-16 16:42:30 +02:00
idtf.write(2,"}");
idtf.write(1,"}");
idtf.write(0,"}");
if (mask & vcg::tri::io::Mask::IOM_VERTCOLOR)
{
idtf.write(0,"RESOURCE_LIST \"SHADER\" {");
idtf.write(1,"RESOURCE_COUNT 1");
idtf.write(1,"RESOURCE 0 {");
idtf.write(2,"RESOURCE_NAME \"VcgMesh010\"");
idtf.write(2,"ATTRIBUTE_USE_VERTEX_COLOR \"TRUE\"");
idtf.write(2,"SHADER_ACTIVE_TEXTURE_COUNT 0");
idtf.write(1,"}");
idtf.write(0,"}");
}
2007-12-14 00:15:45 +01:00
if (mask & vcg::tri::io::Mask::IOM_WEDGTEXCOORD)
{
idtf.write(0,"");
idtf.write(0,"MODIFIER \"SHADING\" {");
idtf.write(1,"MODIFIER_NAME \"VcgMesh01\"");
idtf.write(1,"PARAMETERS {");
idtf.write(2,"SHADER_LIST_COUNT " + TextUtility::nmbToStr(m.textures.size()));
idtf.write(2,"SHADING_GROUP {");
for(unsigned int ii = 0; ii < m.textures.size();++ii)
{
idtf.write(3,"SHADER_LIST " + TextUtility::nmbToStr(ii) + "{");
idtf.write(4,"SHADER_COUNT 1");
idtf.write(4,"SHADER_NAME_LIST {");
idtf.write(5,"SHADER 0 NAME: \"ModelShader" + TextUtility::nmbToStr(ii) + "\"");
idtf.write(4,"}");
idtf.write(3,"}");
}
idtf.write(2,"}");
idtf.write(1,"}");
idtf.write(0,"}");
}
2007-11-26 21:38:54 +01:00
return E_NOERROR;
2007-10-16 16:42:30 +02:00
}
static int GetExportMaskCapability()
{
int capability = 0;
//vert
capability |= vcg::tri::io::Mask::IOM_VERTNORMAL;
capability |= vcg::tri::io::Mask::IOM_VERTCOLOR;
////wedg
capability |= vcg::tri::io::Mask::IOM_WEDGTEXCOORD;
capability |= vcg::tri::io::Mask::IOM_WEDGNORMAL;
return capability;
}
2007-10-16 16:42:30 +02:00
};
}
}
}
2007-10-16 16:42:30 +02:00
#endif