/**
 * @file llvopartgroup.cpp
 * @brief Group of particle systems
 *
 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "llviewerprecompiledheaders.h"

#include "llvopartgroup.h"

#include "lldrawpoolalpha.h"

#include "llfasttimer.h"
#include "message.h"
#include "v2math.h"

#include "llagentcamera.h"
#include "lldrawable.h"
#include "llface.h"
#include "llsky.h"
#include "llviewercamera.h"
#include "llviewerpartsim.h"
#include "llviewerregion.h"
#include "pipeline.h"
#include "llspatialpartition.h"

extern U64MicrosecondsImplicit gFrameTime;

void LLVOPartGroup::initClass()
{
}

//static
void LLVOPartGroup::restoreGL()
{

    //TODO: optimize out binormal mask here.  Specular and normal coords as well.
#if 0
    sVB = new LLVertexBuffer(VERTEX_DATA_MASK | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2);
    U32 count = LL_MAX_PARTICLE_COUNT;
    if (!sVB->allocateBuffer(count*4, count*6))
    {
        LL_WARNS() << "Failed to allocate Vertex Buffer to "
            << count*4 << " vertices and "
            << count * 6 << " indices" << LL_ENDL;
        // we are likelly to crash at following getTexCoord0Strider(), so unref and return
        sVB = NULL;
        return;
    }

    //indices and texcoords are always the same, set once
    LLStrider<U16> indicesp;

    LLStrider<LLVector4a> verticesp;

    sVB->getIndexStrider(indicesp);
    sVB->getVertexStrider(verticesp);

    LLVector4a v;
    v.set(0,0,0,0);


    U16 vert_offset = 0;

    for (U32 i = 0; i < LL_MAX_PARTICLE_COUNT; i++)
    {
        *indicesp++ = vert_offset + 0;
        *indicesp++ = vert_offset + 1;
        *indicesp++ = vert_offset + 2;

        *indicesp++ = vert_offset + 1;
        *indicesp++ = vert_offset + 3;
        *indicesp++ = vert_offset + 2;

        *verticesp++ = v;

        vert_offset += 4;
    }

    LLStrider<LLVector2> texcoordsp;
    sVB->getTexCoord0Strider(texcoordsp);

    for (U32 i = 0; i < LL_MAX_PARTICLE_COUNT; i++)
    {
        *texcoordsp++ = LLVector2(0.f, 1.f);
        *texcoordsp++ = LLVector2(0.f, 0.f);
        *texcoordsp++ = LLVector2(1.f, 1.f);
        *texcoordsp++ = LLVector2(1.f, 0.f);
    }

    sVB->unmapBuffer();
#endif

}

//static
void LLVOPartGroup::destroyGL()
{
}

bool ll_is_part_idx_allocated(S32 idx, S32* start, S32* end)
{
    /*while (start < end)
    {
        if (*start == idx)
        { //not allocated (in free list)
            return false;
        }
        ++start;
    }*/

    //allocated (not in free list)
    return false;
}

LLVOPartGroup::LLVOPartGroup(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp)
    :   LLAlphaObject(id, pcode, regionp),
        mViewerPartGroupp(NULL)
{
    setNumTEs(1);
    setTETexture(0, LLUUID::null);
    mbCanSelect = FALSE;            // users can't select particle systems
}


LLVOPartGroup::~LLVOPartGroup()
{
}

BOOL LLVOPartGroup::isActive() const
{
    return FALSE;
}

F32 LLVOPartGroup::getBinRadius()
{
    return mViewerPartGroupp->getBoxSide();
}

void LLVOPartGroup::updateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax)
{
    const LLVector3& pos_agent = getPositionAgent();

    LLVector4a scale;
    LLVector4a p;

    p.load3(pos_agent.mV);

    scale.splat(mScale.mV[0]+mViewerPartGroupp->getBoxSide()*0.5f);

    newMin.setSub(p, scale);
    newMax.setAdd(p,scale);

    llassert(newMin.isFinite3());
    llassert(newMax.isFinite3());

    llassert(p.isFinite3());
    mDrawable->setPositionGroup(p);
}

void LLVOPartGroup::idleUpdate(LLAgent &agent, const F64 &time)
{
}

void LLVOPartGroup::setPixelAreaAndAngle(LLAgent &agent)
{
    // mPixelArea is calculated during render
    F32 mid_scale = getMidScale();
    F32 range = (getRenderPosition()-LLViewerCamera::getInstance()->getOrigin()).length();

    if (range < 0.001f || isHUDAttachment())        // range == zero
    {
        mAppAngle = 180.f;
    }
    else
    {
        mAppAngle = (F32) atan2( mid_scale, range) * RAD_TO_DEG;
    }
}

void LLVOPartGroup::updateTextures()
{
    // Texture stats for particles need to be updated in a different way...
}


LLDrawable* LLVOPartGroup::createDrawable(LLPipeline *pipeline)
{
    pipeline->allocDrawable(this);
    mDrawable->setLit(FALSE);
    mDrawable->setRenderType(LLPipeline::RENDER_TYPE_PARTICLES);
    return mDrawable;
}

 const F32 MAX_PARTICLE_AREA_SCALE = 0.02f; // some tuned constant, limits on how much particle area to draw

 LLUUID LLVOPartGroup::getPartOwner(S32 idx)
 {
     LLUUID ret = LLUUID::null;

     if (idx < (S32) mViewerPartGroupp->mParticles.size())
     {
         ret = mViewerPartGroupp->mParticles[idx]->mPartSourcep->getOwnerUUID();
     }

     return ret;
 }

 LLUUID LLVOPartGroup::getPartSource(S32 idx)
 {
     LLUUID ret = LLUUID::null;

     if (idx < (S32) mViewerPartGroupp->mParticles.size())
     {
         LLViewerPart* part = mViewerPartGroupp->mParticles[idx];
         if (part && part->mPartSourcep.notNull() &&
             part->mPartSourcep->mSourceObjectp.notNull())
         {
             LLViewerObject* source = part->mPartSourcep->mSourceObjectp;
             ret = source->getID();
         }
     }

     return ret;
 }


F32 LLVOPartGroup::getPartSize(S32 idx)
{
    if (idx < (S32) mViewerPartGroupp->mParticles.size())
    {
        return mViewerPartGroupp->mParticles[idx]->mScale.mV[0];
    }

    return 0.f;
}

void LLVOPartGroup::getBlendFunc(S32 idx, LLRender::eBlendFactor& src, LLRender::eBlendFactor& dst)
{
    if (idx < (S32) mViewerPartGroupp->mParticles.size())
    {
        LLViewerPart* part = mViewerPartGroupp->mParticles[idx];
        src = (LLRender::eBlendFactor) part->mBlendFuncSource;
        dst = (LLRender::eBlendFactor) part->mBlendFuncDest;
    }
}

LLVector3 LLVOPartGroup::getCameraPosition() const
{
    return gAgentCamera.getCameraPositionAgent();
}

BOOL LLVOPartGroup::updateGeometry(LLDrawable *drawable)
{
    LL_PROFILE_ZONE_SCOPED;

    dirtySpatialGroup();

    S32 num_parts = mViewerPartGroupp->getCount();
    LLFace *facep;
    LLSpatialGroup* group = drawable->getSpatialGroup();
    if (!group && num_parts)
    {
        drawable->movePartition();
        group = drawable->getSpatialGroup();
    }

    if (group && group->isVisible())
    {
        dirtySpatialGroup();
    }

    if (!num_parts)
    {
        if (group && drawable->getNumFaces())
        {
            group->setState(LLSpatialGroup::GEOM_DIRTY);
        }
        drawable->setNumFaces(0, NULL, getTEImage(0));
        LLPipeline::sCompiles++;
        return TRUE;
    }

    if (!(gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_PARTICLES)))
    {
        return TRUE;
    }

    if (num_parts > drawable->getNumFaces())
    {
        drawable->setNumFacesFast(num_parts+num_parts/4, NULL, getTEImage(0));
    }

    F32 tot_area = 0;

    F32 max_area = LLViewerPartSim::getMaxPartCount() * MAX_PARTICLE_AREA_SCALE;
    F32 pixel_meter_ratio = LLViewerCamera::getInstance()->getPixelMeterRatio();
    pixel_meter_ratio *= pixel_meter_ratio;

    LLViewerPartSim::checkParticleCount(mViewerPartGroupp->mParticles.size()) ;

    S32 count=0;
    mDepth = 0.f;
    S32 i = 0 ;
    LLVector3 camera_agent = getCameraPosition();

    F32 max_scale = 0.f;


    for (i = 0 ; i < (S32)mViewerPartGroupp->mParticles.size(); i++)
    {
        const LLViewerPart *part = mViewerPartGroupp->mParticles[i];


        //remember the largest particle
        max_scale = llmax(max_scale, part->mScale.mV[0], part->mScale.mV[1]);

        if (part->mFlags & LLPartData::LL_PART_RIBBON_MASK)
        { //include ribbon segment length in scale
            const LLVector3* pos_agent = NULL;
            if (part->mParent)
            {
                pos_agent = &(part->mParent->mPosAgent);
            }
            else if (part->mPartSourcep.notNull())
            {
                pos_agent = &(part->mPartSourcep->mPosAgent);
            }

            if (pos_agent)
            {
                F32 dist = (*pos_agent-part->mPosAgent).length();

                max_scale = llmax(max_scale, dist);
            }
        }

        LLVector3 part_pos_agent(part->mPosAgent);
        LLVector3 at(part_pos_agent - camera_agent);


        F32 camera_dist_squared = at.lengthSquared();
        F32 inv_camera_dist_squared;
        if (camera_dist_squared > 1.f)
            inv_camera_dist_squared = 1.f / camera_dist_squared;
        else
            inv_camera_dist_squared = 1.f;

        llassert(llfinite(inv_camera_dist_squared));
        llassert(!llisnan(inv_camera_dist_squared));

        F32 area = part->mScale.mV[0] * part->mScale.mV[1] * inv_camera_dist_squared;
        tot_area = llmax(tot_area, area);

        if (tot_area > max_area)
        {
            break;
        }

        count++;

        facep = drawable->getFace(i);
        if (!facep)
        {
            LL_WARNS() << "No face found for index " << i << "!" << LL_ENDL;
            continue;
        }

        facep->setTEOffset(i);
        const F32 NEAR_PART_DIST_SQ = 5.f*5.f;  // Only discard particles > 5 m from the camera
        const F32 MIN_PART_AREA = .005f*.005f;  // only less than 5 mm x 5 mm at 1 m from camera

        if (camera_dist_squared > NEAR_PART_DIST_SQ && area < MIN_PART_AREA)
        {
            facep->setSize(0, 0);
            continue;
        }

        facep->setSize(4, 6);

        facep->setViewerObject(this);

        if (part->mFlags & LLPartData::LL_PART_EMISSIVE_MASK)
        {
            facep->setState(LLFace::FULLBRIGHT);
        }
        else
        {
            facep->clearState(LLFace::FULLBRIGHT);
        }

        facep->mCenterLocal = part->mPosAgent;
        facep->setFaceColor(part->mColor);
        facep->setTexture(part->mImagep);

        //check if this particle texture is replaced by a parcel media texture.
        if(part->mImagep.notNull() && part->mImagep->hasParcelMedia())
        {
            part->mImagep->getParcelMedia()->addMediaToFace(facep) ;
        }

        mPixelArea = tot_area * pixel_meter_ratio;
        const F32 area_scale = 10.f; // scale area to increase priority a bit
        facep->setVirtualSize(mPixelArea*area_scale);
    }
    for (i = count; i < drawable->getNumFaces(); i++)
    {
        LLFace* facep = drawable->getFace(i);
        if (!facep)
        {
            LL_WARNS() << "No face found for index " << i << "!" << LL_ENDL;
            continue;
        }
        facep->setTEOffset(i);
        facep->setSize(0, 0);
    }

    //record max scale (used to stretch bounding box for visibility culling)

    mScale.set(max_scale, max_scale, max_scale);

    mDrawable->movePartition();
    LLPipeline::sCompiles++;
    return TRUE;
}


BOOL LLVOPartGroup::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end,
                                          S32 face,
                                          BOOL pick_transparent,
                                          BOOL pick_rigged,
                                          BOOL pick_unselectable,
                                          S32* face_hit,
                                          LLVector4a* intersection,
                                          LLVector2* tex_coord,
                                          LLVector4a* normal,
                                          LLVector4a* bi_normal)
{
    LLVector4a dir;
    dir.setSub(end, start);

    F32 closest_t = 2.f;
    BOOL ret = FALSE;

    for (U32 idx = 0; idx < mViewerPartGroupp->mParticles.size(); ++idx)
    {
        const LLViewerPart &part = *((LLViewerPart*) (mViewerPartGroupp->mParticles[idx]));

        LLVector4a v[4];
        LLStrider<LLVector4a> verticesp;
        verticesp = v;

        getGeometry(part, verticesp);

        F32 a,b,t;
        if (LLTriangleRayIntersect(v[0], v[1], v[2], start, dir, a,b,t) ||
            LLTriangleRayIntersect(v[1], v[3], v[2], start, dir, a,b,t))
        {
            if (t >= 0.f &&
                t <= 1.f &&
                t < closest_t)
            {
                ret = TRUE;
                closest_t = t;
                if (face_hit)
                {
                    *face_hit = idx;
                }

                if (intersection)
                {
                    LLVector4a intersect = dir;
                    intersect.mul(closest_t);
                    intersection->setAdd(intersect, start);
                }
            }
        }
    }

    return ret;
}

void LLVOPartGroup::getGeometry(const LLViewerPart& part,
                                LLStrider<LLVector4a>& verticesp)
{
    if (part.mFlags & LLPartData::LL_PART_RIBBON_MASK)
    {
        LLVector4a axis, pos, paxis, ppos;
        F32 scale, pscale;

        pos.load3(part.mPosAgent.mV);
        axis.load3(part.mAxis.mV);
        scale = part.mScale.mV[0];

        if (part.mParent)
        {
            ppos.load3(part.mParent->mPosAgent.mV);
            paxis.load3(part.mParent->mAxis.mV);
            pscale = part.mParent->mScale.mV[0];
        }
        else
        { //use source object as position

            if (part.mPartSourcep->mSourceObjectp.notNull())
            {
                LLVector3 v = LLVector3(0,0,1);
                v *= part.mPartSourcep->mSourceObjectp->getRenderRotation();
                paxis.load3(v.mV);
                ppos.load3(part.mPartSourcep->mPosAgent.mV);
                pscale = part.mStartScale.mV[0];
            }
            else
            { //no source object, no parent, nothing to draw
                ppos = pos;
                pscale = scale;
                paxis = axis;
            }
        }

        LLVector4a p0, p1, p2, p3;

        scale *= 0.5f;
        pscale *= 0.5f;

        axis.mul(scale);
        paxis.mul(pscale);

        p0.setAdd(pos, axis);
        p1.setSub(pos,axis);
        p2.setAdd(ppos, paxis);
        p3.setSub(ppos, paxis);

        (*verticesp++) = p2;
        (*verticesp++) = p3;
        (*verticesp++) = p0;
        (*verticesp++) = p1;
    }
    else
    {
        LLVector4a part_pos_agent;
        part_pos_agent.load3(part.mPosAgent.mV);
        LLVector4a camera_agent;
        camera_agent.load3(getCameraPosition().mV);
        LLVector4a at;
        at.setSub(part_pos_agent, camera_agent);
        LLVector4a up(0, 0, 1);
        LLVector4a right;

        right.setCross3(at, up);
        right.normalize3fast();

        up.setCross3(right, at);
        up.normalize3fast();

        if (part.mFlags & LLPartData::LL_PART_FOLLOW_VELOCITY_MASK && !part.mVelocity.isExactlyZero())
        {
            LLVector4a normvel;
            normvel.load3(part.mVelocity.mV);
            normvel.normalize3fast();
            LLVector2 up_fracs;
            up_fracs.mV[0] = normvel.dot3(right).getF32();
            up_fracs.mV[1] = normvel.dot3(up).getF32();
            up_fracs.normalize();
            LLVector4a new_up;
            LLVector4a new_right;

            //new_up = up_fracs.mV[0] * right + up_fracs.mV[1]*up;
            LLVector4a t = right;
            t.mul(up_fracs.mV[0]);
            new_up = up;
            new_up.mul(up_fracs.mV[1]);
            new_up.add(t);

            //new_right = up_fracs.mV[1] * right - up_fracs.mV[0]*up;
            t = right;
            t.mul(up_fracs.mV[1]);
            new_right = up;
            new_right.mul(up_fracs.mV[0]);
            t.sub(new_right);

            up = new_up;
            right = t;
            up.normalize3fast();
            right.normalize3fast();
        }

        right.mul(0.5f*part.mScale.mV[0]);
        up.mul(0.5f*part.mScale.mV[1]);


        //HACK -- the verticesp->mV[3] = 0.f here are to set the texture index to 0 (particles don't use texture batching, maybe they should)
        // this works because there is actually a 4th float stored after the vertex position which is used as a texture index
        // also, somebody please VECTORIZE THIS

        LLVector4a ppapu;
        LLVector4a ppamu;

        ppapu.setAdd(part_pos_agent, up);
        ppamu.setSub(part_pos_agent, up);

        verticesp->setSub(ppapu, right);
        (*verticesp++).getF32ptr()[3] = 0.f;
        verticesp->setSub(ppamu, right);
        (*verticesp++).getF32ptr()[3] = 0.f;
        verticesp->setAdd(ppapu, right);
        (*verticesp++).getF32ptr()[3] = 0.f;
        verticesp->setAdd(ppamu, right);
        (*verticesp++).getF32ptr()[3] = 0.f;
    }
}



void LLVOPartGroup::getGeometry(S32 idx,
                                LLStrider<LLVector4a>& verticesp,
                                LLStrider<LLVector3>& normalsp,
                                LLStrider<LLVector2>& texcoordsp,
                                LLStrider<LLColor4U>& colorsp,
                                LLStrider<LLColor4U>& emissivep,
                                LLStrider<U16>& indicesp)
{
    if (idx >= (S32) mViewerPartGroupp->mParticles.size())
    {
        return;
    }

    const LLViewerPart &part = *((LLViewerPart*) (mViewerPartGroupp->mParticles[idx]));

    getGeometry(part, verticesp);

    LLColor4U pcolor;
    LLColor4U color = part.mColor;

    LLColor4U pglow;

    if (part.mFlags & LLPartData::LL_PART_RIBBON_MASK)
    { //make sure color blends properly
        if (part.mParent)
        {
            pglow = part.mParent->mGlow;
            pcolor = part.mParent->mColor;
        }
        else
        {
            pglow = LLColor4U(0, 0, 0, (U8) ll_round(255.f*part.mStartGlow));
            pcolor = part.mStartColor;
        }
    }
    else
    {
        pglow = part.mGlow;
        pcolor = color;
    }

    *colorsp++ = pcolor;
    *colorsp++ = pcolor;
    *colorsp++ = color;
    *colorsp++ = color;

    //if (pglow.mV[3] || part.mGlow.mV[3])
    { //only write glow if it is not zero
        *emissivep++ = pglow;
        *emissivep++ = pglow;
        *emissivep++ = part.mGlow;
        *emissivep++ = part.mGlow;
    }


    if (!(part.mFlags & LLPartData::LL_PART_EMISSIVE_MASK))
    { //not fullbright, needs normal
        LLVector3 normal = -LLViewerCamera::getInstance()->getXAxis();
        *normalsp++   = normal;
        *normalsp++   = normal;
        *normalsp++   = normal;
        *normalsp++   = normal;
    }
}

U32 LLVOPartGroup::getPartitionType() const
{
    return LLViewerRegion::PARTITION_PARTICLE;
}

LLParticlePartition::LLParticlePartition(LLViewerRegion* regionp)
: LLSpatialPartition(LLDrawPoolAlpha::VERTEX_DATA_MASK | LLVertexBuffer::MAP_TEXTURE_INDEX, TRUE, regionp)
{
    mRenderPass = LLRenderPass::PASS_ALPHA;
    mDrawableType = LLPipeline::RENDER_TYPE_PARTICLES;
    mPartitionType = LLViewerRegion::PARTITION_PARTICLE;
    mSlopRatio = 0.f;
    mLODPeriod = 1;
}

LLHUDParticlePartition::LLHUDParticlePartition(LLViewerRegion* regionp) :
    LLParticlePartition(regionp)
{
    mDrawableType = LLPipeline::RENDER_TYPE_HUD_PARTICLES;
    mPartitionType = LLViewerRegion::PARTITION_HUD_PARTICLE;
}

void LLParticlePartition::rebuildGeom(LLSpatialGroup* group)
{
    LL_PROFILE_ZONE_SCOPED;
    LL_PROFILE_GPU_ZONE("particle vbo");
    if (group->isDead() || !group->hasState(LLSpatialGroup::GEOM_DIRTY))
    {
        return;
    }

    if (group->changeLOD())
    {
        group->mLastUpdateDistance = group->mDistance;
        group->mLastUpdateViewAngle = group->mViewAngle;
    }

    group->clearDrawMap();

    //get geometry count
    U32 index_count = 0;
    U32 vertex_count = 0;

    addGeometryCount(group, vertex_count, index_count);


    if (vertex_count > 0 && index_count > 0)
    {
        group->mBuilt = 1.f;
        if (group->mVertexBuffer.isNull() ||
            group->mVertexBuffer->getNumVerts() < vertex_count || group->mVertexBuffer->getNumIndices() < index_count)
        {
            group->mVertexBuffer = new LLVertexBuffer(LLVOPartGroup::VERTEX_DATA_MASK);
            group->mVertexBuffer->allocateBuffer(vertex_count, index_count);

            // initialize index and texture coordinates only when buffer is reallocated
            U16* indicesp = (U16*)group->mVertexBuffer->mapIndexBuffer(0, index_count);

            U16 geom_idx = 0;
            for (U32 i = 0; i < index_count; i += 6)
            {
                *indicesp++ = geom_idx + 0;
                *indicesp++ = geom_idx + 1;
                *indicesp++ = geom_idx + 2;

                *indicesp++ = geom_idx + 1;
                *indicesp++ = geom_idx + 3;
                *indicesp++ = geom_idx + 2;

                geom_idx += 4;
            }

            LLStrider<LLVector2> texcoordsp;

            group->mVertexBuffer->getTexCoord0Strider(texcoordsp);

            for (U32 i = 0; i < vertex_count; i += 4)
            {
                *texcoordsp++ = LLVector2(0.f, 1.f);
                *texcoordsp++ = LLVector2(0.f, 0.f);
                *texcoordsp++ = LLVector2(1.f, 1.f);
                *texcoordsp++ = LLVector2(1.f, 0.f);
            }

        }


        getGeometry(group);
    }
    else
    {
        group->mVertexBuffer = NULL;
        group->mBufferMap.clear();
    }

    group->mLastUpdateTime = gFrameTimeSeconds;
    group->clearState(LLSpatialGroup::GEOM_DIRTY);
}

void LLParticlePartition::addGeometryCount(LLSpatialGroup* group, U32& vertex_count, U32& index_count)
{
    mFaceList.clear();

    LLViewerCamera* camera = LLViewerCamera::getInstance();
    for (LLSpatialGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i)
    {
        LLDrawable* drawablep = (LLDrawable*)(*i)->getDrawable();

        if (!drawablep || drawablep->isDead())
        {
            continue;
        }

        LLAlphaObject* obj = (LLAlphaObject*) drawablep->getVObj().get();
        obj->mDepth = 0.f;

        U32 count = 0;
        for (S32 j = 0; j < drawablep->getNumFaces(); ++j)
        {
            drawablep->updateFaceSize(j);

            LLFace* facep = drawablep->getFace(j);
            if ( !facep || !facep->hasGeometry())
            {
                continue;
            }

            vertex_count += facep->getGeomCount();
            index_count += facep->getIndicesCount();

            count++;
            facep->mDistance = (facep->mCenterLocal - camera->getOrigin()) * camera->getAtAxis();
            obj->mDepth += facep->mDistance;

            mFaceList.push_back(facep);
            llassert(facep->getIndicesCount() < 65536);
        }

        obj->mDepth /= count;
    }
}


void LLParticlePartition::getGeometry(LLSpatialGroup* group)
{
    LL_PROFILE_ZONE_SCOPED;

    std::sort(mFaceList.begin(), mFaceList.end(), LLFace::CompareDistanceGreater());

    group->clearDrawMap();

    LLVertexBuffer* buffer = group->mVertexBuffer;

    LLStrider<LLVector4a> verticesp;
    LLStrider<LLVector3> normalsp;
    LLStrider<LLColor4U> colorsp;
    LLStrider<LLColor4U> emissivep;

    buffer->getVertexStrider(verticesp);
    buffer->getNormalStrider(normalsp);
    buffer->getColorStrider(colorsp);
    buffer->getEmissiveStrider(emissivep);

    S32 geom_idx = 0;
    S32 indices_idx = 0;

    LLSpatialGroup::drawmap_elem_t& draw_vec = group->mDrawMap[mRenderPass];

    for (std::vector<LLFace*>::iterator i = mFaceList.begin(); i != mFaceList.end(); ++i)
    {
        LLFace* facep = *i;
        LLAlphaObject* object = (LLAlphaObject*) facep->getViewerObject();

        facep->setGeomIndex(geom_idx);
        facep->setIndicesIndex(indices_idx);

        LLStrider<LLVector4a> cur_vert = verticesp + geom_idx;
        LLStrider<LLVector3> cur_norm = normalsp + geom_idx;
        LLStrider<LLColor4U> cur_col = colorsp + geom_idx;
        LLStrider<LLColor4U> cur_glow = emissivep + geom_idx;

        // not actually used
        LLStrider<LLVector2> cur_tc;
        LLStrider<U16> cur_idx;


        geom_idx += 4;
        indices_idx += 6;

        LLColor4U* start_glow = cur_glow.get();

        object->getGeometry(facep->getTEOffset(), cur_vert, cur_norm, cur_tc, cur_col, cur_glow, cur_idx);

        bool has_glow = FALSE;

        if (cur_glow.get() != start_glow)
        {
            has_glow = true;
        }

        llassert(facep->getGeomCount() == 4);
        llassert(facep->getIndicesCount() == 6);

        S32 idx = draw_vec.size()-1;

        bool fullbright = facep->isState(LLFace::FULLBRIGHT);

        bool batched = false;

        LLRender::eBlendFactor bf_src = LLRender::BF_SOURCE_ALPHA;
        LLRender::eBlendFactor bf_dst = LLRender::BF_ONE_MINUS_SOURCE_ALPHA;

        object->getBlendFunc(facep->getTEOffset(), bf_src, bf_dst);


        if (idx >= 0)
        {
            LLDrawInfo* info = draw_vec[idx];

            if (info->mTexture == facep->getTexture() &&
                info->mHasGlow == has_glow &&
                info->mFullbright == fullbright &&
                info->mBlendFuncDst == bf_dst &&
                info->mBlendFuncSrc == bf_src)
            {
                if (draw_vec[idx]->mEnd == facep->getGeomIndex()-1)
                {
                    batched = true;
                    info->mCount += facep->getIndicesCount();
                    info->mEnd += facep->getGeomCount();
                }
                else if (draw_vec[idx]->mStart == facep->getGeomIndex()+facep->getGeomCount()+1)
                {
                    batched = true;
                    info->mCount += facep->getIndicesCount();
                    info->mStart -= facep->getGeomCount();
                    info->mOffset = facep->getIndicesStart();
                }
            }
        }

        if (!batched)
        {
            U32 start = facep->getGeomIndex();
            U32 end = start + facep->getGeomCount()-1;
            U32 offset = facep->getIndicesStart();
            U32 count = facep->getIndicesCount();
            LLDrawInfo* info = new LLDrawInfo(start,end,count,offset,facep->getTexture(),
                buffer, fullbright);

            info->mBlendFuncDst = bf_dst;
            info->mBlendFuncSrc = bf_src;
            info->mHasGlow = has_glow;
            draw_vec.push_back(info);
            //for alpha sorting
            facep->setDrawInfo(info);
        }
    }

    buffer->unmapBuffer();
    mFaceList.clear();
}

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

U32 LLVOHUDPartGroup::getPartitionType() const
{
    return LLViewerRegion::PARTITION_HUD_PARTICLE;
}

LLDrawable* LLVOHUDPartGroup::createDrawable(LLPipeline *pipeline)
{
    pipeline->allocDrawable(this);
    mDrawable->setLit(FALSE);
    mDrawable->setRenderType(LLPipeline::RENDER_TYPE_HUD_PARTICLES);
    return mDrawable;
}

LLVector3 LLVOHUDPartGroup::getCameraPosition() const
{
    return LLVector3(-1,0,0);
}