/** * @file daeexport.cpp * @brief A system which allows saving in-world objects to Collada .DAE files for offline texturizing/shading. * @authors Latif Khalifa * * $LicenseInfo:firstyear=2013&license=LGPLV2.1$ * Copyright (C) 2013 Latif Khalifa * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA */ #include "llviewerprecompiledheaders.h" #include "daeexport.h" //colladadom includes #include "dae.h" #include "dom/domCOLLADA.h" #include "dom/domMatrix.h" // library includes #include "aifilepicker.h" #include "llnotificationsutil.h" // newview includes #include "lfsimfeaturehandler.h" #include "llface.h" #include "llvovolume.h" #include "llviewerinventory.h" #include "llinventorymodel.h" #include "llinventoryfunctions.h" // menu includes #include "llevent.h" #include "llmemberlistener.h" #include "llselectmgr.h" // Floater and UI #include "llfloater.h" #include "lluictrlfactory.h" #include "llscrollcontainer.h" #include "lltexturectrl.h" // Files and cache #include "llcallbacklist.h" #include "lldir.h" #include "lltexturecache.h" #include "llimage.h" #include "llimagej2c.h" #include "llimagepng.h" #include "llimagetga.h" #include "llimagebmp.h" #include "llimagejpeg.h" #define TEXTURE_DOWNLOAD_TIMEOUT 60.0f extern LLUUID gAgentID; //Typedefs used in other files, using here for consistency. typedef LLMemberListener view_listener_t; namespace DAEExportUtil { static LLUUID LL_TEXTURE_PLYWOOD = LLUUID("89556747-24cb-43ed-920b-47caed15465f"); static LLUUID LL_TEXTURE_BLANK = LLUUID("5748decc-f629-461c-9a36-a35a221fe21f"); static LLUUID LL_TEXTURE_INVISIBLE = LLUUID("38b86f85-2575-52a9-a531-23108d8da837"); static LLUUID LL_TEXTURE_TRANSPARENT = LLUUID("8dcd4a48-2d37-4909-9f78-f7a9eb4ef903"); static LLUUID LL_TEXTURE_MEDIA = LLUUID("8b5fec65-8d8d-9dc5-cda8-8fdf2716e361"); enum image_format_type { ft_tga, ft_png, ft_j2c, ft_bmp, ft_jpg }; static const std::string image_format_ext[] = { "tga", "png", "j2c", "bmp", "jpg" }; static bool canExportTexture(const LLUUID& id, std::string* name = NULL) { // Find inventory items with asset id of the sculpt map LLViewerInventoryCategory::cat_array_t cats; LLViewerInventoryItem::item_array_t items; LLAssetIDMatches asset_id_matches(id); gInventory.collectDescendentsIf(LLUUID::null, cats, items, LLInventoryModel::INCLUDE_TRASH, asset_id_matches); // See if any of the inventory items matching this texture id are exportable ExportPolicy policy = LFSimFeatureHandler::instance().exportPolicy(); for (size_t i = 0; i < items.size(); i++) { const LLPermissions item_permissions = items[i]->getPermissions(); if (item_permissions.allowExportBy(gAgentID, policy)) { if (name != NULL) { (*name) = items[i]->getName(); } return true; } } if (name != NULL) { (*name) = id.getString(); } return (policy & ep_full_perm) == ep_full_perm; } static bool canExportNode(LLSelectNode* node) { LLPermissions* perms = node->mPermissions; // Is perms ever NULL? // This tests the PERM_EXPORT bit too, which is not really necessary (just checking if it's set // on the root prim would suffice), but also isn't hurting. if (!(perms && perms->allowExportBy(gAgentID, LFSimFeatureHandler::instance().exportPolicy()))) { return false; } // Additionally chack if this is a sculpt LLViewerObject* obj = node->getObject(); if (obj->isSculpted() && !obj->isMesh()) { 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 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() { LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); if (selection && selection->getFirstRootObject()) { mSaver.mOffset = -selection->getFirstRootObject()->getRenderPosition(); mObjectName = selection->getFirstRootNode()->mName; mTotal = 0; for (LLObjectSelection::iterator iter = selection->begin(); iter != selection->end(); ++iter) { mTotal++; LLSelectNode* node = *iter; if (!node->getObject()->getVolume() || !DAEExportUtil::canExportNode(node)) continue; mSaver.add(node->getObject(), node->mName); } } if (mSaver.mObjects.empty()) { LLNotificationsUtil::add("ExportFailed"); close(); } else { mSaver.updateTextureInfo(); mNumTextures = mSaver.mTextures.size(); mNumExportableTextures = getNumExportableTextures(); } } S32 getNumExportableTextures() { S32 res = 0; for (DAESaver::string_list_t::const_iterator t = mSaver.mTextureNames.begin(); t != mSaver.mTextureNames.end(); ++t) { std::string name = *t; if (!name.empty()) { ++res; } } return res; } void addTexturePreview() { S32 num_text = mNumExportableTextures; if (num_text == 0) return; S32 img_width = 100; S32 img_height = img_width + 15; S32 panel_height = (num_text / 3 + 1) * (img_height) + 10; LLScrollContainer* scroll_container = getChild("textures container"); LLPanel* panel = new LLPanel(std::string(), LLRect(0, panel_height, 350, 0), false); scroll_container->setScrolledView(panel); scroll_container->addChild(panel); panel->setEnabled(FALSE); S32 img_nr = 0; for (U32 i=0; i < mSaver.mTextures.size(); i++) { if (mSaver.mTextureNames[i].empty()) continue; S32 left = 16 + (img_nr % 3) * (img_width + 13); S32 bottom = panel_height - (10 + (img_nr / 3 + 1) * (img_height)); LLRect rect(left, bottom + img_height, left + img_width, bottom); LLTextureCtrl* img = new LLTextureCtrl(std::string(), rect, std::string(), mSaver.mTextures[i], LLUUID(), std::string()); panel->addChild(img); img_nr++; } } void saveTextures() { mTexturesToSave.clear(); for (U32 i=0; i < mSaver.mTextures.size(); i++) { if (mSaver.mTextureNames[i].empty()) continue; mTexturesToSave[mSaver.mTextures[i]] = mSaver.mTextureNames[i]; } mSaver.mImageFormat = DAEExportUtil::image_format_ext[(S32)mTextureTypeCombo->getValue()]; LL_INFOS("ColladaExport") << "Starting to save textures" << LL_ENDL; mTimer.setTimerExpirySec(TEXTURE_DOWNLOAD_TIMEOUT); mTimer.start(); updateTitleProgress(); gIdleCallbacks.addFunction(saveTexturesWorker, this); } class CacheReadResponder : public LLTextureCache::ReadResponder { private: LLPointer 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; } 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: LLStrider mV4aStrider; public: v4adapt(LLVector4a* vp){ mV4aStrider = vp; } inline LLVector3 operator[] (const unsigned int i) { return LLVector3((F32*)&mV4aStrider[i]); } }; void DAESaver::addSource(daeElement* mesh, const char* src_id, std::string params, const std::vector &vals) { daeElement* source = mesh->add("source"); source->setAttribute("id", src_id); daeElement* src_array = source->add("float_array"); src_array->setAttribute("id", llformat("%s-%s", src_id, "array").c_str()); src_array->setAttribute("count", llformat("%d", vals.size()).c_str()); for (U32 i = 0; i < vals.size(); i++) { ((domFloat_array*)src_array)->getValue().append(vals[i]); } domAccessor* acc = daeSafeCast(source->add("technique_common accessor")); acc->setSource(llformat("#%s-%s", src_id, "array").c_str()); acc->setCount(vals.size() / params.size()); acc->setStride(params.size()); for (std::string::iterator p_iter = params.begin(); p_iter != params.end(); ++p_iter) { domElement* pX = acc->add("param"); pX->setAttribute("name", llformat("%c", *p_iter).c_str()); pX->setAttribute("type", "float"); } } void DAESaver::addPolygons(daeElement* mesh, const char* geomID, const char* materialID, LLViewerObject* obj, int_list_t* faces_to_include) { domPolylist* polylist = daeSafeCast(mesh->add("polylist")); polylist->setMaterial(materialID); // Vertices semantic { domInputLocalOffset* input = daeSafeCast(polylist->add("input")); input->setSemantic("VERTEX"); input->setOffset(0); input->setSource(llformat("#%s-%s", geomID, "vertices").c_str()); } // Normals semantic { domInputLocalOffset* input = daeSafeCast(polylist->add("input")); input->setSemantic("NORMAL"); input->setOffset(0); input->setSource(llformat("#%s-%s", geomID, "normals").c_str()); } // UV semantic { domInputLocalOffset* input = daeSafeCast(polylist->add("input")); input->setSemantic("TEXCOORD"); input->setOffset(0); input->setSource(llformat("#%s-%s", geomID, "map0").c_str()); } // Save indices domP* p = daeSafeCast(polylist->add("p")); domPolylist::domVcount *vcount = daeSafeCast(polylist->add("vcount")); S32 index_offset = 0; S32 num_tris = 0; for (S32 face_num = 0; face_num < obj->getVolume()->getNumVolumeFaces(); face_num++) { if (skipFace(obj->getTE(face_num))) continue; const LLVolumeFace* face = (LLVolumeFace*)&obj->getVolume()->getVolumeFace(face_num); if (faces_to_include == NULL || (std::find(faces_to_include->begin(), faces_to_include->end(), face_num) != faces_to_include->end())) { for (S32 i = 0; i < face->mNumIndices; i++) { U16 index = index_offset + face->mIndices[i]; (p->getValue()).append(index); if (i % 3 == 0) { (vcount->getValue()).append(3); num_tris++; } } } index_offset += face->mNumVertices; } polylist->setCount(num_tris); } void DAESaver::transformTexCoord(S32 num_vert, LLVector2* coord, LLVector3* positions, LLVector3* normals, LLTextureEntry* te, LLVector3 scale) { F32 cosineAngle = cos(te->getRotation()); F32 sinAngle = sin(te->getRotation()); for (S32 ii=0; 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); // Obligatory elements in header daeElement* asset = root->add("asset"); // Get ISO format time time_t rawtime; time(&rawtime); struct tm* utc_time = gmtime(&rawtime); std::string date = llformat("%04d-%02d-%02dT%02d:%02d:%02d", utc_time->tm_year + 1900, utc_time->tm_mon + 1, utc_time->tm_mday, utc_time->tm_hour, utc_time->tm_min, utc_time->tm_sec); daeElement* created = asset->add("created"); created->setCharData(date); daeElement* modified = asset->add("modified"); modified->setCharData(date); daeElement* unit = asset->add("unit"); unit->setAttribute("name", "meter"); unit->setAttribute("value", "1"); daeElement* up_axis = asset->add("up_axis"); up_axis->setCharData("Z_UP"); // File creator daeElement* contributor = asset->add("contributor"); contributor->add("author")->setCharData(LLAppViewer::instance()->getSecondLifeTitle() + " User"); contributor->add("authoring_tool")->setCharData(LLAppViewer::instance()->getSecondLifeTitle() + " Collada Export"); daeElement* images = root->add("library_images"); daeElement* geomLib = root->add("library_geometries"); daeElement* effects = root->add("library_effects"); daeElement* materials = root->add("library_materials"); daeElement* scene = root->add("library_visual_scenes visual_scene"); scene->setAttribute("id", "Scene"); scene->setAttribute("name", "Scene"); if (gSavedSettings.getBOOL("DAEExportTextures")) { generateImagesSection(images); } S32 prim_nr = 0; for (obj_info_t::iterator obj_iter = mObjects.begin(); obj_iter != mObjects.end(); ++obj_iter) { LLViewerObject* obj = obj_iter->first; S32 total_num_vertices = 0; std::string name = ""; if (name.empty()) name = llformat("prim%d", prim_nr++); const char* geomID = name.c_str(); daeElement* geom = geomLib->add("geometry"); geom->setAttribute("id", llformat("%s-%s", geomID, "mesh").c_str()); daeElement* mesh = geom->add("mesh"); std::vector 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]; 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 element { daeElement* verticesNode = mesh->add("vertices"); verticesNode->setAttribute("id", llformat("%s-%s", geomID, "vertices").c_str()); daeElement* verticesInput = verticesNode->add("input"); verticesInput->setAttribute("semantic", "POSITION"); verticesInput->setAttribute("source", llformat("#%s-%s", geomID, "positions").c_str()); } material_list_t objMaterials; getMaterials(obj, &objMaterials); // Add triangles if (gSavedSettings.getBOOL("DAEExportConsolidateMaterials")) { for (U32 objMaterial = 0; objMaterial < objMaterials.size(); objMaterial++) { int_list_t faces; getFacesWithMaterial(obj, objMaterials[objMaterial], &faces); std::string matName = objMaterials[objMaterial].name; addPolygons(mesh, geomID, (matName + "-material").c_str(), obj, &faces); } } else { S32 mat_nr = 0; for (S32 face_num = 0; face_num < num_faces; face_num++) { if (skipFace(obj->getTE(face_num))) continue; int_list_t faces; faces.push_back(face_num); std::string matName = objMaterials[mat_nr++].name; addPolygons(mesh, geomID, (matName + "-material").c_str(), obj, &faces); } } daeElement* node = scene->add("node"); node->setAttribute("type", "NODE"); node->setAttribute("id", geomID); node->setAttribute("name", geomID); // Set tranform matrix (node position, rotation and scale) domMatrix* matrix = (domMatrix*)node->add("matrix"); LLXform srt; srt.setScale(obj->getScale()); srt.setPosition(obj->getRenderPosition() + mOffset); srt.setRotation(obj->getRenderRotation()); LLMatrix4 m4; srt.getLocalMat4(m4); for (int i=0; i<4; i++) for (int j=0; j<4; j++) (matrix->getValue()).append(m4.mMatrix[j][i]); // Geometry of the node daeElement* nodeGeometry = node->add("instance_geometry"); // Bind materials daeElement* tq = nodeGeometry->add("bind_material technique_common"); for (U32 objMaterial = 0; objMaterial < objMaterials.size(); objMaterial++) { std::string matName = objMaterials[objMaterial].name; daeElement* instanceMaterial = tq->add("instance_material"); instanceMaterial->setAttribute("symbol", (matName + "-material").c_str()); instanceMaterial->setAttribute("target", ("#" + matName + "-material").c_str()); } nodeGeometry->setAttribute("url", llformat("#%s-%s", geomID, "mesh").c_str()); } // Effects (face texture, color, alpha) generateEffects(effects); // Materials for (U32 objMaterial = 0; objMaterial < mAllMaterials.size(); objMaterial++) { daeElement* mat = materials->add("material"); mat->setAttribute("id", (mAllMaterials[objMaterial].name + "-material").c_str()); daeElement* matEffect = mat->add("instance_effect"); matEffect->setAttribute("url", ("#" + mAllMaterials[objMaterial].name + "-fx").c_str()); } root->add("scene instance_visual_scene")->setAttribute("url", "#Scene"); return dae.writeAll(); } DAESaver::DAESaver() {} bool DAESaver::skipFace(LLTextureEntry *te) { return (gSavedSettings.getBOOL("DAEExportSkipTransparent") && (te->getColor().mV[3] < 0.01f || te->getID() == DAEExportUtil::LL_TEXTURE_TRANSPARENT)); } DAESaver::MaterialInfo DAESaver::getMaterial(LLTextureEntry* te) { if (gSavedSettings.getBOOL("DAEExportConsolidateMaterials")) { for (U32 i=0; i < mAllMaterials.size(); i++) { if (mAllMaterials[i].matches(te)) { return mAllMaterials[i]; } } } MaterialInfo ret; ret.textureID = te->getID(); ret.color = te->getColor(); ret.name = llformat("Material%d", mAllMaterials.size()); mAllMaterials.push_back(ret); return mAllMaterials[mAllMaterials.size() - 1]; } void DAESaver::getMaterials(LLViewerObject* obj, material_list_t* ret) { S32 num_faces = obj->getVolume()->getNumVolumeFaces(); for (S32 face_num = 0; face_num < num_faces; ++face_num) { LLTextureEntry* te = obj->getTE(face_num); if (skipFace(te)) { continue; } MaterialInfo mat = getMaterial(te); if (!gSavedSettings.getBOOL("DAEExportConsolidateMaterials") || std::find(ret->begin(), ret->end(), mat) == ret->end()) { ret->push_back(mat); } } } void DAESaver::getFacesWithMaterial(LLViewerObject* obj, MaterialInfo& mat, int_list_t* ret) { S32 num_faces = obj->getVolume()->getNumVolumeFaces(); for (S32 face_num = 0; face_num < num_faces; ++face_num) { if (mat == getMaterial(obj->getTE(face_num))) { ret->push_back(face_num); } } } void DAESaver::generateEffects(daeElement *effects) { // Effects (face color, alpha) bool export_textures = gSavedSettings.getBOOL("DAEExportTextures"); for (U32 mat = 0; mat < mAllMaterials.size(); mat++) { LLColor4 color = mAllMaterials[mat].color; domEffect* effect = (domEffect*)effects->add("effect"); effect->setId((mAllMaterials[mat].name + "-fx").c_str()); daeElement* profile = effect->add("profile_COMMON"); std::string colladaName; if (export_textures) { LLUUID textID; U32 i = 0; for (; i < mTextures.size(); i++) { if (mAllMaterials[mat].textureID == mTextures[i]) { textID = mTextures[i]; break; } } if (!textID.isNull() && !mTextureNames[i].empty()) { colladaName = mTextureNames[i] + "_" + mImageFormat; daeElement* newparam = profile->add("newparam"); newparam->setAttribute("sid", (colladaName + "-surface").c_str()); daeElement* surface = newparam->add("surface"); surface->setAttribute("type", "2D"); surface->add("init_from")->setCharData(colladaName.c_str()); newparam = profile->add("newparam"); newparam->setAttribute("sid", (colladaName + "-sampler").c_str()); newparam->add("sampler2D source")->setCharData((colladaName + "-surface").c_str()); } } daeElement* t = profile->add("technique"); t->setAttribute("sid", "common"); domElement* phong = t->add("phong"); domElement* diffuse = phong->add("diffuse"); // Only one 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 (U32 i=0; i < mTextureNames.size(); i++) { std::string name = mTextureNames[i]; if (name.empty()) continue; std::string colladaName = name + "_" + mImageFormat; daeElement* image = images->add("image"); image->setAttribute("id", colladaName.c_str()); image->setAttribute("name", colladaName.c_str()); image->add("init_from")->setCharData(LLURI::escape(name + "." + mImageFormat)); } } class DAESaveSelectedObjects : public view_listener_t { bool handleEvent(LLPointer 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"); }