Skip to content
Snippets Groups Projects
llmodelpreview.cpp 141 KiB
Newer Older
    return 0;
}

void LLModelPreview::addEmptyFace(LLModel* pTarget)
{
    U32 type_mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0;

    LLPointer<LLVertexBuffer> buff = new LLVertexBuffer(type_mask, 0);

    buff->allocateBuffer(1, 3, true);
    memset((U8*)buff->getMappedData(), 0, buff->getSize());
    memset((U8*)buff->getIndicesPointer(), 0, buff->getIndicesSize());

    buff->validateRange(0, buff->getNumVerts() - 1, buff->getNumIndices(), 0);

    LLStrider<LLVector3> pos;
    LLStrider<LLVector3> norm;
    LLStrider<LLVector2> tc;
    LLStrider<U16> index;

    buff->getVertexStrider(pos);

    if (type_mask & LLVertexBuffer::MAP_NORMAL)
    {
        buff->getNormalStrider(norm);
    }
    if (type_mask & LLVertexBuffer::MAP_TEXCOORD0)
    {
        buff->getTexCoord0Strider(tc);
    }

    buff->getIndexStrider(index);

    //resize face array
    int faceCnt = pTarget->getNumVolumeFaces();
    pTarget->setNumVolumeFaces(faceCnt + 1);
    pTarget->setVolumeFaceData(faceCnt + 1, pos, norm, tc, index, buff->getNumVerts(), buff->getNumIndices());

}

//-----------------------------------------------------------------------------
// render()
//-----------------------------------------------------------------------------
// Todo: we shouldn't be setting all those UI elements on render.
// Note: Render happens each frame with skinned avatars
BOOL LLModelPreview::render()
{
    assert_main_thread();

    LLMutexLock lock(this);
    mNeedsUpdate = FALSE;

    bool edges = mViewOption["show_edges"];
    bool joint_overrides = mViewOption["show_joint_overrides"];
    bool joint_positions = mViewOption["show_joint_positions"];
    bool skin_weight = mViewOption["show_skin_weight"];
    bool textures = mViewOption["show_textures"];
    bool physics = mViewOption["show_physics"];

    S32 width = getWidth();
    S32 height = getHeight();

    LLGLSUIDefault def; // GL_BLEND, GL_ALPHA_TEST, GL_CULL_FACE, depth test
    LLGLDisable no_blend(GL_BLEND);
    LLGLDepthTest depth(GL_FALSE); // SL-12781 disable z-buffer to render background color
    LLGLDisable fog(GL_FOG);

    {
        //clear background to grey
        gGL.matrixMode(LLRender::MM_PROJECTION);
        gGL.pushMatrix();
        gGL.loadIdentity();
        gGL.ortho(0.0f, width, 0.0f, height, -1.0f, 1.0f);

        gGL.matrixMode(LLRender::MM_MODELVIEW);
        gGL.pushMatrix();
        gGL.loadIdentity();

        gGL.color4fv(PREVIEW_CANVAS_COL.mV);
        gl_rect_2d_simple(width, height);

        gGL.matrixMode(LLRender::MM_PROJECTION);
        gGL.popMatrix();

        gGL.matrixMode(LLRender::MM_MODELVIEW);
        gGL.popMatrix();
    }

    LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;

    bool has_skin_weights = false;
    bool upload_skin = mFMP->childGetValue("upload_skin").asBoolean();
    bool upload_joints = mFMP->childGetValue("upload_joints").asBoolean();

    if (upload_joints != mLastJointUpdate)
    {
        mLastJointUpdate = upload_joints;
    }

    for (LLModelLoader::scene::iterator iter = mScene[mPreviewLOD].begin(); iter != mScene[mPreviewLOD].end(); ++iter)
    {
        for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end(); ++model_iter)
        {
            LLModelInstance& instance = *model_iter;
            LLModel* model = instance.mModel;
            model->mPelvisOffset = mPelvisZOffset;
            if (!model->mSkinWeights.empty())
            {
                has_skin_weights = true;
            }
        }
    }

    if (has_skin_weights && lodsReady())
    { //model has skin weights, enable view options for skin weights and joint positions
        U32 flags = getLegacyRigFlags();
        if (fmp)
        {
            if (flags == LEGACY_RIG_OK)
            {
                if (mFirstSkinUpdate)
                {
                    // auto enable weight upload if weights are present
                    // (note: all these UI updates need to be somewhere that is not render)
                    fmp->childSetValue("upload_skin", true);
                    mFirstSkinUpdate = false;
                    upload_skin = true;
                    skin_weight = true;
                    mViewOption["show_skin_weight"] = true;
                fmp->enableViewOption("show_skin_weight");
                fmp->setViewOptionEnabled("show_joint_overrides", skin_weight);
                fmp->setViewOptionEnabled("show_joint_positions", skin_weight);
                mFMP->childEnable("upload_skin");
                mFMP->childSetValue("show_skin_weight", skin_weight);
            }
            else if ((flags & LEGACY_RIG_FLAG_TOO_MANY_JOINTS) > 0)
            {
                mFMP->childSetVisible("skin_too_many_joints", true);
            }
            else if ((flags & LEGACY_RIG_FLAG_UNKNOWN_JOINT) > 0)
            {
                mFMP->childSetVisible("skin_unknown_joint", true);
            }
        }
    }
    else
    {
        mFMP->childDisable("upload_skin");
        if (fmp)
        {
            mViewOption["show_skin_weight"] = false;
            fmp->disableViewOption("show_skin_weight");
            fmp->disableViewOption("show_joint_overrides");
            fmp->disableViewOption("show_joint_positions");

            skin_weight = false;
            mFMP->childSetValue("show_skin_weight", false);
            fmp->setViewOptionEnabled("show_skin_weight", skin_weight);
        }
    }

    if (upload_skin && !has_skin_weights)
    { //can't upload skin weights if model has no skin weights
        mFMP->childSetValue("upload_skin", false);
        upload_skin = false;
    }

    if (!upload_skin && upload_joints)
    { //can't upload joints if not uploading skin weights
        mFMP->childSetValue("upload_joints", false);
        upload_joints = false;
    }

            // will populate list of joints
            fmp->updateAvatarTab(upload_joints);
        else
        {
            fmp->clearAvatarTab();
        }
    }

    if (upload_skin && upload_joints)
    {
        mFMP->childEnable("lock_scale_if_joint_position");
    }
    else
    {
        mFMP->childDisable("lock_scale_if_joint_position");
        mFMP->childSetValue("lock_scale_if_joint_position", false);
    }

    //Only enable joint offsets if it passed the earlier critiquing
    if (isRigValidForJointPositionUpload())
    {
        mFMP->childSetEnabled("upload_joints", upload_skin);
    }

    F32 explode = mFMP->childGetValue("physics_explode").asReal();

    LLGLDepthTest gls_depth(GL_TRUE); // SL-12781 re-enable z-buffer for 3D model preview

    LLRect preview_rect;

    preview_rect = mFMP->getChildView("preview_panel")->getRect();

    F32 aspect = (F32)preview_rect.getWidth() / preview_rect.getHeight();

    LLViewerCamera::getInstance()->setAspect(aspect);

    LLViewerCamera::getInstance()->setView(LLViewerCamera::getInstance()->getDefaultFOV() / mCameraZoom);

    LLVector3 offset = mCameraOffset;
    LLVector3 target_pos = mPreviewTarget + offset;

    F32 z_near = 0.001f;
    F32 z_far = mCameraDistance*10.0f + mPreviewScale.magVec() + mCameraOffset.magVec();

    if (skin_weight)
    {
        target_pos = getPreviewAvatar()->getPositionAgent() + offset;
        z_near = 0.01f;
        z_far = 1024.f;

        //render avatar previews every frame
        refresh();
    }


    gGL.loadIdentity();
    gPipeline.enableLightsPreview();

    LLQuaternion camera_rot = LLQuaternion(mCameraPitch, LLVector3::y_axis) *
        LLQuaternion(mCameraYaw, LLVector3::z_axis);

    LLQuaternion av_rot = camera_rot;
    F32 camera_distance = skin_weight ? SKIN_WEIGHT_CAMERA_DISTANCE : mCameraDistance;
    LLViewerCamera::getInstance()->setOriginAndLookAt(
        target_pos + ((LLVector3(camera_distance, 0.f, 0.f) + offset) * av_rot),		// camera
        LLVector3::z_axis,																	// up
        target_pos);											// point of interest


    z_near = llclamp(z_far * 0.001f, 0.001f, 0.1f);

    LLViewerCamera::getInstance()->setPerspective(FALSE, mOrigin.mX, mOrigin.mY, width, height, FALSE, z_near, z_far);

    stop_glerror();

    gGL.pushMatrix();
    gGL.color4fv(PREVIEW_EDGE_COL.mV);

    const U32 type_mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0;

    LLGLEnable normalize(GL_NORMALIZE);

    if (!mBaseModel.empty() && mVertexBuffer[5].empty())
    {
        genBuffers(-1, skin_weight);
        //genBuffers(3);
    }

    if (!mModel[mPreviewLOD].empty())
    {
        mFMP->childEnable("reset_btn");

        bool regen = mVertexBuffer[mPreviewLOD].empty();
        if (!regen)
        {
            const std::vector<LLPointer<LLVertexBuffer> >& vb_vec = mVertexBuffer[mPreviewLOD].begin()->second;
            if (!vb_vec.empty())
            {
                const LLVertexBuffer* buff = vb_vec[0];
                regen = buff->hasDataType(LLVertexBuffer::TYPE_WEIGHT4) != skin_weight;
            }
            else
            {
                LL_INFOS() << "Vertex Buffer[" << mPreviewLOD << "]" << " is EMPTY!!!" << LL_ENDL;
                regen = TRUE;
            }
        }

        if (regen)
        {
            genBuffers(mPreviewLOD, skin_weight);
        }

        if (physics && mVertexBuffer[LLModel::LOD_PHYSICS].empty())
        {
            genBuffers(LLModel::LOD_PHYSICS, false);
        }

        if (!skin_weight)
        {
            for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter)
            {
                LLModelInstance& instance = *iter;

                LLModel* model = instance.mLOD[mPreviewLOD];

                if (!model)
                {
                    continue;
                }

                gGL.pushMatrix();
                LLMatrix4 mat = instance.mTransform;

                gGL.multMatrix((GLfloat*)mat.mMatrix);


                U32 num_models = mVertexBuffer[mPreviewLOD][model].size();
                for (U32 i = 0; i < num_models; ++i)
                {
                    LLVertexBuffer* buffer = mVertexBuffer[mPreviewLOD][model][i];

                    buffer->setBuffer(type_mask & buffer->getTypeMask());

                    if (textures)
                    {
                        int materialCnt = instance.mModel->mMaterialList.size();
                        if (i < materialCnt)
                        {
                            const std::string& binding = instance.mModel->mMaterialList[i];
                            const LLImportMaterial& material = instance.mMaterial[binding];

                            gGL.diffuseColor4fv(material.mDiffuseColor.mV);

                            // Find the tex for this material, bind it, and add it to our set
                            //
                            LLViewerFetchedTexture* tex = bindMaterialDiffuseTexture(material);
                            if (tex)
                            {
                                mTextureSet.insert(tex);
                            }
                        }
                    }
                    else
                    {
                        gGL.diffuseColor4fv(PREVIEW_BASE_COL.mV);
                    }

                    buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts() - 1, buffer->getNumIndices(), 0);
                    gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
                    gGL.diffuseColor4fv(PREVIEW_EDGE_COL.mV);
                        glLineWidth(PREVIEW_EDGE_WIDTH);
                        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
                        buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts() - 1, buffer->getNumIndices(), 0);
                        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
                        glLineWidth(1.f);
                    }
                }
                gGL.popMatrix();
            }

            if (physics)
            {
                glClear(GL_DEPTH_BUFFER_BIT);

                for (U32 pass = 0; pass < 2; pass++)
                {
                    if (pass == 0)
                    { //depth only pass
                        gGL.setColorMask(false, false);
                    }
                    else
                    {
                        gGL.setColorMask(true, true);
                    }

                    //enable alpha blending on second pass but not first pass
                    LLGLState blend(GL_BLEND, pass);

                    gGL.blendFunc(LLRender::BF_SOURCE_ALPHA, LLRender::BF_ONE_MINUS_SOURCE_ALPHA);

                    for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter)
                    {
                        LLModelInstance& instance = *iter;

                        LLModel* model = instance.mLOD[LLModel::LOD_PHYSICS];

                        if (!model)
                        {
                            continue;
                        }

                        gGL.pushMatrix();
                        LLMatrix4 mat = instance.mTransform;

                        gGL.multMatrix((GLfloat*)mat.mMatrix);


                        bool render_mesh = true;
                        LLPhysicsDecomp* decomp = gMeshRepo.mDecompThread;
                        if (decomp)
                        {
                            LLMutexLock(decomp->mMutex);

                            LLModel::Decomposition& physics = model->mPhysics;

                            if (!physics.mHull.empty())
                            {
                                render_mesh = false;

                                if (physics.mMesh.empty())
                                { //build vertex buffer for physics mesh
                                    gMeshRepo.buildPhysicsMesh(physics);
                                }

                                if (!physics.mMesh.empty())
                                { //render hull instead of mesh
                                    // SL-16993 physics.mMesh[i].mNormals were being used to light the exploded
                                    // analyzed physics shape but the drawArrays() interface changed
                                    //  causing normal data <0,0,0> to be passed to the shader.
                                    // The Phyics Preview shader uses plain vertex coloring so the physics hull is full lit.
                                    // We could also use interface/ui shaders.
                                    gObjectPreviewProgram.unbind();
                                    gPhysicsPreviewProgram.bind();

                                    for (U32 i = 0; i < physics.mMesh.size(); ++i)
                                    {
                                        if (explode > 0.f)
                                        {
                                            gGL.pushMatrix();

                                            LLVector3 offset = model->mHullCenter[i] - model->mCenterOfHullCenters;
                                            offset *= explode;

                                            gGL.translatef(offset.mV[0], offset.mV[1], offset.mV[2]);
                                        }

                                        static std::vector<LLColor4U> hull_colors;

                                        if (i + 1 >= hull_colors.size())
                                        {
                                            hull_colors.push_back(LLColor4U(rand() % 128 + 127, rand() % 128 + 127, rand() % 128 + 127, 128));
                                        }

                                        gGL.diffuseColor4ubv(hull_colors[i].mV);
                                        LLVertexBuffer::drawArrays(LLRender::TRIANGLES, physics.mMesh[i].mPositions);

                                    gPhysicsPreviewProgram.unbind();
                                    gObjectPreviewProgram.bind();
                                }
                            }
                        }

                        if (render_mesh)
                        {
                            U32 num_models = mVertexBuffer[LLModel::LOD_PHYSICS][model].size();
                            if (pass > 0){
                                for (U32 i = 0; i < num_models; ++i)
                                {
                                    LLVertexBuffer* buffer = mVertexBuffer[LLModel::LOD_PHYSICS][model][i];

                                    gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
                                    gGL.diffuseColor4fv(PREVIEW_PSYH_FILL_COL.mV);

                                    buffer->setBuffer(type_mask & buffer->getTypeMask());
                                    buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts() - 1, buffer->getNumIndices(), 0);

                                    gGL.diffuseColor4fv(PREVIEW_PSYH_EDGE_COL.mV);
                                    glLineWidth(PREVIEW_PSYH_EDGE_WIDTH);
                                    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
                                    buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts() - 1, buffer->getNumIndices(), 0);

                                    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
                                    glLineWidth(1.f);
                                }
                            }
                        }
                        gGL.popMatrix();
                    }

                    // only do this if mDegenerate was set in the preceding mesh checks [Check this if the ordering ever breaks]
                        glLineWidth(PREVIEW_DEG_EDGE_WIDTH);
                        glPointSize(PREVIEW_DEG_POINT_SIZE);
                        gPipeline.enableLightsFullbright();
                        //show degenerate triangles
                        LLGLDepthTest depth(GL_TRUE, GL_TRUE, GL_ALWAYS);
                        LLGLDisable cull(GL_CULL_FACE);
                        gGL.diffuseColor4f(1.f, 0.f, 0.f, 1.f);
                        const LLVector4a scale(0.5f);

                        for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter)
                        {
                            LLModelInstance& instance = *iter;

                            LLModel* model = instance.mLOD[LLModel::LOD_PHYSICS];

                            if (!model)
                            {
                                continue;
                            }

                            gGL.pushMatrix();
                            LLMatrix4 mat = instance.mTransform;

                            gGL.multMatrix((GLfloat*)mat.mMatrix);


                            LLPhysicsDecomp* decomp = gMeshRepo.mDecompThread;
                            if (decomp)
                            {
                                LLMutexLock(decomp->mMutex);

                                LLModel::Decomposition& physics = model->mPhysics;

                                if (physics.mHull.empty())
                                {
                                    U32 num_models = mVertexBuffer[LLModel::LOD_PHYSICS][model].size();
                                    for (U32 v = 0; v < num_models; ++v)
                                    {
                                        LLVertexBuffer* buffer = mVertexBuffer[LLModel::LOD_PHYSICS][model][v];

                                        buffer->setBuffer(type_mask & buffer->getTypeMask());

                                        LLStrider<LLVector3> pos_strider;
                                        buffer->getVertexStrider(pos_strider, 0);
                                        LLVector4a* pos = (LLVector4a*)pos_strider.get();

                                        LLStrider<U16> idx;
                                        buffer->getIndexStrider(idx, 0);

                                        for (U32 i = 0; i < buffer->getNumIndices(); i += 3)
                                            LLVector4a v1; v1.setMul(pos[*idx++], scale);
                                            LLVector4a v2; v2.setMul(pos[*idx++], scale);
                                            LLVector4a v3; v3.setMul(pos[*idx++], scale);
                                                buffer->draw(LLRender::LINE_LOOP, 3, i);
                                                buffer->draw(LLRender::POINTS, 3, i);
                                    }
                                }
                            }

                            gGL.popMatrix();
                        }
                        glLineWidth(1.f);
                        glPointSize(1.f);
                        gPipeline.enableLightsPreview();
                        gGL.setSceneBlendType(LLRender::BT_ALPHA);
                    }
                }
            }
        }
        else
        {
            target_pos = getPreviewAvatar()->getPositionAgent();
            getPreviewAvatar()->clearAttachmentOverrides(); // removes pelvis fixup
            LLUUID fake_mesh_id;
            fake_mesh_id.generate();
            getPreviewAvatar()->addPelvisFixup(mPelvisZOffset, fake_mesh_id);
            bool pelvis_recalc = false;

            LLViewerCamera::getInstance()->setOriginAndLookAt(
                target_pos + ((LLVector3(camera_distance, 0.f, 0.f) + offset) * av_rot),		// camera
                LLVector3::z_axis,																	// up
                target_pos);											// point of interest

            for (LLModelLoader::scene::iterator iter = mScene[mPreviewLOD].begin(); iter != mScene[mPreviewLOD].end(); ++iter)
            {
                for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end(); ++model_iter)
                {
                    LLModelInstance& instance = *model_iter;
                    LLModel* model = instance.mModel;

                    if (!model->mSkinWeights.empty())
                    {
                        const LLMeshSkinInfo *skin = &model->mSkinInfo;
                        LLSkinningUtil::initJointNums(&model->mSkinInfo, getPreviewAvatar());// inits skin->mJointNums if nessesary
                        U32 joint_count = LLSkinningUtil::getMeshJointCount(skin);
                        U32 bind_count = skin->mAlternateBindMatrix.size();
                        if (joint_overrides
                            && bind_count > 0
                            && joint_count == bind_count)
                        {
                            // mesh_id is used to determine which mesh gets to
                            // set the joint offset, in the event of a conflict. Since
                            // we don't know the mesh id yet, we can't guarantee that
                            // joint offsets will be applied with the same priority as
                            // in the uploaded model. If the file contains multiple
                            // meshes with conflicting joint offsets, preview may be
                            // incorrect.
                            LLUUID fake_mesh_id;
                            fake_mesh_id.generate();
                            for (U32 j = 0; j < joint_count; ++j)
                            {
                                LLJoint *joint = getPreviewAvatar()->getJoint(skin->mJointNums[j]);
                                if (joint)
                                {
                                    const LLVector3& jointPos = LLVector3(skin->mAlternateBindMatrix[j].getTranslation());
                                    if (joint->aboveJointPosThreshold(jointPos))
                                    {
                                        bool override_changed;
                                        joint->addAttachmentPosOverride(jointPos, fake_mesh_id, "model", override_changed);

                                        if (override_changed)
                                        {
                                            //If joint is a pelvis then handle old/new pelvis to foot values
                                            if (joint->getName() == "mPelvis")// or skin->mJointNames[j]
                                            {
                                                pelvis_recalc = true;
                                            }
                                        }
                                        if (skin->mLockScaleIfJointPosition)
                                        {
                                            // Note that unlike positions, there's no threshold check here,
                                            // just a lock at the default value.
                                            joint->addAttachmentScaleOverride(joint->getDefaultScale(), fake_mesh_id, "model");
                                        }
                                    }
                                }
                            }
                        }

                        for (U32 i = 0, e = mVertexBuffer[mPreviewLOD][model].size(); i < e; ++i)
                        {
                            LLVertexBuffer* buffer = mVertexBuffer[mPreviewLOD][model][i];

                            const LLVolumeFace& face = model->getVolumeFace(i);

                            LLStrider<LLVector3> position;
                            buffer->getVertexStrider(position);

                            LLStrider<LLVector4> weight;
                            buffer->getWeight4Strider(weight);

                            //quick 'n dirty software vertex skinning

                            //build matrix palette

                            LLMatrix4a mat[LL_MAX_JOINTS_PER_MESH_OBJECT];
                            LLSkinningUtil::initSkinningMatrixPalette(mat, joint_count,
                            const LLMatrix4a& bind_shape_matrix = skin->mBindShapeMatrix;
                            U32 max_joints = LLSkinningUtil::getMaxJointCount();
                            for (U32 j = 0; j < buffer->getNumVerts(); ++j)
                            {
                                LLMatrix4a final_mat;
                                F32 *wptr = weight[j].mV;
                                LLSkinningUtil::getPerVertexSkinMatrix(wptr, mat, true, final_mat, max_joints);

                                //VECTORIZE THIS
                                LLVector4a& v = face.mPositions[j];

                                LLVector4a t;
                                LLVector4a dst;
                                bind_shape_matrix.affineTransform(v, t);
                                final_mat.affineTransform(t, dst);

                                position[j][0] = dst[0];
                                position[j][1] = dst[1];
                                position[j][2] = dst[2];
                            }

                            llassert(model->mMaterialList.size() > i);
                            const std::string& binding = instance.mModel->mMaterialList[i];
                            const LLImportMaterial& material = instance.mMaterial[binding];

                            buffer->setBuffer(type_mask & buffer->getTypeMask());
                            gGL.diffuseColor4fv(material.mDiffuseColor.mV);
                            gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);

                            // Find the tex for this material, bind it, and add it to our set
                            //
                            LLViewerFetchedTexture* tex = bindMaterialDiffuseTexture(material);
                            if (tex)
                            {
                                mTextureSet.insert(tex);
                            }

                            buffer->draw(LLRender::TRIANGLES, buffer->getNumIndices(), 0);

                            if (edges)
                            {
                                gGL.diffuseColor4fv(PREVIEW_EDGE_COL.mV);
                                glLineWidth(PREVIEW_EDGE_WIDTH);
                                glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
                                buffer->draw(LLRender::TRIANGLES, buffer->getNumIndices(), 0);
                                glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
                                glLineWidth(1.f);
                            }
                        }
                    }
                }
            }

            if (joint_positions)
            {
                LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr;
                if (shader)
                {
                    gDebugProgram.bind();
                }
                getPreviewAvatar()->renderCollisionVolumes();
                if (fmp->mTabContainer->getCurrentPanelIndex() == fmp->mAvatarTabIndex)
                {
                    getPreviewAvatar()->renderBones(fmp->mSelectedJointName);
                }
                else
                {
                    getPreviewAvatar()->renderBones();
                }
                renderGroundPlane(mPelvisZOffset);
                if (shader)
                {
                    shader->bind();
                }
            }

            if (pelvis_recalc)
            {
                // size/scale recalculation
                getPreviewAvatar()->postPelvisSetRecalc();
            }
        }
    }

void LLModelPreview::renderGroundPlane(float z_offset)
{   // Not necesarilly general - beware - but it seems to meet the needs of LLModelPreview::render

	gGL.diffuseColor3f( 1.0f, 0.0f, 1.0f );

	gGL.begin(LLRender::LINES);
	gGL.vertex3fv(mGroundPlane[0].mV);
	gGL.vertex3fv(mGroundPlane[1].mV);
	gGL.vertex3fv(mGroundPlane[1].mV);
	gGL.vertex3fv(mGroundPlane[2].mV);
	gGL.vertex3fv(mGroundPlane[2].mV);
	gGL.vertex3fv(mGroundPlane[3].mV);
	gGL.vertex3fv(mGroundPlane[3].mV);
	gGL.vertex3fv(mGroundPlane[0].mV);
//-----------------------------------------------------------------------------
// refresh()
//-----------------------------------------------------------------------------
void LLModelPreview::refresh()
{
    mNeedsUpdate = TRUE;
}

//-----------------------------------------------------------------------------
// rotate()
//-----------------------------------------------------------------------------
void LLModelPreview::rotate(F32 yaw_radians, F32 pitch_radians)
{
    mCameraYaw = mCameraYaw + yaw_radians;

    mCameraPitch = llclamp(mCameraPitch + pitch_radians, F_PI_BY_TWO * -0.8f, F_PI_BY_TWO * 0.8f);
}

//-----------------------------------------------------------------------------
// zoom()
//-----------------------------------------------------------------------------
void LLModelPreview::zoom(F32 zoom_amt)
{
    F32 new_zoom = mCameraZoom + zoom_amt;
    // TODO: stop clamping in render
    mCameraZoom = llclamp(new_zoom, 1.f, PREVIEW_ZOOM_LIMIT);
}

void LLModelPreview::pan(F32 right, F32 up)
{
    bool skin_weight = mViewOption["show_skin_weight"];
    F32 camera_distance = skin_weight ? SKIN_WEIGHT_CAMERA_DISTANCE : mCameraDistance;
    mCameraOffset.mV[VY] = llclamp(mCameraOffset.mV[VY] + right * camera_distance / mCameraZoom, -1.f, 1.f);
    mCameraOffset.mV[VZ] = llclamp(mCameraOffset.mV[VZ] + up * camera_distance / mCameraZoom, -1.f, 1.f);
}

void LLModelPreview::setPreviewLOD(S32 lod)
{
    lod = llclamp(lod, 0, (S32)LLModel::LOD_HIGH);

    if (lod != mPreviewLOD)
    {
        mPreviewLOD = lod;

        LLComboBox* combo_box = mFMP->getChild<LLComboBox>("preview_lod_combo");
        combo_box->setCurrentByIndex((NUM_LOD - 1) - mPreviewLOD); // combo box list of lods is in reverse order
        mFMP->childSetValue("lod_file_" + lod_name[mPreviewLOD], mLODFile[mPreviewLOD]);

        LLColor4 highlight_color = LLUIColorTable::instance().getColor("MeshImportTableHighlightColor");
        LLColor4 normal_color = LLUIColorTable::instance().getColor("MeshImportTableNormalColor");

        for (S32 i = 0; i <= LLModel::LOD_HIGH; ++i)
        {
            const LLColor4& color = (i == lod) ? highlight_color : normal_color;

            mFMP->childSetColor(lod_status_name[i], color);
            mFMP->childSetColor(lod_label_name[i], color);
            mFMP->childSetColor(lod_triangles_name[i], color);
            mFMP->childSetColor(lod_vertices_name[i], color);
        }

        LLFloaterModelPreview* fmp = (LLFloaterModelPreview*)mFMP;
        if (fmp)
        {
            // make preview repopulate tab
            fmp->clearAvatarTab();
        }
    }
    refresh();
    updateStatusMessages();
}

//static
void LLModelPreview::textureLoadedCallback(
    BOOL success,
    LLViewerFetchedTexture *src_vi,
    LLImageRaw* src,
    LLImageRaw* src_aux,
    S32 discard_level,
    BOOL final,
    void* userdata)
{
    LLModelPreview* preview = (LLModelPreview*)userdata;
    preview->refresh();

    if (final && preview->mModelLoader)
    {
        if (preview->mModelLoader->mNumOfFetchingTextures > 0)
        {
            preview->mModelLoader->mNumOfFetchingTextures--;
        }
    }
}

// static
bool LLModelPreview::lodQueryCallback()
{
    // not the best solution, but model preview belongs to floater
    // so it is an easy way to check that preview still exists.
    LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;
    if (fmp && fmp->mModelPreview)
    {
        LLModelPreview* preview = fmp->mModelPreview;
        if (preview->mLodsQuery.size() > 0)
        {
            S32 lod = preview->mLodsQuery.back();
            preview->mLodsQuery.pop_back();
            preview->genMeshOptimizerLODs(lod, MESH_OPTIMIZER_AUTO);
            if (preview->mLookUpLodFiles && (lod == LLModel::LOD_HIGH))
            {
                preview->lookupLODModelFiles(LLModel::LOD_HIGH);
            }

            return preview->mLodsQuery.empty();
void LLModelPreview::onLODMeshOptimizerParamCommit(S32 requested_lod, bool enforce_tri_limit, S32 mode)
        genMeshOptimizerLODs(requested_lod, mode, 3, enforce_tri_limit);