/**
 * @file llreflectionmap.cpp
 * @brief LLReflectionMap class implementation
 *
 * $LicenseInfo:firstyear=2022&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2022, 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 "llreflectionmap.h"
#include "pipeline.h"
#include "llviewerwindow.h"
#include "llviewerregion.h"

extern F32SecondsImplicit gFrameTimeSeconds;

LLReflectionMap::LLReflectionMap()
{
}

void LLReflectionMap::update(U32 resolution, U32 face)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
    mLastUpdateTime = gFrameTimeSeconds;
    llassert(mCubeArray.notNull());
    llassert(mCubeIndex != -1);
    llassert(LLPipeline::sRenderDeferred);
    
    // make sure we don't walk off the edge of the render target
    while (resolution > gPipeline.mRT->deferredScreen.getWidth() ||
        resolution > gPipeline.mRT->deferredScreen.getHeight())
    {
        resolution /= 2;
    }
    gViewerWindow->cubeSnapshot(LLVector3(mOrigin), mCubeArray, mCubeIndex, face, getNearClip(), getIsDynamic());
}

void LLReflectionMap::autoAdjustOrigin()
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;

    if (mGroup)
    {
        const LLVector4a* bounds = mGroup->getBounds();
        auto* node = mGroup->getOctreeNode();

        if (mGroup->getSpatialPartition()->mPartitionType == LLViewerRegion::PARTITION_TERRAIN)
        {
            // for terrain, make probes float a couple meters above the highest point in the surface patch
            mOrigin = bounds[0];
            mOrigin.getF32ptr()[2] += bounds[1].getF32ptr()[2] + 3.f;

            // update radius to encompass bounding box
            LLVector4a d;
            d.setAdd(bounds[0], bounds[1]);
            d.sub(mOrigin);
            mRadius = d.getLength3().getF32();
            mPriority = 1;
        }
        else if (mGroup->getSpatialPartition()->mPartitionType == LLViewerRegion::PARTITION_VOLUME)
        {
            mPriority = 1;
            // cast a ray towards 8 corners of bounding box
            // nudge origin towards center of empty space

            if (node->isLeaf() || node->getChildCount() > 1 || node->getData().size() > 0)
            { // use center of object bounding box for leaf nodes or nodes with multiple child nodes
                mOrigin = bounds[0];

                LLVector4a size = bounds[1];

                LLVector4a corners[] =
                {
                    { 1, 1, 1 },
                    { -1, 1, 1 },
                    { 1, -1, 1 },
                    { -1, -1, 1 },
                    { 1, 1, -1 },
                    { -1, 1, -1 },
                    { 1, -1, -1 },
                    { -1, -1, -1 }
                };

                for (int i = 0; i < 8; ++i)
                {
                    corners[i].mul(size);
                    corners[i].add(bounds[0]);
                }

                LLVector4a extents[2];
                extents[0].setAdd(bounds[0], bounds[1]);
                extents[1].setSub(bounds[0], bounds[1]);

                bool hit = false;
                for (int i = 0; i < 8; ++i)
                {
                    int face = -1;
                    LLVector4a intersection;
                    LLDrawable* drawable = mGroup->lineSegmentIntersect(bounds[0], corners[i], true, false, true, &face, &intersection);
                    if (drawable != nullptr)
                    {
                        hit = true;
                        update_min_max(extents[0], extents[1], intersection);
                    }
                    else
                    {
                        update_min_max(extents[0], extents[1], corners[i]);
                    }
                }

                if (hit)
                {
                    mOrigin.setAdd(extents[0], extents[1]);
                    mOrigin.mul(0.5f);
                }

                // make sure radius encompasses all objects
                LLSimdScalar r2 = 0.0;
                for (int i = 0; i < 8; ++i)
                {
                    LLVector4a v;
                    v.setSub(corners[i], mOrigin);

                    LLSimdScalar d = v.dot3(v);

                    if (d > r2)
                    {
                        r2 = d;
                    }
                }

                mRadius = llmax(sqrtf(r2.getF32()), 8.f);
            }
            else
            {
                // user placed probe
                mPriority = 2;

                // use center of octree node volume for nodes that are just branches without data
                mOrigin = node->getCenter();

                // update radius to encompass entire octree node volume
                mRadius = node->getSize().getLength3().getF32();

                //mOrigin = bounds[0];
                //mRadius = bounds[1].getLength3().getF32();

            }
        }
    }
    else if (mViewerObject)
    {
        mPriority = 64;
        mOrigin.load3(mViewerObject->getPositionAgent().mV);
        mRadius = mViewerObject->getScale().mV[0]*0.5f;
    }
}

bool LLReflectionMap::intersects(LLReflectionMap* other)
{
    // TODO: incorporate getBox
    LLVector4a delta;
    delta.setSub(other->mOrigin, mOrigin);

    F32 dist = delta.dot3(delta).getF32();

    F32 r2 = mRadius + other->mRadius;

    r2 *= r2;

    return dist < r2;
}

extern LLControlGroup gSavedSettings;

F32 LLReflectionMap::getAmbiance()
{
    F32 ret = 0.f;
    if (mViewerObject && mViewerObject->getVolume())
    {
        ret = ((LLVOVolume*)mViewerObject)->getReflectionProbeAmbiance();
    }

    return ret;
}

F32 LLReflectionMap::getNearClip()
{
    const F32 MINIMUM_NEAR_CLIP = 0.1f;

    F32 ret = 0.f;

    if (mViewerObject && mViewerObject->getVolume())
    {
        ret = ((LLVOVolume*)mViewerObject)->getReflectionProbeNearClip();
    }

    return llmax(ret, MINIMUM_NEAR_CLIP);
}

bool LLReflectionMap::getIsDynamic()
{
    if (gSavedSettings.getS32("RenderReflectionProbeDetail") > (S32) LLReflectionMapManager::DetailLevel::STATIC_ONLY &&
        mViewerObject && 
        mViewerObject->getVolume())
    {
        return ((LLVOVolume*)mViewerObject)->getReflectionProbeIsDynamic();
    }

    return false;
}

bool LLReflectionMap::getBox(LLMatrix4& box)
{ 
    if (mViewerObject)
    {
        LLVolume* volume = mViewerObject->getVolume();
        if (volume)
        {
            LLVOVolume* vobjp = (LLVOVolume*)mViewerObject;

            if (vobjp->getReflectionProbeIsBox())
            {
                glh::matrix4f mv(gGLModelView);
                glh::matrix4f scale;
                LLVector3 s = vobjp->getScale().scaledVec(LLVector3(0.5f, 0.5f, 0.5f));
                mRadius = s.magVec();
                scale.set_scale(glh::vec3f(s.mV));
                if (vobjp->mDrawable != nullptr)
                {
                    glh::matrix4f rm((F32*)vobjp->mDrawable->getWorldMatrix().mMatrix);

                    glh::matrix4f rt((F32*)vobjp->getRelativeXform().mMatrix);

                    mv = mv * rm * scale; // *rt;
                    mv = mv.inverse();

                    box = LLMatrix4(mv.m);

                    return true;
                }
            }
        }
    }

    return false;
}
