1068 lines
31 KiB
C++
1068 lines
31 KiB
C++
/**
|
|
* @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 "llavatarnamecache.h"
|
|
#include "llnotificationsutil.h"
|
|
|
|
// newview includes
|
|
#include "lfsimfeaturehandler.h"
|
|
#include "llinventorymodel.h"
|
|
#include "llinventoryfunctions.h"
|
|
#include "llface.h"
|
|
#include "llversioninfo.h"
|
|
#include "llviewerinventory.h"
|
|
#include "llviewertexturelist.h"
|
|
#include "llvovolume.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
|
|
{
|
|
const auto LL_TEXTURE_PLYWOOD = LLUUID("89556747-24cb-43ed-920b-47caed15465f");
|
|
const auto LL_TEXTURE_BLANK = LLUUID("5748decc-f629-461c-9a36-a35a221fe21f");
|
|
const auto LL_TEXTURE_INVISIBLE = LLUUID("38b86f85-2575-52a9-a531-23108d8da837");
|
|
const auto LL_TEXTURE_TRANSPARENT = LLUUID("8dcd4a48-2d37-4909-9f78-f7a9eb4ef903");
|
|
const auto 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 (const auto& item : items)
|
|
{
|
|
const LLPermissions item_permissions = item->getPermissions();
|
|
if (item_permissions.allowExportBy(gAgentID, policy))
|
|
{
|
|
if (name) *name = item->getName();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (name) *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())
|
|
{
|
|
const LLSculptParams *sculpt_params = obj->getSculptParams();
|
|
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() override
|
|
{
|
|
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 (const auto& name : mSaver.mTextureNames)
|
|
{
|
|
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 final : 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;
|
|
}
|
|
|
|
void completed(bool success) override
|
|
{
|
|
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::findFetchedTexture(id, TEX_LIST_STANDARD);
|
|
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, const 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 (const auto& val : vals)
|
|
{
|
|
static_cast<domFloat_array*>(src_array)->getValue().append(val);
|
|
}
|
|
|
|
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 (const auto& param : params)
|
|
{
|
|
domElement* pX = acc->add("param");
|
|
pX->setAttribute("name", (LLStringUtil::null + param).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++)
|
|
{
|
|
U32 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)
|
|
{
|
|
// Collada expects file and folder names to be escaped
|
|
// Note: cdom::nativePathToUri()
|
|
// Same as in LLDAELoader::OpenFile()
|
|
const char* allowed =
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
"abcdefghijklmnopqrstuvwxyz"
|
|
"0123456789"
|
|
"%-._~:\"|\\/";
|
|
std::string uri_filename = LLURI::escape(filename, allowed);
|
|
|
|
mAllMaterials.clear();
|
|
mTotalNumMaterials = 0;
|
|
DAE dae;
|
|
// First set the filename to save
|
|
daeElement* root = dae.add(uri_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
|
|
std::string author;
|
|
if (!LLAvatarNameCache::getNSName(gAgentID, author))
|
|
author = "Unknown";
|
|
|
|
daeElement* contributor = asset->add("contributor");
|
|
contributor->add("author")->setCharData(author);
|
|
contributor->add("authoring_tool")->setCharData(LLVersionInfo::getChannelAndVersion() + " 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 (const auto& objMaterial : objMaterials)
|
|
{
|
|
int_list_t faces;
|
|
getFacesWithMaterial(obj, objMaterial, &faces);
|
|
addPolygons(mesh, geomID, (objMaterial.name + "-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 (const auto& objMaterial : objMaterials)
|
|
{
|
|
daeElement* instanceMaterial = tq->add("instance_material");
|
|
std::string matName = objMaterial.name + "-material";
|
|
instanceMaterial->setAttribute("symbol", matName.c_str());
|
|
instanceMaterial->setAttribute("target", ('#' + matName).c_str());
|
|
}
|
|
|
|
nodeGeometry->setAttribute("url", llformat("#%s-%s", geomID, "mesh").c_str());
|
|
|
|
}
|
|
|
|
// Effects (face texture, color, alpha)
|
|
generateEffects(effects);
|
|
|
|
// Materials
|
|
for (const auto& objMaterial : mAllMaterials)
|
|
{
|
|
daeElement* mat = materials->add("material");
|
|
mat->setAttribute("id", (objMaterial.name + "-material").c_str());
|
|
daeElement* matEffect = mat->add("instance_effect");
|
|
matEffect->setAttribute("url", ('#' + 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 (const auto& mat : mAllMaterials)
|
|
{
|
|
if (mat.matches(te))
|
|
{
|
|
return mat;
|
|
}
|
|
}
|
|
}
|
|
|
|
MaterialInfo ret;
|
|
ret.textureID = te->getID();
|
|
ret.color = te->getColor();
|
|
ret.name = llformat("Material%d", mAllMaterials.size());
|
|
mAllMaterials.push_back(ret);
|
|
return ret;
|
|
}
|
|
|
|
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, const 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 (const auto& mat : mAllMaterials)
|
|
{
|
|
LLColor4 color = mat.color;
|
|
domEffect* effect = (domEffect*)effects->add("effect");
|
|
effect->setId((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 (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 (const auto& name : mTextureNames)
|
|
{
|
|
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 final : 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");
|
|
}
|