public void ClientCaptureCallback
        (
            byte[] bgrData,
            int imageWidth,
            int imageHeight
        )
        {
            // Copy the captured data to a local buffer
            this.CopyDataFromCaptureDataToLocalBuffer(bgrData, imageWidth, imageHeight);

            // Process the image data
            this.ClassifyRegion( );

            // Update the associated texture
            GR gr = this.mGR;

            gr.glBindTexture(GR.GL_TEXTURE_2D, this.mTextureOpenGLHandleBGR256x256);

            gr.glTexSubImage2D
            (
                GR.GL_TEXTURE_2D,           // target
                0,                          // level
                0,                          // xoffset
                0,                          // yoffset
                256,                        // width
                256,                        // height
                GR.GL_BGR_EXT,              // format
                GR.GL_UNSIGNED_BYTE,        // type
                this.mTextureDataBGR256x256 // texel data
            );
        }
        private static void MakeFontTexture(GR gr)
        {
            byte[] rgbaBuffer = new byte[128 * 128 * 4];


            // fill in buffer with RGBA version of font pixels
            byte val = (byte)0;
            int  k   = 0;

            for (int j = 0; j < 128; j++)
            {
                for (int i = 0; i < 128; i++)
                {
                    k     = 4 * ((128 * (127 - j)) + i);
                    val   = Courier10FontBitmap[(16 * j) + (i >> 3)];
                    val <<= (i & 0x7); // 0..7
                    if (0 != (val & 0x80))
                    {
                        // opaque white
                        rgbaBuffer[k + 0] = 255; // R
                        rgbaBuffer[k + 1] = 255; // G
                        rgbaBuffer[k + 2] = 255; // B
                        rgbaBuffer[k + 3] = 255; // A
                    }
                    else
                    {
                        // transparent
                        rgbaBuffer[k + 0] = 0; // R
                        rgbaBuffer[k + 1] = 0; // G
                        rgbaBuffer[k + 2] = 0; // B
                        rgbaBuffer[k + 3] = 0; // A
                    }
                }
            }


            // create an OpenGL texture and transfer data to the texture
            int[] temp = new int[1];
            gr.glGenTextures(1, temp);
            mFontTextureName = temp[0];
            gr.glBindTexture(GR.GL_TEXTURE_2D, mFontTextureName);

            gr.glTexParameteri(GR.GL_TEXTURE_2D, GR.GL_TEXTURE_MAG_FILTER, GR.GL_NEAREST);
            gr.glTexParameteri(GR.GL_TEXTURE_2D, GR.GL_TEXTURE_MIN_FILTER, GR.GL_NEAREST);
            gr.glTexImage2D(GR.GL_TEXTURE_2D, 0, GR.GL_RGBA, 128, 128, 0, GR.GL_RGBA, GR.GL_UNSIGNED_BYTE, rgbaBuffer);
        }
        public static void HandleVideoCaptureGUI
        (
            GR gr,
            float videoSheetX,
            float videoSheetY,
            float videoSheetWidth,
            float videoSheetHeight,
            STGame game,
            int clientWidth,
            int clientHeight,
            int clientRelativeCursorX,
            int clientRelativeCursorY
        )
        {
            STGameState gameState = game.GetGameState( );


            if (false == game.GameIsSpawnFromVideoCapture( ))
            {
                return;
            }


            gr.glBindTexture
            (
                GR.GL_TEXTURE_2D,
                STEngine.GetVideoProcessing( ).mTextureOpenGLHandleBGR256x256
            );


            float x1 = 0.0f;
            float y1 = 0.0f;
            float x2 = 0.0f;
            float y2 = 0.0f;

            x1 = videoSheetX;
            y1 = videoSheetY;
            x2 = x1 + (videoSheetWidth - 1.0f);
            y2 = y1 + (videoSheetHeight - 1.0f);

            float u1 = 0.0f;
            float v1 = 0.0f;
            float u2 = 0.0f;
            float v2 = 0.0f;

            u1 = 0.0f;
            v1 = 0.0f;
            u2 = 0.5f;
            v2 = 1.0f;

            gr.glEnable(GR.GL_SCISSOR_TEST);
            gr.glScissor((int)(x1), (int)(y1), (int)((x2 - x1) + 1), (int)((y2 - y1) + 1));

            gr.glEnable(GR.GL_TEXTURE_2D);
            gr.glColor3f(1.0f, 1.0f, 1.0f);

            gr.glBegin(GR.GL_QUADS);
            gr.glTexCoord2f(u1, v2);
            gr.glVertex2f(x1, y2);

            gr.glTexCoord2f(u1, v1);
            gr.glVertex2f(x1, y1);

            gr.glTexCoord2f(u2, v1);
            gr.glVertex2f(x2, y1);

            gr.glTexCoord2f(u2, v2);
            gr.glVertex2f(x2, y2);
            gr.glEnd( );

            gr.glDisable(GR.GL_TEXTURE_2D);
            gr.glDisable(GR.GL_SCISSOR_TEST);



            int xTexelMin = 0;
            int yTexelMin = 0;
            int xTexelMax = 0;
            int yTexelMax = 0;


            int xScreenMin = 0;
            int yScreenMin = 0;
            int xScreenMax = 0;
            int yScreenMax = 0;



            // Only listen to the mouse in training/calibration mode
            if (true == gameState.mCalibrationModeFlag)
            {
                if (0 != GetAsyncKeyState(Keys.LButton))
                {
                    // Left button pressed
                    if (0 == gameState.mSelectionState)
                    {
                        gameState.mSelectionState = 1;
                        gameState.mSelectionX1    = clientRelativeCursorX;
                        gameState.mSelectionY1    = ((clientHeight - 1) - clientRelativeCursorY);
                        gameState.mSelectionX2    = clientRelativeCursorX;
                        gameState.mSelectionY2    = ((clientHeight - 1) - clientRelativeCursorY);
                    }
                    else
                    {
                        gameState.mSelectionX2 = clientRelativeCursorX;
                        gameState.mSelectionY2 = ((clientHeight - 1) - clientRelativeCursorY);
                    }
                }
                else
                {
                    // Left button released
                    if (0 == gameState.mSelectionState)
                    {
                        // Nothing to do...
                    }
                    else
                    {
                        gameState.mSelectionState = 0;
                    }
                }

                gr.glEnable(GR.GL_SCISSOR_TEST);
                gr.glScissor(0, 0, clientWidth, clientHeight);

                gr.glColor3f(1.0f, 0.0f, 0.0f);
                gr.glBegin(GR.GL_LINES);

                gr.glVertex2f((float)clientRelativeCursorX - 8.0f, (float)((clientHeight - 1) - clientRelativeCursorY));
                gr.glVertex2f((float)clientRelativeCursorX + 8.0f, (float)((clientHeight - 1) - clientRelativeCursorY));

                gr.glVertex2f((float)clientRelativeCursorX, (float)((clientHeight - 1) - clientRelativeCursorY) - 8.0f);
                gr.glVertex2f((float)clientRelativeCursorX, (float)((clientHeight - 1) - clientRelativeCursorY) + 8.0f);
                gr.glEnd( );
            }



            if (0 != ((GetAsyncKeyState(Keys.Shift)) & 0x8000))
            {
                if (0 != ((GetAsyncKeyState(Keys.Left)) & 0x8000))
                {
                    gameState.mSelectionX2--;
                }
                if (0 != ((GetAsyncKeyState(Keys.Right)) & 0x8000))
                {
                    gameState.mSelectionX2++;
                }
                if (0 != ((GetAsyncKeyState(Keys.Down)) & 0x8000))
                {
                    gameState.mSelectionY2--;
                }
                if (0 != ((GetAsyncKeyState(Keys.Up)) & 0x8000))
                {
                    gameState.mSelectionY2++;
                }
            }
            else
            {
                if (0 != ((GetAsyncKeyState(Keys.Left)) & 0x8000))
                {
                    gameState.mSelectionX1--;
                }
                if (0 != ((GetAsyncKeyState(Keys.Right)) & 0x8000))
                {
                    gameState.mSelectionX1++;
                }
                if (0 != ((GetAsyncKeyState(Keys.Down)) & 0x8000))
                {
                    gameState.mSelectionY1--;
                }
                if (0 != ((GetAsyncKeyState(Keys.Up)) & 0x8000))
                {
                    gameState.mSelectionY1++;
                }
            }



            xScreenMin = gameState.mSelectionX1;
            yScreenMin = gameState.mSelectionY1;
            xScreenMax = gameState.mSelectionX2;
            yScreenMax = gameState.mSelectionY2;



            xTexelMin = (int)(256.0f * (((float)xScreenMin - videoSheetX) / videoSheetHeight));
            yTexelMin = (int)(256.0f * (((float)yScreenMin - videoSheetY) / videoSheetHeight));
            xTexelMax = (int)(256.0f * (((float)xScreenMax - videoSheetX) / videoSheetHeight));
            yTexelMax = (int)(256.0f * (((float)yScreenMax - videoSheetY) / videoSheetHeight));

            int disregard = 0;

            if (xTexelMin < 0)
            {
                disregard = 1;
                xTexelMin = 0;
            }
            if (yTexelMin < 0)
            {
                disregard = 1;
                yTexelMin = 0;
            }
            if (xTexelMax < 0)
            {
                disregard = 1;
                xTexelMax = 0;
            }
            if (yTexelMax < 0)
            {
                disregard = 1;
                yTexelMax = 0;
            }

            if (xTexelMin > 255)
            {
                disregard = 1;
                xTexelMin = 255;
            }
            if (yTexelMin > 255)
            {
                disregard = 1;
                yTexelMin = 255;
            }
            if (xTexelMax > 255)
            {
                disregard = 1;
                xTexelMax = 255;
            }
            if (yTexelMax > 255)
            {
                disregard = 1;
                yTexelMax = 255;
            }

            if (xTexelMin > xTexelMax)
            {
                int swap = xTexelMin;
                xTexelMin = xTexelMax;
                xTexelMax = swap;
            }

            if (yTexelMin > yTexelMax)
            {
                int swap = yTexelMin;
                yTexelMin = yTexelMax;
                yTexelMax = swap;
            }


            // Only set region if in training mode!
            if ((true == gameState.mCalibrationModeFlag) && (0 == disregard))
            {
                STEngine.GetVideoProcessing( ).SetRegion(xTexelMin, yTexelMin, xTexelMax, yTexelMax);
            }


            STEngine.GetVideoProcessing( ).GetRegion(ref xTexelMin, ref yTexelMin, ref xTexelMax, ref yTexelMax);

            xScreenMin = (int)(videoSheetX + (videoSheetHeight * (float)xTexelMin / 256.0f));
            yScreenMin = (int)(videoSheetY + (videoSheetHeight * (float)yTexelMin / 256.0f));
            xScreenMax = (int)(videoSheetX + (videoSheetHeight * (float)xTexelMax / 256.0f));
            yScreenMax = (int)(videoSheetY + (videoSheetHeight * (float)yTexelMax / 256.0f));


            x1 = videoSheetX;
            y1 = videoSheetY;
            x2 = x1 + (videoSheetWidth - 1.0f);
            y2 = y1 + (videoSheetHeight - 1.0f);


            int currentClassification = STEngine.GetVideoProcessing( ).GetRegionClassification( );

            if (0 == currentClassification)
            {
                // If the previous classification was a PIECE, and the current classification
                // is something different, then submit the piece (which must have fallen
                // by a row by now).
                if ((gameState.mPreviousClassification >= 1) && (gameState.mPreviousClassification <= 7))
                {
                    game.SpawnSpecifiedPieceShape(STPiece.GetShapeCorrespondingToByteCode((byte)gameState.mPreviousClassification));
                }
            }

            gameState.mPreviousClassification = currentClassification;


            Color color;

            color =
                STGameDrawing.GetCellValueColorARGB // Returns WHITE for unknown
                (
                    (byte)currentClassification,    // 0..6
                    false                           // monochrome mode
                );

            float red   = 0.0f;
            float green = 0.0f;
            float blue  = 0.0f;

            red   = (float)(color.R) / 255.0f;
            green = (float)(color.G) / 255.0f;
            blue  = (float)(color.B) / 255.0f;


            gr.glColor3f(red, green, blue);

            gr.glBegin(GR.GL_LINES);

            gr.glVertex2f((float)xScreenMin, (float)yScreenMin);
            gr.glVertex2f((float)xScreenMin, (float)yScreenMax);

            gr.glVertex2f((float)xScreenMax, (float)yScreenMin);
            gr.glVertex2f((float)xScreenMax, (float)yScreenMax);

            gr.glVertex2f((float)xScreenMin, (float)yScreenMin);
            gr.glVertex2f((float)xScreenMax, (float)yScreenMin);

            gr.glVertex2f((float)xScreenMin, (float)yScreenMax);
            gr.glVertex2f((float)xScreenMax, (float)yScreenMax);

            // Horizontal divider
            gr.glVertex2f((float)xScreenMin, (float)((yScreenMin + yScreenMax) / 2));
            gr.glVertex2f((float)xScreenMax, (float)((yScreenMin + yScreenMax) / 2));

            // Vertical dividers
            gr.glVertex2f((float)(xScreenMin + ((xScreenMax - xScreenMin) / 4)), (float)yScreenMin);
            gr.glVertex2f((float)(xScreenMin + ((xScreenMax - xScreenMin) / 4)), (float)yScreenMax);

            gr.glVertex2f((float)(xScreenMin + 2 * ((xScreenMax - xScreenMin) / 4)), (float)yScreenMin);
            gr.glVertex2f((float)(xScreenMin + 2 * ((xScreenMax - xScreenMin) / 4)), (float)yScreenMax);

            gr.glVertex2f((float)(xScreenMin + 3 * ((xScreenMax - xScreenMin) / 4)), (float)yScreenMin);
            gr.glVertex2f((float)(xScreenMin + 3 * ((xScreenMax - xScreenMin) / 4)), (float)yScreenMax);

            gr.glEnd( );

            gr.glDisable(GR.GL_SCISSOR_TEST);
        }
        Initialize
        (
            GR gr,
            IntPtr hwndParentWindow
        )
        {
            this.Clear( );

            // Cache the GR instance
            this.mGR = gr;


            this.mTextureDataBGR256x256 = new byte[(256 * (256 * 3))];


            this.mSTVideoCapture = new STVideoCapture();

            int capturePeriodMilliseconds = 33; // 33 milliseconds --> 30 frames per second (max)

            // Attempt to initialize video capture
            bool videoCaptureInitializationResult = false;

            videoCaptureInitializationResult =
                this.mSTVideoCapture.Initialize
                (
                    hwndParentWindow,
                    STVideoCapture.STVideoCaptureFormat.BGR320x240,
                    capturePeriodMilliseconds,
                    new STVideoCapture.DelegateClientCaptureCallback(this.ClientCaptureCallback)
                );

            if (false == videoCaptureInitializationResult)
            {
                return(false);
            }



            // Create OpenGL texture object
            int[] temp = new int[1];
            gr.glGenTextures(1, temp);
            this.mTextureOpenGLHandleBGR256x256 = temp[0];


            // Fill the OpenGL texture with initial data
            gr.glBindTexture
            (
                GR.GL_TEXTURE_2D,
                this.mTextureOpenGLHandleBGR256x256
            );

            gr.glTexImage2D
            (
                GR.GL_TEXTURE_2D,
                0,
                3,
                256,
                256,
                0,
                GR.GL_BGR_EXT,
                GR.GL_UNSIGNED_BYTE,
                this.mTextureDataBGR256x256
            );

            // Set texture mapping mode and filtering modes
            gr.glTexParameteri(GR.GL_TEXTURE_2D, GR.GL_TEXTURE_WRAP_S, GR.GL_REPEAT);
            gr.glTexParameteri(GR.GL_TEXTURE_2D, GR.GL_TEXTURE_WRAP_T, GR.GL_REPEAT);

            gr.glTexParameterf(GR.GL_TEXTURE_2D, GR.GL_TEXTURE_MIN_FILTER, GR.GL_LINEAR);
            gr.glTexParameterf(GR.GL_TEXTURE_2D, GR.GL_TEXTURE_MAG_FILTER, GR.GL_LINEAR);


            // deselect the current texture
            gr.glBindTexture(GR.GL_TEXTURE_2D, 0);


            return(true);
        }
        public static void FontPrint(GR gr, float x, float y, String asciiText)
        {
            int n = 0;

            n = asciiText.Length;
            if (n <= 0)
            {
                return;
            }


            if (0 == mFontTextureName)
            {
                MakeFontTexture(gr);
            }

            // DISABLE Z-WRITES AND Z-TESTING
            gr.glDisable(GR.GL_DEPTH_TEST);

            gr.glEnable(GR.GL_BLEND);
            gr.glBlendFunc(GR.GL_SRC_ALPHA, GR.GL_ONE_MINUS_SRC_ALPHA);
            gr.glEnable(GR.GL_TEXTURE_2D);
            gr.glBindTexture(GR.GL_TEXTURE_2D, mFontTextureName);


            gr.glBegin(GR.GL_TRIANGLES);


            float du = 0.0f;
            float dv = 0.0f;

            du = (0.0625f);
            dv = (0.1250f);

            int   i     = 0;
            int   j     = 0;
            int   k     = 0;
            int   index = 0;
            float u     = 0.0f;
            float v     = 0.0f;

            for (k = 0; k < n; k++)
            {
                index = (int)(asciiText[k]);

                if (index > 255)
                {
                    index = 0;
                }
                else if (index >= 192)
                {
                    index -= 96;
                }
                else if (index >= 32)
                {
                    index -= 32;
                }
                else if (index < 32)
                {
                    index = 0;
                }

                if (index < 0)
                {
                    index = 0;
                }
                if (index >= 128)
                {
                    index = 0;
                }

                i = (index & 0xf);
                j = (7 - (index >> 4));

                u = du * ((float)i);
                v = dv * ((float)j);

                gr.glTexCoord2f(u, v);
                gr.glVertex3f(x + (float)(FONT_WIDTH * k), y - (float)(FONT_HEIGHT), 0.0f);
                gr.glTexCoord2f((u + du), v);
                gr.glVertex3f(x + (float)(FONT_WIDTH * (k + 1)), y - (float)(FONT_HEIGHT), 0.0f);
                gr.glTexCoord2f(u, (v + dv));
                gr.glVertex3f(x + (float)(FONT_WIDTH * k), y, 0.0f);

                gr.glTexCoord2f(u, (v + dv));
                gr.glVertex3f(x + (float)(FONT_WIDTH * k), y, 0.0f);
                gr.glTexCoord2f((u + du), v);
                gr.glVertex3f(x + (float)(FONT_WIDTH * (k + 1)), y - (float)(FONT_HEIGHT), 0.0f);
                gr.glTexCoord2f((u + du), (v + dv));
                gr.glVertex3f(x + (float)(FONT_WIDTH * (k + 1)), y, 0.0f);
            }

            gr.glEnd( );

            gr.glDisable(GR.GL_TEXTURE_2D);
            gr.glDisable(GR.GL_BLEND);
            gr.glEnable(GR.GL_DEPTH_TEST);
        }