Skip to content
Snippets Groups Projects
llmodelpreview.cpp 141 KiB
Newer Older
/**
 * @file llmodelpreview.cpp
 * @brief LLModelPreview class implementation
 *
 * $LicenseInfo:firstyear=2020&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2020, Linden Research, Inc.
 *
 * 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;
 * version 2.1 of the License only.
 *
 * 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
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "llviewerprecompiledheaders.h"

#include "llmodelloader.h"
#include "lldaeloader.h"
#include "llfloatermodelpreview.h"

#include "llagent.h"
#include "llanimationstates.h"
#include "llcallbacklist.h"
#include "lldatapacker.h"
#include "lldrawable.h"
#include "llface.h"
#include "lliconctrl.h"
#include "llmatrix4a.h"
#include "llmeshrepository.h"
Andrey Kleshchev's avatar
Andrey Kleshchev committed
#include "llmeshoptimizer.h"
#include "llrender.h"
#include "llsdutil_math.h"
#include "llskinningutil.h"
#include "llstring.h"
#include "llsdserialize.h"
#include "lltoolmgr.h"
#include "llui.h"
#include "llvector4a.h"
#include "llviewercamera.h"
#include "llviewercontrol.h"
#include "llviewerobjectlist.h"
#include "llviewernetwork.h"
#include "llviewershadermgr.h"
#include "llviewertexteditor.h"
#include "llviewertexturelist.h"
#include "llvoavatar.h"
#include "pipeline.h"

// ui controls (from floater)
#include "llbutton.h"
#include "llcombobox.h"
#include "llspinctrl.h"
#include "lltabcontainer.h"
#include "lltextbox.h"

#include <boost/algorithm/string.hpp>

bool LLModelPreview::sIgnoreLoadedCallback = false;

// Extra configurability, to be exposed later in xml (LLModelPreview probably
// should become UI control at some point or get split into preview control)
static const LLColor4 PREVIEW_CANVAS_COL(0.169f, 0.169f, 0.169f, 1.f);
static const LLColor4 PREVIEW_EDGE_COL(0.4f, 0.4f, 0.4f, 1.0);
static const LLColor4 PREVIEW_BASE_COL(1.f, 1.f, 1.f, 1.f);
static const LLColor3 PREVIEW_BRIGHTNESS(0.9f, 0.9f, 0.9f);
static const F32 PREVIEW_EDGE_WIDTH(1.f);
static const LLColor4 PREVIEW_PSYH_EDGE_COL(0.f, 0.25f, 0.5f, 0.25f);
static const LLColor4 PREVIEW_PSYH_FILL_COL(0.f, 0.5f, 1.0f, 0.5f);
static const F32 PREVIEW_PSYH_EDGE_WIDTH(1.f);
static const LLColor4 PREVIEW_DEG_EDGE_COL(1.f, 0.f, 0.f, 1.f);
static const LLColor4 PREVIEW_DEG_FILL_COL(1.f, 0.f, 0.f, 0.5f);
static const F32 PREVIEW_DEG_EDGE_WIDTH(3.f);
static const F32 PREVIEW_DEG_POINT_SIZE(8.f);
static const F32 PREVIEW_ZOOM_LIMIT(10.f);
static const std::string DEFAULT_PHYSICS_MESH_NAME = "default_physics_shape";
const F32 SKIN_WEIGHT_CAMERA_DISTANCE = 16.f;

LLViewerFetchedTexture* bindMaterialDiffuseTexture(const LLImportMaterial& material)
{
    LLViewerFetchedTexture *texture = LLViewerTextureManager::getFetchedTexture(material.getDiffuseMap(), FTT_DEFAULT, TRUE, LLGLTexture::BOOST_PREVIEW);

    if (texture)
    {
        if (texture->getDiscardLevel() > -1)
        {
            gGL.getTexUnit(0)->bind(texture, true);
            return texture;
        }
    }

    return NULL;
}

std::string stripSuffix(std::string name)
{
    if ((name.find("_LOD") != -1) || (name.find("_PHYS") != -1))
    {
        return name.substr(0, name.rfind('_'));
    }
    return name;
}

std::string getLodSuffix(S32 lod)
{
    std::string suffix;
    switch (lod)
    {
    case LLModel::LOD_IMPOSTOR: suffix = "_LOD0"; break;
    case LLModel::LOD_LOW:      suffix = "_LOD1"; break;
    case LLModel::LOD_MEDIUM:   suffix = "_LOD2"; break;
    case LLModel::LOD_PHYSICS:  suffix = "_PHYS"; break;
    case LLModel::LOD_HIGH:                       break;
    }
    return suffix;
}

void FindModel(LLModelLoader::scene& scene, const std::string& name_to_match, LLModel*& baseModelOut, LLMatrix4& matOut)
{
    LLModelLoader::scene::iterator base_iter = scene.begin();
    bool found = false;
    while (!found && (base_iter != scene.end()))
    {
        matOut = base_iter->first;

        LLModelLoader::model_instance_list::iterator base_instance_iter = base_iter->second.begin();
        while (!found && (base_instance_iter != base_iter->second.end()))
        {
            LLModelInstance& base_instance = *base_instance_iter++;
            LLModel* base_model = base_instance.mModel;

            if (base_model && (base_model->mLabel == name_to_match))
            {
                baseModelOut = base_model;
                return;
            }
        }
        base_iter++;
    }
}

//-----------------------------------------------------------------------------
// LLModelPreview
//-----------------------------------------------------------------------------

LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp)
    : LLViewerDynamicTexture(width, height, 3, ORDER_MIDDLE, FALSE), LLMutex()
    , mLodsQuery()
    , mLodsWithParsingError()
    , mPelvisZOffset(0.0f)
    , mLegacyRigFlags(U32_MAX)
    , mRigValidJointUpload(false)
    , mPhysicsSearchLOD(LLModel::LOD_PHYSICS)
    , mResetJoints(false)
    , mModelNoErrors(true)
    , mLastJointUpdate(false)
    , mFirstSkinUpdate(true)
    , mHasDegenerate(false)
    , mImporterDebug(LLCachedControl<bool>(gSavedSettings, "ImporterDebug", false))
{
    mNeedsUpdate = TRUE;
    mCameraDistance = 0.f;
    mCameraYaw = 0.f;
    mCameraPitch = 0.f;
    mCameraZoom = 1.f;
    mTextureName = 0;
    mPreviewLOD = 0;
    mModelLoader = NULL;
    mMaxTriangleLimit = 0;
    mDirty = false;
    mGenLOD = false;
    mLoading = false;
    mLoadState = LLModelLoader::STARTING;
    mGroup = 0;
    mLODFrozen = false;

    for (U32 i = 0; i < LLModel::NUM_LODS; ++i)
    {
        mRequestedTriangleCount[i] = 0;
        mRequestedCreaseAngle[i] = -1.f;
        mRequestedLoDMode[i] = 0;
        mRequestedErrorThreshold[i] = 0.f;
    }

    mViewOption["show_textures"] = false;

    mFMP = fmp;

    mHasPivot = false;
    mModelPivot = LLVector3(0.0f, 0.0f, 0.0f);

    createPreviewAvatar();
}

LLModelPreview::~LLModelPreview()
{
    if (mModelLoader)
    {
        mModelLoader->shutdown();
    }

    if (mPreviewAvatar)
    {
        mPreviewAvatar->markDead();
        mPreviewAvatar = NULL;
    }
void LLModelPreview::updateDimentionsAndOffsets()
{
    assert_main_thread();

    rebuildUploadData();

    std::set<LLModel*> accounted;

    mPelvisZOffset = mFMP ? mFMP->childGetValue("pelvis_offset").asReal() : 3.0f;

    if (mFMP && mFMP->childGetValue("upload_joints").asBoolean())
    {
        // FIXME if preview avatar ever gets reused, this fake mesh ID stuff will fail.
        // see also call to addAttachmentPosOverride.
        LLUUID fake_mesh_id;
        fake_mesh_id.generate();
        getPreviewAvatar()->addPelvisFixup(mPelvisZOffset, fake_mesh_id);
    }

    for (U32 i = 0; i < mUploadData.size(); ++i)
    {
        LLModelInstance& instance = mUploadData[i];

        if (accounted.find(instance.mModel) == accounted.end())
        {
            accounted.insert(instance.mModel);

            //update instance skin info for each lods pelvisZoffset 
            for (int j = 0; j<LLModel::NUM_LODS; ++j)
            {
                if (instance.mLOD[j])
                {
                    instance.mLOD[j]->mSkinInfo.mPelvisOffset = mPelvisZOffset;
                }
            }
        }
    }

    F32 scale = mFMP ? mFMP->childGetValue("import_scale").asReal()*2.f : 2.f;

    mDetailsSignal((F32)(mPreviewScale[0] * scale), (F32)(mPreviewScale[1] * scale), (F32)(mPreviewScale[2] * scale));

    updateStatusMessages();
}

void LLModelPreview::rebuildUploadData()
{
    assert_main_thread();

    mUploadData.clear();
    mTextureSet.clear();

    //fill uploaddata instance vectors from scene data

    std::string requested_name = mFMP->getChild<LLUICtrl>("description_form")->getValue().asString();

    LLSpinCtrl* scale_spinner = mFMP->getChild<LLSpinCtrl>("import_scale");

    F32 scale = scale_spinner->getValue().asReal();

    LLMatrix4 scale_mat;
    scale_mat.initScale(LLVector3(scale, scale, scale));

    F32 max_scale = 0.f;

    BOOL legacyMatching = gSavedSettings.getBOOL("ImporterLegacyMatching");
Andrey Kleshchev's avatar
Andrey Kleshchev committed
    U32 load_state = 0;

    for (LLModelLoader::scene::iterator iter = mBaseScene.begin(); iter != mBaseScene.end(); ++iter)
    { //for each transform in scene
        LLMatrix4 mat = iter->first;

        // compute position
        LLVector3 position = LLVector3(0, 0, 0) * mat;

        // compute scale
        LLVector3 x_transformed = LLVector3(1, 0, 0) * mat - position;
        LLVector3 y_transformed = LLVector3(0, 1, 0) * mat - position;
        LLVector3 z_transformed = LLVector3(0, 0, 1) * mat - position;
        F32 x_length = x_transformed.normalize();
        F32 y_length = y_transformed.normalize();
        F32 z_length = z_transformed.normalize();

        max_scale = llmax(llmax(llmax(max_scale, x_length), y_length), z_length);

        mat *= scale_mat;

        for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end();)
        { //for each instance with said transform applied 
            LLModelInstance instance = *model_iter++;

            LLModel* base_model = instance.mModel;

            if (base_model && !requested_name.empty())
            {
                base_model->mRequestedLabel = requested_name;
            }

            for (int i = LLModel::NUM_LODS - 1; i >= LLModel::LOD_IMPOSTOR; i--)
            {
                LLModel* lod_model = NULL;
                if (!legacyMatching)
                {
                    // Fill LOD slots by finding matching meshes by label with name extensions
                    // in the appropriate scene for each LOD. This fixes all kinds of issues
                    // where the indexed method below fails in spectacular fashion.
                    // If you don't take the time to name your LOD and PHYS meshes
                    // with the name of their corresponding mesh in the HIGH LOD,
                    // then the indexed method will be attempted below.

                    LLMatrix4 transform;

                    std::string name_to_match = instance.mLabel;
                    llassert(!name_to_match.empty());

                    int extensionLOD;
                    if (i != LLModel::LOD_PHYSICS || mModel[LLModel::LOD_PHYSICS].empty())
                    {
                        extensionLOD = i;
                    }
                    else
                    {
                        //Physics can be inherited from other LODs or loaded, so we need to adjust what extension we are searching for
                        extensionLOD = mPhysicsSearchLOD;
                    }

                    std::string toAdd = getLodSuffix(extensionLOD);

                    if (name_to_match.find(toAdd) == -1)
                    {
                        name_to_match += toAdd;
                    }

                    FindModel(mScene[i], name_to_match, lod_model, transform);

                    if (!lod_model && i != LLModel::LOD_PHYSICS)
                    {
                        if (mImporterDebug)
                        {
                            std::ostringstream out;
                            out << "Search of" << name_to_match;
                            out << " in LOD" << i;
                            out << " list failed. Searching for alternative among LOD lists.";
                            LL_INFOS() << out.str() << LL_ENDL;
                            LLFloaterModelPreview::addStringToLog(out, false);
                        }

                        int searchLOD = (i > LLModel::LOD_HIGH) ? LLModel::LOD_HIGH : i;
                        while ((searchLOD <= LLModel::LOD_HIGH) && !lod_model)
                        {
                            std::string name_to_match = instance.mLabel;
                            llassert(!name_to_match.empty());

                            std::string toAdd = getLodSuffix(searchLOD);

                            if (name_to_match.find(toAdd) == -1)
                            {
                                name_to_match += toAdd;
                            }

                            // See if we can find an appropriately named model in LOD 'searchLOD'
                            //
                            FindModel(mScene[searchLOD], name_to_match, lod_model, transform);
                            searchLOD++;
                        }
                    }
                }
                else
                {
                    // Use old method of index-based association
                    U32 idx = 0;
                    for (idx = 0; idx < mBaseModel.size(); ++idx)
                    {
                        // find reference instance for this model
                        if (mBaseModel[idx] == base_model)
                        {
                            if (mImporterDebug)
                            {
                                std::ostringstream out;
                                out << "Attempting to use model index " << idx;
                                out << " for LOD" << i;
                                out << " of " << instance.mLabel;
                                LL_INFOS() << out.str() << LL_ENDL;
                                LLFloaterModelPreview::addStringToLog(out, false);
                            }
                            break;
                        }
                    }

                    // If the model list for the current LOD includes that index...
                    //
                    if (mModel[i].size() > idx)
                    {
                        // Assign that index from the model list for our LOD as the LOD model for this instance
                        //
                        lod_model = mModel[i][idx];
                        if (mImporterDebug)
                        {
                            std::ostringstream out;
                            out << "Indexed match of model index " << idx << " at LOD " << i << " to model named " << lod_model->mLabel;
                            LL_INFOS() << out.str() << LL_ENDL;
                            LLFloaterModelPreview::addStringToLog(out, false);
                        }
                    }
                    else if (mImporterDebug)
                    {
                        std::ostringstream out;
                        out << "List of models does not include index " << idx;
                        LL_INFOS() << out.str() << LL_ENDL;
                        LLFloaterModelPreview::addStringToLog(out, false);
                    }
                }
                if (mWarnOfUnmatchedPhyicsMeshes && !lod_model && (i == LLModel::LOD_PHYSICS))
                {
                    // Despite the various strategies above, if we don't now have a physics model, we're going to end up with decomposition.
                    // That's ok, but might not what they wanted. Use default_physics_shape if found.
                    out << "No physics model specified for " << instance.mLabel;
                    if (mDefaultPhysicsShapeP)
                    {
                        out << " - using: " << DEFAULT_PHYSICS_MESH_NAME;
                        lod_model = mDefaultPhysicsShapeP;
                    }
Howard Stearns's avatar
Howard Stearns committed
                    LL_WARNS() << out.str() << LL_ENDL;
                    LLFloaterModelPreview::addStringToLog(out, !mDefaultPhysicsShapeP); // Flash log tab if no default.

                if (lod_model)
                {
                    if (mImporterDebug)
                    {
                        std::ostringstream out;
                        if (i == LLModel::LOD_PHYSICS)
                        {
                            out << "Assigning collision for " << instance.mLabel << " to match " << lod_model->mLabel;
                        }
                        else
                        {
                            out << "Assigning LOD" << i << " for " << instance.mLabel << " to found match " << lod_model->mLabel;
                        }
                        LL_INFOS() << out.str() << LL_ENDL;
                        LLFloaterModelPreview::addStringToLog(out, false);
                    }
                    instance.mLOD[i] = lod_model;
                }
                else
                {
                    if (i < LLModel::LOD_HIGH && !lodsReady())
                    {
                        // assign a placeholder from previous LOD until lod generation is complete.
                        // Note: we might need to assign it regardless of conditions like named search does, to prevent crashes.
                        instance.mLOD[i] = instance.mLOD[i + 1];
                    }
                    if (mImporterDebug)
                    {
                        std::ostringstream out;
                        out << "List of models does not include " << instance.mLabel;
                        LL_INFOS() << out.str() << LL_ENDL;
                        LLFloaterModelPreview::addStringToLog(out, false);
                    }
                }
            }

            LLModel* high_lod_model = instance.mLOD[LLModel::LOD_HIGH];
            if (!high_lod_model)
            {
                LLFloaterModelPreview::addStringToLog("Model " + instance.mLabel + " has no High Lod (LOD3).", true);
Andrey Kleshchev's avatar
Andrey Kleshchev committed
                load_state = LLModelLoader::ERROR_MATERIALS;
                mFMP->childDisable("calculate_btn");
            }
            else
            {
                for (U32 i = 0; i < LLModel::NUM_LODS - 1; i++)
                {
                    int refFaceCnt = 0;
                    int modelFaceCnt = 0;
                    llassert(instance.mLOD[i]);
                    if (instance.mLOD[i] && !instance.mLOD[i]->matchMaterialOrder(high_lod_model, refFaceCnt, modelFaceCnt))
                    {
                        LLFloaterModelPreview::addStringToLog("Model " + instance.mLabel + " has mismatching materials between lods." , true);
Andrey Kleshchev's avatar
Andrey Kleshchev committed
                        load_state = LLModelLoader::ERROR_MATERIALS;
                        mFMP->childDisable("calculate_btn");
                    }
                }
                LLFloaterModelPreview* fmp = (LLFloaterModelPreview*)mFMP;
                bool upload_skinweights = fmp && fmp->childGetValue("upload_skin").asBoolean();
                if (upload_skinweights && high_lod_model->mSkinInfo.mJointNames.size() > 0)
                {
                    LLQuaternion bind_rot = LLSkinningUtil::getUnscaledQuaternion(LLMatrix4(high_lod_model->mSkinInfo.mBindShapeMatrix));
                    LLQuaternion identity;
                    if (!bind_rot.isEqualEps(identity, 0.01))
                    {
Andrey Kleshchev's avatar
Andrey Kleshchev committed
                        // Bind shape matrix is not in standard X-forward orientation.
                        // Might be good idea to only show this once. It can be spammy.
                        std::ostringstream out;
                        out << "non-identity bind shape rot. mat is ";
                        out << high_lod_model->mSkinInfo.mBindShapeMatrix;
                        out << " bind_rot ";
                        out << bind_rot;
                        LL_WARNS() << out.str() << LL_ENDL;

                        LLFloaterModelPreview::addStringToLog(out, getLoadState() != LLModelLoader::WARNING_BIND_SHAPE_ORIENTATION);
Andrey Kleshchev's avatar
Andrey Kleshchev committed
                        load_state = LLModelLoader::WARNING_BIND_SHAPE_ORIENTATION;
                    }
                }
            }
            instance.mTransform = mat;
            mUploadData.push_back(instance);
        }
    }

    for (U32 lod = 0; lod < LLModel::NUM_LODS - 1; lod++)
    {
        // Search for models that are not included into upload data
        // If we found any, that means something we loaded is not a sub-model.
        for (U32 model_ind = 0; model_ind < mModel[lod].size(); ++model_ind)
        {
            bool found_model = false;
            for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter)
            {
                LLModelInstance& instance = *iter;
                if (instance.mLOD[lod] == mModel[lod][model_ind])
                {
                    found_model = true;
                    break;
                }
            }
            if (!found_model && mModel[lod][model_ind] && !mModel[lod][model_ind]->mSubmodelID)
            {
                if (mImporterDebug)
                {
                    std::ostringstream out;
                    out << "Model " << mModel[lod][model_ind]->mLabel << " was not used - mismatching lod models.";
                    LL_INFOS() << out.str() << LL_ENDL;
                    LLFloaterModelPreview::addStringToLog(out, true);
Andrey Kleshchev's avatar
Andrey Kleshchev committed
                load_state = LLModelLoader::ERROR_MATERIALS;
Andrey Kleshchev's avatar
Andrey Kleshchev committed
    // Update state for notifications
    if (load_state > 0)
    {
        // encountered issues
        setLoadState(load_state);
    }
    else if (getLoadState() == LLModelLoader::ERROR_MATERIALS
             || getLoadState() == LLModelLoader::WARNING_BIND_SHAPE_ORIENTATION)
    {
        // This is only valid for these two error types because they are 
        // only used inside rebuildUploadData() and updateStatusMessages()
        // updateStatusMessages() is called after rebuildUploadData()
        setLoadState(LLModelLoader::DONE);
    }

    F32 max_import_scale = (DEFAULT_MAX_PRIM_SCALE - 0.1f) / max_scale;

    F32 max_axis = llmax(mPreviewScale.mV[0], mPreviewScale.mV[1]);
    max_axis = llmax(max_axis, mPreviewScale.mV[2]);
    max_axis *= 2.f;

    //clamp scale so that total imported model bounding box is smaller than 240m on a side
    max_import_scale = llmin(max_import_scale, 240.f / max_axis);

    scale_spinner->setMaxValue(max_import_scale);

    if (max_import_scale < scale)
    {
        scale_spinner->setValue(max_import_scale);
    }

}

void LLModelPreview::saveUploadData(bool save_skinweights, bool save_joint_positions, bool lock_scale_if_joint_position)
{
    if (!mLODFile[LLModel::LOD_HIGH].empty())
    {
        std::string filename = mLODFile[LLModel::LOD_HIGH];
        std::string slm_filename;

        if (LLModelLoader::getSLMFilename(filename, slm_filename))
        {
            saveUploadData(slm_filename, save_skinweights, save_joint_positions, lock_scale_if_joint_position);
        }
    }
}

void LLModelPreview::saveUploadData(const std::string& filename,
    bool save_skinweights, bool save_joint_positions, bool lock_scale_if_joint_position)
{

    std::set<LLPointer<LLModel> > meshes;
    std::map<LLModel*, std::string> mesh_binary;

    LLModel::hull empty_hull;

    LLSD data;

    data["version"] = SLM_SUPPORTED_VERSION;
    if (!mBaseModel.empty())
    {
        data["name"] = mBaseModel[0]->getName();
    }

    S32 mesh_id = 0;

    //build list of unique models and initialize local id
    for (U32 i = 0; i < mUploadData.size(); ++i)
    {
        LLModelInstance& instance = mUploadData[i];

        if (meshes.find(instance.mModel) == meshes.end())
        {
            instance.mModel->mLocalID = mesh_id++;
            meshes.insert(instance.mModel);

            std::stringstream str;
            LLModel::Decomposition& decomp =
                instance.mLOD[LLModel::LOD_PHYSICS].notNull() ?
                instance.mLOD[LLModel::LOD_PHYSICS]->mPhysics :
                instance.mModel->mPhysics;

            LLModel::writeModel(str,
                instance.mLOD[LLModel::LOD_PHYSICS],
                instance.mLOD[LLModel::LOD_HIGH],
                instance.mLOD[LLModel::LOD_MEDIUM],
                instance.mLOD[LLModel::LOD_LOW],
                instance.mLOD[LLModel::LOD_IMPOSTOR],
                decomp,
                save_skinweights,
                save_joint_positions,
                lock_scale_if_joint_position,
                FALSE, TRUE, instance.mModel->mSubmodelID);

            data["mesh"][instance.mModel->mLocalID] = str.str();
        }

        data["instance"][i] = instance.asLLSD();
    }

    llofstream out(filename.c_str(), std::ios_base::out | std::ios_base::binary);
    LLSDSerialize::toBinary(data, out);
    out.flush();
    out.close();
}

void LLModelPreview::clearModel(S32 lod)
{
    if (lod < 0 || lod > LLModel::LOD_PHYSICS)
    {
        return;
    }

    mVertexBuffer[lod].clear();
    mModel[lod].clear();
    mScene[lod].clear();
}

void LLModelPreview::getJointAliases(JointMap& joint_map)
{
    // Get all standard skeleton joints from the preview avatar.
    LLVOAvatar *av = getPreviewAvatar();

    //Joint names and aliases come from avatar_skeleton.xml

    joint_map = av->getJointAliases();

    std::vector<std::string> cv_names, attach_names;
    av->getSortedJointNames(1, cv_names);
    av->getSortedJointNames(2, attach_names);
    for (std::vector<std::string>::iterator it = cv_names.begin(); it != cv_names.end(); ++it)
    {
        joint_map[*it] = *it;
    }
    for (std::vector<std::string>::iterator it = attach_names.begin(); it != attach_names.end(); ++it)
    {
        joint_map[*it] = *it;
    }
}

void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable_slm)
{
    assert_main_thread();

    LLMutexLock lock(this);

    if (lod < LLModel::LOD_IMPOSTOR || lod > LLModel::NUM_LODS - 1)
    {
        std::ostringstream out;
        out << "Invalid level of detail: ";
        out << lod;
        LL_WARNS() << out.str() << LL_ENDL;
        LLFloaterModelPreview::addStringToLog(out, true);
        assert(lod >= LLModel::LOD_IMPOSTOR && lod < LLModel::NUM_LODS);
        return;
    }

    // This triggers if you bring up the file picker and then hit CANCEL.
    // Just use the previous model (if any) and ignore that you brought up
    // the file picker.

    if (filename.empty())
    {
        if (mBaseModel.empty())
        {
            // this is the initial file picking. Close the whole floater
            // if we don't have a base model to show for high LOD.
            mFMP->closeFloater(false);
        }
        mLoading = false;
        return;
    }

    if (mModelLoader)
    {
        LL_WARNS() << "Incompleted model load operation pending." << LL_ENDL;
        return;
    }

    mLODFile[lod] = filename;

    std::map<std::string, std::string> joint_alias_map;
    getJointAliases(joint_alias_map);

    mModelLoader = new LLDAELoader(
        filename,
        lod,
        &LLModelPreview::loadedCallback,
        &LLModelPreview::lookupJointByName,
        &LLModelPreview::loadTextures,
        &LLModelPreview::stateChangedCallback,
        this,
        mJointTransformMap,
        mJointsFromNode,
        joint_alias_map,
        LLSkinningUtil::getMaxJointCount(),
        gSavedSettings.getU32("ImporterModelLimit"),
        gSavedSettings.getBOOL("ImporterPreprocessDAE"));

    if (force_disable_slm)
    {
        mModelLoader->mTrySLM = false;
    }
    else
    {
        // For MAINT-6647, we have set force_disable_slm to true,
        // which means this code path will never be taken. Trying to
        // re-use SLM files has never worked properly; in particular,
        // it tends to force the UI into strange checkbox options
        // which cannot be altered.

        //only try to load from slm if viewer is configured to do so and this is the 
        //initial model load (not an LoD or physics shape)
        mModelLoader->mTrySLM = gSavedSettings.getBOOL("MeshImportUseSLM") && mUploadData.empty();
    }
    mModelLoader->start();

    mFMP->childSetTextArg("status", "[STATUS]", mFMP->getString("status_reading_file"));

    setPreviewLOD(lod);

    if (getLoadState() >= LLModelLoader::ERROR_PARSING)
    {
        mFMP->childDisable("ok_btn");
        mFMP->childDisable("calculate_btn");
    }

    if (lod == mPreviewLOD)
    {
        mFMP->childSetValue("lod_file_" + lod_name[lod], mLODFile[lod]);
    }
    else if (lod == LLModel::LOD_PHYSICS)
    {
        mFMP->childSetValue("physics_file", mLODFile[lod]);
    }

    mFMP->openFloater();
}

void LLModelPreview::setPhysicsFromLOD(S32 lod)
{
    assert_main_thread();

    if (lod >= 0 && lod <= 3)
    {
        mPhysicsSearchLOD = lod;
        mModel[LLModel::LOD_PHYSICS] = mModel[lod];
        mScene[LLModel::LOD_PHYSICS] = mScene[lod];
        mLODFile[LLModel::LOD_PHYSICS].clear();
        mFMP->childSetValue("physics_file", mLODFile[LLModel::LOD_PHYSICS]);
        mVertexBuffer[LLModel::LOD_PHYSICS].clear();
        rebuildUploadData();
        refresh();
        updateStatusMessages();
    }
}

void LLModelPreview::clearIncompatible(S32 lod)
{
    //Don't discard models if specified model is the physic rep
    if (lod == LLModel::LOD_PHYSICS)
    {
        return;
    }

    // at this point we don't care about sub-models,
    // different amount of sub-models means face count mismatch, not incompatibility
    U32 lod_size = countRootModels(mModel[lod]);
    bool replaced_base_model = (lod == LLModel::LOD_HIGH);
    for (U32 i = 0; i <= LLModel::LOD_HIGH; i++)
    {
        // Clear out any entries that aren't compatible with this model
        if (i != lod)
        {
            if (countRootModels(mModel[i]) != lod_size)
            {
                mModel[i].clear();
                mScene[i].clear();
                mVertexBuffer[i].clear();

                if (i == LLModel::LOD_HIGH)
                {
                    mBaseModel = mModel[lod];
                    mBaseScene = mScene[lod];
                    mVertexBuffer[5].clear();
    if (replaced_base_model && !mGenLOD)
    {
        // In case base was replaced, we might need to restart generation
        bool subscribe_for_generation = mLodsQuery.empty();
        
        // Remove previously scheduled work
        mLodsQuery.clear();

        LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;
        if (!fmp) return;

        for (S32 i = LLModel::LOD_HIGH; i >= 0; --i)
        {
            if (mModel[i].empty())
            {
                // Base model was replaced, regenerate this lod if applicable
                LLComboBox* lod_combo = mFMP->findChild<LLComboBox>("lod_source_" + lod_name[i]);
                if (!lod_combo) return;

                S32 lod_mode = lod_combo->getCurrentIndex();
                if (lod_mode != LOD_FROM_FILE)
                {
                    mLodsQuery.push_back(i);
        // Subscribe if we have pending work and not subscribed yet
        if (!mLodsQuery.empty() && subscribe_for_generation)
        {
            doOnIdleRepeating(lodQueryCallback);
        }
    }
}

void LLModelPreview::loadModelCallback(S32 loaded_lod)
{
    assert_main_thread();

    LLMutexLock lock(this);
    if (!mModelLoader)
    {
        mLoading = false;
        return;
    }
    if (getLoadState() >= LLModelLoader::ERROR_PARSING)
    {
        mLoading = false;
        mModelLoader = NULL;
        mLodsWithParsingError.push_back(loaded_lod);
        return;
    }

    mLodsWithParsingError.erase(std::remove(mLodsWithParsingError.begin(), mLodsWithParsingError.end(), loaded_lod), mLodsWithParsingError.end());
    if (mLodsWithParsingError.empty())
    {
        mFMP->childEnable("calculate_btn");
    }

    // Copy determinations about rig so UI will reflect them
    //
    setRigValidForJointPositionUpload(mModelLoader->isRigValidForJointPositionUpload());
    setLegacyRigFlags(mModelLoader->getLegacyRigFlags());

    mModelLoader->loadTextures();

    if (loaded_lod == -1)
    { //populate all LoDs from model loader scene
        mBaseModel.clear();
        mBaseScene.clear();

        bool skin_weights = false;
        bool joint_overrides = false;
        bool lock_scale_if_joint_position = false;

        for (S32 lod = 0; lod < LLModel::NUM_LODS; ++lod)
        { //for each LoD

            //clear scene and model info
            mScene[lod].clear();
            mModel[lod].clear();
            mVertexBuffer[lod].clear();

            if (mModelLoader->mScene.begin()->second[0].mLOD[lod].notNull())
            { //if this LoD exists in the loaded scene

                //copy scene to current LoD
                mScene[lod] = mModelLoader->mScene;

                //touch up copied scene to look like current LoD
                for (LLModelLoader::scene::iterator iter = mScene[lod].begin(); iter != mScene[lod].end(); ++iter)
                {
                    LLModelLoader::model_instance_list& list = iter->second;

                    for (LLModelLoader::model_instance_list::iterator list_iter = list.begin(); list_iter != list.end(); ++list_iter)
                    {
                        //override displayed model with current LoD
                        list_iter->mModel = list_iter->mLOD[lod];

                        if (!list_iter->mModel)
                        {
                            continue;
                        }

                        //add current model to current LoD's model list (LLModel::mLocalID makes a good vector index)
                        S32 idx = list_iter->mModel->mLocalID;

                        if (mModel[lod].size() <= idx)
                        { //stretch model list to fit model at given index
                            mModel[lod].resize(idx + 1);
                        }

                        mModel[lod][idx] = list_iter->mModel;
                        if (!list_iter->mModel->mSkinWeights.empty())
                        {
                            skin_weights = true;

                            if (!list_iter->mModel->mSkinInfo.mAlternateBindMatrix.empty())
                            {
                                joint_overrides = true;
                            }
                            if (list_iter->mModel->mSkinInfo.mLockScaleIfJointPosition)
                            {
                                lock_scale_if_joint_position = true;
                            }
                        }
                    }
                }
            }
        }

        if (mFMP)
        {
            LLFloaterModelPreview* fmp = (LLFloaterModelPreview*)mFMP;

            if (skin_weights)
            { //enable uploading/previewing of skin weights if present in .slm file
                fmp->enableViewOption("show_skin_weight");
                mViewOption["show_skin_weight"] = true;
                fmp->childSetValue("upload_skin", true);