/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//  plRealTimeLights.cpp - Implementation for some MAX RT lights             //
//  Cyan, Inc.                                                               //
//                                                                           //
//// Version History //////////////////////////////////////////////////////////
//                                                                           //
//  8.2.2001 mcn - Created.                                                  //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

#include "HeadSpin.h"
#include "plRealTimeLights.h"

#include "MaxPlasmaMtls/Layers/plLayerTex.h"
#include "MaxPlasmaMtls/Layers/plLayerTexBitmapPB.h"
#include "MaxComponent/plMaxAnimUtils.h"
#include "plGLight/plLightKonstants.h"

void DummyLightCodeIncludeFunc() {}


//// Static ClassDesc2 Get Functions //////////////////////////////////////////

plRTOmniLightDesc   plRTOmniLightDesc::fStaticDesc;
plRTSpotLightDesc   plRTSpotLightDesc::fStaticDesc;
plRTDirLightDesc    plRTDirLightDesc::fStaticDesc;


//// ParamBlock2 DlgProc Functions ////////////////////////////////////////////

const char* DecayLevel[] = {
                            "None",
                            "Inverse",
                            "Inverse Square",
                            nullptr
                            };

const char* ShadowState[] = {
                            "Shadow Map",
                            nullptr
                            };


class LightDlgProc : public plBaseLightProc
{
protected:
    void    IValidateSpinners( TimeValue t, WPARAM wParam, IParamBlock2 *pb, IParamMap2 *map )
    {
        /// Make sure falloff is >= hotspot (adjust the one we're not editing)
        float   hotspot, falloff;
        hotspot = pb->GetFloat( plRTLightBase::kHotSpot, t );
        falloff = pb->GetFloat( plRTLightBase::kFallOff, t );

        if( falloff < hotspot )
        {
            if( LOWORD( wParam ) == IDC_LHOTSIZESPINNER )
                pb->SetValue( plRTLightBase::kFallOff, t, hotspot );
            else
                pb->SetValue( plRTLightBase::kHotSpot, t, falloff );

            map->Invalidate( plRTLightBase::kHotSpot );
            map->Invalidate( plRTLightBase::kFallOff );
        }
        IBuildLightMesh( (plRTLightBase *)pb->GetOwner(), falloff );
    }

public:
    INT_PTR DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) override
    {
        IParamBlock2 *pb = map->GetParamBlock();
        plRTLightBase *gl = (plRTLightBase *) pb->GetOwner();

        switch (msg)
        {
            case WM_COMMAND:
                if(LOWORD(wParam) == IDC_PROJ_MAPNAME )
                {
                    if( gl->ClassID() != RTOMNI_LIGHT_CLASSID )
//                  if(pb->GetInt(plRTLightBase::kLightType) != plRTLightBase::RT_OMNI)
                    
                    //  map->SetValue(plRTSpotLight::kProjMapTexButton, t, 
                        //gl->SetProjMap(
                        map->Invalidate(plRTSpotLight::kProjMapTexButton);
                    return FALSE;
                }
                else if( LOWORD( wParam ) == IDC_LHOTSIZE || LOWORD( wParam ) == IDC_LFALLOFF )
                {
                    if( HIWORD( wParam ) == EN_CHANGE )
                        IValidateSpinners( t, wParam, pb, map );
                }
                break;

            case CC_SPINNER_CHANGE:      
                if( LOWORD( wParam ) == IDC_LHOTSIZESPINNER || LOWORD( wParam ) == IDC_LFALLOFFSPINNER )
                    IValidateSpinners( t, wParam, pb, map );
                break;
        }

        return plBaseLightProc::DlgProc( t, map, hWnd, msg, wParam, lParam );;
    }
    void DeleteThis() override { }
};
static LightDlgProc gLiteDlgProc;

// Having these includes down here is a hack.
#include "plRealTimeLightsPBDec.h"
#include "plRTObjLightDesc.h"

///////////////////////////////////////////////////////////////////////
//
//  Light Descriptors declared below for the different plRTLights...
//
//
//
//

//--- Base Light Class derived from the ObjLightDesc 

#define COS_45 0.7071067f
#define COS_45_2X 1.4142136f

static float stepFactor[] = {50.0f,80.0f,140.0f};
#define MAXSTEPS 1000


BaseObjLight::BaseObjLight(INode *n) : ObjLightDesc(n) 
{
    ObjectState os = n->EvalWorldState(TimeValue(0));
    assert(os.obj->SuperClassID()==LIGHT_CLASS_ID);
    gl = (os.obj->GetInterface(I_MAXSCRIPTPLUGIN) != nullptr) ? (plRTLightBase*)os.obj->GetReference(0) : (plRTLightBase*)os.obj;  // JBW 4/7/99
}   

static Color blackCol(0,0,0);

int BaseObjLight::Update(TimeValue t, const RendContext& rc, RenderGlobalContext * rgc, BOOL shadows, BOOL shadowGeomChanged) {
    ObjLightDesc::Update(t,rc,rgc,shadows,shadowGeomChanged);
    intensCol = ls.intens*ls.color*rc.GlobalLightLevel();
    ObjectState os = inode->EvalWorldState(t);
    plRTLightBase* lob = (plRTLightBase *)os.obj;       
    contrast = 0; //lob->GetParamBlock2()->GetFloat(plRTLightBase::kContrast, t);
    diffSoft = 0; //lob->GetDiffuseSoft(t)/100.0f;

    float a = .5; //contrast/204.0f + 0.5f; // so "a" varies from .5 to .99
    kA = (2.0f-1.0f/a);
    kB = 1.0f-kA;
    diffSoften = false; //lob->GetParamBlock2()->GetInt(plRTLightBase::kDiffOn, t);     //GetSoftenDiffuse();

    return 1;
}






//--- Omni Light ------------------------------------------------


OmniLight::OmniLight(INode *inode, BOOL forceShadowBuf ) : BaseObjLight(inode){

    //projector = /*doShadows =  shadowRay =*/  FALSE; 
    //projMap = nullptr;
    needMultiple = FALSE;

}

OmniLight::~OmniLight() 
{

}




int OmniLight::UpdateViewDepParams(const Matrix3& worldToCam) {
    BaseObjLight::UpdateViewDepParams(worldToCam);
    return 1;
    }

static Point3 MapToDir(Point3 p, int k) {
    switch(k) {
        case 0: return Point3(  p.z, p.y, -p.x); // +X
        case 1: return Point3( -p.z, p.y,  p.x); // -X
        case 2: return Point3(  p.x, p.z, -p.y); // +Y 
        case 3: return Point3(  p.x,-p.z,  p.y); // -Y
        case 4: return Point3( -p.x, p.y, -p.z); // +Z
        case 5: return p;                        // -Z
        }
    return p;
    }

static void GetMatrixForDir(Matrix3 &origm, Matrix3 &tm, int k ) {
    tm = origm;
    switch(k) {
        case 0: tm.PreRotateY(-hsConstants::half_pi<float>); break;  // Map 0: +X axis
        case 1: tm.PreRotateY( hsConstants::half_pi<float>); break;  // Map 1: -X axis
        case 2: tm.PreRotateX( hsConstants::half_pi<float>); break;  // Map 2: +Y axis
        case 3: tm.PreRotateX(-hsConstants::half_pi<float>); break;  // Map 3: -Y axis
        case 4: tm.PreRotateY( hsConstants::pi<float>);      break;  // Map 4: +Z axis
        case 5:                                              break;  // Map 5: -Z axis
        }
    }

static int WhichDir(Point3 &p) {
    int j = MaxComponent(p);  // the component with the maximum abs value
    return  (p[j]<0.0f) ? 2*j+1 : 2*j;
    }

int OmniLight::Update(TimeValue t, const RendContext & rc,
        RenderGlobalContext *rgc, BOOL shadows, BOOL shadowGeomChanged)
{
    BaseObjLight::Update(t,rc,rgc,shadows,shadowGeomChanged);   

    ObjectState os = inode->EvalWorldState(t);
    LightObject* lob = (LightObject *)os.obj;       
    assert(os.obj->SuperClassID()==LIGHT_CLASS_ID);
    plRTOmniLight* gl = (lob->GetInterface(I_MAXSCRIPTPLUGIN) != nullptr) ? (plRTOmniLight*)lob->GetReference(0) : (plRTOmniLight*)lob;  // JBW 4/7/99

    decayType = gl->GetDecayType(); 
    decayRadius = gl->GetDecayRadius(t);

    fov = HALFPI; // 90 degree fov
    int res=1;
    if(gl->GetTex())
        gl->GetTex()->Update(t, FOREVER);
    //projector =  gl->GetProjector();
    //if (projector){
    //  projMap = gl->GetProjMap();
    //  if( projMap ) projMap->Update(t,FOREVER);
    //}

    return res;
}



////------------------------------------------------------------------
//      
//
//          SpotLight descriptors.....
//
//
//
//
//

SpotLight::SpotLight(INode *inode, BOOL forceShadowBuf ):BaseObjLight(inode) 
{
    projMap = nullptr;
}

int SpotLight::Update(TimeValue t, const RendContext &rc, RenderGlobalContext *rgc, BOOL shadows, BOOL shadowGeomChanged)
{
    int res = 1;
    BaseObjLight::Update(t,rc,rgc,shadows, shadowGeomChanged);

    float hs = DegToRad(ls.hotsize);
    float fs = DegToRad(ls.fallsize);
    fall_tan = (float)tan(fs/2.0f);
    hot_cos = (float)cos(hs/2.0f);
    fall_cos =(float)cos(fs/2.0f);
    fall_sin = (float)sin(fs/2.0f);
    hotpct = ls.hotsize/ls.fallsize;
    ihotpct = 1.0f - hotpct;        

    ObjectState os = inode->EvalWorldState(t);
    LightObject* lob = (LightObject *)os.obj;       
    assert(os.obj->SuperClassID()==LIGHT_CLASS_ID);
    plRTLightBase* gl = (lob->GetInterface(I_MAXSCRIPTPLUGIN) != nullptr) ? (plRTLightBase*)lob->GetReference(0) : (plRTLightBase*)lob;  // JBW 4/7/99

    decayType = gl->GetDecayType(); 
    decayRadius = gl->GetDecayRadius(t);

    projector =  gl->GetProjector();
    fov = std::max(fs, hs);

    float aspect = 1.0f;
     
    fov = 2.0f* (float)atan(tan(fov*0.5f)*sqrt(aspect));
    zfac = -sz2 /(float)tan(0.5*(double)fov);
    xscale = zfac;                              
    yscale = -zfac*aspect;
    curve =(float)fabs(1.0f/xscale); 

    rectv0.y = fall_sin * (float)sqrt(aspect);
    rectv1.y = fall_sin / (float)sqrt(aspect);

    rectv0.x = rectv1.x = fall_cos;
    rectv0 = Normalize(rectv0);
    rectv1 = Normalize(rectv1);

    Interval v;
    if (projector){
        projMap = gl->GetProjMap();
        if( projMap ) projMap->Update(t,v);
    }

    return res;
}

int  SpotLight::UpdateViewDepParams(const Matrix3& worldToCam) 
{
    BaseObjLight::UpdateViewDepParams(worldToCam);
    lightDir = -FNormalize(lightToCam.GetRow(2));
    return 1;
}

BOOL SpotLight::IsFacingLight(Point3 &dir)
{
    return dir.z>0.0f;
}



//--- Directional Light ------------------------------------------------

DirLight::DirLight(INode *inode, BOOL forceShadowBuf ) : BaseObjLight(inode)
{
    projMap = nullptr;
}

int DirLight::Update(TimeValue t, const RendContext &rc, 
        RenderGlobalContext *rgc, BOOL shadows, BOOL shadowGeomChanged)
{
    int res = 1;
    BaseObjLight::Update(t,rc,rgc,shadows,shadowGeomChanged);
    hotsz = ls.hotsize;
    fallsz = ls.fallsize;
    fallsq = fallsz*fallsz;
    hotpct = ls.hotsize/ls.fallsize;
    ihotpct = 1.0f - hotpct;

    ObjectState os = inode->EvalWorldState(t);
    LightObject* lob = (LightObject *)os.obj;       
    assert(os.obj->SuperClassID()==LIGHT_CLASS_ID);
    plRTDirLight* gl = (lob->GetInterface(I_MAXSCRIPTPLUGIN) != nullptr) ? (plRTDirLight*)lob->GetReference(0) : (plRTDirLight*)lob;  // JBW 4/7/99

    //projector =  gl->GetProjector();

    aspect = 1.0f;

    //if (projector){
    //      projMap = gl->GetProjMap();
    //      if( projMap ) projMap->Update(t,FOREVER);
    //  }
    return res;
};

int DirLight::UpdateViewDepParams(const Matrix3& worldToCam) {
    BaseObjLight::UpdateViewDepParams(worldToCam);
    lightDir = FNormalize(lightToCam.GetRow(2));

    return 1;
    }







////////////////////////////////////////////////////////////////////////////////////
//
//  plRTOmni Stuff below
//
//
//
//
//

plRTOmniLight::plRTOmniLight()
{
    fIP = nullptr;
    fLightPB = nullptr;
    fClassDesc = plRTOmniLightDesc::GetDesc();
    fClassDesc->MakeAutoParamBlocks(this);

    fLightPB->SetValue(kLightColor, 0,  Color(255,255,255));
    SetHSVColor(0, Point3(255, 255, 255));
    
    fTex = nullptr;

    meshBuilt = 0; 
    
    IBuildMeshes(true);
}

ObjLightDesc *plRTOmniLight::CreateLightDesc(INode *n, BOOL forceShadowBuf)
{
    return new OmniLight( n, forceShadowBuf );
}


RefTargetHandle plRTOmniLight::Clone(RemapDir &remap)
{

    plRTOmniLight *obj = new plRTOmniLight;//(plRTLightBase*) fClassDesc->Create(false);
    obj->ReplaceReference(kRefSpotLight, fLightPB->Clone(remap));
    BaseClone(this, obj, remap);

    return obj;
}

void plRTOmniLight::IBuildMeshes( BOOL isnew ) 
{
    BuildStaticMeshes();
    fMesh = staticMesh[ plRTLightBase::RT_OMNI ];
}

//// DrawConeAndLine /////////////////////////////////////////////////////////

int     plRTOmniLight::DrawConeAndLine( TimeValue t, INode* inode, GraphicsWindow *gw, int drawing ) 
{
    float   atOneHalfDist;
    Matrix3 tm = inode->GetObjectTM( t );


    gw->setTransform( tm );
    gw->clearHitCode();

    if( ( extDispFlags & EXT_DISP_ONLY_SELECTED ) )
    {
        if( GetUseAtten() )
        {
            // Draw hotspot as the point at which light is 1/2 intensity (just to help the visual)
            gw->setColor( LINE_COLOR, GetUIColor( COLOR_HOTSPOT ) );

            if( fLightPB->GetInt( kAttenTypeRadio, t ) == 0 )
                atOneHalfDist = GetAtten( t, ATTEN_END ) / ( fLightPB->GetFloat( kIntensity, t ) * plSillyLightKonstants::GetFarPowerKonst() - 1.f );
            else
                atOneHalfDist = sqrt( GetAtten( t, ATTEN_END ) * GetAtten( t, ATTEN_END ) / ( fLightPB->GetFloat( kIntensity, t ) * plSillyLightKonstants::GetFarPowerKonst() - 1.f ) );

            if( atOneHalfDist > 0.0f )
                DrawCone( t, gw, atOneHalfDist );

            gw->setColor( LINE_COLOR, GetUIColor( COLOR_FALLOFF ) );
            DrawCone( t, gw, GetAtten( t, ATTEN_END ) );
        }
        else
            DrawArrows( t, gw, 50 );
    }

    return gw->checkHitCode();
}

//// DrawCone ////////////////////////////////////////////////////////////////
//  Function called by MAX to render the cone shape in the viewport for this
//  light. Note that this is the cone, not the actual object itself!

void    plRTOmniLight::DrawCone( TimeValue t, GraphicsWindow *gw, float dist ) 
{
    Point3  pts[ NUM_CIRC_PTS * 3 + 1 ];


    /// Draw sphere-thingy
    GetAttenPoints( t, dist, pts );

    gw->polyline( NUM_CIRC_PTS, pts,                    nullptr, nullptr, true, nullptr);
    gw->polyline( NUM_CIRC_PTS, pts + NUM_CIRC_PTS,     nullptr, nullptr, true, nullptr);
    gw->polyline( NUM_CIRC_PTS, pts + 2 * NUM_CIRC_PTS, nullptr, nullptr, true, nullptr);
}


//// DrawArrows //////////////////////////////////////////////////////////////
//  Renders some arrows in all directions, to show a radiating, attenuation-less
//  omni light.

void    plRTOmniLight::DrawArrows( TimeValue t, GraphicsWindow *gw, float dist ) 
{
    Point3  directions[] = { Point3( 1, 0, 0 ), Point3( -1, 0, 0 ), Point3( 0, 1, 0 ), Point3( 0, -1, 0 ),
                             Point3( 0, 0, 1 ), Point3( 0, 0, -1 ), 
                             Point3( 2, 2, 0 ), Point3( 2, -2, 0 ), Point3( 2, 0, 2 ), Point3( 2, 0, -2 ),
                             Point3( -2, 2, 0 ), Point3( -2, -2, 0 ), Point3( -2, 0, 2 ), Point3( -2, 0, -2 ),
                             Point3( 0, 2, 2 ), Point3( 0, 2, -2 ), Point3( 0, -2, 2 ), Point3( 0, -2, -2 ),
                             Point3( 0, 0, 0 ) };
    Point3  empty( 0, 0, 0 );
    int     i;
    Point3  pts[ 5 ];


    /// Adjust directions
    for( i = 0; directions[ i ] != empty; i++ )
    {
        if( directions[ i ].x == 2.f )
            directions[ i ].x = 0.7f;
        else if( directions[ i ].x == -2.f )
            directions[ i ].x = -0.7f;

        if( directions[ i ].y == 2.f )
            directions[ i ].y = 0.7f;
        else if( directions[ i ].y == -2.f )
            directions[ i ].y = -0.7f;

        if( directions[ i ].z == 2.f )
            directions[ i ].z = 0.7f;
        else if( directions[ i ].z == -2.f )
            directions[ i ].z = -0.7f;
    }

    /// Draw da arrows
    gw->setColor( LINE_COLOR, GetUIColor( COLOR_HOTSPOT ) );
    for( i = 0; directions[ i ] != empty; i++ )
        DrawArrow( t, gw, directions[ i ], dist );
}

//// GetLocalBoundBox ////////////////////////////////////////////////////////

void    plRTOmniLight::GetLocalBoundBox( TimeValue t, INode *node, ViewExp *vpt, Box3 &box )
{
    Point3  loc = node->GetObjectTM( t ).GetTrans();
    float   scaleFactor = vpt->NonScalingObjectSize() * vpt->GetVPWorldWidth( loc ) / 360.0f;
    float   width;

    box = fMesh.getBoundingBox();
    // Because we want to scale about the origin, not the box center, we have to do this funky offset
    Point3  boxCenter = box.Center();
    box.Translate( -boxCenter );
    box.Scale( scaleFactor );
    boxCenter *= scaleFactor;
    box.Translate( boxCenter );

    // Include points for the spotlight. That means either the attenuated cone or 
    // our unattenuated cone display
    if( ( extDispFlags & EXT_DISP_ONLY_SELECTED ) )
    {
        if( GetUseAtten() ) 
            width = GetAtten( t, ATTEN_END );
        else
            width = 50.f;       // Include our arrows

        box += Point3( -width, -width, -width );
        box += Point3( width, width, width );
    }
}

////////////////////////////////////////////////////////////////////////////////////////
//  
//
//          SpotLight Stuff
//
//


//// Local Copy of shared Anim PB /////////////////////////////////////////////

plRTSpotLight::plRTSpotLight()
{
    fIP = nullptr;
    fLightPB = nullptr;
    fClassDesc = plRTSpotLightDesc::GetDesc();
    fClassDesc->MakeAutoParamBlocks(this);

    fLightPB->SetValue(kLightColor, 0,  Color(255,255,255));
    SetHSVColor(0, Point3(255, 255, 255));
    
    fTex = nullptr;
    meshBuilt = 0; 
    
    IBuildMeshes(true);
}

ObjLightDesc *plRTSpotLight::CreateLightDesc(INode *n, BOOL forceShadowBuf)
{
    return new SpotLight( n, forceShadowBuf );
}


RefTargetHandle plRTSpotLight::Clone(RemapDir &remap)
{

    plRTSpotLight *obj = new plRTSpotLight;//(plRTLightBase*) fClassDesc->Create(false);
    obj->ReplaceReference(kRefSpotLight, fLightPB->Clone(remap));
    BaseClone(this, obj, remap);
    return obj;
}

Texmap  *plRTSpotLight::GetProjMap()
{
    if( !fLightPB->GetInt( kUseProjectorBool ) )
        return nullptr;

    Interval valid = Interval(0,0); 
    if( !GetTex() )
    {
        if( fLightPB->GetInt( kUseProjectorBool ) )
        {
            PBBitmap* bitmap = fLightPB->GetBitmap( kProjMapTexButton, 0 );         
            SetProjMap( &bitmap->bi );
        }
    }

    if( GetTex() )
    {
        auto dbgTexName = GetTex()->GetName();

        IParamBlock2 *bitmapPB = fTex->GetParamBlockByID(plLayerTex::kBlkBitmap);
        hsAssert(bitmapPB, "LayerTex with no param block");

        int noCompress = fLightPB->GetInt(kProjNoCompress);
        int noMip = fLightPB->GetInt(kProjNoMip);
        bitmapPB->SetValue(kBmpNonCompressed, TimeValue(0), noCompress);
        bitmapPB->SetValue(kBmpNoFilter, TimeValue(0), noMip);
    }

    return (Texmap *)GetTex();
}

void    plRTSpotLight::IBuildMeshes( BOOL isnew ) 
{
    float val = fLightPB->GetFloat( kHotSpot, TimeValue(0) );       //Init val of HotSpot
    if( isnew ) 
    {
        val = fLightPB->GetFloat(kHotSpot, TimeValue(0));
        SetHotspot(  TimeValue(0), val);
        //val = 45.0;
        val = fLightPB->GetFloat(kFallOff, TimeValue(0));
        SetFallsize(  TimeValue(0), val);
        val = fLightPB->GetFloat(kAttenMaxFalloffEdit, TimeValue(0)); //fLightPB->GetFloat(kTargetDist, TimeValue(0));
        if(val < 1.0f)
            SetTDist(  TimeValue(0), DEF_TDIST);
        else
            SetTDist(  TimeValue(0), val);

        val = fLightPB->GetFloat(kHotSpot, TimeValue(0));
    
    }

    BuildSpotMesh( val );

    fMesh = spotMesh;
}

//// DrawConeAndLine /////////////////////////////////////////////////////////

int     plRTSpotLight::DrawConeAndLine( TimeValue t, INode* inode, GraphicsWindow *gw, int drawing ) 
{
    Matrix3 tm = inode->GetObjectTM( t );


    gw->setTransform( tm );
    gw->clearHitCode();

    if( extDispFlags & EXT_DISP_ONLY_SELECTED )
        DrawCone( t, gw, GetAtten( t, ATTEN_END ) );

    return gw->checkHitCode();
}

//// DrawCone ////////////////////////////////////////////////////////////////
//  Function called by MAX to render the cone shape in the viewport for this
//  light. Note that this is the cone, not the actual object itself!

void    plRTSpotLight::DrawCone( TimeValue t, GraphicsWindow *gw, float dist ) 
{
    int     i;
    Point3  pts[ NUM_CIRC_PTS + 1 ], u[ 3 ];


    if( !GetUseAtten() )
    {
        /// Don't use atten, but still want a cone, so draw the cone w/ a dist of 100
        /// and the lines extending past it (thus indicating that it keeps going)
        dist = 100;
    }

    /// Draw hotspot cone
    gw->setColor( LINE_COLOR, GetUIColor( COLOR_HOTSPOT ) );
    GetConePoints( t, -1.0f, GetHotspot( t ), dist, pts );
    gw->polyline(NUM_CIRC_PTS, pts, nullptr, nullptr, true, nullptr);

    if( GetUseAtten() )
    {
        u[ 0 ] = Point3( 0, 0, 0 );
        for( i = 0; i < NUM_CIRC_PTS; i += SEG_INDEX )
        {
            u[ 1 ] = pts[ i ];
            gw->polyline(2, u, nullptr, nullptr, true, nullptr);
        }
    }
    else
    {
        for( i = 0; i < NUM_CIRC_PTS; i += SEG_INDEX )
        {
            pts[ i ] = pts[ i ].Normalize();
            DrawArrow( t, gw, pts[ i ], dist + 50.f );
        }
    }

    /// Draw falloff cone if necessary
    if( GetHotspot( t ) < GetFallsize( t ) )
    {
        gw->setColor( LINE_COLOR, GetUIColor( COLOR_FALLOFF ) );
        GetConePoints( t, -1.0f, GetFallsize( t ), dist, pts );
        gw->polyline(NUM_CIRC_PTS, pts, nullptr, nullptr, true, nullptr);

        if( GetUseAtten() )
        {
            u[ 0 ] = Point3( 0, 0, 0 );
            for( i = 0; i < NUM_CIRC_PTS; i += SEG_INDEX )
            {
                u[ 1 ] = pts[ i ];
                gw->polyline(2, u, nullptr, nullptr, true, nullptr);
            }
        }
        else
        {
            for( i = 0; i < NUM_CIRC_PTS; i += SEG_INDEX )
            {
                pts[ i ] = pts[ i ].Normalize();
                DrawArrow( t, gw, pts[ i ], dist + 50.f );
            }
        }
    }
}

//// GetLocalBoundBox ////////////////////////////////////////////////////////

void    plRTSpotLight::GetLocalBoundBox( TimeValue t, INode *node, ViewExp *vpt, Box3 &box )
{
    Point3  loc = node->GetObjectTM( t ).GetTrans();
    float   scaleFactor = vpt->NonScalingObjectSize() * vpt->GetVPWorldWidth( loc ) / 360.0f;
    float   width, depth;

    box = fMesh.getBoundingBox();
    // Because we want to scale about the origin, not the box center, we have to do this funky offset
    Point3  boxCenter = box.Center();
    box.Translate( -boxCenter );
    box.Scale( scaleFactor );
    boxCenter *= scaleFactor;
    box.Translate( boxCenter );

    // Include points for the spotlight. That means either the attenuated cone or 
    // our unattenuated cone display
    if( ( extDispFlags & EXT_DISP_ONLY_SELECTED ) )
    {
        if( GetUseAtten() ) 
            depth = GetAtten( t, ATTEN_END );
        else
            depth = 100.f + 50.f;   // Include arrows

        width = depth * tan( DegToRad( GetFallsize( t ) / 2.f ) );

        box += Point3( -width, -width, 0.f );
        box += Point3( width, width, -depth );
    }
}

///////////////////////////////////////////////////////////////////////////////
//// Directional Light ////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

plRTDirLight::plRTDirLight()
{
    fIP = nullptr;
    fLightPB = nullptr;
    fClassDesc = plRTDirLightDesc::GetDesc();
    fClassDesc->MakeAutoParamBlocks(this);

    fLightPB->SetValue(kLightColor, 0,  Color(255,255,255));
    SetHSVColor(0, Point3(255, 255, 255));
    
    fTex = nullptr;
    meshBuilt = 0; 
    
    IBuildMeshes(true);
}

ObjLightDesc *plRTDirLight::CreateLightDesc(INode *n, BOOL forceShadowBuf)
{
    return new DirLight( n, forceShadowBuf );
}


RefTargetHandle plRTDirLight::Clone(RemapDir &remap)
{

    plRTDirLight *obj = new plRTDirLight;//(plRTLightBase*) fClassDesc->Create(false);
    obj->ReplaceReference(kRefDirLight, fLightPB->Clone(remap));
    BaseClone(this, obj, remap);
    return obj;
}

//// IBuildMeshes ////////////////////////////////////////////////////////////

void    plRTDirLight::IBuildMeshes( BOOL isnew ) 
{
    BuildStaticMeshes();

    fMesh = staticMesh[ plRTLightBase::RT_OMNI + 1 ];
}

//// DrawCone ////////////////////////////////////////////////////////////////
//  Function called by MAX to render the cone shape in the viewport for this
//  light. Note that this is the cone, not the actual object itself!

void    plRTDirLight::DrawCone( TimeValue t, GraphicsWindow *gw, float dist ) 
{
    Point3  arrow[ 7 ];
    int     i, j, r;
    float   d;
    const float spacing = 20.f;


    // Draw some funky arrows to represent our direction
    dist = 100.f;
    gw->setColor( LINE_COLOR, GetUIColor( COLOR_HOTSPOT ) );

    for( i = -2; i <= 2; i++ )
    {
        for( j = -2; j <= 2; j++ )
        {
            r = ( i * i ) + ( j * j );
            if( r <= 4 )
            {
                d = dist * ( 5 - r ) / 5;
                IBuildZArrow( i * spacing, j * spacing, -d, -10.f, arrow );
                gw->polyline(6, arrow, nullptr, nullptr, true, nullptr);
            }
        }
    }
}

//// IBuildZArrow ////////////////////////////////////////////////////////////

void    plRTDirLight::IBuildZArrow( float x, float y, float zDist, float arrowSize, Point3 *pts )
{
    pts[ 0 ] = Point3( x, y, 0.f );
    pts[ 1 ] = Point3( x, y, zDist );
    pts[ 2 ] = Point3( x + arrowSize / 2.f, y, zDist - arrowSize );
    pts[ 3 ] = Point3( x, y, zDist - arrowSize );
    pts[ 4 ] = Point3( x, y + arrowSize / 2.f, zDist - arrowSize );
    pts[ 5 ] = Point3( x, y, zDist );
}

//// GetLocalBoundBox ////////////////////////////////////////////////////////

void    plRTDirLight::GetLocalBoundBox( TimeValue t, INode *node, ViewExp *vpt, Box3 &box )
{
    Point3  loc = node->GetObjectTM( t ).GetTrans();
    float   scaleFactor = vpt->NonScalingObjectSize() * vpt->GetVPWorldWidth( loc ) / 360.0f;
    float   width, height, depth;

    box = fMesh.getBoundingBox();
    // Because we want to scale about the origin, not the box center, we have to do this funky offset
    Point3  boxCenter = box.Center();
    box.Translate( -boxCenter );
    box.Scale( scaleFactor );
    boxCenter *= scaleFactor;
    box.Translate( boxCenter );

    if( ( extDispFlags & EXT_DISP_ONLY_SELECTED ) )
    {
        width = 2 * 20.f + ( 10.f / 2.f );  // Add in half arrow size
        height = 2 * 20.f + ( 10.f / 2.f );
        depth = 100.f;

        box += Point3( -width, -height, 0.f );
        box += Point3( width, height, -depth );
    }
}
