/**
 * @file lldrawable.cpp
 * @brief LLDrawable class implementation
 *
 * $LicenseInfo:firstyear=2002&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$
 */

#include "llviewerprecompiledheaders.h"

#include "lldrawable.h"

// library includes
#include "material_codes.h"

// viewer includes
#include "llagent.h"
#include "llcriticaldamp.h"
#include "llface.h"
#include "lllightconstants.h"
#include "llmatrix4a.h"
#include "llsky.h"
#include "llsurfacepatch.h"
#include "llviewercamera.h"
#include "llviewerregion.h"
#include "llvolume.h"
#include "llvoavatar.h"
#include "llvovolume.h"
#include "llvosurfacepatch.h" // for debugging
#include "llworld.h"
#include "pipeline.h"
#include "llspatialpartition.h"
#include "llviewerobjectlist.h"
#include "llviewerwindow.h"
#include "llvocache.h"
#include "llcontrolavatar.h"
#include "lldrawpoolavatar.h"

const F32 MIN_INTERPOLATE_DISTANCE_SQUARED = 0.001f * 0.001f;
const F32 MAX_INTERPOLATE_DISTANCE_SQUARED = 10.f * 10.f;
const F32 OBJECT_DAMPING_TIME_CONSTANT = 0.06f;

extern bool gShiftFrame;


////////////////////////
//
// Inline implementations.
//
//



//////////////////////////////
//
// Drawable code
//
//

// static
U32 LLDrawable::sNumZombieDrawables = 0;
F32 LLDrawable::sCurPixelAngle = 0;
std::vector<LLPointer<LLDrawable> > LLDrawable::sDeadList;

#define FORCE_INVISIBLE_AREA 16.f

// static
void LLDrawable::incrementVisible()
{
    LLViewerOctreeEntryData::incrementVisible();
    sCurPixelAngle = (F32) gViewerWindow->getWindowHeightRaw()/LLViewerCamera::getInstance()->getView();
}

LLDrawable::LLDrawable(LLViewerObject *vobj, bool new_entry)
:   LLViewerOctreeEntryData(LLViewerOctreeEntry::LLDRAWABLE),
    mVObjp(vobj)
{
    init(new_entry);
}

void LLDrawable::init(bool new_entry)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    // mXform
    mParent = NULL;
    mRenderType = 0;
    mCurrentScale = LLVector3(1,1,1);
    mDistanceWRTCamera = 0.0f;
    mState     = 0;

    // mFaces
    mRadius = 0.f;
    mGeneration = -1;
    mSpatialBridge = NULL;

    LLViewerOctreeEntry* entry = NULL;
    LLVOCacheEntry* vo_entry = NULL;
    if(!new_entry && mVObjp && getRegion() != NULL)
    {
        vo_entry = getRegion()->getCacheEntryForOctree(mVObjp->getLocalID());
        if(vo_entry)
        {
            entry = vo_entry->getEntry();
        }
    }
    setOctreeEntry(entry);
    if(vo_entry)
    {
        if(!entry)
        {
            vo_entry->setOctreeEntry(mEntry);
        }

        getRegion()->addActiveCacheEntry(vo_entry);

        if(vo_entry->getNumOfChildren() > 0)
        {
            getRegion()->addVisibleChildCacheEntry(vo_entry, NULL); //to load all children.
        }

        llassert(!vo_entry->getGroup()); //not in the object cache octree.
    }

    llassert(!vo_entry || vo_entry->getEntry() == mEntry);

    initVisible(sCurVisible - 2);//invisible for the current frame and the last frame.
}

void LLDrawable::unload()
{
    LLVOVolume *pVVol = getVOVolume();
    pVVol->setNoLOD();
    pVVol->markForUpdate();
}

// static
void LLDrawable::initClass()
{
}


void LLDrawable::destroy()
{
    if (gDebugGL)
    {
        gPipeline.checkReferences(this);
    }

    if (isDead())
    {
        sNumZombieDrawables--;
    }

    // Attempt to catch violations of this in debug,
    // knowing that some false alarms may result
    //
    llassert(!LLSpatialGroup::sNoDelete);

    /* cannot be guaranteed and causes crashes on false alarms
    if (LLSpatialGroup::sNoDelete)
    {
        LL_ERRS() << "Illegal deletion of LLDrawable!" << LL_ENDL;
    }*/

    std::for_each(mFaces.begin(), mFaces.end(), DeletePointer());
    mFaces.clear();


    /*if (!(sNumZombieDrawables % 10))
    {
        LL_INFOS() << "- Zombie drawables: " << sNumZombieDrawables << LL_ENDL;
    }*/

}

void LLDrawable::markDead()
{
    if (isDead())
    {
        LL_WARNS() << "Warning!  Marking dead multiple times!" << LL_ENDL;
        return;
    }
    setState(DEAD);

    if (mSpatialBridge)
    {
        mSpatialBridge->markDead();
        mSpatialBridge = NULL;
    }

    sNumZombieDrawables++;

    // We're dead.  Free up all of our references to other objects
    cleanupReferences();
//  sDeadList.push_back(this);
}

LLVOVolume* LLDrawable::getVOVolume() const
{
    LLViewerObject* objectp = mVObjp;
    if ( !isDead() && objectp && (objectp->getPCode() == LL_PCODE_VOLUME))
    {
        return ((LLVOVolume*)objectp);
    }
    else
    {
        return NULL;
    }
}

const LLMatrix4& LLDrawable::getRenderMatrix() const
{
    return isRoot() ? getWorldMatrix() : getParent()->getWorldMatrix();
}

bool LLDrawable::isLight() const
{
    LLViewerObject* objectp = mVObjp;
    if ( objectp && (objectp->getPCode() == LL_PCODE_VOLUME) && !isDead())
    {
        return ((LLVOVolume*)objectp)->getIsLight();
    }
    else
    {
        return false;
    }
}

void LLDrawable::cleanupReferences()
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    std::for_each(mFaces.begin(), mFaces.end(), DeletePointer());
    mFaces.clear();

    gPipeline.unlinkDrawable(this);

    removeFromOctree();

    // Cleanup references to other objects
    mVObjp = NULL;
    mParent = NULL;
}

void LLDrawable::removeFromOctree()
{
    if(!mEntry)
    {
        return;
    }

    mEntry->removeData(this);
    if(mEntry->hasVOCacheEntry())
    {
        getRegion()->removeActiveCacheEntry((LLVOCacheEntry*)mEntry->getVOCacheEntry(), this);
    }
    mEntry = NULL;
}

void LLDrawable::cleanupDeadDrawables()
{
    /*
    S32 i;
    for (i = 0; i < sDeadList.size(); i++)
    {
        if (sDeadList[i]->getNumRefs() > 1)
        {
            LL_WARNS() << "Dead drawable has " << sDeadList[i]->getNumRefs() << " remaining refs" << LL_ENDL;
            gPipeline.findReferences(sDeadList[i]);
        }
    }
    */
    sDeadList.clear();
}

S32 LLDrawable::findReferences(LLDrawable *drawablep)
{
    S32 count = 0;
    if (mParent == drawablep)
    {
        LL_INFOS() << this << ": parent reference" << LL_ENDL;
        count++;
    }
    return count;
}

LLFace* LLDrawable::addFace(LLFacePool *poolp, LLViewerTexture *texturep)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    LLFace *face;
    {
        face = new LLFace(this, mVObjp);
    }

    if (!face) LL_ERRS() << "Allocating new Face: " << mFaces.size() << LL_ENDL;

    if (face)
    {
        mFaces.push_back(face);

        if (poolp)
        {
            face->setPool(poolp, texturep);
        }

        if (isState(UNLIT))
        {
            face->setState(LLFace::FULLBRIGHT);
        }
    }
    return face;
}

LLFace* LLDrawable::addFace(const LLTextureEntry *te, LLViewerTexture *texturep)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    LLFace *face;

    face = new LLFace(this, mVObjp);

    face->setTEOffset(static_cast<S32>(mFaces.size()));
    face->setTexture(texturep);
    face->setPoolType(gPipeline.getPoolTypeFromTE(te, texturep));

    mFaces.push_back(face);

    if (isState(UNLIT))
    {
        face->setState(LLFace::FULLBRIGHT);
    }

    return face;

}

LLFace* LLDrawable::addFace(const LLTextureEntry *te, LLViewerTexture *texturep, LLViewerTexture *normalp)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    LLFace *face;
    face = new LLFace(this, mVObjp);

    face->setTEOffset(static_cast<S32>(mFaces.size()));
    face->setTexture(texturep);
    face->setNormalMap(normalp);
    face->setPoolType(gPipeline.getPoolTypeFromTE(te, texturep));

    mFaces.push_back(face);

    if (isState(UNLIT))
    {
        face->setState(LLFace::FULLBRIGHT);
    }

    return face;

}

LLFace* LLDrawable::addFace(const LLTextureEntry *te, LLViewerTexture *texturep, LLViewerTexture *normalp, LLViewerTexture *specularp)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    LLFace *face;
    face = new LLFace(this, mVObjp);

    face->setTEOffset(static_cast<S32>(mFaces.size()));
    face->setTexture(texturep);
    face->setNormalMap(normalp);
    face->setSpecularMap(specularp);
    face->setPoolType(gPipeline.getPoolTypeFromTE(te, texturep));

    mFaces.push_back(face);

    if (isState(UNLIT))
    {
        face->setState(LLFace::FULLBRIGHT);
    }

    return face;

}

void LLDrawable::setNumFaces(const S32 newFaces, LLFacePool *poolp, LLViewerTexture *texturep)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    if (newFaces == (S32)mFaces.size())
    {
        return;
    }
    else if (newFaces < (S32)mFaces.size())
    {
        std::for_each(mFaces.begin() + newFaces, mFaces.end(), DeletePointer());
        mFaces.erase(mFaces.begin() + newFaces, mFaces.end());
    }
    else // (newFaces > mFaces.size())
    {
        mFaces.reserve(newFaces);
        for (auto i = mFaces.size(); i<newFaces; i++)
        {
            addFace(poolp, texturep);
        }
    }

    llassert_always(mFaces.size() == newFaces);
}

void LLDrawable::setNumFacesFast(const S32 newFaces, LLFacePool *poolp, LLViewerTexture *texturep)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    if (newFaces <= (S32)mFaces.size() && newFaces >= (S32)mFaces.size()/2)
    {
        return;
    }
    else if (newFaces < (S32)mFaces.size())
    {
        std::for_each(mFaces.begin() + newFaces, mFaces.end(), DeletePointer());
        mFaces.erase(mFaces.begin() + newFaces, mFaces.end());
    }
    else // (newFaces > mFaces.size())
    {
        mFaces.reserve(newFaces);
        for (auto i = mFaces.size(); i<newFaces; i++)
        {
            addFace(poolp, texturep);
        }
    }

    llassert_always(mFaces.size() == newFaces) ;
}

void LLDrawable::mergeFaces(LLDrawable* src)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    auto face_count = mFaces.size() + src->mFaces.size();

    mFaces.reserve(face_count);
    for (size_t i = 0; i < src->mFaces.size(); i++)
    {
        LLFace* facep = src->mFaces[i];
        facep->setDrawable(this);
        mFaces.push_back(facep);
    }
    src->mFaces.clear();
}

void LLDrawable::deleteFaces(S32 offset, S32 count)
{
    face_list_t::iterator face_begin = mFaces.begin() + offset;
    face_list_t::iterator face_end = face_begin + count;

    std::for_each(face_begin, face_end, DeletePointer());
    mFaces.erase(face_begin, face_end);
}

void LLDrawable::update()
{
    LL_ERRS() << "Shouldn't be called!" << LL_ENDL;
}


void LLDrawable::updateMaterial()
{
}

void LLDrawable::makeActive()
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

#if !LL_RELEASE_FOR_DOWNLOAD
    if (mVObjp.notNull())
    {
        U32 pcode = mVObjp->getPCode();
        if (pcode == LLViewerObject::LL_VO_WATER ||
            pcode == LLViewerObject::LL_VO_VOID_WATER ||
            pcode == LLViewerObject::LL_VO_SURFACE_PATCH ||
            pcode == LLViewerObject::LL_VO_PART_GROUP ||
            pcode == LLViewerObject::LL_VO_HUD_PART_GROUP ||
            pcode == LLViewerObject::LL_VO_SKY)
        {
            LL_ERRS() << "Static viewer object has active drawable!" << LL_ENDL;
        }
    }
#endif

    if (!isState(ACTIVE)) // && mGeneration > 0)
    {
        setState(ACTIVE);

        //parent must be made active first
        if (!isRoot() && !mParent->isActive())
        {
            mParent->makeActive();
            //NOTE: linked set will now NEVER become static
            mParent->setState(LLDrawable::ACTIVE_CHILD);
        }

        //all child objects must also be active
        llassert_always(mVObjp);

        LLViewerObject::const_child_list_t& child_list = mVObjp->getChildren();
        for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
             iter != child_list.end(); iter++)
        {
            LLViewerObject* child = *iter;
            LLDrawable* drawable = child->mDrawable;
            if (drawable)
            {
                drawable->makeActive();
            }
        }

        if (mVObjp->getPCode() == LL_PCODE_VOLUME)
        {
            gPipeline.markRebuild(this, LLDrawable::REBUILD_VOLUME);
        }
        updatePartition();
    }
    else if (!isRoot() && !mParent->isActive()) //this should not happen, but occasionally it does...
    {
        mParent->makeActive();
        //NOTE: linked set will now NEVER become static
        mParent->setState(LLDrawable::ACTIVE_CHILD);
    }

    llassert(isAvatar() || isRoot() || mParent->isActive());
}


void LLDrawable::makeStatic(bool warning_enabled)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    if (isState(ACTIVE) &&
        !isState(ACTIVE_CHILD) &&
        !mVObjp->isAttachment() &&
        !mVObjp->isFlexible() &&
        !mVObjp->isAnimatedObject())
    {
        clearState(ACTIVE | ANIMATED_CHILD);

        //drawable became static with active parent, not acceptable
        llassert(mParent.isNull() || !mParent->isActive() || !warning_enabled);

        LLViewerObject::const_child_list_t& child_list = mVObjp->getChildren();
        for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
             iter != child_list.end(); iter++)
        {
            LLViewerObject* child = *iter;
            LLDrawable* child_drawable = child->mDrawable;
            if (child_drawable)
            {
                if (child_drawable->getParent() != this)
                {
                    LL_WARNS() << "Child drawable has unknown parent." << LL_ENDL;
                }
                child_drawable->makeStatic(warning_enabled);
            }
        }

        if (mVObjp->getPCode() == LL_PCODE_VOLUME)
        {
            gPipeline.markRebuild(this, LLDrawable::REBUILD_VOLUME);
        }

        if (mSpatialBridge)
        {
            mSpatialBridge->markDead();
            setSpatialBridge(NULL);
        }
        updatePartition();
    }

    llassert(isAvatar() || isRoot() || mParent->isStatic());
}

// Returns "distance" between target destination and resulting xfrom
F32 LLDrawable::updateXform(bool undamped)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    bool damped = !undamped;

    // Position
    const LLVector3 old_pos(mXform.getPosition());
    LLVector3 target_pos;
    if (mXform.isRoot())
    {
        // get root position in your agent's region
        target_pos = mVObjp->getPositionAgent();
    }
    else
    {
        // parent-relative position
        target_pos = mVObjp->getPosition();
    }

    // Rotation
    const LLQuaternion old_rot(mXform.getRotation());
    LLQuaternion target_rot = mVObjp->getRotation();
    //scaling
    LLVector3 target_scale = mVObjp->getScale();
    LLVector3 old_scale = mCurrentScale;

    // Damping
    F32 dist_squared = 0.f;
    F32 camdist2 = (mDistanceWRTCamera * mDistanceWRTCamera);

    if (damped && isVisible())
    {
        F32 lerp_amt = llclamp(LLSmoothInterpolation::getInterpolant(OBJECT_DAMPING_TIME_CONSTANT), 0.f, 1.f);
        LLVector3 new_pos = lerp(old_pos, target_pos, lerp_amt);
        dist_squared = dist_vec_squared(new_pos, target_pos);

        LLQuaternion new_rot = nlerp(lerp_amt, old_rot, target_rot);
        // FIXME: This can be negative! It is be possible for some rots to 'cancel out' pos or size changes.
        dist_squared += (1.f - dot(new_rot, target_rot)) * 10.f;

        LLVector3 new_scale = lerp(old_scale, target_scale, lerp_amt);
        dist_squared += dist_vec_squared(new_scale, target_scale);

        if ((dist_squared >= MIN_INTERPOLATE_DISTANCE_SQUARED * camdist2) &&
            (dist_squared <= MAX_INTERPOLATE_DISTANCE_SQUARED))
        {
            // interpolate
            target_pos = new_pos;
            target_rot = new_rot;
            target_scale = new_scale;
        }
        else if (mVObjp->getAngularVelocity().isExactlyZero())
        {
            // snap to final position (only if no target omega is applied)
            dist_squared = 0.0f;
            //set target scale here, because of dist_squared = 0.0f remove object from move list
            mCurrentScale = target_scale;

            if (getVOVolume() && !isRoot())
            { //child prim snapping to some position, needs a rebuild
                gPipeline.markRebuild(this, LLDrawable::REBUILD_POSITION);
            }
        }
    }
    else
    {
        // The following fixes MAINT-1742 but breaks vehicles similar to MAINT-2275
        // dist_squared = dist_vec_squared(old_pos, target_pos);

        // The following fixes MAINT-2247 but causes MAINT-2275
        //dist_squared += (1.f - dot(old_rot, target_rot)) * 10.f;
        //dist_squared += dist_vec_squared(old_scale, target_scale);
    }

    const LLVector3 vec = mCurrentScale-target_scale;

    //It's a very important on each cycle on Drawable::update form(), when object remained in move
    //, list update the CurrentScale member, because if do not do that, it remained in this list forever
    //or when the delta time between two frames a become a sufficiently large (due to interpolation)
    //for overcome the MIN_INTERPOLATE_DISTANCE_SQUARED.
    mCurrentScale = target_scale;

    if (vec*vec > MIN_INTERPOLATE_DISTANCE_SQUARED)
    { //scale change requires immediate rebuild
        gPipeline.markRebuild(this, LLDrawable::REBUILD_POSITION);
    }
    else if (!isRoot() &&
         (!mVObjp->getAngularVelocity().isExactlyZero() ||
            dist_squared > 0.f))
    { //child prim moving relative to parent, tag as needing to be rendered atomically and rebuild
        dist_squared = 1.f; //keep this object on the move list
        if (!isState(LLDrawable::ANIMATED_CHILD))
        {
            setState(LLDrawable::ANIMATED_CHILD);
            gPipeline.markRebuild(this, LLDrawable::REBUILD_ALL);
            mVObjp->dirtySpatialGroup();
        }
    }
    else if (!isRoot() &&
            ((dist_vec_squared(old_pos, target_pos) > 0.f)
            || (1.f - dot(old_rot, target_rot)) > 0.f))
    { //fix for BUG-840, MAINT-2275, MAINT-1742, MAINT-2247
        mVObjp->shrinkWrap();
        gPipeline.markRebuild(this, LLDrawable::REBUILD_POSITION);
    }
    else if (!getVOVolume() && !isAvatar())
    {
        movePartition();
    }

    // Update
    mXform.setPosition(target_pos);
    mXform.setRotation(target_rot);
    mXform.setScale(LLVector3(1,1,1)); //no scale in drawable transforms (IT'S A RULE!)
    mXform.updateMatrix();
    if (isRoot() && mVObjp->isAnimatedObject() && mVObjp->getControlAvatar())
    {
        mVObjp->getControlAvatar()->matchVolumeTransform();
    }

    if (mSpatialBridge)
    {
        gPipeline.markMoved(mSpatialBridge, false);
    }
    return dist_squared;
}

void LLDrawable::setRadius(F32 radius)
{
    if (mRadius != radius)
    {
        mRadius = radius;
    }
}

void LLDrawable::moveUpdatePipeline(bool moved)
{
    if (moved)
    {
        makeActive();
    }

    // Update the face centers.
    for (S32 i = 0; i < getNumFaces(); i++)
    {
        LLFace* face = getFace(i);
        if (face)
        {
            face->updateCenterAgent();
        }
    }
}

void LLDrawable::movePartition()
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    LLSpatialPartition* part = getSpatialPartition();
    if (part)
    {
        part->move(this, getSpatialGroup());
    }
}

bool LLDrawable::updateMove()
{
    if (isDead())
    {
        LL_WARNS() << "Update move on dead drawable!" << LL_ENDL;
        return true;
    }

    if (mVObjp.isNull())
    {
        return false;
    }

    makeActive();

    return isState(MOVE_UNDAMPED) ? updateMoveUndamped() : updateMoveDamped();
}

bool LLDrawable::updateMoveUndamped()
{
    F32 dist_squared = updateXform(true);

    mGeneration++;

    if (!isState(LLDrawable::INVISIBLE))
    {
        bool moved = (dist_squared > 0.001f && dist_squared < 255.99f);
        moveUpdatePipeline(moved);
        mVObjp->updateText();
    }

    mVObjp->clearChanged(LLXform::MOVED);
    return true;
}

void LLDrawable::updatePartition()
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    if (!getVOVolume())
    {
        movePartition();
    }
    else if (mSpatialBridge)
    {
        gPipeline.markMoved(mSpatialBridge, false);
    }
    else
    {
        //a child prim moved and needs its verts regenerated
        gPipeline.markRebuild(this, LLDrawable::REBUILD_POSITION);
    }
}

bool LLDrawable::updateMoveDamped()
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    F32 dist_squared = updateXform(false);

    mGeneration++;

    if (!isState(LLDrawable::INVISIBLE))
    {
        bool moved = (dist_squared > 0.001f && dist_squared < 128.0f);
        moveUpdatePipeline(moved);
        mVObjp->updateText();
    }

    bool done_moving = dist_squared == 0.0f;

    if (done_moving)
    {
        mVObjp->clearChanged(LLXform::MOVED);
    }

    return done_moving;
}

void LLDrawable::updateDistance(LLCamera& camera, bool force_update)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    if (LLViewerCamera::sCurCameraID != LLViewerCamera::CAMERA_WORLD)
    {
        LL_WARNS() << "Attempted to update distance for non-world camera." << LL_ENDL;
        return;
    }

    if (gShiftFrame)
    {
        return;
    }

    //switch LOD with the spatial group to avoid artifacts
    //LLSpatialGroup* sg = getSpatialGroup();

    LLVector3 pos;

    //if (!sg || sg->changeLOD())
    {
        LLVOVolume* volume = getVOVolume();
        if (volume)
        {
            if (getGroup())
            {
                pos.set(getPositionGroup().getF32ptr());
            }
            else
            {
                pos = getPositionAgent();
            }

            if (isState(LLDrawable::HAS_ALPHA))
            {
                for (S32 i = 0; i < getNumFaces(); i++)
                {
                    LLFace* facep = getFace(i);
                    if (facep &&
                        (force_update || facep->isInAlphaPool()))
                    {
                        LLVector4a box;
                        box.setSub(facep->mExtents[1], facep->mExtents[0]);
                        box.mul(0.25f);
                        LLVector3 v = (facep->mCenterLocal-camera.getOrigin());
                        const LLVector3& at = camera.getAtAxis();
                        for (U32 j = 0; j < 3; j++)
                        {
                            v.mV[j] -= box[j] * at.mV[j];
                        }
                        facep->mDistance = v * camera.getAtAxis();
                    }
                }
            }


            // MAINT-7926 Handle volumes in an animated object as a special case
            // SL-937: add dynamic box handling for rigged mesh on regular avatars.
            //if (volume->getAvatar() && volume->getAvatar()->isControlAvatar())
            if (volume->getAvatar())
            {
                const LLVector3* av_box = volume->getAvatar()->getLastAnimExtents();
                LLVector3 cam_pos_from_agent = LLViewerCamera::getInstance()->getOrigin();
                LLVector3 cam_to_box_offset = point_to_box_offset(cam_pos_from_agent, av_box);
                mDistanceWRTCamera = llmax(0.01f, ll_round(cam_to_box_offset.magVec(), 0.01f));
                mVObjp->updateLOD();
                return;
            }
        }
        else
        {
            pos = LLVector3(getPositionGroup().getF32ptr());
        }

        pos -= camera.getOrigin();
        mDistanceWRTCamera = ll_round(pos.magVec(), 0.01f);
        mVObjp->updateLOD();
    }
}

void LLDrawable::updateTexture()
{
    if (isDead())
    {
        LL_WARNS() << "Dead drawable updating texture!" << LL_ENDL;
        return;
    }

    if (getNumFaces() != mVObjp->getNumTEs())
    { //drawable is transitioning its face count
        return;
    }

    if (getVOVolume())
    {
        gPipeline.markRebuild(this, LLDrawable::REBUILD_MATERIAL);
    }
}

bool LLDrawable::updateGeometry()
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    llassert(mVObjp.notNull());
    bool res = mVObjp && mVObjp->updateGeometry(this);
    return res;
}

void LLDrawable::shiftPos(const LLVector4a &shift_vector)
{
    if (isDead())
    {
        LL_WARNS() << "Shifting dead drawable" << LL_ENDL;
        return;
    }

    if (mParent)
    {
        mXform.setPosition(mVObjp->getPosition());
    }
    else
    {
        mXform.setPosition(mVObjp->getPositionAgent());
    }

    mXform.updateMatrix();

    if (isStatic())
    {
        LLVOVolume* volume = getVOVolume();

        bool rebuild = (!volume &&
                        getRenderType() != LLPipeline::RENDER_TYPE_TREE &&
                        getRenderType() != LLPipeline::RENDER_TYPE_TERRAIN &&
                        getRenderType() != LLPipeline::RENDER_TYPE_SKY);

        if (rebuild)
        {
            gPipeline.markRebuild(this, LLDrawable::REBUILD_ALL);
        }

        for (S32 i = 0; i < getNumFaces(); i++)
        {
            LLFace *facep = getFace(i);
            if (facep)
            {
                facep->mCenterAgent += LLVector3(shift_vector.getF32ptr());
                facep->mExtents[0].add(shift_vector);
                facep->mExtents[1].add(shift_vector);

                if (rebuild && facep->hasGeometry())
                {
                    facep->clearVertexBuffer();
                }
            }
        }

        shift(shift_vector);
    }
    else if (mSpatialBridge)
    {
        mSpatialBridge->shiftPos(shift_vector);
    }
    else if (isAvatar())
    {
        shift(shift_vector);
    }

    mVObjp->onShift(shift_vector);
}

const LLVector3& LLDrawable::getBounds(LLVector3& min, LLVector3& max) const
{
    mXform.getMinMax(min,max);
    return mXform.getPositionW();
}

void LLDrawable::updateSpatialExtents()
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    if (mVObjp)
    {
        const LLVector4a* exts = getSpatialExtents();
        LLVector4a extents[2] = { exts[0], exts[1] };

        mVObjp->updateSpatialExtents(extents[0], extents[1]);
        setSpatialExtents(extents[0], extents[1]);
    }

    updateBinRadius();

    if (mSpatialBridge.notNull())
    {
        getGroupPosition().splat(0.f);
    }
}


void LLDrawable::updateBinRadius()
{
    if (mVObjp.notNull())
    {
        setBinRadius(llmin(mVObjp->getBinRadius(), 256.f));
    }
    else
    {
        setBinRadius(llmin(getRadius()*4.f, 256.f));
    }
}

void LLDrawable::updateSpecialHoverCursor(bool enabled)
{
    // TODO: maintain a list of objects that have special
    // hover cursors, then use that list for per-frame
    // hover cursor selection. JC
}

F32 LLDrawable::getVisibilityRadius() const
{
    if (isDead())
    {
        return 0.f;
    }
    else if (isLight())
    {
        const LLVOVolume *vov = getVOVolume();
        if (vov)
        {
            return llmax(getRadius(), vov->getLightRadius());
        } else {
            // LL_WARNS() ?
        }
    }
    return getRadius();
}

void LLDrawable::updateUVMinMax()
{
}

//virtual
bool LLDrawable::isVisible() const
{
    if (LLViewerOctreeEntryData::isVisible())
    {
            return true;
    }

    {
        LLViewerOctreeGroup* group = mEntry->getGroup();
        if (group && group->isVisible())
        {
            LLViewerOctreeEntryData::setVisible();
            return true;
        }
    }

    return false;
}

//virtual
bool LLDrawable::isRecentlyVisible() const
{
    //currently visible or visible in the previous frame.
    bool vis = LLViewerOctreeEntryData::isRecentlyVisible();

    if(!vis)
    {
        const U32 MIN_VIS_FRAME_RANGE = 2 ; //two frames:the current one and the last one.
        vis = (sCurVisible - getVisible() < MIN_VIS_FRAME_RANGE);
    }

    return vis ;
}

void LLDrawable::setGroup(LLViewerOctreeGroup *groupp)
    {
    LLSpatialGroup* cur_groupp = (LLSpatialGroup*)getGroup();

    //precondition: mGroupp MUST be null or DEAD or mGroupp MUST NOT contain this
    //llassert(!cur_groupp || cur_groupp->isDead() || !cur_groupp->hasElement(this));

    //precondition: groupp MUST be null or groupp MUST contain this
    llassert(!groupp || (LLSpatialGroup*)groupp->hasElement(this));

    if (cur_groupp != groupp && getVOVolume())
    {
        //NULL out vertex buffer references for volumes on spatial group change to maintain
        //requirement that every face vertex buffer is either NULL or points to a vertex buffer
        //contained by its drawable's spatial group
        for (S32 i = 0; i < getNumFaces(); ++i)
        {
            LLFace* facep = getFace(i);
            if (facep)
            {
                facep->clearVertexBuffer();
            }
        }
    }

    //postcondition: if next group is NULL, previous group must be dead OR NULL OR binIndex must be -1
    //postcondition: if next group is NOT NULL, binIndex must not be -1
    //llassert(groupp == NULL ? (cur_groupp == NULL || cur_groupp->isDead()) || (!getEntry() || getEntry()->getBinIndex() == -1) :
    //                      (getEntry() && getEntry()->getBinIndex() != -1));

    LLViewerOctreeEntryData::setGroup(groupp);
}

/*
* Get the SpatialPartition this Drawable should use.
* Checks current SpatialPartition assignment and corrects if it is invalid.
*/
LLSpatialPartition* LLDrawable::getSpatialPartition()
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    LLSpatialPartition* retval = NULL;

    if (!mVObjp ||
        !getVOVolume() ||
        isStatic())
    {
        retval = gPipeline.getSpatialPartition((LLViewerObject*)mVObjp);
    }
    else if (isRoot())
    {
        // determine if the spatial bridge has changed
        if (mSpatialBridge)
        {
            U32 partition_type = mSpatialBridge->asPartition()->mPartitionType;
            bool is_hud = mVObjp->isHUDAttachment();
            bool is_animesh = mVObjp->isAnimatedObject() && mVObjp->getControlAvatar() != NULL;
            bool is_attachment = mVObjp->isAttachment() && !is_hud && !is_animesh;
            if ((partition_type == LLViewerRegion::PARTITION_HUD) != is_hud)
            {
                // Was/became HUD
                // remove obsolete bridge
                mSpatialBridge->markDead();
                setSpatialBridge(NULL);
            }
            else if ((partition_type == LLViewerRegion::PARTITION_CONTROL_AV) != is_animesh)
            {
                // Was/became part of animesh
                // remove obsolete bridge
                mSpatialBridge->markDead();
                setSpatialBridge(NULL);
            }
            else if ((partition_type == LLViewerRegion::PARTITION_AVATAR) != is_attachment)
            {
                // Was/became part of avatar
                // remove obsolete bridge
                mSpatialBridge->markDead();
                setSpatialBridge(NULL);
            }
        }
        //must be an active volume
        if (!mSpatialBridge)
        {
            if (mVObjp->isHUDAttachment())
            {
                setSpatialBridge(new LLHUDBridge(this, getRegion()));
            }
            else if (mVObjp->isAnimatedObject() && mVObjp->getControlAvatar())
            {
                setSpatialBridge(new LLControlAVBridge(this, getRegion()));
                mVObjp->getControlAvatar()->mControlAVBridge = (LLControlAVBridge*)getSpatialBridge();
            }
            // check HUD first, because HUD is also attachment
            else if (mVObjp->isAttachment())
            {
                // Attachment
                // Use AvatarBridge of root object in attachment linkset
                setSpatialBridge(new LLAvatarBridge(this, getRegion()));
            }
            else
            {
                // Moving linkset, use VolumeBridge of root object in linkset
                setSpatialBridge(new LLVolumeBridge(this, getRegion()));
            }
        }
        return mSpatialBridge->asPartition();
    }
    else
    {
        retval = getParent()->getSpatialPartition();
    }

    if (retval && mSpatialBridge.notNull())
    {
        mSpatialBridge->markDead();
        setSpatialBridge(NULL);
    }

    return retval;
}

//=======================================
// Spatial Partition Bridging Drawable
//=======================================

LLSpatialBridge::LLSpatialBridge(LLDrawable* root, bool render_by_group, U32 data_mask, LLViewerRegion* regionp) :
    LLDrawable(root->getVObj(), true),
    LLSpatialPartition(data_mask, render_by_group, regionp)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;
    mOcclusionEnabled = false;
    mBridge = this;
    mDrawable = root;
    root->setSpatialBridge(this);

    mRenderType = mDrawable->mRenderType;
    mDrawableType = mDrawable->mRenderType;

    mPartitionType = LLViewerRegion::PARTITION_VOLUME;

    mOctree->balance();

    llassert(mDrawable);
    llassert(mDrawable->getRegion());
    LLSpatialPartition *part = mDrawable->getRegion()->getSpatialPartition(mPartitionType);
    llassert(part);

    if (part)
    {
        part->put(this);
    }
}

LLSpatialBridge::~LLSpatialBridge()
{
    if(mEntry)
    {
    LLSpatialGroup* group = getSpatialGroup();
    if (group)
    {
        group->getSpatialPartition()->remove(this, group);
    }
    }

    //delete octree here so listeners will still be able to access bridge specific state
    destroyTree();
}

void LLSpatialBridge::destroyTree()
{
    delete mOctree;
    mOctree = NULL;
}

void LLSpatialBridge::updateSpatialExtents()
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    LLSpatialGroup* root = (LLSpatialGroup*) mOctree->getListener(0);

    root->rebound();

    const LLVector4a* root_bounds = root->getBounds();
    LLVector4a offset;
    LLVector4a size = root_bounds[1];

    //VECTORIZE THIS
    LLMatrix4a mat;
    mat.loadu(mDrawable->getXform()->getWorldMatrix());

    LLVector4a t;
    t.splat(0.f);

    LLVector4a center;
    mat.affineTransform(t, center);

    mat.rotate(root_bounds[0], offset);
    center.add(offset);

    LLVector4a v[4];

    //get 4 corners of bounding box
    mat.rotate(size,v[0]);

    LLVector4a scale;

    scale.set(-1.f, -1.f, 1.f);
    scale.mul(size);
    mat.rotate(scale, v[1]);

    scale.set(1.f, -1.f, -1.f);
    scale.mul(size);
    mat.rotate(scale, v[2]);

    scale.set(-1.f, 1.f, -1.f);
    scale.mul(size);
    mat.rotate(scale, v[3]);

    LLVector4a newMin;
    LLVector4a newMax;
    newMin = newMax = center;
    for (U32 i = 0; i < 4; i++)
    {
        LLVector4a delta;
        delta.setAbs(v[i]);
        LLVector4a min;
        min.setSub(center, delta);
        LLVector4a max;
        max.setAdd(center, delta);

        newMin.setMin(newMin, min);
        newMax.setMax(newMax, max);
    }
    setSpatialExtents(newMin, newMax);

    LLVector4a diagonal;
    diagonal.setSub(newMax, newMin);
    mRadius = diagonal.getLength3().getF32() * 0.5f;

    LLVector4a& pos = getGroupPosition();
    pos.setAdd(newMin,newMax);
    pos.mul(0.5f);
    updateBinRadius();
}

void LLSpatialBridge::updateBinRadius()
{
    setBinRadius(llmin( mOctree->getSize()[0]*0.5f, 256.f));
}

LLCamera LLSpatialBridge::transformCamera(LLCamera& camera)
{
    LLCamera ret = camera;
    LLXformMatrix* mat = mDrawable->getXform();
    LLVector3 center = LLVector3(0,0,0) * mat->getWorldMatrix();

    LLVector3 delta = ret.getOrigin() - center;
    LLQuaternion rot = ~mat->getRotation();

    delta *= rot;
    LLVector3 lookAt = ret.getAtAxis();
    LLVector3 up_axis = ret.getUpAxis();
    LLVector3 left_axis = ret.getLeftAxis();

    lookAt *= rot;
    up_axis *= rot;
    left_axis *= rot;

    if (!delta.isFinite())
    {
        delta.clearVec();
    }

    ret.setOrigin(delta);
    ret.setAxes(lookAt, left_axis, up_axis);

    return ret;
}

void LLSpatialBridge::transformExtents(const LLVector4a* src, LLVector4a* dst)
{
    LLMatrix4 mat = mDrawable->getXform()->getWorldMatrix();
    mat.invert();

    LLMatrix4a world_to_bridge(mat);

    matMulBoundBox(world_to_bridge, src, dst);
}


void LLDrawable::setVisible(LLCamera& camera, std::vector<LLDrawable*>* results, bool for_select)
{
    LLViewerOctreeEntryData::setVisible();

#if 0 && !LL_RELEASE_FOR_DOWNLOAD
    //crazy paranoid rules checking
    if (getVOVolume())
    {
        if (!isRoot())
        {
            if (isActive() && !mParent->isActive())
            {
                LL_ERRS() << "Active drawable has static parent!" << LL_ENDL;
            }

            if (isStatic() && !mParent->isStatic())
            {
                LL_ERRS() << "Static drawable has active parent!" << LL_ENDL;
            }

            if (mSpatialBridge)
            {
                LL_ERRS() << "Child drawable has spatial bridge!" << LL_ENDL;
            }
        }
        else if (isActive() && !mSpatialBridge)
        {
            LL_ERRS() << "Active root drawable has no spatial bridge!" << LL_ENDL;
        }
        else if (isStatic() && mSpatialBridge.notNull())
        {
            LL_ERRS() << "Static drawable has spatial bridge!" << LL_ENDL;
        }
    }
#endif
}

class LLOctreeMarkNotCulled: public OctreeTraveler
{
public:
    LLCamera* mCamera;

    LLOctreeMarkNotCulled(LLCamera* camera_in) : mCamera(camera_in) { }

    virtual void traverse(const OctreeNode* node)
    {
        LLSpatialGroup* group = (LLSpatialGroup*) node->getListener(0);
        group->setVisible();
        OctreeTraveler::traverse(node);
    }

    void visit(const OctreeNode* branch)
    {
        gPipeline.markNotCulled((LLSpatialGroup*) branch->getListener(0), *mCamera);
    }
};

void LLSpatialBridge::setVisible(LLCamera& camera_in, std::vector<LLDrawable*>* results, bool for_select)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    if (!gPipeline.hasRenderType(mDrawableType))
    {
        return;
    }


    //HACK don't draw attachments for avatars that haven't been visible in more than a frame
    LLViewerObject *vobj = mDrawable->getVObj();
    if (vobj && vobj->isAttachment() && !vobj->isHUDAttachment())
    {
        LLDrawable* av;
        LLDrawable* parent = mDrawable->getParent();

        if (parent)
        {
            LLViewerObject* objparent = parent->getVObj();
            av = objparent->mDrawable;
            LLSpatialGroup* group = av->getSpatialGroup();

            bool impostor = false;
            bool loaded = false;
            if (objparent->isAvatar())
            {
                LLVOAvatar* avatarp = (LLVOAvatar*) objparent;
                if (avatarp->isVisible())
                {
                    impostor = objparent->isAvatar() && !LLPipeline::sImpostorRender && ((LLVOAvatar*) objparent)->isImpostor();
                    loaded   = objparent->isAvatar() && ((LLVOAvatar*) objparent)->isFullyLoaded();
                }
                else
                {
                    return;
                }
            }

            if (!group ||
                LLDrawable::getCurrentFrame() - av->getVisible() > 1 ||
                impostor ||
                !loaded)
            {
                return;
            }
        }
    }


    LLSpatialGroup* group = (LLSpatialGroup*) mOctree->getListener(0);
    group->rebound();

    LLVector4a center;
    const LLVector4a* exts = getSpatialExtents();
    center.setAdd(exts[0], exts[1]);
    center.mul(0.5f);
    LLVector4a size;
    size.setSub(exts[1], exts[0]);
    size.mul(0.5f);

    if ((LLPipeline::sShadowRender && camera_in.AABBInFrustum(center, size)) ||
        LLPipeline::sImpostorRender ||
        (camera_in.AABBInFrustumNoFarClip(center, size) &&
        AABBSphereIntersect(exts[0], exts[1], camera_in.getOrigin(), camera_in.mFrustumCornerDist)))
    {
        if (!LLPipeline::sImpostorRender &&
            !LLPipeline::sShadowRender &&
            LLPipeline::calcPixelArea(center, size, camera_in) < FORCE_INVISIBLE_AREA)
        {
            return;
        }

        LLDrawable::setVisible(camera_in);

        if (for_select)
        {
            results->push_back(mDrawable);
            if (mDrawable->getVObj())
            {
                LLViewerObject::const_child_list_t& child_list = mDrawable->getVObj()->getChildren();
                for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
                     iter != child_list.end(); iter++)
                {
                    LLViewerObject* child = *iter;
                    LLDrawable* drawable = child->mDrawable;
                    results->push_back(drawable);
                }
            }
        }
        else
        {
            LLCamera trans_camera = transformCamera(camera_in);
            LLOctreeMarkNotCulled culler(&trans_camera);
            culler.traverse(mOctree);
        }
    }
}

void LLSpatialBridge::updateDistance(LLCamera& camera_in, bool force_update)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE;

    if (mDrawable == NULL)
    {
        markDead();
        return;
    }

    if (gShiftFrame)
    {
        return;
    }

    if (mDrawable->getVObj())
    {
        // Don't update if we are part of impostor, unles it's an impostor pass
        if (!LLPipeline::sImpostorRender && mDrawable->getVObj()->isAttachment())
        {
            LLDrawable* parent = mDrawable->getParent();
            if (parent && parent->getVObj())
            {
                LLVOAvatar* av = parent->getVObj()->asAvatar();
                if (av && av->isImpostor())
                {
                    return;
                }
            }
        }

        LLCamera camera = transformCamera(camera_in);

        mDrawable->updateDistance(camera, force_update);

        for (LLViewerObject* child : mDrawable->getVObj()->getChildren())
        {
            LLDrawable* drawable = child->mDrawable;
            if (drawable && !drawable->isDead() && !drawable->isAvatar())
            {
                drawable->updateDistance(camera, force_update);
            }
        }
    }
}

void LLSpatialBridge::makeActive()
{ //it is an error to make a spatial bridge active (it's already active)
    LL_ERRS() << "makeActive called on spatial bridge" << LL_ENDL;
}

void LLSpatialBridge::move(LLDrawable *drawablep, LLSpatialGroup *curp, bool immediate)
{
    LLSpatialPartition::move(drawablep, curp, immediate);
    gPipeline.markMoved(this, false);
}

bool LLSpatialBridge::updateMove()
{
    llassert_always(mDrawable);
    llassert_always(mDrawable->mVObjp);
    llassert_always(mDrawable->getRegion());
    LLSpatialPartition* part = mDrawable->getRegion()->getSpatialPartition(mPartitionType);
    llassert_always(part);

    mOctree->balance();
    if (part)
    {
        part->move(this, getSpatialGroup(), true);
    }
    return true;
}

void LLSpatialBridge::shiftPos(const LLVector4a& vec)
{
    LLDrawable::shift(vec);
}

void LLSpatialBridge::cleanupReferences()
{
    LLDrawable::cleanupReferences();
    if (mDrawable)
    {
        mDrawable->setGroup(NULL);

        if (mDrawable->getVObj())
        {
            LLViewerObject::const_child_list_t& child_list = mDrawable->getVObj()->getChildren();
            for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
                 iter != child_list.end(); iter++)
            {
                LLViewerObject* child = *iter;
                LLDrawable* drawable = child->mDrawable;
                if (drawable)
                {
                    drawable->setGroup(NULL);
                }
                }
            }

        LLDrawable* drawablep = mDrawable;
        mDrawable = NULL;
        drawablep->setSpatialBridge(NULL);
    }
}

const LLVector3 LLDrawable::getPositionAgent() const
{
    if (getVOVolume())
    {
        if (isActive())
        {
            LLVector3 pos(0,0,0);
            if (!isRoot())
            {
                pos = mVObjp->getPosition();
            }
            return pos * getRenderMatrix();
        }
        else
        {
            return mVObjp->getPositionAgent();
        }
    }
    else
    {
        return getWorldPosition();
    }
}

bool LLDrawable::isAnimating() const
{
    if (!getVObj())
    {
        return true;
    }

    if (getScale() != mVObjp->getScale())
    {
        return true;
    }

    if (mVObjp->getPCode() == LLViewerObject::LL_VO_PART_GROUP)
    {
        return true;
    }
    if (mVObjp->getPCode() == LLViewerObject::LL_VO_HUD_PART_GROUP)
    {
        return true;
    }

    /*if (!isRoot() && !mVObjp->getAngularVelocity().isExactlyZero())
    { //target omega
        return true;
    }*/

    return false;
}

void LLDrawable::updateFaceSize(S32 idx)
{
    if (mVObjp.notNull())
    {
        mVObjp->updateFaceSize(idx);
    }
}

LLDrawable* LLDrawable::getRoot()
{
    LLDrawable* ret = this;
    while (!ret->isRoot())
    {
        ret = ret->getParent();
    }

    return ret;
}

LLBridgePartition::LLBridgePartition(LLViewerRegion* regionp)
: LLSpatialPartition(0, false, regionp)
{
    mDrawableType = LLPipeline::RENDER_TYPE_VOLUME;
    mPartitionType = LLViewerRegion::PARTITION_BRIDGE;
    mLODPeriod = 16;
    mSlopRatio = 0.25f;
}

LLAvatarPartition::LLAvatarPartition(LLViewerRegion* regionp)
    : LLBridgePartition(regionp)
{
    mDrawableType = LLPipeline::RENDER_TYPE_AVATAR;
    mPartitionType = LLViewerRegion::PARTITION_AVATAR;
}

LLControlAVPartition::LLControlAVPartition(LLViewerRegion* regionp)
    : LLBridgePartition(regionp)
{
    mDrawableType = LLPipeline::RENDER_TYPE_CONTROL_AV;
    mPartitionType = LLViewerRegion::PARTITION_CONTROL_AV;
}

LLHUDBridge::LLHUDBridge(LLDrawable* drawablep, LLViewerRegion* regionp)
: LLVolumeBridge(drawablep, regionp)
{
    mDrawableType = LLPipeline::RENDER_TYPE_HUD;
    mPartitionType = LLViewerRegion::PARTITION_HUD;
    mSlopRatio = 0.0f;
}

F32 LLHUDBridge::calcPixelArea(LLSpatialGroup* group, LLCamera& camera)
{
    return 1024.f;
}


void LLHUDBridge::shiftPos(const LLVector4a& vec)
{
    //don't shift hud bridges on region crossing
}