/**
 * @file llvowater.cpp
 * @brief LLVOWater class implementation
 *
 * $LicenseInfo:firstyear=2005&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 "llvowater.h"

#include "llviewercontrol.h"

#include "lldrawable.h"
#include "lldrawpoolwater.h"
#include "llface.h"
#include "llsky.h"
#include "llsurface.h"
#include "llviewercamera.h"
#include "llviewertexturelist.h"
#include "llviewerregion.h"
#include "llworld.h"
#include "pipeline.h"
#include "llspatialpartition.h"

///////////////////////////////////

template<class T> inline T LERP(T a, T b, F32 factor)
{
    return a + (b - a) * factor;
}

LLVOWater::LLVOWater(const LLUUID &id,
                     const LLPCode pcode,
                     LLViewerRegion *regionp) :
    LLStaticViewerObject(id, pcode, regionp),
    mRenderType(LLPipeline::RENDER_TYPE_WATER)
{
    // Terrain must draw during selection passes so it can block objects behind it.
    mbCanSelect = false;
    setScale(LLVector3(256.f, 256.f, 0.f)); // Hack for setting scale for bounding boxes/visibility.

    mIsEdgePatch = false;
}


void LLVOWater::markDead()
{
    LLViewerObject::markDead();
}


bool LLVOWater::isActive() const
{
    return false;
}


void LLVOWater::setPixelAreaAndAngle(LLAgent &agent)
{
    mAppAngle = 50;
    mPixelArea = 500*500;
}


// virtual
void LLVOWater::updateTextures()
{
}

// Never gets called
void  LLVOWater::idleUpdate(LLAgent &agent, const F64 &time)
{
}

LLDrawable *LLVOWater::createDrawable(LLPipeline *pipeline)
{
    pipeline->allocDrawable(this);
    mDrawable->setLit(false);
    mDrawable->setRenderType(mRenderType);

    LLDrawPoolWater *pool = (LLDrawPoolWater*) gPipeline.getPool(LLDrawPool::POOL_WATER);

    mDrawable->setNumFaces(1, pool, LLWorld::getInstance()->getDefaultWaterTexture());

    return mDrawable;
}

bool LLVOWater::updateGeometry(LLDrawable *drawable)
{
    LL_PROFILE_ZONE_SCOPED;
    LLFace *face;

    if (drawable->getNumFaces() < 1)
    {
        LLDrawPoolWater *poolp = (LLDrawPoolWater*) gPipeline.getPool(LLDrawPool::POOL_WATER);
        drawable->addFace(poolp, NULL);
    }
    face = drawable->getFace(0);
    if (!face)
    {
        return true;
    }

//  LLVector2 uvs[4];
//  LLVector3 vtx[4];

    LLStrider<LLVector3> verticesp, normalsp;
    LLStrider<LLVector2> texCoordsp;
    LLStrider<U16> indicesp;
    U16 index_offset;


    // A quad is 4 vertices and 6 indices (making 2 triangles)
    static const unsigned int vertices_per_quad = 4;
    static const unsigned int indices_per_quad = 6;

    S32 size_x = LLPipeline::sRenderTransparentWater ? 8 : 1;
    S32 size_y = LLPipeline::sRenderTransparentWater ? 8 : 1;

    const LLVector3& scale = getScale();
    size_x *= (S32)llmin(llround(scale.mV[0] / 256.f), 8);
    size_y *= (S32)llmin(llround(scale.mV[1] / 256.f), 8);

    const S32 num_quads = size_x * size_y;
    face->setSize(vertices_per_quad * num_quads,
                  indices_per_quad * num_quads);

    LLVertexBuffer* buff = face->getVertexBuffer();
    if (!buff ||
        buff->getNumIndices() != face->getIndicesCount() ||
        buff->getNumVerts() != face->getGeomCount() ||
        face->getIndicesStart() != 0 ||
        face->getGeomIndex() != 0)
    {
        buff = new LLVertexBuffer(LLDrawPoolWater::VERTEX_DATA_MASK);
        if (!buff->allocateBuffer(face->getGeomCount(), face->getIndicesCount()))
        {
            LL_WARNS() << "Failed to allocate Vertex Buffer on water update to "
                << face->getGeomCount() << " vertices and "
                << face->getIndicesCount() << " indices" << LL_ENDL;
        }
        face->setIndicesIndex(0);
        face->setGeomIndex(0);
        face->setVertexBuffer(buff);
    }

    index_offset = face->getGeometry(verticesp,normalsp,texCoordsp, indicesp);

    LLVector3 position_agent;
    position_agent = getPositionAgent();
    face->mCenterAgent = position_agent;
    face->mCenterLocal = position_agent;

    S32 x, y;
    F32 step_x = getScale().mV[0] / size_x;
    F32 step_y = getScale().mV[1] / size_y;

    const LLVector3 up(0.f, step_y * 0.5f, 0.f);
    const LLVector3 right(step_x * 0.5f, 0.f, 0.f);
    const LLVector3 normal(0.f, 0.f, 1.f);

    F32 size_inv_x = 1.f / size_x;
    F32 size_inv_y = 1.f / size_y;

    for (y = 0; y < size_y; y++)
    {
        for (x = 0; x < size_x; x++)
        {
            S32 toffset = index_offset + 4*(y*size_x + x);
            position_agent = getPositionAgent() - getScale() * 0.5f;
            position_agent.mV[VX] += (x + 0.5f) * step_x;
            position_agent.mV[VY] += (y + 0.5f) * step_y;

            position_agent.mV[VX] = (F32)llround(position_agent.mV[VX]);
            position_agent.mV[VY] = (F32)llround(position_agent.mV[VY]);

            *verticesp++  = position_agent - right + up;
            *verticesp++  = position_agent - right - up;
            *verticesp++  = position_agent + right + up;
            *verticesp++  = position_agent + right - up;

            *texCoordsp++ = LLVector2(x*size_inv_x, (y+1)*size_inv_y);
            *texCoordsp++ = LLVector2(x*size_inv_x, y*size_inv_y);
            *texCoordsp++ = LLVector2((x+1)*size_inv_x, (y+1)*size_inv_y);
            *texCoordsp++ = LLVector2((x+1)*size_inv_x, y*size_inv_y);

            *normalsp++   = normal;
            *normalsp++   = normal;
            *normalsp++   = normal;
            *normalsp++   = normal;

            *indicesp++ = toffset + 0;
            *indicesp++ = toffset + 1;
            *indicesp++ = toffset + 2;

            *indicesp++ = toffset + 1;
            *indicesp++ = toffset + 3;
            *indicesp++ = toffset + 2;
        }
    }

    buff->unmapBuffer();

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

void LLVOWater::initClass()
{
}

void LLVOWater::cleanupClass()
{
}

void setVecZ(LLVector3& v)
{
    v.mV[VX] = 0;
    v.mV[VY] = 0;
    v.mV[VZ] = 1;
}

void LLVOWater::setIsEdgePatch(const bool edge_patch)
{
    mIsEdgePatch = edge_patch;
}

void LLVOWater::updateSpatialExtents(LLVector4a &newMin, LLVector4a& newMax)
{
    LLVector4a pos;
    pos.load3(getPositionAgent().mV);
    LLVector4a scale;
    scale.load3(getScale().mV);
    scale.mul(0.5f);

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

    pos.setAdd(newMin,newMax);
    pos.mul(0.5f);

    mDrawable->setPositionGroup(pos);
}

U32 LLVOWater::getPartitionType() const
{
    if (mIsEdgePatch)
    {
        return LLViewerRegion::PARTITION_VOIDWATER;
    }

    return LLViewerRegion::PARTITION_WATER;
}

U32 LLVOVoidWater::getPartitionType() const
{
    return LLViewerRegion::PARTITION_VOIDWATER;
}

LLWaterPartition::LLWaterPartition(LLViewerRegion* regionp)
: LLSpatialPartition(0, false, regionp)
{
    mInfiniteFarClip = true;
    mDrawableType = LLPipeline::RENDER_TYPE_WATER;
    mPartitionType = LLViewerRegion::PARTITION_WATER;
}

LLVoidWaterPartition::LLVoidWaterPartition(LLViewerRegion* regionp) : LLWaterPartition(regionp)
{
    mOcclusionEnabled = false;
    mDrawableType = LLPipeline::RENDER_TYPE_VOIDWATER;
    mPartitionType = LLViewerRegion::PARTITION_VOIDWATER;
}