/**
 * @file llphysicsshapebuilder.cpp
 * @brief Generic system to convert LL(Physics)VolumeParams to physics shapes
 *
 * $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 "llphysicsshapebuilderutil.h"

/* static */
void LLPhysicsShapeBuilderUtil::determinePhysicsShape( const LLPhysicsVolumeParams& volume_params, const LLVector3& scale, PhysicsShapeSpecification& specOut)
{
    const LLProfileParams& profile_params = volume_params.getProfileParams();
    const LLPathParams& path_params = volume_params.getPathParams();

    specOut.mScale = scale;

    const F32 avgScale = ( scale[VX] + scale[VY] + scale[VZ] )/3.0f;

    // count the scale elements that are small
    S32 min_size_counts = 0;
    for (S32 i = 0; i < 3; ++i)
    {
        if (scale[i] < SHAPE_BUILDER_CONVEXIFICATION_SIZE)
        {
            ++min_size_counts;
        }
    }

    const bool profile_complete = ( profile_params.getBegin() <= SHAPE_BUILDER_IMPLICIT_THRESHOLD_PATH_CUT/avgScale ) &&
        ( profile_params.getEnd() >= (1.0f - SHAPE_BUILDER_IMPLICIT_THRESHOLD_PATH_CUT/avgScale) );

    const bool path_complete =  ( path_params.getBegin() <= SHAPE_BUILDER_IMPLICIT_THRESHOLD_PATH_CUT/avgScale ) &&
        ( path_params.getEnd() >= (1.0f - SHAPE_BUILDER_IMPLICIT_THRESHOLD_PATH_CUT/avgScale) );

    const bool simple_params = ( volume_params.getHollow() <= SHAPE_BUILDER_IMPLICIT_THRESHOLD_HOLLOW/avgScale )
        && ( fabs(path_params.getShearX()) <= SHAPE_BUILDER_IMPLICIT_THRESHOLD_SHEAR/avgScale )
        && ( fabs(path_params.getShearY()) <= SHAPE_BUILDER_IMPLICIT_THRESHOLD_SHEAR/avgScale )
        && ( !volume_params.isMeshSculpt() && !volume_params.isSculpt() );

    if (simple_params && profile_complete)
    {
        // Try to create an implicit shape or convexified
        bool no_taper = ( fabs(path_params.getScaleX() - 1.0f) <= SHAPE_BUILDER_IMPLICIT_THRESHOLD_TAPER/avgScale )
            && ( fabs(path_params.getScaleY() - 1.0f) <= SHAPE_BUILDER_IMPLICIT_THRESHOLD_TAPER/avgScale );

        bool no_twist = ( fabs(path_params.getTwistBegin()) <= SHAPE_BUILDER_IMPLICIT_THRESHOLD_TWIST/avgScale )
            && ( fabs(path_params.getTwistEnd()) <= SHAPE_BUILDER_IMPLICIT_THRESHOLD_TWIST/avgScale);

        // Box
        if(
            ( profile_params.getCurveType() == LL_PCODE_PROFILE_SQUARE )
            && ( path_params.getCurveType() == LL_PCODE_PATH_LINE )
            && no_taper
            && no_twist
            )
        {
            specOut.mType = PhysicsShapeSpecification::BOX;
            if ( path_complete )
            {
                return;
            }
            else
            {
                // Side lengths
                specOut.mScale[VX] = llmax( scale[VX], SHAPE_BUILDER_MIN_GEOMETRY_SIZE );
                specOut.mScale[VY] = llmax( scale[VY], SHAPE_BUILDER_MIN_GEOMETRY_SIZE );
                specOut.mScale[VZ] = llmax( scale[VZ] * (path_params.getEnd() - path_params.getBegin()), SHAPE_BUILDER_MIN_GEOMETRY_SIZE );

                specOut.mCenter.set( 0.f, 0.f, 0.5f * scale[VZ] * ( path_params.getEnd() + path_params.getBegin() - 1.0f ) );
                return;
            }
        }

        // Sphere
        if(     path_complete
            && ( profile_params.getCurveType() == LL_PCODE_PROFILE_CIRCLE_HALF )
            && ( path_params.getCurveType() == LL_PCODE_PATH_CIRCLE )
            && ( fabs(volume_params.getTaper()) <= SHAPE_BUILDER_IMPLICIT_THRESHOLD_TAPER/avgScale )
            && no_twist
            )
        {
            if (   ( scale[VX] == scale[VZ] )
                && ( scale[VY] == scale[VZ] ) )
            {
                // perfect sphere
                specOut.mType   = PhysicsShapeSpecification::SPHERE;
                specOut.mScale  = scale;
                return;
            }
            else if (min_size_counts > 1)
            {
                // small or narrow sphere -- we can boxify
                for (S32 i=0; i<3; ++i)
                {
                    if (specOut.mScale[i] < SHAPE_BUILDER_CONVEXIFICATION_SIZE)
                    {
                        // reduce each small dimension size to split the approximation errors
                        specOut.mScale[i] *= 0.75f;
                    }
                }
                specOut.mType  = PhysicsShapeSpecification::BOX;
                return;
            }
        }

        // Cylinder
        if(    (scale[VX] == scale[VY])
            && ( profile_params.getCurveType() == LL_PCODE_PROFILE_CIRCLE )
            && ( path_params.getCurveType() == LL_PCODE_PATH_LINE )
            && ( volume_params.getBeginS() <= SHAPE_BUILDER_IMPLICIT_THRESHOLD_PATH_CUT/avgScale )
            && ( volume_params.getEndS() >= (1.0f - SHAPE_BUILDER_IMPLICIT_THRESHOLD_PATH_CUT/avgScale) )
            && no_taper
            )
        {
            if (min_size_counts > 1)
            {
                // small or narrow sphere -- we can boxify
                for (S32 i=0; i<3; ++i)
                {
                    if (specOut.mScale[i] < SHAPE_BUILDER_CONVEXIFICATION_SIZE)
                    {
                        // reduce each small dimension size to split the approximation errors
                        specOut.mScale[i] *= 0.75f;
                    }
                }

                specOut.mType = PhysicsShapeSpecification::BOX;
            }
            else
            {
                specOut.mType = PhysicsShapeSpecification::CYLINDER;
                F32 length = (volume_params.getPathParams().getEnd() - volume_params.getPathParams().getBegin()) * scale[VZ];

                specOut.mScale[VY] = specOut.mScale[VX];
                specOut.mScale[VZ] = length;
                // The minus one below fixes the fact that begin and end range from 0 to 1 not -1 to 1.
                specOut.mCenter.set( 0.f, 0.f, 0.5f * (volume_params.getPathParams().getBegin() + volume_params.getPathParams().getEnd() - 1.f) * scale[VZ] );
            }

            return;
        }
    }

    if (    (min_size_counts == 3 )
        // Possible dead code here--who wants to take it out?
        ||  (path_complete
                && profile_complete
                && ( path_params.getCurveType() == LL_PCODE_PATH_LINE )
                && (min_size_counts > 1 ) )
        )
    {
        // it isn't simple but
        // we might be able to convexify this shape if the path and profile are complete
        // or the path is linear and both path and profile are complete --> we can boxify it
        specOut.mType = PhysicsShapeSpecification::BOX;
        specOut.mScale = scale;
        return;
    }

    // Special case for big, very thin objects - bump the small dimensions up to the COLLISION_TOLERANCE
    if (min_size_counts == 1        // One dimension is small
        && avgScale > 3.f)          //  ... but others are fairly large
    {
        for (S32 i = 0; i < 3; ++i)
        {
            specOut.mScale[i] = llmax( specOut.mScale[i], COLLISION_TOLERANCE );
        }
    }

    if ( volume_params.shouldForceConvex() )
    {
        // Server distinguishes between convex of a prim vs isSculpt, but we don't care.
        specOut.mType = PhysicsShapeSpecification::USER_CONVEX;
    }
    // Make a simpler convex shape if we can.
    else if (volume_params.isConvex()           // is convex
            || min_size_counts > 1 )            // two or more small dimensions
    {
        specOut.mType = PhysicsShapeSpecification::PRIM_CONVEX;
    }
    else if (volume_params.isMeshSculpt() &&
             // Check overall dimensions, not individual triangles.
             (scale.mV[0] < SHAPE_BUILDER_USER_MESH_CONVEXIFICATION_SIZE ||
              scale.mV[1] < SHAPE_BUILDER_USER_MESH_CONVEXIFICATION_SIZE ||
              scale.mV[2] < SHAPE_BUILDER_USER_MESH_CONVEXIFICATION_SIZE
              ) )
    {
        // Server distinguishes between user-specified or default convex mesh, vs server's thin-triangle override, but we don't.
        specOut.mType = PhysicsShapeSpecification::PRIM_CONVEX;
    }
    else if ( volume_params.isSculpt() ) // Is a sculpt of any kind (mesh or legacy)
    {
        specOut.mType = volume_params.isMeshSculpt() ? PhysicsShapeSpecification::USER_MESH : PhysicsShapeSpecification::SCULPT;
    }
    else // Resort to mesh
    {
        specOut.mType = PhysicsShapeSpecification::PRIM_MESH;
    }
}