Files
SingularityViewer/indra/newview/awavefront.cpp
2013-11-01 22:34:55 +01:00

508 lines
15 KiB
C++

/**
* @file awavefront.cpp
* @brief A system which allows saving in-world objects to Wavefront .OBJ files for offline texturizing/shading.
* @authors Apelsin, Lirusaito
*
* $LicenseInfo:firstyear=2011&license=LGPLV3$
* Copyright (C) 2011-2013 Apelsin
*
* 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 3 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 "awavefront.h"
// library includes
#include "aifilepicker.h"
#include "llnotificationsutil.h"
// newview includes
#include "lfsimfeaturehandler.h"
#include "llavatarappearancedefines.h"
#include "llface.h"
#include "llvoavatar.h"
#include "llvovolume.h"
#include "llviewerinventory.h"
#include "llinventorymodel.h"
#include "llinventoryfunctions.h"
// menu includes
#include "llevent.h"
#include "llmemberlistener.h"
#include "llview.h"
#include "llselectmgr.h"
LLVOAvatar* find_avatar_from_object(LLViewerObject* object);
extern LLUUID gAgentID;
//Typedefs used in other files, using here for consistency.
typedef std::vector<LLAvatarJoint*> avatar_joint_list_t;
typedef LLMemberListener<LLView> view_listener_t;
namespace
{
const std::string OBJ(".obj");
void save_wavefront_continued(WavefrontSaver* wfsaver, AIFilePicker* filepicker)
{
if (filepicker->hasFilename())
{
const std::string selected_filename = filepicker->getFilename();
if (LLFILE* fp = LLFile::fopen(selected_filename, "wb"))
{
wfsaver->saveFile(fp);
llinfos << "OBJ file saved to " << selected_filename << llendl;
if (gSavedSettings.getBOOL("OBJExportNotifySuccess"))
LLNotificationsUtil::add("WavefrontExportSuccess", LLSD().with("FILENAME", selected_filename));
fclose(fp);
}
else llerrs << "can't open: " << selected_filename << llendl;
}
else llwarns << "No file; bailing" << llendl;
delete wfsaver;
}
void save_wavefront_picker(WavefrontSaver* wfsaver, std::string name)
{
AIFilePicker* filepicker = AIFilePicker::create();
filepicker->open(name);
filepicker->run(boost::bind(&save_wavefront_continued, wfsaver, filepicker));
}
void save_wavefront_on_confirm(const LLSD& notification, const LLSD& response, WavefrontSaver* wfsaver, std::string name)
{
if (LLNotificationsUtil::getSelectedOption(notification, response) == 0) // 0 - Proceed, first choice
{
save_wavefront_picker(wfsaver, name);
}
else
{
delete wfsaver;
}
}
}
Wavefront::Wavefront(vert_t v, tri_t t)
: name("")
, vertices(v)
, triangles(t)
{
}
Wavefront::Wavefront(const LLVolumeFace* face, const LLXform* transform, const LLXform* transform_normals)
: name("")
{
class v4adapt
{
private:
LLStrider<LLVector4a> mV4aStrider;
public:
v4adapt(LLVector4a* vp){ mV4aStrider = vp; }
inline LLVector3 operator[] (const unsigned int i)
{
return LLVector3((F32*)&mV4aStrider[i]);
}
};
v4adapt verts(face->mPositions);
for (S32 i = 0; i < face->mNumVertices; ++i)
{
LLVector3 v = verts[i];
vertices.push_back(std::pair<LLVector3, LLVector2>(v, face->mTexCoords[i]));
}
if (transform) Transform(vertices, transform);
v4adapt norms(face->mNormals);
for (S32 i = 0; i < face->mNumVertices; ++i)
normals.push_back(norms[i]);
if (transform_normals) Transform(normals, transform_normals);
for (S32 i = 0; i < face->mNumIndices/3; ++i)
{
triangles.push_back(tri(face->mIndices[i*3+0], face->mIndices[i*3+1], face->mIndices[i*3+2]));
}
}
Wavefront::Wavefront(LLFace* face, LLPolyMesh* mesh, const LLXform* transform, const LLXform* transform_normals)
: name("")
{
LLVertexBuffer* vb = face->getVertexBuffer();
if (!vb) return;
LLStrider<LLVector3> getVerts;
LLStrider<LLVector3> getNorms;
LLStrider<LLVector2> getCoord;
LLStrider<U16> getIndices;
face->getGeometry(getVerts, getNorms, getCoord, getIndices);
const U16 start = face->getGeomStart();
const U32 end = start + (mesh ? mesh->getNumVertices() : vb->getNumVerts()) - 1; //vertices
for (U32 i = start; i <= end; ++i)
vertices.push_back(std::make_pair(getVerts[i], getCoord[i]));
if (transform) Transform(vertices, transform);
for (U32 i = start; i <= end; ++i)
normals.push_back(getNorms[i]);
if (transform_normals) Transform(normals, transform_normals);
const U32 pcount = mesh ? mesh->getNumFaces() : (vb->getNumIndices()/3); //indices
const U16 offset = face->getIndicesStart(); //indices
for (U32 i = 0; i < pcount; ++i)
{
triangles.push_back(tri(getIndices[i * 3 + offset] + start, getIndices[i * 3 + 1 + offset] + start, getIndices[i * 3 + 2 + offset] + start));
}
}
void Wavefront::Transform(vert_t& v, const LLXform* x) //recursive
{
LLMatrix4 m;
x->getLocalMat4(m);
for (vert_t::iterator iterv = v.begin(); iterv != v.end(); ++iterv)
{
iterv->first = iterv->first * m;
}
if (const LLXform* xp = x->getParent()) Transform(v, xp);
}
void Wavefront::Transform(vec3_t& v, const LLXform* x) //recursive
{
LLMatrix4 m;
x->getLocalMat4(m);
for (vec3_t::iterator iterv = v.begin(); iterv != v.end(); ++iterv)
{
*iterv = *iterv * m;
}
if (const LLXform* xp = x->getParent()) Transform(v, xp);
}
WavefrontSaver::WavefrontSaver()
{}
void WavefrontSaver::Add(const Wavefront& obj)
{
obj_v.push_back(obj);
}
void WavefrontSaver::Add(const LLVolume* vol, const LLXform* transform, const LLXform* transform_normals)
{
const int faces = vol->getNumVolumeFaces();
for(int i = 0; i < faces; ++i) //each face will be treated as a separate Wavefront object
{
Add(Wavefront(&vol->getVolumeFace(i), transform, transform_normals));
}
}
void WavefrontSaver::Add(const LLViewerObject* some_vo)
{
LLXform v_form;
v_form.setScale(some_vo->getScale());
v_form.setPosition(some_vo->getRenderPosition());
v_form.setRotation(some_vo->getRenderRotation());
LLXform normfix;
normfix.setRotation(v_form.getRotation()); //Should work...
Add(some_vo->getVolume(), &v_form, &normfix);
}
namespace
{
// Identical to the one in daeexport.cpp.
bool can_export_node(LLSelectNode* node)
{
LLPermissions* perms = node->mPermissions; // Is perms ever NULL?
// This tests the PERM_EXPORT bit too, which is not really necessary (just checking if it's set
// on the root prim would suffice), but also isn't hurting.
if (!(perms && perms->allowExportBy(gAgentID, LFSimFeatureHandler::instance().exportPolicy())))
{
return false;
}
// Additionally chack if this is a sculpt
LLViewerObject* obj = node->getObject();
if (obj->isSculpted() && !obj->isMesh())
{
LLSculptParams *sculpt_params = (LLSculptParams *)obj->getParameterEntry(LLNetworkData::PARAMS_SCULPT);
LLUUID sculpt_id = sculpt_params->getSculptTexture();
// 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;
}
else // not sculpt, we already checked generic permissions
{
return true;
}
}
class LFSaveSelectedObjects : public view_listener_t
{
bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata)
{
if (LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection())
{
if (!selection->getFirstRootObject())
{
if (gSavedSettings.getBOOL("OBJExportNotifyFailed"))
LLNotificationsUtil::add("ExportFailed");
return true;
}
WavefrontSaver* wfsaver = new WavefrontSaver; // deleted in callback
wfsaver->offset = -selection->getFirstRootObject()->getRenderPosition();
S32 total = 0;
S32 included = 0;
for (LLObjectSelection::iterator iter = selection->begin(); iter != selection->end(); ++iter)
{
total++;
LLSelectNode* node = *iter;
if (!can_export_node(node)) continue;
included++;
wfsaver->Add(node->getObject());
}
if (wfsaver->obj_v.empty())
{
if (gSavedSettings.getBOOL("OBJExportNotifyFailed"))
LLNotificationsUtil::add("ExportFailed");
delete wfsaver;
return true;
}
if (total != included)
{
LLSD args;
args["TOTAL"] = total;
args["FAILED"] = total - included;
LLNotificationsUtil::add("WavefrontExportPartial", args, LLSD(), boost::bind(&save_wavefront_on_confirm, _1, _2, wfsaver, selection->getFirstNode()->mName.c_str() + OBJ));
return true;
}
save_wavefront_picker(wfsaver, selection->getFirstNode()->mName.c_str() + OBJ);
}
return true;
}
};
}
void WavefrontSaver::Add(const LLVOAvatar* av_vo) //adds attachments, too!
{
offset = -av_vo->getRenderPosition();
avatar_joint_list_t vjv = av_vo->mMeshLOD;
for (avatar_joint_list_t::const_iterator itervj = vjv.begin(); itervj != vjv.end(); ++itervj)
{
const LLViewerJoint* vj = dynamic_cast<LLViewerJoint*>(*itervj);
if (!vj || vj->mMeshParts.empty()) continue;
LLViewerJointMesh* vjm = dynamic_cast<LLViewerJointMesh*>(vj->mMeshParts[0]); //highest LOD
if (!vjm) continue;
vjm->updateJointGeometry();
LLFace* face = vjm->mFace;
if (!face) continue;
//Beware: this is a hack because LLFace has multiple LODs
//'pm' supplies the right number of vertices and triangles!
LLPolyMesh* pm = vjm->getMesh();
if (!pm) continue;
LLXform normfix;
normfix.setRotation(pm->getRotation());
//Special case for balls...I mean eyeballs!
static const std::string eyeLname = LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::getInstance()->getMeshEntry(LLAvatarAppearanceDefines::MESH_ID_EYEBALL_LEFT)->mName;
static const std::string eyeRname = LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::getInstance()->getMeshEntry(LLAvatarAppearanceDefines::MESH_ID_EYEBALL_RIGHT)->mName;
const std::string name = vj->getName();
if (name == eyeLname || name == eyeRname)
{
LLXform lol;
lol.setPosition(-offset);
Add(Wavefront(face, pm, &lol, &normfix));
}
else
Add(Wavefront(face, pm, NULL, &normfix));
}
for (LLVOAvatar::attachment_map_t::const_iterator iter = av_vo->mAttachmentPoints.begin(); iter != av_vo->mAttachmentPoints.end(); ++iter)
{
LLViewerJointAttachment* ja = iter->second;
if (!ja) continue;
for (LLViewerJointAttachment::attachedobjs_vec_t::iterator itero = ja->mAttachedObjects.begin(); itero != ja->mAttachedObjects.end(); ++itero)
{
LLViewerObject* o = *itero;
if (!o) continue;
LLDynamicArray<LLViewerObject*> prims = LLDynamicArray<LLViewerObject*>();
o->addThisAndAllChildren(prims);
for (LLDynamicArray<LLViewerObject*>::iterator iterc = prims.begin(); iterc != prims.end(); ++iterc)
{
const LLViewerObject* c = *iterc;
if (!c) continue;
if (LLSelectNode* n = LLSelectMgr::getInstance()->getSelection()->findNode(const_cast<LLViewerObject*>(c)))
{
if (!can_export_node(n)) continue;
}
else continue;
const LLVolume* vol = c->getVolume();
if (!vol) continue;
LLXform v_form;
v_form.setScale(c->getScale());
v_form.setPosition(c->getRenderPosition());
v_form.setRotation(c->getRenderRotation());
LLXform normfix;
normfix.setRotation(v_form.getRotation());
if (c->isHUDAttachment())
{
v_form.addPosition(-offset);
//Normals of HUD elements are funky
//TO-DO: fix 'em!
}
Add(vol, &v_form, &normfix);
}
}
}
}
namespace
{
class LFSaveSelectedAvatar : public view_listener_t
{
bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata)
{
if (const LLVOAvatar* avatar = find_avatar_from_object(LLSelectMgr::getInstance()->getSelection()->getPrimaryObject()))
{
if (!avatar->isSelf())
{
if (gSavedSettings.getBOOL("OBJExportNotifyFailed")) LLNotificationsUtil::add("ExportFailed");
return true;
}
WavefrontSaver* wfsaver = new WavefrontSaver; // deleted in callback
wfsaver->Add(avatar);
if (wfsaver->obj_v.empty())
{
if (gSavedSettings.getBOOL("OBJExportNotifyFailed"))
LLNotificationsUtil::add("ExportFailed");
delete wfsaver;
return true;
}
AIFilePicker* filepicker = AIFilePicker::create();
filepicker->open(avatar->getFullname()+OBJ);
filepicker->run(boost::bind(save_wavefront_continued, wfsaver, filepicker));
}
return true;
}
};
}
namespace
{
void write_or_bust(LLFILE* fp, const std::string outstring)
{
const size_t size = outstring.length();
if (fwrite(outstring.c_str(), 1, size, fp) != size)
llwarns << "Short write" << llendl;
}
}
bool WavefrontSaver::saveFile(LLFILE* fp)
{
if (!fp) return false;
int num = 0;
int index = 0;
for (std::vector<Wavefront>::iterator w_iter = obj_v.begin(); w_iter != obj_v.end(); ++w_iter)
{
int count = 0;
std::string name = (*w_iter).name;
if (name.empty()) name = llformat("%d", num++);
vert_t vertices = (*w_iter).vertices;
vec3_t normals = (*w_iter).normals;
tri_t triangles = (*w_iter).triangles;
//Write Object
write_or_bust(fp, "o " + name + "\n");
//Write vertices; swap axes if necessary
static const LLCachedControl<bool> swapYZ("OBJExportSwapYZ", false);
const double xm = swapYZ ? -1.0 : 1.0;
const int y = swapYZ ? 2 : 1;
const int z = swapYZ ? 1 : 2;
for (vert_t::iterator v_iter = vertices.begin(); v_iter != vertices.end(); ++v_iter)
{
++count;
const LLVector3 v = v_iter->first + offset;
write_or_bust(fp, llformat("v %f %f %f\n",v[0] * xm, v[y], v[z]));
}
for (vec3_t::iterator n_iter = normals.begin(); n_iter != normals.end(); ++n_iter)
{
const LLVector3 n = *n_iter;
write_or_bust(fp, llformat("vn %f %f %f\n",n[0] * xm, n[y], n[z]));
}
for (vert_t::iterator v_iter = vertices.begin(); v_iter != vertices.end(); ++v_iter)
{
write_or_bust(fp, llformat("vt %f %f\n", v_iter->second[0], v_iter->second[1]));
}
//Write triangles
for (tri_t::iterator t_iter = triangles.begin(); t_iter != triangles.end(); ++t_iter)
{
const int f1 = t_iter->v0 + index + 1;
const int f2 = t_iter->v1 + index + 1;
const int f3 = t_iter->v2 + index + 1;
write_or_bust(fp, llformat("f %d/%d/%d %d/%d/%d %d/%d/%d\n",
f1,f1,f1,f2,f2,f2,f3,f3,f3));
}
index += count;
}
return true;
}
void addMenu(view_listener_t* menu, const std::string& name);
void add_wave_listeners() // Called in llviewermenu with other addMenu calls, function linked against
{
addMenu(new LFSaveSelectedObjects(), "Object.SaveAsOBJ");
addMenu(new LFSaveSelectedAvatar(), "Avatar.SaveAsOBJ");
}