Files
SingularityViewer/indra/newview/daeexport.cpp
2020-04-07 15:30:13 -04:00

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");
}