/**
* @file daeexport.cpp
* @brief A system which allows saving in-world objects to Collada .DAE files for offline texturizing/shading.
* @authors Latif Khalifa
*
* $LicenseInfo:firstyear=2013&license=LGPLV2.1$
* Copyright (C) 2013 Latif Khalifa
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
#include "llviewerprecompiledheaders.h"

#include "daeexport.h"

//colladadom includes
#include "dae.h"
#include "dom/domCOLLADA.h"
#include "dom/domMatrix.h"

// library includes
#include "aifilepicker.h"
#include "llnotificationsutil.h"

// newview includes
#include "lfsimfeaturehandler.h"
#include "llface.h"
#include "llvovolume.h"
#include "llviewerinventory.h"
#include "llinventorymodel.h"
#include "llinventoryfunctions.h"

// menu includes
#include "llevent.h"
#include "llmemberlistener.h"
#include "llselectmgr.h"

// Floater and UI
#include "llfloater.h"
#include "lluictrlfactory.h"
#include "llscrollcontainer.h"
#include "lltexturectrl.h"

// Files and cache
#include "llcallbacklist.h"
#include "lldir.h"
#include "lltexturecache.h"
#include "llimage.h"
#include "llimagej2c.h"
#include "llimagepng.h"
#include "llimagetga.h"
#include "llimagebmp.h"
#include "llimagejpeg.h"

#define TEXTURE_DOWNLOAD_TIMEOUT 60.0f

extern LLUUID gAgentID;

//Typedefs used in other files, using here for consistency.
typedef LLMemberListener<LLView> view_listener_t;

namespace DAEExportUtil
{
	static LLUUID LL_TEXTURE_PLYWOOD		= LLUUID("89556747-24cb-43ed-920b-47caed15465f");
	static LLUUID LL_TEXTURE_BLANK			= LLUUID("5748decc-f629-461c-9a36-a35a221fe21f");
	static LLUUID LL_TEXTURE_INVISIBLE		= LLUUID("38b86f85-2575-52a9-a531-23108d8da837");
	static LLUUID LL_TEXTURE_TRANSPARENT	= LLUUID("8dcd4a48-2d37-4909-9f78-f7a9eb4ef903");
	static LLUUID LL_TEXTURE_MEDIA			= LLUUID("8b5fec65-8d8d-9dc5-cda8-8fdf2716e361");

	enum image_format_type
	{
		ft_tga,
		ft_png,
		ft_j2c,
		ft_bmp,
		ft_jpg
	};

	static const std::string image_format_ext[] = { "tga", "png", "j2c", "bmp", "jpg" };

	static bool canExportTexture(const LLUUID& id, std::string* name = NULL)
	{
		// Find inventory items with asset id of the sculpt map
		LLViewerInventoryCategory::cat_array_t cats;
		LLViewerInventoryItem::item_array_t items;
		LLAssetIDMatches asset_id_matches(id);
		gInventory.collectDescendentsIf(LLUUID::null,
			cats,
			items,
			LLInventoryModel::INCLUDE_TRASH,
			asset_id_matches);

		// See if any of the inventory items matching this texture id are exportable
		ExportPolicy policy = LFSimFeatureHandler::instance().exportPolicy();
		for (S32 i = 0; i < items.count(); i++)
		{
			const LLPermissions item_permissions = items[i]->getPermissions();
			if (item_permissions.allowExportBy(gAgentID, policy))
			{
				if (name != NULL)
				{
					(*name) = items[i]->getName();
				}
				return true;
			}
		}

		if (name != NULL)
		{
			(*name) = id.getString();
		}

		return (policy & ep_full_perm) == ep_full_perm;
	}

	static bool canExportNode(LLSelectNode* node)
	{
		LLPermissions* perms = node->mPermissions;	// Is perms ever NULL?
		// This tests the PERM_EXPORT bit too, which is not really necessary (just checking if it's set
		// on the root prim would suffice), but also isn't hurting.
		if (!(perms && perms->allowExportBy(gAgentID, LFSimFeatureHandler::instance().exportPolicy())))
		{
			return false;
		}

		// Additionally chack if this is a sculpt
		LLViewerObject* obj = node->getObject();
		if (obj->isSculpted() && !obj->isMesh())
		{
			LLSculptParams *sculpt_params = (LLSculptParams *)obj->getParameterEntry(LLNetworkData::PARAMS_SCULPT);
			LLUUID sculpt_id = sculpt_params->getSculptTexture();
			return canExportTexture(sculpt_id);
		}
		else // not sculpt, we already checked generic permissions
		{
			return true;
		}
	}
}


class ColladaExportFloater
	: public LLFloater
{
private:
	typedef std::map<LLUUID, std::string> texture_list_t;
	LLView* mExportBtn;
	LLView* mFileName;
	LLView* mTextureTypeCombo;

	DAESaver mSaver;
	texture_list_t mTexturesToSave;
	S32 mTotal;
	S32 mNumTextures;
	S32 mNumExportableTextures;
	std::string mObjectName;
	LLTimer mTimer;
	LLUIString mTitleProgress;

public:
	ColladaExportFloater()
		: LLFloater(std::string("Collada Export"), std::string("ColladaExportFloaterRect"), LLStringUtil::null)
	{
		mCommitCallbackRegistrar.add("ColladaExport.FilePicker",		boost::bind(&ColladaExportFloater::onClickBrowse, this));
		mCommitCallbackRegistrar.add("ColladaExport.Export",			boost::bind(&ColladaExportFloater::onClickExport, this));
		mCommitCallbackRegistrar.add("ColladaExport.TextureTypeCombo",	boost::bind(&ColladaExportFloater::onTextureTypeCombo, this, boost::bind(&LLUICtrl::getControlName, _1), _2));
		mCommitCallbackRegistrar.add("ColladaExport.TextureExport",		boost::bind(&ColladaExportFloater::onTextureExportCheck, this, _2));
		LLUICtrlFactory::getInstance()->buildFloater(this, "floater_dae_export.xml");

		addSelectedObjects();
		if (LLUICtrl* ctrl = findChild<LLUICtrl>("Object Name"))
		{
			ctrl->setTextArg("[NAME]", mObjectName);
		}
		if (LLUICtrl* ctrl = findChild<LLUICtrl>("Exportable Prims"))
		{
			ctrl->setTextArg("[COUNT]", llformat("%d", mSaver.mObjects.size()));
			ctrl->setTextArg("[TOTAL]", llformat("%d", mTotal));
		}
		if (LLUICtrl* ctrl = findChild<LLUICtrl>("Exportable Textures"))
		{
			ctrl->setTextArg("[COUNT]", llformat("%d", mNumExportableTextures));
			ctrl->setTextArg("[TOTAL]", llformat("%d", mNumTextures));
		}
		addTexturePreview();
	}

	virtual ~ColladaExportFloater()
	{
		if (gIdleCallbacks.containsFunction(saveTexturesWorker, this))
		{
			gIdleCallbacks.deleteFunction(saveTexturesWorker, this);
		}
	}

	BOOL postBuild()
	{
		mFileName				= getChildView("file name editor");
		mExportBtn				= getChildView("export button");
		mTextureTypeCombo       = getChildView("texture type combo");
		mTitleProgress			= getString("texture_progress");

		mTextureTypeCombo->setValue(gSavedSettings.getS32(mTextureTypeCombo->getControlName()));
		onTextureExportCheck(getChildView("texture export check")->getValue());
		return TRUE;
	}

	void updateTitleProgress()
	{
		mTitleProgress.setArg("COUNT", llformat("%d", mTexturesToSave.size()));
		setTitle(mTitleProgress);
	}

	void onTextureExportCheck(const LLSD& value)
	{
		mTextureTypeCombo->setEnabled(value);
	}

	void onTextureTypeCombo(const std::string& control_name, const LLSD& value)
	{
		gSavedSettings.setS32(control_name, value);
	}

	void onClickBrowse()
	{
		static const std::string file_ext = ".dae";
		AIFilePicker* filepicker = AIFilePicker::create();
		filepicker->open(mObjectName + file_ext);
		filepicker->run(boost::bind(&ColladaExportFloater::onFilePicker, this, filepicker));
	}

	void onFilePicker(AIFilePicker* filepicker)
	{
		if (filepicker->hasFilename())
		{
			mFileName->setValue(filepicker->getFilename());
			mExportBtn->setEnabled(TRUE);
		}
	}

	void onClickExport()
	{
		if (gSavedSettings.getBOOL("DAEExportTextures"))
		{
			saveTextures();
		}
		else
		{
			onTexturesSaved();
		}
	}

	void onTexturesSaved()
	{
		std::string selected_filename = mFileName->getValue();
		mSaver.saveDAE(selected_filename);
		LLNotificationsUtil::add("WavefrontExportSuccess", LLSD().with("FILENAME", selected_filename));
		close();
	}

	void addSelectedObjects()
	{
		LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection();

		if (selection && selection->getFirstRootObject())
		{
			mSaver.mOffset = -selection->getFirstRootObject()->getRenderPosition();
			mObjectName = selection->getFirstRootNode()->mName;
			mTotal = 0;

			for (LLObjectSelection::iterator iter = selection->begin(); iter != selection->end(); ++iter)
			{
				mTotal++;
				LLSelectNode* node = *iter;
				if (!node->getObject()->getVolume() || !DAEExportUtil::canExportNode(node)) continue;
				mSaver.add(node->getObject(), node->mName);
			}
		}

		if (mSaver.mObjects.empty())
		{
			LLNotificationsUtil::add("ExportFailed");
			close();
		}
		else
		{
			mSaver.updateTextureInfo();
			mNumTextures = mSaver.mTextures.size();
			mNumExportableTextures = getNumExportableTextures();
		}
	}

	S32 getNumExportableTextures()
	{
		S32 res = 0;

		for (DAESaver::string_list_t::const_iterator t = mSaver.mTextureNames.begin(); t != mSaver.mTextureNames.end(); ++t)
		{
			std::string name = *t;
			if (!name.empty())
			{
				++res;
			}
		}

		return res;
	}

	void addTexturePreview()
	{
		S32 num_text = mNumExportableTextures;
		if (num_text == 0) return;

		S32 img_width = 100;
		S32 img_height = img_width + 15;
		S32 panel_height = (num_text / 3 + 1) * (img_height) + 10;
		LLScrollContainer* scroll_container = getChild<LLScrollContainer>("textures container");
		LLPanel* panel = new LLPanel(std::string(), LLRect(0, panel_height, 350, 0), false);
		scroll_container->setScrolledView(panel);
		scroll_container->addChild(panel);
		panel->setEnabled(FALSE);
		S32 img_nr = 0;
		for (U32 i=0; i < mSaver.mTextures.size(); i++)
		{
			if (mSaver.mTextureNames[i].empty()) continue;

			S32 left = 16 + (img_nr % 3) * (img_width + 13);
			S32 bottom = panel_height - (10 + (img_nr / 3 + 1) * (img_height));

			LLRect rect(left, bottom + img_height, left + img_width, bottom);
			LLTextureCtrl* img = new LLTextureCtrl(std::string(), rect, std::string(), mSaver.mTextures[i], LLUUID(), std::string());
			panel->addChild(img);
			img_nr++;
		}
	}

	void saveTextures()
	{
		mTexturesToSave.clear();
		for (U32 i=0; i < mSaver.mTextures.size(); i++)
		{
			if (mSaver.mTextureNames[i].empty()) continue;
			mTexturesToSave[mSaver.mTextures[i]] = mSaver.mTextureNames[i];
		}

		mSaver.mImageFormat = DAEExportUtil::image_format_ext[(S32)mTextureTypeCombo->getValue()];

		LL_INFOS("ColladaExport") << "Starting to save textures" << LL_ENDL;
		mTimer.setTimerExpirySec(TEXTURE_DOWNLOAD_TIMEOUT);
		mTimer.start();
		updateTitleProgress();
		gIdleCallbacks.addFunction(saveTexturesWorker, this);
	}

	class CacheReadResponder : public LLTextureCache::ReadResponder
	{
	private:
		LLPointer<LLImageFormatted> mFormattedImage;
		LLUUID mID;
		std::string mName;
		S32 mImageType;

	public:
		CacheReadResponder(const LLUUID& id, LLImageFormatted* image, std::string name, S32 img_type)
			: mFormattedImage(image), mID(id), mName(name), mImageType(img_type)
		{
			setImage(image);
		}

		void setData(U8* data, S32 datasize, S32 imagesize, S32 imageformat, BOOL imagelocal)
		{
			if (imageformat == IMG_CODEC_TGA && mFormattedImage->getCodec() == IMG_CODEC_J2C)
			{
				LL_WARNS("ColladaExport") << "FAILED: texture " << mID << " is formatted as TGA. Not saving." << LL_ENDL;
				mFormattedImage = NULL;
				mImageSize = 0;
				return;
			}

			if (mFormattedImage.notNull())
			{	
				if (mFormattedImage->getCodec() == imageformat)
				{
					mFormattedImage->appendData(data, datasize);
				}
				else 
				{
					LL_WARNS("ColladaExport") << "FAILED: texture " << mID << " in wrong format." << LL_ENDL;
					mFormattedImage = NULL;
					mImageSize = 0;
					return;
				}
			}
			else
			{
				mFormattedImage = LLImageFormatted::createFromType(imageformat);
				mFormattedImage->setData(data, datasize);
			}
			mImageSize = imagesize;
			mImageLocal = imagelocal;
		}

		virtual void completed(bool success)
		{
			if (success && mFormattedImage.notNull() && mImageSize > 0)
			{
				bool ok = false;

				// If we are saving jpeg2000, no need to do anything, just write to disk
				if (mImageType == DAEExportUtil::ft_j2c)
				{
					mName += "." + mFormattedImage->getExtension();
					ok = mFormattedImage->save(mName);
				}
				else
				{
					// For other formats we need to decode first
					if (mFormattedImage->updateData() && (mFormattedImage->getWidth() * mFormattedImage->getHeight() * mFormattedImage->getComponents()))
					{
						LLPointer<LLImageRaw> raw = new LLImageRaw;
						raw->resize(mFormattedImage->getWidth(), mFormattedImage->getHeight(),	mFormattedImage->getComponents());

						if (mFormattedImage->decode(raw, 0))
						{
							LLPointer<LLImageFormatted> img = NULL;
							switch (mImageType)
							{
							case DAEExportUtil::ft_tga:
								img = new LLImageTGA;
								break;
							case DAEExportUtil::ft_png:
								img = new LLImagePNG;
								break;
							case DAEExportUtil::ft_bmp:
								img = new LLImageBMP;
								break;
							case DAEExportUtil::ft_jpg:
								img = new LLImageJPEG;
								break;
							}

							if (!img.isNull())
							{
								if (img->encode(raw, 0))
								{
									mName += "." + img->getExtension();
									ok = img->save(mName);
								}
							}
						}
					}
				}

				if (ok)
				{
					LL_INFOS("ColladaExport") << "Saved texture to " << mName << LL_ENDL;
				}
				else
				{
					LL_WARNS("ColladaExport") << "FAILED to save texture " << mID << LL_ENDL;
				}
			}
			else
			{
				LL_WARNS("ColladaExport") << "FAILED to save texture " << mID << LL_ENDL;
			}
		}
	};

	static void saveTexturesWorker(void* data)
	{
		ColladaExportFloater* me = (ColladaExportFloater *)data;
		if (me->mTexturesToSave.size() == 0)
		{
			LL_INFOS("ColladaExport") << "Done saving textures" << LL_ENDL;
			me->updateTitleProgress();
			gIdleCallbacks.deleteFunction(saveTexturesWorker, me);
			me->mTimer.stop();
			me->onTexturesSaved();
			return;
		}

		LLUUID id = me->mTexturesToSave.begin()->first;
		LLViewerTexture* imagep = LLViewerTextureManager::findTexture(id);
		if (!imagep)
		{
			me->mTexturesToSave.erase(id);
			me->updateTitleProgress();
			me->mTimer.reset();
		}
		else
		{
			if (imagep->getDiscardLevel() == 0) // image download is complete
			{
				LL_INFOS("ColladaExport") << "Saving texture " << id << LL_ENDL;
				LLImageFormatted* img = new LLImageJ2C;
				S32 img_type = me->mTextureTypeCombo->getValue();
				std::string name = gDirUtilp->getDirName(me->mFileName->getValue());
				name += gDirUtilp->getDirDelimiter() + me->mTexturesToSave[id];
				CacheReadResponder* responder = new CacheReadResponder(id, img, name, img_type);
				LLAppViewer::getTextureCache()->readFromCache(id, LLWorkerThread::PRIORITY_HIGH, 0, 999999, responder);
				me->mTexturesToSave.erase(id);
				me->updateTitleProgress();
				me->mTimer.reset();
			}
			else if (me->mTimer.hasExpired())
			{
				LL_WARNS("ColladaExport") << "Timed out downloading texture " << id << LL_ENDL;
				me->mTexturesToSave.erase(id);
				me->updateTitleProgress();
				me->mTimer.reset();
			}
		}
	}

};

void DAESaver::add(const LLViewerObject* prim, const std::string name)
{
	mObjects.push_back(std::pair<LLViewerObject*,std::string>((LLViewerObject*)prim, name));
}

void DAESaver::updateTextureInfo()
{
	mTextures.clear();
	mTextureNames.clear();

	for (obj_info_t::iterator obj_iter = mObjects.begin(); obj_iter != mObjects.end(); ++obj_iter)
	{
		LLViewerObject* obj = obj_iter->first;
		S32 num_faces = obj->getVolume()->getNumVolumeFaces();
		for (S32 face_num = 0; face_num < num_faces; ++face_num)
		{
			LLTextureEntry* te = obj->getTE(face_num);
			const LLUUID id = te->getID();
			if (std::find(mTextures.begin(), mTextures.end(), id) != mTextures.end())
			{
				continue;
			}
			mTextures.push_back(id);
			std::string name;
			if (id != DAEExportUtil::LL_TEXTURE_BLANK && DAEExportUtil::canExportTexture(id, &name))
			{
				std::string safe_name = gDirUtilp->getScrubbedFileName(name);
				std::replace(safe_name.begin(), safe_name.end(), ' ', '_');
				mTextureNames.push_back(safe_name);
			}
			else
			{
				mTextureNames.push_back(std::string());
			}
		}
	}
}


class v4adapt
{
private:
	LLStrider<LLVector4a> mV4aStrider;
public:
	v4adapt(LLVector4a* vp){ mV4aStrider = vp; }
	inline LLVector3 operator[] (const unsigned int i)
	{
		return LLVector3((F32*)&mV4aStrider[i]);
	}
};

void DAESaver::addSource(daeElement* mesh, const char* src_id, std::string params, const std::vector<F32> &vals)
{
	daeElement* source = mesh->add("source");
	source->setAttribute("id", src_id);
	daeElement* src_array = source->add("float_array");

	src_array->setAttribute("id", llformat("%s-%s", src_id, "array").c_str());
	src_array->setAttribute("count", llformat("%d", vals.size()).c_str());

	for (U32 i = 0; i < vals.size(); i++)
	{
		((domFloat_array*)src_array)->getValue().append(vals[i]);
	}

	domAccessor* acc = daeSafeCast<domAccessor>(source->add("technique_common accessor"));
	acc->setSource(llformat("#%s-%s", src_id, "array").c_str());
	acc->setCount(vals.size() / params.size());
	acc->setStride(params.size());

	for (std::string::iterator p_iter = params.begin(); p_iter != params.end(); ++p_iter)
	{
		domElement* pX = acc->add("param");
		pX->setAttribute("name", llformat("%c", *p_iter).c_str());
		pX->setAttribute("type", "float");
	}
}

void DAESaver::addPolygons(daeElement* mesh, const char* geomID, const char* materialID, LLViewerObject* obj, int_list_t* faces_to_include)
{
	domPolylist* polylist = daeSafeCast<domPolylist>(mesh->add("polylist"));
	polylist->setMaterial(materialID);

	// Vertices semantic
	{
		domInputLocalOffset* input = daeSafeCast<domInputLocalOffset>(polylist->add("input"));
		input->setSemantic("VERTEX");
		input->setOffset(0);
		input->setSource(llformat("#%s-%s", geomID, "vertices").c_str());
	}

	// Normals semantic
	{
		domInputLocalOffset* input = daeSafeCast<domInputLocalOffset>(polylist->add("input"));
		input->setSemantic("NORMAL");
		input->setOffset(0);
		input->setSource(llformat("#%s-%s", geomID, "normals").c_str());
	}

	// UV semantic
	{
		domInputLocalOffset* input = daeSafeCast<domInputLocalOffset>(polylist->add("input"));
		input->setSemantic("TEXCOORD");
		input->setOffset(0);
		input->setSource(llformat("#%s-%s", geomID, "map0").c_str());
	}

	// Save indices
	domP* p = daeSafeCast<domP>(polylist->add("p"));
	domPolylist::domVcount *vcount = daeSafeCast<domPolylist::domVcount>(polylist->add("vcount"));
	S32 index_offset = 0;
	S32 num_tris = 0;
	for (S32 face_num = 0; face_num < obj->getVolume()->getNumVolumeFaces(); face_num++)
	{
		if (skipFace(obj->getTE(face_num))) continue;

		const LLVolumeFace* face = (LLVolumeFace*)&obj->getVolume()->getVolumeFace(face_num);

		if (faces_to_include == NULL || (std::find(faces_to_include->begin(), faces_to_include->end(), face_num) != faces_to_include->end()))
		{
			for (S32 i = 0; i < face->mNumIndices; i++)
			{
				U16 index = index_offset + face->mIndices[i];
				(p->getValue()).append(index);
				if (i % 3 == 0)
				{
					(vcount->getValue()).append(3);
					num_tris++;
				}
			}
		}
		index_offset += face->mNumVertices;
	}
	polylist->setCount(num_tris);
}

void DAESaver::transformTexCoord(S32 num_vert, LLVector2* coord, LLVector3* positions, LLVector3* normals, LLTextureEntry* te, LLVector3 scale)
{
	F32 cosineAngle = cos(te->getRotation());
	F32 sinAngle = sin(te->getRotation());

	for (S32 ii=0; ii<num_vert; ii++)
	{
		if (LLTextureEntry::TEX_GEN_PLANAR == te->getTexGen())
		{
			LLVector3 normal = normals[ii];
			LLVector3 pos = positions[ii];
			LLVector3 binormal;
			F32 d = normal * LLVector3::x_axis;
			if (d >= 0.5f || d <= -0.5f)
			{
				binormal = LLVector3::y_axis;
				if (normal.mV[0] < 0) binormal *= -1.0f;
			}
			else
			{
				binormal = LLVector3::x_axis;
				if (normal.mV[1] > 0) binormal *= -1.0f;
			}
			LLVector3 tangent = binormal % normal;
			LLVector3 scaledPos = pos.scaledVec(scale);
			coord[ii].mV[0] = 1.f + ((binormal * scaledPos) * 2.f - 0.5f);
			coord[ii].mV[1] = -((tangent * scaledPos) * 2.f - 0.5f);
		}

		F32 repeatU;
		F32 repeatV;
		te->getScale(&repeatU, &repeatV);
		F32 tX = coord[ii].mV[0] - 0.5f;
		F32 tY = coord[ii].mV[1] - 0.5f;

		F32 offsetU;
		F32 offsetV;
		te->getOffset(&offsetU, &offsetV);

		coord[ii].mV[0] = (tX * cosineAngle + tY * sinAngle) * repeatU + offsetU + 0.5f;
		coord[ii].mV[1] = (-tX * sinAngle + tY * cosineAngle) * repeatV + offsetV + 0.5f;
	}
}

bool DAESaver::saveDAE(std::string filename)
{
	mAllMaterials.clear();
	mTotalNumMaterials = 0;
	DAE dae;
	// First set the filename to save
	daeElement* root = dae.add(filename);

	// Obligatory elements in header
	daeElement* asset = root->add("asset");
	// Get ISO format time
	time_t rawtime;
	time(&rawtime);
	struct tm* utc_time = gmtime(&rawtime);
	std::string date = llformat("%04d-%02d-%02dT%02d:%02d:%02d", utc_time->tm_year + 1900, utc_time->tm_mon + 1, utc_time->tm_mday, utc_time->tm_hour, utc_time->tm_min, utc_time->tm_sec);
	daeElement* created = asset->add("created");
	created->setCharData(date);
	daeElement* modified = asset->add("modified");
	modified->setCharData(date);
	daeElement* unit = asset->add("unit");
	unit->setAttribute("name", "meter");
	unit->setAttribute("value", "1");
	daeElement* up_axis = asset->add("up_axis");
	up_axis->setCharData("Z_UP");

	// File creator
	daeElement* contributor = asset->add("contributor");
	contributor->add("author")->setCharData(LLAppViewer::instance()->getSecondLifeTitle() + " User");
	contributor->add("authoring_tool")->setCharData(LLAppViewer::instance()->getSecondLifeTitle() + " Collada Export");

	daeElement* images = root->add("library_images");
	daeElement* geomLib = root->add("library_geometries");
	daeElement* effects = root->add("library_effects");
	daeElement* materials = root->add("library_materials");
	daeElement* scene = root->add("library_visual_scenes visual_scene");
	scene->setAttribute("id", "Scene");
	scene->setAttribute("name", "Scene");

	if (gSavedSettings.getBOOL("DAEExportTextures"))
	{
		generateImagesSection(images);
	}

	S32 prim_nr = 0;

	for (obj_info_t::iterator obj_iter = mObjects.begin(); obj_iter != mObjects.end(); ++obj_iter)
	{
		LLViewerObject* obj = obj_iter->first;
		S32 total_num_vertices = 0;

		std::string name = "";
		if (name.empty()) name = llformat("prim%d", prim_nr++);

		const char* geomID = name.c_str();

		daeElement* geom = geomLib->add("geometry");
		geom->setAttribute("id", llformat("%s-%s", geomID, "mesh").c_str());
		daeElement* mesh = geom->add("mesh");

		std::vector<F32> position_data;
		std::vector<F32> normal_data;
		std::vector<F32> uv_data;
		bool applyTexCoord = gSavedSettings.getBOOL("DAEExportTextureParams");

		S32 num_faces = obj->getVolume()->getNumVolumeFaces();
		for (S32 face_num = 0; face_num < num_faces; face_num++)
		{
			if (skipFace(obj->getTE(face_num))) continue;

			const LLVolumeFace* face = (LLVolumeFace*)&obj->getVolume()->getVolumeFace(face_num);
			total_num_vertices += face->mNumVertices;

			v4adapt verts(face->mPositions);
			v4adapt norms(face->mNormals);

			LLVector2* newCoord = NULL;

			if (applyTexCoord)
			{
				newCoord = new LLVector2[face->mNumVertices];
				LLVector3* newPos = new LLVector3[face->mNumVertices];
				LLVector3* newNormal = new LLVector3[face->mNumVertices];
				for (S32 i = 0; i < face->mNumVertices; i++)
				{
					newPos[i] = verts[i];
					newNormal[i] = norms[i];
					newCoord[i] = face->mTexCoords[i];
				}
				transformTexCoord(face->mNumVertices, newCoord, newPos, newNormal, obj->getTE(face_num), obj->getScale());
				delete[] newPos;
				delete[] newNormal;
			}

			for (S32 i=0; i < face->mNumVertices; i++)
			{
				const LLVector3 v = verts[i];
				position_data.push_back(v.mV[VX]);
				position_data.push_back(v.mV[VY]);
				position_data.push_back(v.mV[VZ]);

				const LLVector3 n = norms[i];
				normal_data.push_back(n.mV[VX]);
				normal_data.push_back(n.mV[VY]);
				normal_data.push_back(n.mV[VZ]);

				const LLVector2 uv = applyTexCoord ? newCoord[i] : face->mTexCoords[i];

				uv_data.push_back(uv.mV[VX]);
				uv_data.push_back(uv.mV[VY]);
			}

			if (applyTexCoord)
			{
				delete[] newCoord;
			}
		}


		addSource(mesh, llformat("%s-%s", geomID, "positions").c_str(), "XYZ", position_data);
		addSource(mesh, llformat("%s-%s", geomID, "normals").c_str(), "XYZ", normal_data);
		addSource(mesh, llformat("%s-%s", geomID, "map0").c_str(), "ST", uv_data);

		// Add the <vertices> element
		{
			daeElement*	verticesNode = mesh->add("vertices");
			verticesNode->setAttribute("id", llformat("%s-%s", geomID, "vertices").c_str());
			daeElement* verticesInput = verticesNode->add("input");
			verticesInput->setAttribute("semantic", "POSITION");
			verticesInput->setAttribute("source", llformat("#%s-%s", geomID, "positions").c_str());
		}

		material_list_t objMaterials;
		getMaterials(obj, &objMaterials);

		// Add triangles
		if (gSavedSettings.getBOOL("DAEExportConsolidateMaterials"))
		{
			for (U32 objMaterial = 0; objMaterial < objMaterials.size(); objMaterial++)
			{
				int_list_t faces;
				getFacesWithMaterial(obj, objMaterials[objMaterial], &faces);
				std::string matName = objMaterials[objMaterial].name;
				addPolygons(mesh, geomID, (matName + "-material").c_str(), obj, &faces);
			}
		}
		else
		{
			S32 mat_nr = 0;
			for (S32 face_num = 0; face_num < num_faces; face_num++)
			{
				if (skipFace(obj->getTE(face_num))) continue;
				int_list_t faces;
				faces.push_back(face_num);
				std::string matName = objMaterials[mat_nr++].name;
				addPolygons(mesh, geomID, (matName + "-material").c_str(), obj, &faces);
			}
		}

		daeElement* node = scene->add("node");
		node->setAttribute("type", "NODE");
		node->setAttribute("id", geomID);
		node->setAttribute("name", geomID);

		// Set tranform matrix (node position, rotation and scale)
		domMatrix* matrix = (domMatrix*)node->add("matrix");
		LLXform srt;
		srt.setScale(obj->getScale());
		srt.setPosition(obj->getRenderPosition() + mOffset);
		srt.setRotation(obj->getRenderRotation());
		LLMatrix4 m4;
		srt.getLocalMat4(m4);
		for (int i=0; i<4; i++)
			for (int j=0; j<4; j++)
				(matrix->getValue()).append(m4.mMatrix[j][i]);

		// Geometry of the node
		daeElement* nodeGeometry = node->add("instance_geometry");

		// Bind materials
		daeElement* tq = nodeGeometry->add("bind_material technique_common");
		for (U32 objMaterial = 0; objMaterial < objMaterials.size(); objMaterial++)
		{
			std::string matName = objMaterials[objMaterial].name;
			daeElement* instanceMaterial = tq->add("instance_material");
			instanceMaterial->setAttribute("symbol", (matName + "-material").c_str());
			instanceMaterial->setAttribute("target", ("#" + matName + "-material").c_str());
		}

		nodeGeometry->setAttribute("url", llformat("#%s-%s", geomID, "mesh").c_str());

	}

	// Effects (face texture, color, alpha)
	generateEffects(effects);

	// Materials
	for (U32 objMaterial = 0; objMaterial < mAllMaterials.size(); objMaterial++)
	{
		daeElement* mat = materials->add("material");
		mat->setAttribute("id", (mAllMaterials[objMaterial].name + "-material").c_str());
		daeElement* matEffect = mat->add("instance_effect");
		matEffect->setAttribute("url", ("#" + mAllMaterials[objMaterial].name + "-fx").c_str());
	}

	root->add("scene instance_visual_scene")->setAttribute("url", "#Scene");

	return dae.writeAll();
}

DAESaver::DAESaver()
{}

bool DAESaver::skipFace(LLTextureEntry *te)
{
	return (gSavedSettings.getBOOL("DAEExportSkipTransparent")
		&& (te->getColor().mV[3] < 0.01f || te->getID() == DAEExportUtil::LL_TEXTURE_TRANSPARENT));
}

DAESaver::MaterialInfo DAESaver::getMaterial(LLTextureEntry* te)
{
	if (gSavedSettings.getBOOL("DAEExportConsolidateMaterials"))
	{
		for (U32 i=0; i < mAllMaterials.size(); i++)
		{
			if (mAllMaterials[i].matches(te))
			{
				return mAllMaterials[i];
			}
		}
	}

	MaterialInfo ret;
	ret.textureID = te->getID();
	ret.color = te->getColor();
	ret.name = llformat("Material%d", mAllMaterials.size());
	mAllMaterials.push_back(ret);
	return mAllMaterials[mAllMaterials.size() - 1];
}

void DAESaver::getMaterials(LLViewerObject* obj, material_list_t* ret)
{
	S32 num_faces = obj->getVolume()->getNumVolumeFaces();
	for (S32 face_num = 0; face_num < num_faces; ++face_num)
	{
		LLTextureEntry* te = obj->getTE(face_num);

		if (skipFace(te))
		{
			continue;
		}

		MaterialInfo mat = getMaterial(te);

		if (!gSavedSettings.getBOOL("DAEExportConsolidateMaterials") || std::find(ret->begin(), ret->end(), mat) == ret->end())
		{
			ret->push_back(mat);
		}
	}
}

void DAESaver::getFacesWithMaterial(LLViewerObject* obj, MaterialInfo& mat, int_list_t* ret)
{
	S32 num_faces = obj->getVolume()->getNumVolumeFaces();
	for (S32 face_num = 0; face_num < num_faces; ++face_num)
	{
		if (mat == getMaterial(obj->getTE(face_num)))
		{
			ret->push_back(face_num);
		}
	}
}

void DAESaver::generateEffects(daeElement *effects)
{
	// Effects (face color, alpha)
	bool export_textures = gSavedSettings.getBOOL("DAEExportTextures");

	for (U32 mat = 0; mat < mAllMaterials.size(); mat++)
	{
		LLColor4 color = mAllMaterials[mat].color;
		domEffect* effect = (domEffect*)effects->add("effect");
		effect->setId((mAllMaterials[mat].name + "-fx").c_str());
		daeElement* profile = effect->add("profile_COMMON");
		std::string colladaName;

		if (export_textures)
		{
			LLUUID textID;
			U32 i = 0;
			for (; i < mTextures.size(); i++)
			{
				if (mAllMaterials[mat].textureID == mTextures[i])
				{
					textID = mTextures[i];
					break;
				}
			}

			if (!textID.isNull() && !mTextureNames[i].empty())
			{
				colladaName = mTextureNames[i] + "_" + mImageFormat;
				daeElement* newparam = profile->add("newparam");
				newparam->setAttribute("sid", (colladaName + "-surface").c_str());
				daeElement* surface = newparam->add("surface");
				surface->setAttribute("type", "2D");
				surface->add("init_from")->setCharData(colladaName.c_str());
				newparam = profile->add("newparam");
				newparam->setAttribute("sid", (colladaName + "-sampler").c_str());
				newparam->add("sampler2D source")->setCharData((colladaName + "-surface").c_str());
			}
		}

		daeElement* t = profile->add("technique");
		t->setAttribute("sid", "common");
		domElement* phong = t->add("phong");
		domElement* diffuse = phong->add("diffuse");
		// Only one <color> or <texture> can appear inside diffuse element
		if (!colladaName.empty())
		{
			daeElement* txtr = diffuse->add("texture");
			txtr->setAttribute("texture", (colladaName + "-sampler").c_str());
			txtr->setAttribute("texcoord", colladaName.c_str());
		}
		else
		{
			daeElement* diffuseColor = diffuse->add("color");
			diffuseColor->setAttribute("sid", "diffuse");
			diffuseColor->setCharData(llformat("%f %f %f %f", color.mV[0], color.mV[1], color.mV[2], color.mV[3]).c_str());
			phong->add("transparency float")->setCharData(llformat("%f", color.mV[3]).c_str());
		}
	}
}

void DAESaver::generateImagesSection(daeElement* images)
{
	for (U32 i=0; i < mTextureNames.size(); i++)
	{
		std::string name = mTextureNames[i];
		if (name.empty()) continue;
		std::string colladaName = name + "_" + mImageFormat;
		daeElement* image = images->add("image");
		image->setAttribute("id", colladaName.c_str());
		image->setAttribute("name", colladaName.c_str());
		image->add("init_from")->setCharData(LLURI::escape(name + "." + mImageFormat));
	}
}

class DAESaveSelectedObjects : public view_listener_t
{
	bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata)
	{
		(new ColladaExportFloater())->open();
		return true;
	}
};

void addMenu(view_listener_t* menu, const std::string& name);
void add_dae_listeners() // Called in llviewermenu with other addMenu calls, function linked against
{
	addMenu(new DAESaveSelectedObjects(), "Object.SaveAsDAE");
}
