/** 
 * @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(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(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(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 (int 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 (int 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

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

	mFaces.reserve(face_count);
	for (U32 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) ? TRUE : FALSE;

	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);
	
		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)
			{
				continue;
			}

			if (!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
}