/**
 * @file llpolymorph.cpp
 * @brief Implementation of LLPolyMesh class
 *
 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, 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$
 */

//-----------------------------------------------------------------------------
// Header Files
//-----------------------------------------------------------------------------

#include "llpolymorph.h"
#include "llavatarappearance.h"
#include "llavatarjoint.h"
#include "llwearable.h"
#include "llxmltree.h"
#include "llendianswizzle.h"
#include "llpolymesh.h"
#include "llfasttimer.h"

//#include "../tools/imdebug/imdebug.h"

const F32 NORMAL_SOFTEN_FACTOR = 0.65f;

//-----------------------------------------------------------------------------
// LLPolyMorphData()
//-----------------------------------------------------------------------------
LLPolyMorphData::LLPolyMorphData(const std::string& morph_name)
    : mName(morph_name)
{
    mNumIndices = 0;
    mCurrentIndex = 0;
    mTotalDistortion = 0.f;
    mAvgDistortion.clear();
    mMaxDistortion = 0.f;
    mVertexIndices = NULL;
    mCoords = NULL;
    mNormals = NULL;
    mBinormals = NULL;
    mTexCoords = NULL;

    mMesh = NULL;
}

LLPolyMorphData::LLPolyMorphData(const LLPolyMorphData &rhs) :
    mName(rhs.mName),
    mNumIndices(rhs.mNumIndices),
    mTotalDistortion(rhs.mTotalDistortion),
    mAvgDistortion(rhs.mAvgDistortion),
    mMaxDistortion(rhs.mMaxDistortion),
    mVertexIndices(NULL),
    mCoords(NULL),
    mNormals(NULL),
    mBinormals(NULL),
    mTexCoords(NULL)
{
    const S32 numVertices = mNumIndices;

    U32 size = sizeof(LLVector4a)*numVertices;

    mCoords = static_cast<LLVector4a*>( ll_aligned_malloc_16(size) );
    mNormals = static_cast<LLVector4a*>( ll_aligned_malloc_16(size) );
    mBinormals = static_cast<LLVector4a*>( ll_aligned_malloc_16(size) );
    mTexCoords = new LLVector2[numVertices];
    mVertexIndices = new U32[numVertices];

    for (S32 v=0; v < numVertices; v++)
    {
        mCoords[v] = rhs.mCoords[v];
        mNormals[v] = rhs.mNormals[v];
        mBinormals[v] = rhs.mBinormals[v];
        mTexCoords[v] = rhs.mTexCoords[v];
        mVertexIndices[v] = rhs.mVertexIndices[v];
    }
}

//-----------------------------------------------------------------------------
// ~LLPolyMorphData()
//-----------------------------------------------------------------------------
LLPolyMorphData::~LLPolyMorphData()
{
    freeData();
}

//-----------------------------------------------------------------------------
// loadBinary()
//-----------------------------------------------------------------------------
bool LLPolyMorphData::loadBinary(LLFILE *fp, LLPolyMeshSharedData *mesh)
{
    S32 numVertices;
    size_t numRead;

    numRead = fread(&numVertices, sizeof(S32), 1, fp);
    llendianswizzle(&numVertices, sizeof(S32), 1);
    if (numRead != 1)
    {
        LL_WARNS() << "Can't read number of morph target vertices" << LL_ENDL;
        return false;
    }

    //-------------------------------------------------------------------------
    // free any existing data
    //-------------------------------------------------------------------------
    freeData();

    //-------------------------------------------------------------------------
    // allocate vertices
    //-------------------------------------------------------------------------

    U32 size = sizeof(LLVector4a)*numVertices;

    mCoords = static_cast<LLVector4a*>(ll_aligned_malloc_16(size));
    mNormals = static_cast<LLVector4a*>(ll_aligned_malloc_16(size));
    mBinormals = static_cast<LLVector4a*>(ll_aligned_malloc_16(size));

    mTexCoords = new LLVector2[numVertices];
    // Actually, we are allocating more space than we need for the skiplist
    mVertexIndices = new U32[numVertices];
    mNumIndices = 0;
    mTotalDistortion = 0.f;
    mMaxDistortion = 0.f;
    mAvgDistortion.clear();
    mMesh = mesh;

    //-------------------------------------------------------------------------
    // read vertices
    //-------------------------------------------------------------------------
    for(S32 v = 0; v < numVertices; v++)
    {
        numRead = fread(&mVertexIndices[v], sizeof(U32), 1, fp);
        llendianswizzle(&mVertexIndices[v], sizeof(U32), 1);
        if (numRead != 1)
        {
            LL_WARNS() << "Can't read morph target vertex number" << LL_ENDL;
            return false;
        }

        if (mVertexIndices[v] > 10000)
        {
            // Bad install? These are usually .llm files from 'character' fodler
            LL_WARNS() << "Bad morph index " << v << ": " << mVertexIndices[v] << LL_ENDL;
            return false;
        }


        numRead = fread(&mCoords[v], sizeof(F32), 3, fp);
        llendianswizzle(&mCoords[v], sizeof(F32), 3);
        if (numRead != 3)
        {
            LL_WARNS() << "Can't read morph target vertex coordinates" << LL_ENDL;
            return false;
        }

        F32 magnitude = mCoords[v].getLength3().getF32();

        mTotalDistortion += magnitude;
        LLVector4a t;
        t.setAbs(mCoords[v]);
        mAvgDistortion.add(t);

        if (magnitude > mMaxDistortion)
        {
            mMaxDistortion = magnitude;
        }

        numRead = fread(&mNormals[v], sizeof(F32), 3, fp);
        llendianswizzle(&mNormals[v], sizeof(F32), 3);
        if (numRead != 3)
        {
            LL_WARNS() << "Can't read morph target normal" << LL_ENDL;
            return false;
        }

        numRead = fread(&mBinormals[v], sizeof(F32), 3, fp);
        llendianswizzle(&mBinormals[v], sizeof(F32), 3);
        if (numRead != 3)
        {
            LL_WARNS() << "Can't read morph target binormal" << LL_ENDL;
            return false;
        }


        numRead = fread(&mTexCoords[v].mV, sizeof(F32), 2, fp);
        llendianswizzle(&mTexCoords[v].mV, sizeof(F32), 2);
        if (numRead != 2)
        {
            LL_WARNS() << "Can't read morph target uv" << LL_ENDL;
            return false;
        }

        mNumIndices++;
    }

    mAvgDistortion.mul(1.f/(F32)mNumIndices);
    mAvgDistortion.normalize3fast();

    return true;
}

//-----------------------------------------------------------------------------
// freeData()
//-----------------------------------------------------------------------------
void LLPolyMorphData::freeData()
{
    if (mCoords != NULL)
    {
        ll_aligned_free_16(mCoords);
        mCoords = NULL;
    }

    if (mNormals != NULL)
    {
        ll_aligned_free_16(mNormals);
        mNormals = NULL;
    }

    if (mBinormals != NULL)
    {
        ll_aligned_free_16(mBinormals);
        mBinormals = NULL;
    }

    if (mTexCoords != NULL)
    {
        delete [] mTexCoords;
        mTexCoords = NULL;
    }

    if (mVertexIndices != NULL)
    {
        delete [] mVertexIndices;
        mVertexIndices = NULL;
    }
}

//-----------------------------------------------------------------------------
// LLPolyMorphTargetInfo()
//-----------------------------------------------------------------------------
LLPolyMorphTargetInfo::LLPolyMorphTargetInfo()
    : mIsClothingMorph(false)
{
}

bool LLPolyMorphTargetInfo::parseXml(LLXmlTreeNode* node)
{
    llassert( node->hasName( "param" ) && node->getChildByName( "param_morph" ) );

    if (!LLViewerVisualParamInfo::parseXml(node))
        return false;

    // Get mixed-case name
    static LLStdStringHandle name_string = LLXmlTree::addAttributeString("name");
    if( !node->getFastAttributeString( name_string, mMorphName ) )
    {
        LL_WARNS() << "Avatar file: <param> is missing name attribute" << LL_ENDL;
        return false;  // Continue, ignoring this tag
    }

    static LLStdStringHandle clothing_morph_string = LLXmlTree::addAttributeString("clothing_morph");
    node->getFastAttributeBOOL(clothing_morph_string, mIsClothingMorph);

    LLXmlTreeNode *paramNode = node->getChildByName("param_morph");

        if (NULL == paramNode)
        {
                LL_WARNS() << "Failed to getChildByName(\"param_morph\")"
                        << LL_ENDL;
                return false;
        }

    for (LLXmlTreeNode* child_node = paramNode->getFirstChild();
         child_node;
         child_node = paramNode->getNextChild())
    {
        static LLStdStringHandle name_string = LLXmlTree::addAttributeString("name");
        if (child_node->hasName("volume_morph"))
        {
            std::string volume_name;
            if (child_node->getFastAttributeString(name_string, volume_name))
            {
                LLVector3 scale;
                static LLStdStringHandle scale_string = LLXmlTree::addAttributeString("scale");
                child_node->getFastAttributeVector3(scale_string, scale);

                LLVector3 pos;
                static LLStdStringHandle pos_string = LLXmlTree::addAttributeString("pos");
                child_node->getFastAttributeVector3(pos_string, pos);

                mVolumeInfoList.push_back(LLPolyVolumeMorphInfo(volume_name,scale,pos));
            }
        }
    }

    return true;
}

//-----------------------------------------------------------------------------
// LLPolyMorphTarget()
//-----------------------------------------------------------------------------
LLPolyMorphTarget::LLPolyMorphTarget(LLPolyMesh *poly_mesh)
    : LLViewerVisualParam(),
    mMorphData(NULL),
    mMesh(poly_mesh),
    mVertMask(NULL),
    mLastSex(SEX_FEMALE),
    mNumMorphMasksPending(0),
    mVolumeMorphs()
{
}

//-----------------------------------------------------------------------------
// LLPolyMorphTarget()
//-----------------------------------------------------------------------------
LLPolyMorphTarget::LLPolyMorphTarget(const LLPolyMorphTarget& pOther)
    : LLViewerVisualParam(pOther),
    mMorphData(pOther.mMorphData),
    mMesh(pOther.mMesh),
    mVertMask(pOther.mVertMask == NULL ? NULL : new LLPolyVertexMask(*pOther.mVertMask)),
    mLastSex(pOther.mLastSex),
    mNumMorphMasksPending(pOther.mNumMorphMasksPending),
    mVolumeMorphs(pOther.mVolumeMorphs)
{
}

//-----------------------------------------------------------------------------
// ~LLPolyMorphTarget()
//-----------------------------------------------------------------------------
LLPolyMorphTarget::~LLPolyMorphTarget()
{
    delete mVertMask;
    mVertMask = NULL;
}

//-----------------------------------------------------------------------------
// setInfo()
//-----------------------------------------------------------------------------
bool LLPolyMorphTarget::setInfo(LLPolyMorphTargetInfo* info)
{
    llassert(mInfo == NULL);
    if (info->mID < 0)
        return false;
    mInfo = info;
    mID = info->mID;
    setWeight(getDefaultWeight());

    LLAvatarAppearance* avatarp = mMesh->getAvatar();
    for (LLPolyVolumeMorphInfo& volume_info : getInfo()->mVolumeInfoList)
    {
        for (S32 i = 0; i < avatarp->mNumCollisionVolumes; i++)
        {
            if (avatarp->mCollisionVolumes[i].getName() == volume_info.mName)
            {
                mVolumeMorphs.push_back(
                    LLPolyVolumeMorph(&avatarp->mCollisionVolumes[i],
                                                          volume_info.mScale,
                                                          volume_info.mPos));
                break;
            }
        }
    }

    std::string morph_param_name = getInfo()->mMorphName;

    mMorphData = mMesh->getMorphData(morph_param_name);
    if (!mMorphData)
    {
        const std::string driven_tag = "_Driven";
        auto pos = morph_param_name.find(driven_tag);
        if (pos != std::string::npos && pos > 0)
        {
            morph_param_name = morph_param_name.substr(0,pos);
            mMorphData = mMesh->getMorphData(morph_param_name);
        }
    }
    if (!mMorphData)
    {
        LL_WARNS() << "No morph target named " << morph_param_name << " found in mesh." << LL_ENDL;
        return false;  // Continue, ignoring this tag
    }
    return true;
}

/*virtual*/ LLViewerVisualParam* LLPolyMorphTarget::cloneParam(LLWearable* wearable) const
{
    return new LLPolyMorphTarget(*this);
}

#if 0 // obsolete
//-----------------------------------------------------------------------------
// parseData()
//-----------------------------------------------------------------------------
bool LLPolyMorphTarget::parseData(LLXmlTreeNode* node)
{
    LLPolyMorphTargetInfo* info = new LLPolyMorphTargetInfo;

    info->parseXml(node);
    if (!setInfo(info))
    {
        delete info;
        return false;
    }
    return true;
}
#endif

//-----------------------------------------------------------------------------
// getVertexDistortion()
//-----------------------------------------------------------------------------
LLVector4a LLPolyMorphTarget::getVertexDistortion(S32 requested_index, LLPolyMesh *mesh)
{
    if (!mMorphData || mMesh != mesh) return LLVector4a::getZero();

    for(U32 index = 0; index < mMorphData->mNumIndices; index++)
    {
        if (mMorphData->mVertexIndices[index] == (U32)requested_index)
        {
            return mMorphData->mCoords[index];
        }
    }

    return LLVector4a::getZero();
}

//-----------------------------------------------------------------------------
// getFirstDistortion()
//-----------------------------------------------------------------------------
const LLVector4a *LLPolyMorphTarget::getFirstDistortion(U32 *index, LLPolyMesh **poly_mesh)
{
    if (!mMorphData) return &LLVector4a::getZero();

    LLVector4a* resultVec;
    mMorphData->mCurrentIndex = 0;
    if (mMorphData->mNumIndices)
    {
        resultVec = &mMorphData->mCoords[mMorphData->mCurrentIndex];
        if (index != NULL)
        {
            *index = mMorphData->mVertexIndices[mMorphData->mCurrentIndex];
        }
        if (poly_mesh != NULL)
        {
            *poly_mesh = mMesh;
        }

        return resultVec;
    }
    return NULL;
}

//-----------------------------------------------------------------------------
// getNextDistortion()
//-----------------------------------------------------------------------------
const LLVector4a *LLPolyMorphTarget::getNextDistortion(U32 *index, LLPolyMesh **poly_mesh)
{
    if (!mMorphData) return &LLVector4a::getZero();

    LLVector4a* resultVec;
    mMorphData->mCurrentIndex++;
    if (mMorphData->mCurrentIndex < mMorphData->mNumIndices)
    {
        resultVec = &mMorphData->mCoords[mMorphData->mCurrentIndex];
        if (index != NULL)
        {
            *index = mMorphData->mVertexIndices[mMorphData->mCurrentIndex];
        }
        if (poly_mesh != NULL)
        {
            *poly_mesh = mMesh;
        }
        return resultVec;
    }
    return NULL;
}

//-----------------------------------------------------------------------------
// getTotalDistortion()
//-----------------------------------------------------------------------------
F32 LLPolyMorphTarget::getTotalDistortion()
{
    if (mMorphData)
    {
        return mMorphData->mTotalDistortion;
    }
    else
    {
        return 0.f;
    }
}

//-----------------------------------------------------------------------------
// getAvgDistortion()
//-----------------------------------------------------------------------------
const LLVector4a& LLPolyMorphTarget::getAvgDistortion()
{
    if (mMorphData)
    {
        return mMorphData->mAvgDistortion;
    }
    else
    {
        return LLVector4a::getZero();
    }
}

//-----------------------------------------------------------------------------
// getMaxDistortion()
//-----------------------------------------------------------------------------
F32 LLPolyMorphTarget::getMaxDistortion()
{
    if (mMorphData)
    {
        return mMorphData->mMaxDistortion;
    }
    else
    {
        return 0.f;
    }
}

//-----------------------------------------------------------------------------
// apply()
//-----------------------------------------------------------------------------
void LLPolyMorphTarget::apply( ESex avatar_sex )
{
    if (!mMorphData || mNumMorphMasksPending > 0)
    {
        return;
    }

    LL_PROFILE_ZONE_SCOPED;

    mLastSex = avatar_sex;

    // Check for NaN condition (NaN is detected if a variable doesn't equal itself.
    if (mCurWeight != mCurWeight)
    {
        mCurWeight = 0.0;
    }
    if (mLastWeight != mLastWeight)
    {
        mLastWeight = mCurWeight+.001f;
    }

    // perform differential update of morph
    F32 delta_weight = ( getSex() & avatar_sex ) ? (mCurWeight - mLastWeight) : (getDefaultWeight() - mLastWeight);
    // store last weight
    mLastWeight += delta_weight;

    if (delta_weight != 0.f)
    {
        llassert(!mMesh->isLOD());
        LLVector4a *coords = mMesh->getWritableCoords();

        LLVector4a *scaled_normals = mMesh->getScaledNormals();
        LLVector4a *normals = mMesh->getWritableNormals();

        LLVector4a *scaled_binormals = mMesh->getScaledBinormals();
        LLVector4a *binormals = mMesh->getWritableBinormals();

        LLVector4a *clothing_weights = mMesh->getWritableClothingWeights();
        LLVector2 *tex_coords = mMesh->getWritableTexCoords();

        F32 *maskWeightArray = (mVertMask) ? mVertMask->getMorphMaskWeights() : NULL;

        for(U32 vert_index_morph = 0; vert_index_morph < mMorphData->mNumIndices; vert_index_morph++)
        {
            S32 vert_index_mesh = mMorphData->mVertexIndices[vert_index_morph];

            F32 maskWeight = 1.f;
            if (maskWeightArray)
            {
                maskWeight = maskWeightArray[vert_index_morph];
            }


            LLVector4a pos = mMorphData->mCoords[vert_index_morph];
            pos.mul(delta_weight*maskWeight);
            coords[vert_index_mesh].add(pos);

            if (getInfo()->mIsClothingMorph && clothing_weights)
            {
                LLVector4a clothing_offset = mMorphData->mCoords[vert_index_morph];
                clothing_offset.mul(delta_weight * maskWeight);
                LLVector4a* clothing_weight = &clothing_weights[vert_index_mesh];
                clothing_weight->add(clothing_offset);
                clothing_weight->getF32ptr()[VW] = maskWeight;
            }

            // calculate new normals based on half angles
            LLVector4a norm = mMorphData->mNormals[vert_index_morph];
            norm.mul(delta_weight*maskWeight*NORMAL_SOFTEN_FACTOR);
            scaled_normals[vert_index_mesh].add(norm);
            norm = scaled_normals[vert_index_mesh];

            // guard against degenerate input data before we create NaNs below!
            //
            norm.normalize3fast();
            normals[vert_index_mesh] = norm;

            // calculate new binormals
            LLVector4a binorm = mMorphData->mBinormals[vert_index_morph];

            // guard against degenerate input data before we create NaNs below!
            //
            if (!binorm.isFinite3() || (binorm.dot3(binorm).getF32() <= F_APPROXIMATELY_ZERO))
            {
                binorm.set(1,0,0,1);
            }

            binorm.mul(delta_weight*maskWeight*NORMAL_SOFTEN_FACTOR);
            scaled_binormals[vert_index_mesh].add(binorm);
            LLVector4a tangent;
            tangent.setCross3(scaled_binormals[vert_index_mesh], norm);
            LLVector4a& normalized_binormal = binormals[vert_index_mesh];

            normalized_binormal.setCross3(norm, tangent);
            normalized_binormal.normalize3fast();

            tex_coords[vert_index_mesh] += mMorphData->mTexCoords[vert_index_morph] * delta_weight * maskWeight;
        }

        // now apply volume changes
        for(LLPolyVolumeMorph& volume_morph : mVolumeMorphs)
        {
            LLVector3 scale_delta = volume_morph.mScale * delta_weight;
            LLVector3 pos_delta = volume_morph.mPos * delta_weight;

            volume_morph.mVolume->setScale(volume_morph.mVolume->getScale() + scale_delta);
            // SL-315
            volume_morph.mVolume->setPosition(volume_morph.mVolume->getPosition() + pos_delta);
        }
    }

    if (mNext)
    {
        mNext->apply(avatar_sex);
    }
}

//-----------------------------------------------------------------------------
// applyMask()
//-----------------------------------------------------------------------------
void    LLPolyMorphTarget::applyMask(const U8 *maskTextureData, S32 width, S32 height, S32 num_components, bool invert)
{
    LLVector4a *clothing_weights = getInfo()->mIsClothingMorph ? mMesh->getWritableClothingWeights() : NULL;

    if (!mVertMask)
    {
        mVertMask = new LLPolyVertexMask(mMorphData);
        mNumMorphMasksPending--;
    }
    else
    {
        // remove effect of previous mask
        F32 *maskWeights = (mVertMask) ? mVertMask->getMorphMaskWeights() : NULL;

        if (maskWeights)
        {
            LLVector4a *coords = mMesh->getWritableCoords();
            LLVector4a *scaled_normals = mMesh->getScaledNormals();
            LLVector4a *scaled_binormals = mMesh->getScaledBinormals();
            LLVector2 *tex_coords = mMesh->getWritableTexCoords();

            LLVector4Logical clothing_mask;
            clothing_mask.clear();
            clothing_mask.setElement<0>();
            clothing_mask.setElement<1>();
            clothing_mask.setElement<2>();


            for(U32 vert = 0; vert < mMorphData->mNumIndices; vert++)
            {
                F32 lastMaskWeight = mLastWeight * maskWeights[vert];
                S32 out_vert = mMorphData->mVertexIndices[vert];

                // remove effect of existing masked morph
                LLVector4a t;
                t = mMorphData->mCoords[vert];
                t.mul(lastMaskWeight);
                coords[out_vert].sub(t);

                t = mMorphData->mNormals[vert];
                t.mul(lastMaskWeight*NORMAL_SOFTEN_FACTOR);
                scaled_normals[out_vert].sub(t);

                t = mMorphData->mBinormals[vert];
                t.mul(lastMaskWeight*NORMAL_SOFTEN_FACTOR);
                scaled_binormals[out_vert].sub(t);

                tex_coords[out_vert] -= mMorphData->mTexCoords[vert] * lastMaskWeight;

                if (clothing_weights)
                {
                    LLVector4a clothing_offset = mMorphData->mCoords[vert];
                    clothing_offset.mul(lastMaskWeight);
                    LLVector4a* clothing_weight = &clothing_weights[out_vert];
                    LLVector4a t;
                    t.setSub(*clothing_weight, clothing_offset);
                    clothing_weight->setSelectWithMask(clothing_mask, t, *clothing_weight);
                }
            }
        }
    }

    // set last weight to 0, since we've removed the effect of this morph
    mLastWeight = 0.f;

    mVertMask->generateMask(maskTextureData, width, height, num_components, invert, clothing_weights);

    apply(mLastSex);
}

void LLPolyMorphTarget::applyVolumeChanges(F32 delta_weight)
{
    // now apply volume changes
    for(LLPolyVolumeMorph& volume_morph : mVolumeMorphs)
    {
        LLVector3 scale_delta = volume_morph.mScale * delta_weight;
        LLVector3 pos_delta = volume_morph.mPos * delta_weight;

        volume_morph.mVolume->setScale(volume_morph.mVolume->getScale() + scale_delta);
        // SL-315
        volume_morph.mVolume->setPosition(volume_morph.mVolume->getPosition() + pos_delta);
    }
}

//-----------------------------------------------------------------------------
// LLPolyVertexMask()
//-----------------------------------------------------------------------------
LLPolyVertexMask::LLPolyVertexMask(LLPolyMorphData* morph_data)
    : mWeights(new F32[morph_data->mNumIndices]),
    mMorphData(morph_data),
    mWeightsGenerated(false)
{
    llassert(mMorphData != NULL);
    llassert(mMorphData->mNumIndices > 0);
}

//-----------------------------------------------------------------------------
// LLPolyVertexMask()
//-----------------------------------------------------------------------------
LLPolyVertexMask::LLPolyVertexMask(const LLPolyVertexMask& pOther)
    : mWeights(new F32[pOther.mMorphData->mNumIndices]),
    mMorphData(pOther.mMorphData),
    mWeightsGenerated(pOther.mWeightsGenerated)
{
    llassert(mMorphData != NULL);
    llassert(mMorphData->mNumIndices > 0);
    memcpy(mWeights, pOther.mWeights, sizeof(F32) * mMorphData->mNumIndices);
}

//-----------------------------------------------------------------------------
// ~LLPolyVertexMask()
//-----------------------------------------------------------------------------
LLPolyVertexMask::~LLPolyVertexMask()
{
    delete [] mWeights;
    mWeights = NULL;
}

//-----------------------------------------------------------------------------
// generateMask()
//-----------------------------------------------------------------------------
void LLPolyVertexMask::generateMask(const U8 *maskTextureData, S32 width, S32 height, S32 num_components, bool invert, LLVector4a *clothing_weights)
{
// RN debug output that uses Image Debugger (http://www.cs.unc.edu/~baxter/projects/imdebug/)
//  bool debugImg = false;
//  if (debugImg)
//  {
//      if (invert)
//      {
//          imdebug("lum rbga=rgba b=8 w=%d h=%d *-1 %p", width, height, maskTextureData);
//      }
//      else
//      {
//          imdebug("lum rbga=rgba b=8 w=%d h=%d %p", width, height, maskTextureData);
//      }
//  }
    for (U32 index = 0; index < mMorphData->mNumIndices; index++)
    {
        S32 vertIndex = mMorphData->mVertexIndices[index];
        const S32 *sharedVertIndex = mMorphData->mMesh->getSharedVert(vertIndex);
        LLVector2 uvCoords;

        if (sharedVertIndex)
        {
            uvCoords = mMorphData->mMesh->getUVs(*sharedVertIndex);
        }
        else
        {
            uvCoords = mMorphData->mMesh->getUVs(vertIndex);
        }
        U32 s = llclamp((U32)(uvCoords.mV[VX] * (F32)(width - 1)), (U32)0, (U32)width - 1);
        U32 t = llclamp((U32)(uvCoords.mV[VY] * (F32)(height - 1)), (U32)0, (U32)height - 1);

        mWeights[index] = maskTextureData ? ((F32) maskTextureData[((t * width + s) * num_components) + (num_components - 1)]) / 255.f : 0.0f;

        if (invert)
        {
            mWeights[index] = 1.f - mWeights[index];
        }

        // now apply step function
        // mWeights[index] = mWeights[index] > 0.95f ? 1.f : 0.f;

        if (clothing_weights)
        {
            clothing_weights[vertIndex].getF32ptr()[VW] = mWeights[index];
        }
    }
    mWeightsGenerated = true;
}

//-----------------------------------------------------------------------------
// getMaskForMorphIndex()
//-----------------------------------------------------------------------------
F32* LLPolyVertexMask::getMorphMaskWeights()
{
    if (!mWeightsGenerated)
    {
        return NULL;
    }

    return mWeights;
}