//------------------------------------------------------------------------------
// File: Demonstration.CPP
//
// Desc: DirectShow sample code
//       Implementation of CDemonstration, 
//       "special effects" module to represent capabilities of VMR.
//       This class is called from CVMRMixDlg by "Play" button.
//       CDemonstration contains CVMRCore member (VMR engine) through which 
//       it performs initialization of the graph builder and presentation.
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------

#include "stdafx.h"
#include <math.h>
#include "Demonstration.h"

#define DO_NOT_RUN_YET true


//------------------------------------------------------------------------------
// Perform 
// Desc: Performs presentation. Just call it from the 'parent' dialog
// Return HRESULT
//------------------------------------------------------------------------------
HRESULT CDemonstration::Perform()
{
    HRESULT hr = S_OK;
    clock_t tStart;
    clock_t tCurrent;
    clock_t tUpdate;

    if( false == m_bInitialized )
    {
        hr = Initialize();
    }
    if( FAILED(hr) )
    {
        return hr;
    }

    try
    {
        hr = m_pCore->Play();
        if( FAILED(hr) )
        {
            _stprintf( m_szMsg, _T("Failed to run VMR, method returned %s\n"), 
                       hresultNameLookup(hr));
            DbgOutString(m_szMsg);
            return hr;
        }
    }
    catch(...)
    {
        DbgOutString(_T("Unhandled exception in CDemonstration::Perform()\n"));
        ShellAbort(m_pCore);
        return E_POINTER;
    }
    
    tStart = clock();
    tCurrent = clock();
    tUpdate = clock();

    // presentation loop: wait until presentation is over
    // or user closed the window and VMRCore is inactive
    while( (tCurrent - tStart) / CLOCKS_PER_SEC <  m_MList.GetAvgDuration() / 10000000L &&
            m_pCore->IsActive() )
    {
        Sleep(10);
        UpdateStreams(tStart);
        tCurrent = clock();
    }

    return S_OK;
}


//------------------------------------------------------------------------------
// Initialize()
// Desc: Initializes demonstration; fills start parameters, 
//       creates the graph for VMR; renders files
// Return: HRESULT
//------------------------------------------------------------------------------
HRESULT CDemonstration::Initialize()
{
    HRESULT hr = S_OK;
    LONGLONG llCurrent = 0L;
    LONGLONG llStop = 0L;
    LONGLONG llDelay = 0L;
    clock_t tStart;

    // calculate playback time
    m_MList.AdjustDuration();                         
    m_MList.SetInitialParameters();

    // create VMRCore
    m_pCore = new CVMRCore(m_pDlg, &m_MList ); 
    if( !m_pCore)
    {
        return E_OUTOFMEMORY;
    }

    tStart = clock();

    // here, VMRCore creates the graph and if successfully, runs IMediaControl
    // for the first file from the media list
    hr = m_pCore->Play(DO_NOT_RUN_YET); 
    if(FAILED(hr))
    {
        return hr;
    }

    // if user selected "show bitmap" option, load the bitmap
    // from the resource and apply color key
    if( m_pDlg->IsBitmapToUse())
    {
        hr = SetAlphaBitmapColorKey(IDB_BITMAP_LOGO);

        if(FAILED(hr))
        {
            DbgOutString(_T("Failed to SetAlphaBitmapColorKey() in CDemonstration::Initialize()\n"));
            return E_INVALIDARG;
        }
    }

    m_bInitialized = true;

    return S_OK;
}


//------------------------------------------------------------------------------
//  SetAlphaBitmapColorKey
//  Desc: initializes proper members of VMRALPHABITMAP for solor key support
//  Parameters: [in]    UINT ImageID - resource ID
//  Return: HRESULT
//------------------------------------------------------------------------------
HRESULT CDemonstration::SetAlphaBitmapColorKey(UINT ImageID)
{
    HRESULT hr;

    if( ! m_pCore )
    {
        DbgOutString(_T("Function SetAlphaBitmapColorKey() is called before initializing m_pCore\n"));
        return E_INVALIDARG;
    }
    // prepare the valid default bitmap 
    if (FAILED(GetValidVMRBitmap(ImageID)))
    {
        DbgOutString(_T("Unable to get a valid VMRALPHABITMAP\n"));
        return E_INVALIDARG;
    }

    m_sBmpParams.fAlpha = 1.0f;
    m_sBmpParams.clrSrcKey = RGB(255,255,255);
    m_sBmpParams.dwFlags |= VMRBITMAP_SRCCOLORKEY;

    try 
    {
        hr = m_pCore->GetMixerBitmap()->SetAlphaBitmap(&m_sBmpParams);

        if( FAILED(hr) )
        {
            return hr;
        }
    } // try
    catch(...)
    {
        // Display a message box about the unhandled exception and exit
        ShellAbort(m_pCore); 
    }
    return S_OK;
}


//------------------------------------------------------------------------------
//  GetValidVMRBitmap
//  Desc: creates bitmap compatible with VMR
//  Parameters: [in]    UINT ImageID - resource ID
//              [out]   VMRALPHABITMAP * vmrBmp - what IVMRMixerBitmap uses 
//  Return: HRESULT
//------------------------------------------------------------------------------
HRESULT CDemonstration::GetValidVMRBitmap(UINT ImageID)
{
    HRESULT hr = S_OK;
    long cx, cy;
    HDC hdc;
    HBITMAP hbmpVmr;
    BITMAP bmp;

    hr = m_pCore->GetVMRWndless()->GetNativeVideoSize(&cx, &cy, NULL, NULL);
    if( FAILED(hr) )
    {
        DbgOutString(_T("Failed in GetNativeVideoSize()\n"));
        return hr;
    }

    // get compatible DC
    hdc    = GetDC(m_pCore->GetClientHwnd());   
    m_hbmp = CreateCompatibleBitmap(hdc, cx, cy); /*** RELEASE ***/
    
    hbmpVmr = (HBITMAP)LoadImage(AfxGetInstanceHandle(),MAKEINTRESOURCE(ImageID),IMAGE_BITMAP,0,0,/*LR_LOADFROMFILE|*/LR_CREATEDIBSECTION);
    if( !hbmpVmr )
    {
        DbgOutString(_T("Failed to load resource\n"));     
        DeleteObject(m_hbmp);
        return E_INVALIDARG;
    }

    // Get size of the bitmap
    GetObject( hbmpVmr, sizeof(bmp), &bmp );
    
    HDC hdcBmp = CreateCompatibleDC(hdc); /*** RELEASE ***/
    HDC hdcVMR = CreateCompatibleDC(hdc);

    ReleaseDC(m_pCore->GetClientHwnd(), hdc);

    HBITMAP hbmpold = (HBITMAP)SelectObject(hdcBmp, m_hbmp);// in hdcBmp, select hbmp
    hbmpVmr = (HBITMAP)SelectObject(hdcVMR, hbmpVmr);// in hdcVmr, select hbmpVmr (the pic we loaded)

    BitBlt(hdcBmp, 0, 0, bmp.bmWidth, bmp.bmHeight, hdcVMR, 0, 0, SRCPAINT);// put loaded pic from hdcVmr to hdcBmp
    DeleteObject(SelectObject(hdcVMR, hbmpVmr));// ok, we do not need hbmpVmr any more
    DeleteDC(hdcVMR);

    ZeroMemory(&m_sBmpParams, sizeof(VMRALPHABITMAP) );
    m_sBmpParams.dwFlags = VMRBITMAP_HDC;
    m_sBmpParams.hdc = hdcBmp;

    // set source rectangle (entire original bitmap)
    RECT rc;
    SetRect(&rc, 0, 0, bmp.bmWidth, bmp.bmHeight);
    m_sBmpParams.rSrc = rc;

    float fCoeff = 0.2f + 1.7f * (float)rand()/RAND_MAX;
    // set destination rectangle (keeping aspect ratio of the original image)
    // please note that normalized rect is always in [0.0, 1.0] range for
    // all its members
    m_sBmpParams.rDest.left = 0.f;  
    m_sBmpParams.rDest.top = 0.f;
    m_sBmpParams.rDest.right = 0.9f*(float)bmp.bmWidth / (float)cx;
    m_sBmpParams.rDest.bottom = 0.9f*(float)bmp.bmHeight / (float)cy;
    m_sBmpParams.fAlpha = 0.5f;

    // quite important, otherwise VMR would give error
    m_sBmpParams.pDDS = NULL;

    // Note: this demo uses bitmap directly, but often it is better to create
    // DirectDrawSurface of appropriate format and set m_sBmpParams.pDDS to it 
    // (especially if you experience performance issues)

    return S_OK;
}


//------------------------------------------------------------------------------
//  UpdateStreams
//  Desc: updates presentation parameters (destination rectangles and alpha level)
//        for each media file
//  Parameters: clock_t tStart -- presentation start; used as a 'time' variable 
//              in calculations
//  Return: HRESULT
//------------------------------------------------------------------------------
HRESULT CDemonstration::UpdateStreams(clock_t tStart)
{
    HRESULT hr = S_OK;
    int i;
    clock_t tCurrent;
    NORMALIZEDRECT rectD0;
    NORMALIZEDRECT rectD;
    NORMALIZEDRECT rectDRes; 
    double Alpha0;
    double Alpha;

    ASSERT( tStart >0 );

    for( i=0; i< this->m_MList.Size(); i++)
    {
        if( false == m_MList.GetItem(i)->m_bInUse)
            continue;

        tCurrent = clock() - tStart;

        rectD0 = m_MList.GetItem(i)->m_rD;
        Alpha0 = m_MList.GetItem(i)->m_fAlpha;
        
        Alpha = 1.f;
        rectD.left = rectD.top = 0.f;
        rectD.right = rectD.bottom = 1.f;

        FountainPath(   tCurrent, 
                        (long)(m_MList.GetAvgDuration() / 10000000 * CLOCKS_PER_SEC),
                        i,
                        rectD0,
                        Alpha0, 
                        &rectD, 
                        &Alpha);
            
        hr = m_pCore->GetMixerControl()->SetAlpha(i, (float)Alpha);
        if( !SUCCEEDED(hr))
        {
            _stprintf( m_szMsg, _T("Failure in CDemonstration::UpdateStreams, GetMixerControl()->SetAlpha, method returned %s\n"),
                     hresultNameLookup(hr));
            DbgOutString(m_szMsg);
        }

        hr = m_pCore->GetMixerControl()->SetOutputRect(i, &rectD);
        if( !SUCCEEDED(hr))
        {
            _stprintf( m_szMsg, _T("Failure in CDemonstration::UpdateStreams, GetMixerControl()->SetOutputRect, method returned %s\n"),
                     hresultNameLookup(hr));
            DbgOutString(m_szMsg);
        }

        hr = m_pCore->GetMixerControl()->GetOutputRect(i, &rectDRes);
        if( !SUCCEEDED(hr))
        {
            _stprintf( m_szMsg, _T("Failure in CDemonstration::UpdateStreams, GetMixerControl()->GetOutputRect, method returned %s\n"),
                     hresultNameLookup(hr));
            DbgOutString(m_szMsg);
        }
        else
        {
            if( fabs(rectD.top - rectDRes.top) > 0.01 ||
                fabs(rectD.bottom - rectDRes.bottom) > 0.01 ||
                fabs(rectD.left - rectDRes.left) > 0.01 ||
                fabs(rectD.right - rectDRes.right) > 0.01  )
            {
                _stprintf( m_szMsg, _T("Failed invalidation of SetOutputRect(): required [l,t,r,b] = [%f,%f,%f,%f] and returned [%f,%f,%f,%f]\n"),
                         rectD.left, rectD.top, rectD.right, rectD.bottom,
                         rectDRes.left, rectDRes.top, rectDRes.right, rectDRes.bottom );
                DbgOutString(m_szMsg);
            }
        }

    }// for

    return hr;
}


//------------------------------------------------------------------------------
// Name: FountainPath
// Purpose: creates 'movie fountain' effect; for each stream, center point 
//          moves by a random ellipse around the center of a playback window;
//          size and alpha-level of stream's output rect changes by cosine with 
//          some random initial delay (to desynchronize streams) 
// Parameters:  
//              [IN]    long t - current timestamp (any units, we use relative time)
//              [IN]    long T - expected total playback time (in the same units as t)
//              [IN]    int n - id of particular stream we want to set
//              [IN]    NORMALIZEDRECT r0 - original OutputRect of the stream
//              [IN]    double A0 - original alpha level (used as a parameter for smoothly 
//                                  changing alpha level)
//              [OUT]   NORMALIZEDRECT * pR - dest. part of playback window
//              [OUT]   double * pA - new alpha level
//------------------------------------------------------------------------------
void CDemonstration::FountainPath(  long t, 
                                    long T, 
                                    int n, 
                                    NORMALIZEDRECT r0,  
                                    double A0, 
                                    NORMALIZEDRECT * pR, 
                                    double * pA)
{
    double cx0;     // original center of the output rect, in normalized coordinates
    double cy0;
    double cx;      // new center of the output rect, in normalized coordinates
    double cy;
    double cx1;     // auxiliary center of the output rect, in normalized coordinates
    double cy1;
    double L0;      // original half-diagonal measure of the rectangle
    double w;       // orig. rect width
    double h;       // orig. rect height
    double beta;    // shift in cosine for L(t). see below
    double L;       // new half-diagonal measure of the rectangle
    double tau;     // time in relative units
    double gamma;   // shift in sine for A(t). see below
    double coeff;   // coefficient that is used to create 'mirror-flip' effect
    double dx;      // new half-width
    double dy;      // new half-heights

    // relative time,  to have about 3 periods over the total playtime
    tau = 18.0 * (double)t / T;

    // alpha level, A = 0.2 + 0.8 sin( tau + gamma) where gamma is such that A(0)=A0
    gamma = (A0 - 0.2)/0.8;
    gamma = (gamma < -1.) ? -1. : gamma;
    gamma = (gamma > 1. ) ?  1. : gamma;
    gamma = asin( gamma);
    *pA = 0.6 + 0.4 * sin(tau + gamma + A0);

    cx0 = (r0.left + r0.right)/2.;
    cy0 = (r0.top + r0.bottom)/2.;
    w   = r0.right - r0.left;
    h   = r0.bottom - r0.top;
    L0  = 0.5 * sqrt(w*w + h*h);
    L0 = (L0 < 0.0001) ? 0.1 : L0;

    // now rectangle. Its half-diagonal measure L = 0.1 + 0.7 cos( tau + beta)
    // where beta is such that L(0) = LO;
    beta = (L0 - 0.1)/0.7;
    beta = (beta < -1.) ? -1. : beta;
    beta = (beta >  1.) ?  1. : beta;
    beta = acos( beta);
    L = 0.35 + 0.45 * cos( tau + beta + 3.*A0);

    // center of the rectangle is moving by ellips
    // cx = cx0 + (-1)^n 0.1 sin(tau);
    // cy = cy0 - 0.2 + 0.2 cos( tau);
    // and turn it buy... say, theta = 7.85 A0 - 1.57;
    coeff = (n%2) ? -1. : 1.;
    cx1 = cx0 + coeff * 0.2 * sin(tau + A0);
    cy1 = cy0 - 0.05 + 0.2 * cos( tau + A0);

    // the lines below are unnecessary, but we could want some
    // additional transformation here, like turn trajectory ellipse
    // by some angle
    cx = cx1;
    cy = cy1;

    dx = L * w / L0;
    dy = L * h / L0;

    pR->left    = (float)(cx - dx);
    pR->right   = (float)(cx + dx);
    pR->top     = (float)(cy - dy);
    pR->bottom  = (float)(cy + dy);
}


