diff --git a/indra/llaudio/llaudioengine.cpp b/indra/llaudio/llaudioengine.cpp
index f07965877..aab511ef8 100644
--- a/indra/llaudio/llaudioengine.cpp
+++ b/indra/llaudio/llaudioengine.cpp
@@ -399,7 +399,7 @@ void LLAudioEngine::idle(F32 max_decode_time)
}
LLAudioChannel *channelp = sourcep->getChannel();
- bool is_stopped = channelp && channelp->isPlaying();
+ bool is_stopped = !channelp || !channelp->isPlaying();
if (is_stopped || (sourcep->isLoop() && channelp->mLoopedThisFrame))
{
// This sound isn't playing, so we just process move the queue
diff --git a/indra/llwindow/glh/glh_linear.h b/indra/llwindow/glh/glh_linear.h
index 04ae1bd06..c46b81531 100644
--- a/indra/llwindow/glh/glh_linear.h
+++ b/indra/llwindow/glh/glh_linear.h
@@ -71,10 +71,10 @@ glh_linear.h
#define GLH_EPSILON GLH_REAL(10e-6)
#define GLH_PI GLH_REAL(3.1415926535897932384626433832795)
-#define equivalent(a,b) (((a < b + GLH_EPSILON) && (a > b - GLH_EPSILON)) ? true : false)
namespace glh
{
+ inline bool equivalent(GLH_REAL a, GLH_REAL b) { return b - GLH_EPSILON < a && a < b + GLH_EPSILON; }
inline GLH_REAL to_degrees(GLH_REAL radians) { return radians*GLH_RAD_TO_DEG; }
inline GLH_REAL to_radians(GLH_REAL degrees) { return degrees*GLH_DEG_TO_RAD; }
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 22c90b7bf..7e7bb4796 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -17513,6 +17513,77 @@ This should be as low as possible, but too low may break functionality
Value
1280.0
+ ColladaExportFloaterRect
+
+ DAEExportConsolidateMaterials
+
+ DAEExportSkipTransparent
+
+ DAEExportTextures
+
+ DAEExportTextureParams
+
+ DAEExportTexturesType
+
diff --git a/indra/newview/daeexport.cpp b/indra/newview/daeexport.cpp
index d56d446af..1e71bf61b 100644
--- a/indra/newview/daeexport.cpp
+++ b/indra/newview/daeexport.cpp
@@ -1,26 +1,26 @@
/**
- * @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
- */
+* @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"
@@ -62,7 +62,6 @@
// library includes
#include "aifilepicker.h"
#include "llnotificationsutil.h"
-#include "boost/date_time/posix_time/posix_time.hpp"
// newview includes
#include "lfsimfeaturehandler.h"
@@ -75,10 +74,26 @@
// menu includes
#include "llevent.h"
#include "llmemberlistener.h"
-#include "llview.h"
#include "llselectmgr.h"
-#define ANY_FACE -1
+// 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;
@@ -87,41 +102,59 @@ typedef LLMemberListener view_listener_t;
namespace DAEExportUtil
{
- void saveImpl(DAESaver* daesaver, AIFilePicker* filepicker)
+ 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
{
- if (filepicker->hasFilename())
+ 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 std::string selected_filename = filepicker->getFilename();
-
- daesaver->saveDAE(selected_filename);
- LLNotificationsUtil::add("WavefrontExportSuccess", LLSD().with("FILENAME", selected_filename));
+ const LLPermissions item_permissions = items[i]->getPermissions();
+ if (item_permissions.allowExportBy(gAgentID, policy))
+ {
+ if (name != NULL)
+ {
+ (*name) = items[i]->getName();
+ }
+ return true;
+ }
}
- else llwarns << "No file; bailing" << llendl;
- delete daesaver;
+ if (name != NULL)
+ {
+ (*name) = id.getString();
+ }
+
+ return (policy & ep_full_perm) == ep_full_perm;
}
- void filePicker(DAESaver* daesaver, std::string name)
- {
- AIFilePicker* filepicker = AIFilePicker::create();
- filepicker->open(name);
- filepicker->run(boost::bind(&saveImpl, daesaver, filepicker));
- }
-
- void onPartialExportConfirm(const LLSD& notification, const LLSD& response, DAESaver* daesaver, std::string name)
- {
- if (LLNotificationsUtil::getSelectedOption(notification, response) == 0) // 0 - Proceed, first choice
- {
- filePicker(daesaver, name);
- }
- else
- {
- delete daesaver;
- }
- }
-
- // Identical to the one in awavefront.cpp
- bool can_export_node(LLSelectNode* node)
+ 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
@@ -137,92 +170,428 @@ namespace DAEExportUtil
{
LLSculptParams *sculpt_params = (LLSculptParams *)obj->getParameterEntry(LLNetworkData::PARAMS_SCULPT);
LLUUID sculpt_id = sculpt_params->getSculptTexture();
-
- // Find inventory items with asset id of the sculpt map
- LLViewerInventoryCategory::cat_array_t cats;
- LLViewerInventoryItem::item_array_t items;
- LLAssetIDMatches asset_id_matches(sculpt_id);
- gInventory.collectDescendentsIf(LLUUID::null,
- cats,
- items,
- LLInventoryModel::INCLUDE_TRASH,
- asset_id_matches);
-
- // See if any of the inventory items matching this sculpt id are exportable
- for (S32 i = 0; i < items.count(); i++)
- {
- const LLPermissions item_permissions = items[i]->getPermissions();
- if (item_permissions.allowExportBy(gAgentID, LFSimFeatureHandler::instance().exportPolicy()))
- {
- return true;
- }
- }
-
- return false;
+ return canExportTexture(sculpt_id);
}
else // not sculpt, we already checked generic permissions
{
return true;
}
}
+}
- void saveSelectedObject()
+
+class ColladaExportFloater
+ : public LLFloater
+{
+private:
+ typedef std::map 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("Object Name"))
+ {
+ ctrl->setTextArg("[NAME]", mObjectName);
+ }
+ if (LLUICtrl* ctrl = findChild("Exportable Prims"))
+ {
+ ctrl->setTextArg("[COUNT]", llformat("%d", mSaver.mObjects.size()));
+ ctrl->setTextArg("[TOTAL]", llformat("%d", mTotal));
+ }
+ if (LLUICtrl* ctrl = findChild("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()
+ {
if (LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection())
{
- DAESaver* daesaver = new DAESaver; // deleted in callback
- daesaver->mOffset = -selection->getFirstRootObject()->getRenderPosition();
- S32 total = 0;
- S32 included = 0;
+ mSaver.mOffset = -selection->getFirstRootObject()->getRenderPosition();
+ mObjectName = selection->getFirstRootNode()->mName;
+ mTotal = 0;
for (LLObjectSelection::iterator iter = selection->begin(); iter != selection->end(); ++iter)
{
- total++;
+ mTotal++;
LLSelectNode* node = *iter;
- if (!node->getObject()->getVolume() || !can_export_node(node)) continue;
- included++;
- daesaver->Add(node->getObject(), node->mName);
+ if (!node->getObject()->getVolume() || !DAEExportUtil::canExportNode(node)) continue;
+ mSaver.add(node->getObject(), node->mName);
}
- if (daesaver->mObjects.empty())
+ if (mSaver.mObjects.empty())
{
LLNotificationsUtil::add("ExportFailed");
- delete daesaver;
+ close();
return;
}
- if (total != included)
- {
- LLSD args;
- args["TOTAL"] = total;
- args["FAILED"] = total - included;
- LLNotificationsUtil::add("WavefrontExportPartial", args, LLSD(), boost::bind(&onPartialExportConfirm, _1, _2, daesaver, selection->getFirstNode()->mName.c_str() + file_ext));
- return;
- }
-
- filePicker(daesaver, selection->getFirstNode()->mName.c_str() + file_ext);
+ mSaver.updateTextureInfo();
+ mNumTextures = mSaver.mTextures.size();
+ mNumExportableTextures = getNumExportableTextures();
}
- return;
}
- class DAESaveSelectedObjects : public view_listener_t
+ S32 getNumExportableTextures()
{
- bool handleEvent(LLPointer event, const LLSD& userdata)
+ S32 res = 0;
+
+ for (DAESaver::string_list_t::const_iterator t = mSaver.mTextureNames.begin(); t != mSaver.mTextureNames.end(); ++t)
{
- saveSelectedObject();
- return true;
+ 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("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 (S32 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 (S32 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 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 raw = new LLImageRaw;
+ raw->resize(mFormattedImage->getWidth(), mFormattedImage->getHeight(), mFormattedImage->getComponents());
+
+ if (mFormattedImage->decode(raw, 0))
+ {
+ LLPointer 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;
+ }
-void DAESaver::Add(const LLViewerObject* prim, const std::string name)
+ 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*)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:
@@ -262,7 +631,7 @@ void DAESaver::addSource(daeElement* mesh, const char* src_id, std::string param
}
}
-void DAESaver::addPolygons(daeElement* mesh, const char* geomID, const char* materialID, LLViewerObject* obj, int face_to_include)
+void DAESaver::addPolygons(daeElement* mesh, const char* geomID, const char* materialID, LLViewerObject* obj, int_list_t* faces_to_include)
{
domPolylist* polylist = daeSafeCast(mesh->add("polylist"));
polylist->setMaterial(materialID);
@@ -298,9 +667,11 @@ void DAESaver::addPolygons(daeElement* mesh, const char* geomID, const char* mat
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 (face_to_include == ANY_FACE || face_to_include == 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++)
{
@@ -318,8 +689,54 @@ void DAESaver::addPolygons(daeElement* mesh, const char* geomID, const char* mat
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; iigetTexGen())
+ {
+ 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);
@@ -327,7 +744,10 @@ bool DAESaver::saveDAE(std::string filename)
// Obligatory elements in header
daeElement* asset = root->add("asset");
// Get ISO format time
- std::string date = boost::posix_time::to_iso_extended_string(boost::posix_time::second_clock::local_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");
@@ -340,9 +760,10 @@ bool DAESaver::saveDAE(std::string filename)
// File creator
daeElement* contributor = asset->add("contributor");
- contributor->add("author")->setCharData("Singularity User");
- contributor->add("authoring_tool")->setCharData("Singularity Viewer Collada Export");
+ 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");
@@ -350,6 +771,11 @@ bool DAESaver::saveDAE(std::string filename)
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)
@@ -369,16 +795,37 @@ bool DAESaver::saveDAE(std::string filename)
std::vector position_data;
std::vector normal_data;
std::vector 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];
@@ -391,13 +838,19 @@ bool DAESaver::saveDAE(std::string filename)
normal_data.push_back(n.mV[VY]);
normal_data.push_back(n.mV[VZ]);
- const LLVector2 uv = face->mTexCoords[i];
+ 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);
@@ -411,33 +864,31 @@ bool DAESaver::saveDAE(std::string filename)
verticesInput->setAttribute("source", llformat("#%s-%s", geomID, "positions").c_str());
}
+ material_list_t objMaterials;
+ getMaterials(obj, &objMaterials);
+
// Add triangles
- for (S32 face_num = 0; face_num < num_faces; face_num++)
+ if (gSavedSettings.getBOOL("DAEExportConsolidateMaterials"))
{
- addPolygons(mesh, geomID, llformat("%s-f%d-%s", geomID, face_num, "material").c_str(), obj, face_num);
+ for (S32 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);
+ }
}
-
- // Effects (face color, alpha)
- for (S32 face_num = 0; face_num < num_faces; face_num++)
+ else
{
- LLTextureEntry* te = obj->getTE(face_num);
- LLColor4 color = te->getColor();
- domEffect* effect = (domEffect*)effects->add("effect");
- effect->setId(llformat("%s-f%d-%s", geomID, face_num, "fx").c_str());
- daeElement* t = effect->add("profile_COMMON technique");
- t->setAttribute("sid", "common");
- domElement* phong = t->add("phong");
- phong->add("diffuse color")->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());
- }
-
- // Materials
- for (S32 face_num = 0; face_num < num_faces; face_num++)
- {
- domMaterial* mat = (domMaterial*)materials->add("material");
- mat->setId(llformat("%s-f%d-%s", geomID, face_num, "material").c_str());
- domElement* matEffect = mat->add("instance_effect");
- matEffect->setAttribute("url", llformat("#%s-f%d-%s", geomID, face_num, "fx").c_str());
+ 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");
@@ -462,16 +913,30 @@ bool DAESaver::saveDAE(std::string filename)
// Bind materials
daeElement* tq = nodeGeometry->add("bind_material technique_common");
- for (S32 face_num = 0; face_num < num_faces; face_num++)
+ for (S32 objMaterial = 0; objMaterial < objMaterials.size(); objMaterial++)
{
+ std::string matName = objMaterials[objMaterial].name;
daeElement* instanceMaterial = tq->add("instance_material");
- instanceMaterial->setAttribute("symbol", llformat("%s-f%d-%s", geomID, face_num, "material").c_str());
- instanceMaterial->setAttribute("target", llformat("#%s-f%d-%s", geomID, face_num, "material").c_str());
+ 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 (S32 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();
@@ -480,8 +945,152 @@ bool DAESaver::saveDAE(std::string filename)
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 (S32 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 (S32 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;
+ S32 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 or 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 (S32 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 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 DAEExportUtil::DAESaveSelectedObjects(), "Object.SaveAsDAE");
+ addMenu(new DAESaveSelectedObjects(), "Object.SaveAsDAE");
}
diff --git a/indra/newview/daeexport.h b/indra/newview/daeexport.h
index 3bfcf2ec6..fbdd5b84c 100644
--- a/indra/newview/daeexport.h
+++ b/indra/newview/daeexport.h
@@ -1,48 +1,110 @@
/**
- * @file daeexport.h
- * @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
- */
+* @file daeexport.h
+* @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
+*/
#ifndef DAEEXPORT_H_
#define DAEEXPORT_H_
#include
+#include "lltextureentry.h"
class LLViewerObject;
class DAESaver
{
- typedef std::vector > obj_info_t;
-
public:
+ class MaterialInfo
+ {
+ public:
+ LLUUID textureID;
+ LLColor4 color;
+ std::string name;
+
+ bool matches(LLTextureEntry* te)
+ {
+ return (textureID == te->getID()) && (color == te->getColor());
+ }
+
+ bool operator== (const MaterialInfo& rhs)
+ {
+ return (textureID == rhs.textureID) && (color == rhs.color) && (name == rhs.name);
+ }
+
+ bool operator!= (const MaterialInfo& rhs)
+ {
+ return !(*this == rhs);
+ }
+
+ MaterialInfo()
+ {
+ }
+
+ MaterialInfo(const MaterialInfo& rhs)
+ {
+ textureID = rhs.textureID;
+ color = rhs.color;
+ name = rhs.name;
+ }
+
+ MaterialInfo& operator= (const MaterialInfo& rhs)
+ {
+ textureID = rhs.textureID;
+ color = rhs.color;
+ name = rhs.name;
+ return *this;
+ }
+
+ };
+
+ typedef std::vector > obj_info_t;
+ typedef std::vector id_list_t;
+ typedef std::vector string_list_t;
+ typedef std::vector int_list_t;
+ typedef std::vector material_list_t;
+
+ material_list_t mAllMaterials;
+ id_list_t mTextures;
+ string_list_t mTextureNames;
obj_info_t mObjects;
LLVector3 mOffset;
+ std::string mImageFormat;
+ S32 mTotalNumMaterials;
+
DAESaver();
- void Add(const LLViewerObject* prim, const std::string name);
+ void updateTextureInfo();
+ void add(const LLViewerObject* prim, const std::string name);
bool saveDAE(std::string filename);
private:
+ void transformTexCoord(S32 num_vert, LLVector2* coord, LLVector3* positions, LLVector3* normals, LLTextureEntry* te, LLVector3 scale);
void addSource(daeElement* mesh, const char* src_id, std::string params, const std::vector &vals);
- void addPolygons(daeElement* mesh, const char* geomID, const char* materialID, LLViewerObject* obj, int face_to_include);
+ void addPolygons(daeElement* mesh, const char* geomID, const char* materialID, LLViewerObject* obj, int_list_t* faces_to_include);
+ bool skipFace(LLTextureEntry *te);
+ MaterialInfo getMaterial(LLTextureEntry* te);
+ void getMaterials(LLViewerObject* obj, material_list_t* ret);
+ void getFacesWithMaterial(LLViewerObject* obj, MaterialInfo& mat, int_list_t* ret);
+ void generateEffects(daeElement *effects);
+ void generateImagesSection(daeElement* images);
};
#endif // DAEEXPORT_H_
diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp
index f8e9390da..3dd268d60 100644
--- a/indra/newview/llinventorybridge.cpp
+++ b/indra/newview/llinventorybridge.cpp
@@ -3440,6 +3440,7 @@ void LLFolderBridge::buildContextMenuFolderOptions(U32 flags)
mItems.push_back(std::string("Replace Outfit"));
}
+ mItems.push_back(std::string("Replace Remove Separator"));
mItems.push_back(std::string("Remove From Outfit"));
if (!LLAppearanceMgr::getCanRemoveFromCOF(mUUID))
{
diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp
index ba734f7bd..b5d6bd86e 100644
--- a/indra/newview/lllogchat.cpp
+++ b/indra/newview/lllogchat.cpp
@@ -92,7 +92,7 @@ std::string LLLogChat::timestamp(bool withdate)
std::string text;
if (withdate)
if (withseconds)
- text = llformat("[%d-%02d-%02d %02d:%02d:%02d] ", (timep->tm_year-100)+2000, timep->tm_mon+1, timep->tm_mday, timep->tm_hour, timep->tm_min, timep->tm_sec);
+ text = llformat("[%d/%02d/%02d %02d:%02d:%02d] ", (timep->tm_year-100)+2000, timep->tm_mon+1, timep->tm_mday, timep->tm_hour, timep->tm_min, timep->tm_sec);
else
text = llformat("[%d/%02d/%02d %02d:%02d] ", (timep->tm_year-100)+2000, timep->tm_mon+1, timep->tm_mday, timep->tm_hour, timep->tm_min);
else
@@ -127,6 +127,19 @@ void LLLogChat::saveHistory(std::string const& filename, std::string line)
}
}
+const std::streamoff BUFFER_SIZE(4096);
+
+// Read a chunk of size from pos in ifstr and prepend it to data
+// return that chunk's newline count
+U32 read_chunk(llifstream& ifstr, const std::streamoff& pos, U32 size, std::string& data)
+{
+ char buffer[BUFFER_SIZE];
+ ifstr.seekg(pos);
+ ifstr.read(buffer, size);
+ data.insert(0, buffer, size);
+ return std::count(buffer, buffer + size, '\n');
+}
+
void LLLogChat::loadHistory(std::string const& filename , void (*callback)(ELogLineType,std::string,void*), void* userdata)
{
if(!filename.size())
@@ -143,10 +156,43 @@ void LLLogChat::loadHistory(std::string const& filename , void (*callback)(ELogL
else
{
static const LLCachedControl lines("LogShowHistoryLines", 32);
- std::string line;
- for (U32 i = 0; i < lines && getline(ifstr, line); ++i)
+ ifstr.seekg(-1, std::ios_base::end);
+ if (!lines || !ifstr)
{
- callback(LOG_LINE, line, userdata);
+ callback(LOG_EMPTY,LLStringUtil::null,userdata);
+ return;
+ }
+
+ std::string data;
+ U32 nlines = 0;
+ if (ifstr.get() != '\n') // in case file doesn't end with a newline
+ {
+ data.push_back('\n');
+ ++nlines;
+ }
+
+ // Read BUFFER_SIZE byte chunks until we have enough endlines accumulated
+ for(std::streamoff pos = ifstr.tellg() - BUFFER_SIZE; nlines < lines+1; pos -= BUFFER_SIZE)
+ {
+ if (pos > 0)
+ {
+ nlines += read_chunk(ifstr, pos, BUFFER_SIZE, data);
+ }
+ else // Ran out of file read the remaining from the start
+ {
+ nlines += read_chunk(ifstr, 0, pos + BUFFER_SIZE, data);
+ break;
+ }
+ }
+
+ // Break data into lines
+ std::istringstream sstr(data);
+ for (std::string line; nlines > 0 && getline(sstr, line); --nlines)
+ {
+ if (nlines <= lines)
+ {
+ callback(LOG_LINE, line, userdata);
+ }
}
callback(LOG_END,LLStringUtil::null,userdata);
}
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index b653f4457..1526fc8c1 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -2891,24 +2891,18 @@ class LLObjectEnableExport : public view_listener_t
{
bool handleEvent(LLPointer event, const LLSD& userdata)
{
- LLPermissions perms;
- bool new_value = LLSelectMgr::getInstance()->selectGetPermissions(perms) && perms.isOwned(); // At least one object, accumulated permissions of all objects.
ExportPolicy export_policy = LFSimFeatureHandler::instance().exportPolicy();
- if (new_value && !(export_policy == ep_export_bit && (perms.getMaskEveryone() & PERM_EXPORT))) // No need to call allowExportBy if PERM_EXPORT is set on (all) root objects.
+ bool can_export_any = false;
+ LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection();
+ for (LLObjectSelection::iterator node = selection->begin(); node != selection->end(); ++node)
{
- bool can_export_any = false;
- LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection();
- for (LLObjectSelection::iterator node = selection->begin(); node != selection->end(); ++node)
+ if ((*node)->mPermissions->allowExportBy(gAgent.getID(), export_policy))
{
- if ((*node)->mPermissions->allowExportBy(gAgent.getID(), export_policy))
- {
- can_export_any = true;
- break;
- }
+ can_export_any = true;
+ break;
}
- new_value = can_export_any;
}
- gMenuHolder->findControl(userdata["control"].asString())->setValue(new_value);
+ gMenuHolder->findControl(userdata["control"].asString())->setValue(can_export_any);
return true;
}
};
diff --git a/indra/newview/skins/default/xui/en-us/floater_dae_export.xml b/indra/newview/skins/default/xui/en-us/floater_dae_export.xml
new file mode 100644
index 000000000..8d01c4ee0
--- /dev/null
+++ b/indra/newview/skins/default/xui/en-us/floater_dae_export.xml
@@ -0,0 +1,36 @@
+
+
+ Collada Export: Saving textures ([COUNT] remaining)
+ File Name:
+
+
+
+
+ Object Info
+ Name: [NAME]
+ Exportable Prims: [COUNT]/[TOTAL]
+ Exportable Textures: [COUNT]/[TOTAL]
+
+
+ Options
+
+
+
+
+ TGA
+ PNG
+ J2C
+ BMP
+ JPG
+
+
+
+
+
+
+
+
diff --git a/indra/newview/skins/default/xui/en-us/menu_inventory.xml b/indra/newview/skins/default/xui/en-us/menu_inventory.xml
index 12ebca6f0..a248c26de 100644
--- a/indra/newview/skins/default/xui/en-us/menu_inventory.xml
+++ b/indra/newview/skins/default/xui/en-us/menu_inventory.xml
@@ -243,6 +243,7 @@
mouse_opaque="true" name="Replace Outfit" width="128">
+
diff --git a/indra/plugins/filepicker/llfilepicker.cpp b/indra/plugins/filepicker/llfilepicker.cpp
index 3650fb0cf..ab30d2a0b 100644
--- a/indra/plugins/filepicker/llfilepicker.cpp
+++ b/indra/plugins/filepicker/llfilepicker.cpp
@@ -1435,7 +1435,7 @@ static std::string add_imageload_filter_to_gtkchooser(GtkWindow *picker)
return filtername;
}
-static std::string add_imagesave_filter_to_gtkchooser(GTKWindow *picker)
+static std::string add_imagesave_filter_to_gtkchooser(GtkWindow *picker)
{
GtkFileFilter *gfilter = gtk_file_filter_new();
gtk_file_filter_add_mime_type(gfilter, "image/bmp");