/*==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 . 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==*/ /////////////////////////////////////////////////////////////////////////////// // // // plDetailCurveCtrl Class Functions // // Custom Win32 Control class for drawing the detail map opacity curve so // // the artists can figure out what the hell is going on. // // Cyan, Inc. // // // // To use: // // 1. Create a new plDetailCurveCtrl, giving it a parent window and a // // client rect. // // 2. Set the start and end percentages, along with the start and end // // opacities. // // // //// Version History ////////////////////////////////////////////////////////// // // // 10.1.2001 mcn - Created. // // // /////////////////////////////////////////////////////////////////////////////// #include "hsTypes.h" #include "plDetailCurveCtrl.h" #include "resource.h" //// Static Stuff ///////////////////////////////////////////////////////////// int plDetailCurveCtrl::fClassRefCnt = 0; HINSTANCE plDetailCurveCtrl::fInstance = NULL; HBITMAP plDetailCurveCtrl::fBgndImage = NULL; HFONT plDetailCurveCtrl::fFont = NULL; #ifdef MCN_TWO_GRAPH_MODE HBITMAP plDetailCurveCtrl::fBgndImage2 = NULL; bool plDetailCurveCtrl::fXAsMipmapLevel = false; #endif const char gCtrlClassName[] = "DetailCurveClass"; #define kHiResStep 0.01f void plDetailCurveCtrl::IRegisterCtrl( HINSTANCE instance ) { if( fClassRefCnt == 0 ) { fInstance = instance; WNDCLASSEX clInfo; memset( &clInfo, 0, sizeof( clInfo ) ); clInfo.cbSize = sizeof( clInfo ); clInfo.style = CS_OWNDC | CS_NOCLOSE; clInfo.lpfnWndProc = (WNDPROC)IWndProc; clInfo.cbClsExtra = 0; clInfo.cbWndExtra = 0; clInfo.hInstance = fInstance; clInfo.hIcon = NULL; clInfo.hCursor = LoadCursor( NULL, IDC_CROSS ); clInfo.hbrBackground = NULL; clInfo.lpszMenuName = NULL; clInfo.lpszClassName = gCtrlClassName; clInfo.hIconSm = NULL; RegisterClassEx( &clInfo ); fBgndImage = (HBITMAP)LoadImage( fInstance, MAKEINTRESOURCE( IDB_DETAILBGND ), IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR ); #ifdef MCN_TWO_GRAPH_MODE fBgndImage2 = (HBITMAP)LoadImage( fInstance, MAKEINTRESOURCE( IDB_DETAILBGND2 ), IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR ); #endif HDC hDC = GetDC( NULL ); fFont = CreateFont( -MulDiv( 8, GetDeviceCaps( hDC, LOGPIXELSY ), 72 ), 0, 0, 0, 0, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, "Arial" ); ReleaseDC( NULL, hDC ); } fClassRefCnt++; } void plDetailCurveCtrl::IUnregisterCtrl( void ) { fClassRefCnt--; if( fClassRefCnt == 0 ) { UnregisterClass( gCtrlClassName, fInstance ); if( fFont != NULL ) DeleteObject( fFont ); } } //// Constructor & Destructor ///////////////////////////////////////////////// plDetailCurveCtrl::plDetailCurveCtrl( HWND parentWnd, WPARAM id, RECT *clientRect, HINSTANCE instance ) { // Class init if( instance == NULL ) instance = (HINSTANCE)GetWindowLong( parentWnd, GWL_HINSTANCE ); IRegisterCtrl( instance ); // Per-object init fDblDC = NULL; fDblBitmap = NULL; fStartPercent = 0; fStartOpac = 0; fEndPercent = 1.f; fEndOpac = 1.f; fNumLevels = 8; fDraggingStart = fDraggingEnd = false; fCanDragStart = fCanDragEnd = false; // Note: we create originally as disabled since the default detail setting is disabled. // The MAX Update stuff should change this if necessary after we're created fHWnd = ::CreateWindowEx( WS_EX_CLIENTEDGE, gCtrlClassName, "Detail Curve", WS_CHILD | WS_VISIBLE | WS_BORDER | WS_DISABLED, clientRect->left, clientRect->top, clientRect->right - clientRect->left, clientRect->bottom - clientRect->top, parentWnd, (HMENU)id, instance, 0 ); if( fHWnd == NULL ) return; SetWindowLong( fHWnd, GWL_USERDATA, (LONG)this ); } plDetailCurveCtrl::~plDetailCurveCtrl() { if( fDblDC != NULL ) { SelectObject( fDblDC, (HBITMAP)NULL ); DeleteObject( fDblBitmap ); DeleteDC( fDblDC ); } if( fWhiteBrush != NULL ) DeleteObject( fWhiteBrush ); if( fBluePen != NULL ) DeleteObject( fBluePen ); if( fLiteBluePen != NULL ) DeleteObject( fLiteBluePen ); if( fBlueBrush != NULL ) DeleteObject( fBlueBrush ); // DestroyWindow( fHWnd ); IUnregisterCtrl(); } //// IInitDblBuffer /////////////////////////////////////////////////////////// // Note: For some strange reason, grabbing the HDC of the window doesn't do // any good, 'cause it's black-and-white (right, THAT makes sense). Grabbing // the desktop DC works, however. void plDetailCurveCtrl::IInitDblBuffer( void ) { if( fDblDC == NULL ) { int width, height; RECT r; HDC desk = GetDC( NULL ); GetClientRect( fHWnd, &r ); width = r.right - r.left; height = r.bottom - r.top; fDblDC = CreateCompatibleDC( desk ); fDblBitmap = CreateCompatibleBitmap( desk/*fDblDC*/, width, height ); SelectObject( fDblDC, fDblBitmap ); ReleaseDC( NULL, desk ); fWhiteBrush = CreateSolidBrush( RGB( 255, 255, 255 ) ); fBluePen = CreatePen( PS_SOLID, 1, RGB( 0, 0, 255 ) ); fLiteBluePen = CreatePen( PS_SOLID, 1, RGB( 127, 127, 255 ) ); fBlueBrush = CreateSolidBrush( RGB( 0, 0, 255 ) ); IRefreshDblBuffer(); } } //// IRefreshDblBuffer //////////////////////////////////////////////////////// void plDetailCurveCtrl::IRefreshDblBuffer( void ) { HDC hBgndDC; RECT clientRect, r; SIZE bgndSize; int width, height, x, y; HPEN oldPen; BITMAPINFO bmpInfo; POINT pt1, pt2; IInitDblBuffer(); GetClientRect( fHWnd, &clientRect ); width = clientRect.right - clientRect.left; height = clientRect.bottom - clientRect.top; if( fDblBitmap != NULL ) { FillRect( fDblDC, &clientRect, fWhiteBrush ); if( fBgndImage != NULL ) { // Draw bgnd hBgndDC = CreateCompatibleDC( fDblDC ); #ifdef MCN_TWO_GRAPH_MODE SelectObject( hBgndDC, fXAsMipmapLevel ? fBgndImage2 : fBgndImage ); #else SelectObject( hBgndDC, fBgndImage ); #endif bmpInfo.bmiHeader.biSize = sizeof( bmpInfo.bmiHeader ); bmpInfo.bmiHeader.biBitCount = 0; GetDIBits( hBgndDC, fBgndImage, 0, 0, NULL, &bmpInfo, DIB_RGB_COLORS ); bgndSize.cx = bmpInfo.bmiHeader.biWidth; bgndSize.cy = bmpInfo.bmiHeader.biHeight; x = ( width - bgndSize.cx ) >> 1; y = ( height - bgndSize.cy ) >> 1; BitBlt( fDblDC, x, y, bgndSize.cx, bgndSize.cy, hBgndDC, 0, 0, SRCCOPY ); SelectObject( hBgndDC, (HBITMAP)NULL ); DeleteDC( hBgndDC ); /// Draw graph if( IsWindowEnabled( fHWnd ) ) { bgndSize.cx -= 8; oldPen = (HPEN)SelectObject( fDblDC, fLiteBluePen ); /// This line draws the light blue "actual" curve, which shows what happens /// when you actually interpolate the curve across the mipmap levels. It's /// more accurate in that it shows the actual values computed for each level, /// but less accurate because it doesn't take into account mipmap sampling /// and such. Given the latter, we leave it out (for now) to avoid confusing /// the artists. // IDrawCurve( fDblDC, true, x, y, &bgndSize ); SelectObject( fDblDC, fBluePen ); IDrawCurve( fDblDC, false, x, y, &bgndSize ); SelectObject( fDblDC, oldPen ); if( fStartPercent == 0.07f && fStartOpac == 0.23f && fEndPercent == 0.19f && fEndOpac == 0.79f ) { const char str[] = "\x48\x61\x70\x70\x79\x20\x62\x64\x61\x79\x20\x74\x6f\x20\x6d\x63\x6e\x21"; SetBkMode( fDblDC, TRANSPARENT ); SetTextColor( fDblDC, RGB( 0, 0, 255 ) ); SelectObject( fDblDC, fFont ); TextOut( fDblDC, x, y + bgndSize.cy - 10, str, strlen( str ) ); } // Draw our two markers IXlateValuesToClientPt( fStartPercent, fStartOpac, &pt1, x, y, &bgndSize ); SetRect( &fStartDragPt, pt1.x - 4, pt1.y - 4, pt1.x + 4, pt1.y + 4 ); r = fStartDragPt; if( !fCanDragStart ) InflateRect( &r, -2, -2 ); FillRect( fDblDC, &r, fBlueBrush ); IXlateValuesToClientPt( fEndPercent, fEndOpac, &pt2, x, y, &bgndSize ); SetRect( &fEndDragPt, pt2.x - 4, pt2.y - 4, pt2.x + 4, pt2.y + 4 ); r = fEndDragPt; if( !fCanDragEnd ) InflateRect( &r, -2, -2 ); FillRect( fDblDC, &r, fBlueBrush ); } } } } //// IDrawCurve /////////////////////////////////////////////////////////////// // Draw the damned curve. void plDetailCurveCtrl::IDrawCurve( HDC hDC, bool clampToInts, int cornerX, int cornerY, SIZE *bgndSize ) { float dist, penX, penBaseY, penXStep, penYScale; POINT pt1; // Calc stuff penX = (float)cornerX; penBaseY = (float)( cornerY + bgndSize->cy ); penXStep = (float)bgndSize->cx * kHiResStep; penYScale = (float)bgndSize->cy; // Draw curve pt1.x = (int)penX; pt1.y = (int)( penBaseY - penYScale * fStartOpac ); float artificialBias = 1.f / (float)fNumLevels; // So we never get a howFar less than 0 float artificialMaxDist = 1.f - artificialBias; for( dist = 0.f; dist <= 1.f; dist += kHiResStep ) { float opac = IXlateDistToValue( dist, clampToInts ); if( dist == 0.f ) MoveToEx( hDC, (int)penX, (int)( penBaseY - penYScale * opac ), NULL ); else LineTo( hDC, (int)penX, (int)( penBaseY - penYScale * opac ) ); penX += penXStep; } } //// IXlateDistToValue //////////////////////////////////////////////////////// // I.E. from distance across graph to distance up on graph (percentage-wise) float plDetailCurveCtrl::IXlateDistToValue( float dist, bool clampToInts ) { const float artificialBias = 1.f / (float)fNumLevels; // So we never get a howFar less than 0 const float artificialMaxDist = 1.f - artificialBias; float howFar, opac; howFar = IXlateDistToX( dist, clampToInts ); if( howFar < fStartPercent ) opac = fStartOpac; else if( howFar > fEndPercent ) opac = fEndOpac; else opac = ( howFar - fStartPercent ) * ( fEndOpac - fStartOpac ) / ( fEndPercent - fStartPercent ) + fStartOpac; return opac; } //// IXlateDistToX //////////////////////////////////////////////////////////// // I.E. from the distance in percentage across the graph to the actual x // value on the graph float plDetailCurveCtrl::IXlateDistToX( float dist, bool clampToInts ) { const float artificialBias = 1.f / (float)fNumLevels; // So we never get a howFar less than 0 const float artificialMaxDist = 1.f - artificialBias; float howFar; #ifdef MCN_TWO_GRAPH_MODE if( fXAsMipmapLevel ) { howFar = dist * (float)fNumLevels; if( clampToInts ) howFar = (float)( (int)howFar ); howFar /= (float)fNumLevels; return howFar; } #endif if( dist == 0.f ) howFar = 0.f; else { howFar = 1.f - ( ( 1.f - dist ) * artificialMaxDist ); howFar = ( (float)fNumLevels - 1.f / howFar ); if( howFar < 0.f ) howFar = 0.f; else if( howFar > (float)fNumLevels - 1.f ) howFar = (float)fNumLevels - 1.f; if( clampToInts ) howFar = (float)( (int)howFar ); howFar /= (float)fNumLevels - 1.f; } return howFar; } //// IXlateXToDist //////////////////////////////////////////////////////////// // I.E. from the actual x value of the graph to the actual distance in // percentage across the graph. float plDetailCurveCtrl::IXlateXToDist( float howFar ) { const float artificialBias = 1.f / (float)fNumLevels; // So we never get a howFar less than 0 const float artificialMaxDist = 1.f - artificialBias; float dist; #ifdef MCN_TWO_GRAPH_MODE if( fXAsMipmapLevel ) { return howFar; } #endif if( howFar == 0.f ) dist = 0.f; else { howFar *= (float)fNumLevels - 1.f; howFar = 1.f / ( (float)fNumLevels - howFar ); howFar = ( ( howFar - 1.f ) / artificialMaxDist ) + 1.f; } return howFar; } //// IXlateValuesToClientPt /////////////////////////////////////////////////// // I.E. from graph x,y values to client coordinates void plDetailCurveCtrl::IXlateValuesToClientPt( float x, float y, POINT *pt, int cornerX, int cornerY, SIZE *bgndSize ) { pt->x = cornerX + (int)( IXlateXToDist( x ) * (float)bgndSize->cx ); pt->y = cornerY + bgndSize->cy; pt->y -= (int)( (float)bgndSize->cy * y ); } //// IMapMouseToValues //////////////////////////////////////////////////////// // Map mouse x,y coordinates in clientspace to graph values. If the last param // is true, maps to the start point, else maps to the end point void plDetailCurveCtrl::IMapMouseToValues( int x, int y, bool mapToStart ) { BITMAPINFO bmpInfo; int cX, cY, width, height; RECT clientRect; float vX, vY; SIZE bgndSize; if( fBgndImage == NULL || fDblDC == NULL || !IsWindowEnabled( fHWnd ) ) return; GetClientRect( fHWnd, &clientRect ); width = clientRect.right - clientRect.left; height = clientRect.bottom - clientRect.top; bmpInfo.bmiHeader.biSize = sizeof( bmpInfo.bmiHeader ); bmpInfo.bmiHeader.biBitCount = 0; GetDIBits( fDblDC, fBgndImage, 0, 0, NULL, &bmpInfo, DIB_RGB_COLORS ); bgndSize.cx = bmpInfo.bmiHeader.biWidth; bgndSize.cy = bmpInfo.bmiHeader.biHeight; cX = ( width - bgndSize.cx ) >> 1; cY = ( height - bgndSize.cy ) >> 1; bgndSize.cx -= 8; // Xlate to graph space and clamp x -= cX; y = bgndSize.cy - ( y - cY ); if( x < 0 ) x = 0; else if( x > bgndSize.cx ) x = bgndSize.cx; if( y < 0 ) y = 0; else if( y > bgndSize.cy ) y = bgndSize.cy; vX = IXlateDistToX( (float)x / (float)bgndSize.cx, false ); vY = (float)y / (float)bgndSize.cy; if( mapToStart ) { fStartPercent = vX; fStartOpac = vY; if( fEndPercent < fStartPercent ) { fEndPercent = fStartPercent; ISendDraggedMessage( false ); } } else { fEndPercent = vX; fEndOpac = vY; if( fEndPercent < fStartPercent ) { fStartPercent = fEndPercent; ISendDraggedMessage( true ); } } IRefreshDblBuffer(); InvalidateRect( fHWnd, NULL, false ); RedrawWindow( fHWnd, NULL, NULL, RDW_UPDATENOW ); ISendDraggedMessage( mapToStart ); } //// ISendDraggedMessage ////////////////////////////////////////////////////// void plDetailCurveCtrl::ISendDraggedMessage( bool itWasTheStartPoint ) { HWND parent = GetParent( fHWnd ); if( parent == NULL ) return; SendMessage( parent, PL_DC_POINT_DRAGGED, itWasTheStartPoint ? PL_DC_START_POINT : PL_DC_END_POINT, (LPARAM)this ); } //// SetStart/EndPoint //////////////////////////////////////////////////////// void plDetailCurveCtrl::SetStartPoint( float percentLevel, float opacity ) { fStartPercent = percentLevel; fStartOpac = opacity; IRefreshDblBuffer(); InvalidateRect( fHWnd, NULL, false ); } void plDetailCurveCtrl::SetEndPoint( float percentLevel, float opacity ) { fEndPercent = percentLevel; fEndOpac = opacity; IRefreshDblBuffer(); InvalidateRect( fHWnd, NULL, false ); } void plDetailCurveCtrl::SetNumLevels( int numLevels ) { fNumLevels = numLevels; IRefreshDblBuffer(); InvalidateRect( fHWnd, NULL, false ); } //// IWndProc ///////////////////////////////////////////////////////////////// LRESULT CALLBACK plDetailCurveCtrl::IWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) { HDC hDC; RECT clientRect; int width, height; PAINTSTRUCT pInfo; POINT pt; plDetailCurveCtrl *ctrl = (plDetailCurveCtrl *)GetWindowLong( hWnd, GWL_USERDATA ); GetClientRect( hWnd, &clientRect ); width = clientRect.right - clientRect.left; height = clientRect.bottom - clientRect.top; switch( msg ) { case WM_CREATE: return 0; case WM_ENABLE: if( ctrl != NULL ) ctrl->IRefreshDblBuffer(); return 0; case WM_PAINT: BeginPaint( hWnd, &pInfo ); hDC = (HDC)pInfo.hdc; if( ctrl != NULL ) { if( ctrl->fDblDC == NULL ) ctrl->IInitDblBuffer(); BitBlt( hDC, 0, 0, width, height, ctrl->fDblDC, 0, 0, SRCCOPY ); } EndPaint( hWnd, &pInfo ); return 0; case WM_ERASEBKGND: return TRUE; case WM_LBUTTONDOWN: if( ctrl != NULL && !ctrl->fDraggingStart && !ctrl->fDraggingEnd ) { pt.x = LOWORD( lParam ); pt.y = HIWORD( lParam ); if( PtInRect( &ctrl->fStartDragPt, pt ) ) { if( !ctrl->fCanDragStart && !ctrl->fCanDragEnd ) SetCapture( hWnd ); ctrl->fDraggingStart = true; } else if( PtInRect( &ctrl->fEndDragPt, pt ) ) { if( !ctrl->fCanDragStart && !ctrl->fCanDragEnd ) SetCapture( hWnd ); ctrl->fDraggingEnd = true; } } return 0; case WM_MOUSEMOVE: if( ctrl != NULL ) { pt.x = LOWORD( lParam ); pt.y = HIWORD( lParam ); if( ctrl->fDraggingStart || ctrl->fDraggingEnd ) { ctrl->IMapMouseToValues( (short)LOWORD( lParam ), (short)HIWORD( lParam ), ctrl->fDraggingStart ); } else if( PtInRect( &ctrl->fStartDragPt, pt ) ) { if( !ctrl->fCanDragStart ) { ctrl->fCanDragStart = true; ctrl->fCanDragEnd = false; SetCapture( hWnd ); ctrl->IRefreshDblBuffer(); InvalidateRect( hWnd, NULL, false ); } } else if( PtInRect( &ctrl->fEndDragPt, pt ) ) { if( !ctrl->fCanDragEnd ) { ctrl->fCanDragEnd = true; ctrl->fCanDragStart = false; SetCapture( hWnd ); ctrl->IRefreshDblBuffer(); InvalidateRect( hWnd, NULL, false ); } } else if( ctrl->fCanDragStart || ctrl->fCanDragEnd ) { ctrl->fCanDragStart = false; ctrl->fCanDragEnd = false; ReleaseCapture(); ctrl->IRefreshDblBuffer(); InvalidateRect( hWnd, NULL, false ); } } return 0; case WM_LBUTTONUP: if( ctrl != NULL && ( ctrl->fDraggingStart || ctrl->fDraggingEnd ) ) { if( !ctrl->fCanDragStart && !ctrl->fCanDragEnd ) ReleaseCapture(); ctrl->fDraggingStart = false; ctrl->fDraggingEnd = false; } return 0; #ifdef MCN_TWO_GRAPH_MODE case WM_RBUTTONDOWN: fXAsMipmapLevel = !fXAsMipmapLevel; ctrl->IRefreshDblBuffer(); InvalidateRect( hWnd, NULL, false ); return 0; #endif case WM_DESTROY: delete ctrl; SetWindowLong( hWnd, GWL_USERDATA, 0 ); return 0; default: return DefWindowProc( hWnd, msg, wParam, lParam ); } }