Search code examples
c#opentkpickingmouse-picking

GLControl Picking Issue


I am, yet again, having a slight issue with picking in OpenGL/OpenTK. This time, unlike a thread I had started awhile back, it is actually working. The only issue is that the "tiles" are not correctly chosen. What I mean is that if I click a location on the grid, it selects the grid-tile directly next to it. However, if I move the mouse ever so slightly, it will select the grid-tile correctly. It is rather hard to explain, so I will be uploading two pictures, code, and the executable itself.

Here is what my view looks like:

enter image description here

It is a basic grid-selection system. And it works. However, the selection routine seems to be a little off.

here is a picture of the underlying view, which is not actually seen by the user. It is the scene that is painted to the backbuffer when the user clicks, this is just to help reveal what the picking system actually looks like.

enter image description here

The red circle and x show a location which I had just clicked, but had not been correctly registered; no voxel had been added to the collection for that location. However, if I move the mouse slightly to the right, or nearer the center of the tile, a voxel would be correctly added for that location.

Here is the primary bulk of the code; I will add anything else that is found necessary. Oh, and sorry for how messy it is, I have not had time to clean it up.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
using AnimusEngine.Engine.Datatypes;
using AnimusEngine.Engine.Editor.AnimusEditor.Widgets.GLRenderer.Widgets;
using AnimusEngine.Engine.Utilities;
using AnimusEngineRuntime.Runtime.CameraBindings;
using AnimusEngineRuntime.Runtime.Entities;
using AnimusEngineRuntime.Runtime.Utilities;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
using MouseEventArgs = System.Windows.Forms.MouseEventArgs;

namespace AnimusEngine.Engine.Editor.AnimusEditor.Widgets.GLRenderer {
    public class AnimusRenderingModule : Panel {
        public enum CameraMode {
            FirstPerson ,
            Arcball ,
            Pan
        }
        public GLControl OpenGlLayer;
        public Camera Camera;
        public ToolStrip OpenGlLayerToolstrip;
        public RenderingModuleStatusStrip OpenGlLayerStatusStrip;
        private Point _mouseLocation;
        private Point _lastMousePosition;
        private Stopwatch _renderTimer;
        private FrameCounter _frameCounter;
        private bool _isLooking;
        private int _gridModelDisplayListHandle;
        private int _planeModelDisplayListHandle;
        private int _planeTileSelectionDisplayListHandle;
        private Random _clientRandomInstance;
        private bool _isRotating;
        public CameraMode ActiveCameraMode;
        private List<SelectableTile> _selectionTileList;
        public List<Vector3> VoxelLocations;


        public AnimusRenderingModule() {
            Initialize();
        }

        private void DrawGrid() {
            GL.PushMatrix();
            {
                GL.Translate( -1.0f , 0.0f , -1.0f );
                GL.Begin( PrimitiveType.Lines );
                for( float i = 0 ; i <= 130 ; i += 2 ) {
                    if( i == 0 ) {
                        GL.Color3( .6 , .3 , .3 );
                    } else {
                        GL.Color3( .25 , .25 , .25 );
                    }
                    GL.Vertex3( i , -1.0f , 0.0f );
                    GL.Vertex3( i , -1.0f , 130.0f );
                    if( i == 0 ) {
                        GL.Color3( .3 , .3 , .6 );
                    } else {
                        GL.Color3( .25 , .25 , .25 );
                    }
                    GL.Vertex3( 0.0f , -1.0f , i );
                    GL.Vertex3( 130.0f , -1.0f , i );
                }
                GL.End();
            }
            GL.PopMatrix();
        }

        private void DrawPlane() {
            GL.Begin( PrimitiveType.Triangles );
            // Triangle 1
            GL.Vertex3( -1.0 , -1.0 , -1.0 );
            GL.Vertex3( -1.0 , -1.0 , 1.0 );
            GL.Vertex3( 1.0 , -1.0 , -1.0 );
            // Triangle 2
            GL.Vertex3( -1.0 , -1.0 , 1.0 );
            GL.Vertex3( 1.0 , -1.0 , 1.0 );
            GL.Vertex3( 1.0 , -1.0 , -1.0 );
            GL.End();
        }

        private void PickGeometry( int x , int y ) {
            Console.WriteLine( "Picking..." );
            /**
             * Draw The Geometry As solid
             * colors
             */
            GL.ClearColor( 1.0f , 1.0f , 1.0f , 1.0f );
            GL.Clear( ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit );
            Camera.LookThrough();
            GL.MatrixMode( MatrixMode.Modelview );
            GL.LoadMatrix( ref Camera.CameraMatrix );
            GL.PushMatrix();
            GL.CallList( _planeTileSelectionDisplayListHandle );
            GL.PopMatrix();
            byte[] pixel = new byte[ 3 ];
            int[] viewport = new int[ 4 ];
            GL.GetInteger( GetPName.Viewport , viewport );
            GL.ReadPixels( x , viewport[ 3 ] - y , 1 , 1 , PixelFormat.Rgba , PixelType.UnsignedByte , pixel );
            Color pickedColor = Color.FromArgb( pixel[ 0 ] , pixel[ 1 ] , pixel[ 2 ] );
            foreach( SelectableTile colorTest in _selectionTileList ) {
                if( colorTest.TileColor.R == pickedColor.R && colorTest.TileColor.G == pickedColor.G && colorTest.TileColor.B == pickedColor.B ) {
                    Console.WriteLine( "Tile Location: " + colorTest.Location );
                    VoxelLocations.Add( colorTest.Location );
                }
            }
            Console.WriteLine( pickedColor.ToString() );
            Console.WriteLine( "Picking Done" );
            GL.ClearColor( 0.3f , 0.3f , 0.3f , 0.0f );
        }


        private void DrawCube() {
            GL.Begin( PrimitiveType.Quads );
            GL.Color3( Color.Silver );
            GL.Vertex3( -1.0f , -1.0f , -1.0f );
            GL.Vertex3( -1.0f , 1.0f , -1.0f );
            GL.Vertex3( 1.0f , 1.0f , -1.0f );
            GL.Vertex3( 1.0f , -1.0f , -1.0f );
            GL.Color3( Color.Honeydew );
            GL.Vertex3( -1.0f , -1.0f , -1.0f );
            GL.Vertex3( 1.0f , -1.0f , -1.0f );
            GL.Vertex3( 1.0f , -1.0f , 1.0f );
            GL.Vertex3( -1.0f , -1.0f , 1.0f );
            GL.Color3( Color.Moccasin );
            GL.Vertex3( -1.0f , -1.0f , -1.0f );
            GL.Vertex3( -1.0f , -1.0f , 1.0f );
            GL.Vertex3( -1.0f , 1.0f , 1.0f );
            GL.Vertex3( -1.0f , 1.0f , -1.0f );
            GL.Color3( Color.IndianRed );
            GL.Vertex3( -1.0f , -1.0f , 1.0f );
            GL.Vertex3( 1.0f , -1.0f , 1.0f );
            GL.Vertex3( 1.0f , 1.0f , 1.0f );
            GL.Vertex3( -1.0f , 1.0f , 1.0f );
            GL.Color3( Color.PaleVioletRed );
            GL.Vertex3( -1.0f , 1.0f , -1.0f );
            GL.Vertex3( -1.0f , 1.0f , 1.0f );
            GL.Vertex3( 1.0f , 1.0f , 1.0f );
            GL.Vertex3( 1.0f , 1.0f , -1.0f );
            GL.Color3( Color.ForestGreen );
            GL.Vertex3( 1.0f , -1.0f , -1.0f );
            GL.Vertex3( 1.0f , 1.0f , -1.0f );
            GL.Vertex3( 1.0f , 1.0f , 1.0f );
            GL.Vertex3( 1.0f , -1.0f , 1.0f );
            GL.End();
        }

        private void Render() {
            if( _isLooking ) {
                Point windowCenter = WinFormsExtensions.FindCenterOfControl( this );
                Vector2 mouseDelta = new Vector2( _mouseLocation.X - this.PointToClient( windowCenter ).X , _mouseLocation.Y - this.PointToClient( windowCenter ).Y );
                Cursor.Position = windowCenter;
                Camera.MouseSpeed.X = ( ( mouseDelta.X / 40.0f * Camera.LookSpeed ) * ( float )_renderTimer.Elapsed.TotalMilliseconds );
                Camera.MouseSpeed.Y = ( ( mouseDelta.Y / 40.0f * Camera.LookSpeed ) * ( float )_renderTimer.Elapsed.TotalMilliseconds );
                Camera.Yaw += Camera.MouseSpeed.X;
                Camera.Pitch -= Camera.MouseSpeed.Y;
                if( Camera.Pitch <= -( ( MathHelper.Pi ) * 0.5f ) + 0.01f ) {
                    Camera.Pitch = -( ( MathHelper.Pi ) * 0.5f ) + 0.01f;
                }
                if( Camera.Pitch >= ( ( MathHelper.Pi ) * 0.5f ) - 0.01f ) {
                    Camera.Pitch = ( ( MathHelper.Pi ) * 0.5f ) - 0.01f;
                }
            }
            Camera.LookThrough();
            GL.MatrixMode( MatrixMode.Modelview );
            GL.Clear( ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit );
            GL.LoadMatrix( ref Camera.CameraMatrix );
            //
            //GL.CallList( _planeTileSelectionDisplayListHandle );
            GL.CallList( _gridModelDisplayListHandle );
            foreach( Vector3 location in VoxelLocations ) {
                GL.PushMatrix();
                GL.Translate( location );
                DrawCube();
                GL.PopMatrix();
            }
            //
        }

        private void Initialize() {
            _renderTimer = new Stopwatch();
            this.OpenGlLayer = new GLControl();
            this._frameCounter = new FrameCounter();
            this.OpenGlLayer.Dock = DockStyle.Fill;
            this.OpenGlLayer.MouseDown += OpenGLLayer_MouseDown;
            this.OpenGlLayer.MouseMove += OpenGLLayer_MouseMove;
            this.OpenGlLayer.MouseUp += OpenGLLayer_MouseUp;
            this.OpenGlLayer.Resize += OpenGLLayer_Resize;
            this.OpenGlLayer.Paint += OpenGLLayer_Paint;
            this.OpenGlLayer.Load += OpenGLLayer_Load;
            Application.Idle += Application_Idle;
            this.OpenGlLayerToolstrip = new ToolStrip();
            this.OpenGlLayerToolstrip.Items.Add( new ToolStripButton() );
            this.OpenGlLayerToolstrip.Dock = DockStyle.Top;
            this.OpenGlLayerStatusStrip = new RenderingModuleStatusStrip();
            this.Controls.Add( this.OpenGlLayerToolstrip );
            this.Controls.Add( this.OpenGlLayer );
            this.Controls.Add( this.OpenGlLayerStatusStrip );
            this.OpenGlLayerStatusStrip.UpdateFramerateDisplay( 60 );
            this.OpenGlLayerStatusStrip.UpdateTotalVoxelDisplay( 2000 );
            this.ActiveCameraMode = CameraMode.FirstPerson;
            this._clientRandomInstance = new Random();
            this._selectionTileList = new List<SelectableTile>();
            for( int tileX = 0 ; tileX <= 128 ; tileX += 2 ) {
                for( int tileZ = 0 ; tileZ <= 128 ; tileZ += 2 ) {
                    byte red = ( byte )_clientRandomInstance.Next( 256 );
                    byte green = ( byte )_clientRandomInstance.Next( 256 );
                    byte blue = ( byte )_clientRandomInstance.Next( 256 );
                    this._selectionTileList.Add( new SelectableTile( Color.FromArgb( red , green , blue ) , new Vector3( tileX , 0.0f , tileZ ) ) );
                }
            }
            VoxelLocations = new List<Vector3>();
        }

        private void GenerateEngineResources() {
            _gridModelDisplayListHandle = MeshGenerator.Generate( DrawGrid );
            _planeModelDisplayListHandle = MeshGenerator.Generate( DrawPlane );
            _planeTileSelectionDisplayListHandle = MeshGenerator.Generate( delegate {
                foreach( SelectableTile tile in _selectionTileList ) {
                    GL.PushMatrix();
                    GL.Translate( tile.Location );
                    GL.Color3( tile.TileColor );
                    DrawPlane();
                    GL.PopMatrix();
                }
            } );
        }

        private void OpenGLLayer_Load( object sender , EventArgs e ) {
            _renderTimer.Start();
            GL.Enable( EnableCap.DepthTest );
            GL.ClearColor( 0.3f , 0.3f , 0.3f , 0.0f );
            GL.CullFace( CullFaceMode.Back );
            GL.Enable( EnableCap.CullFace );
            GL.BlendFunc( BlendingFactorSrc.SrcAlpha , BlendingFactorDest.OneMinusSrcAlpha );
            GL.Enable( EnableCap.Blend );
            Camera = new Camera();
            Camera.Location = new Vector3( -10.0f , 0.0f , 0.0f );
            Camera.Pitch = 0.0f;
            Camera.Yaw = 0.0f;
            Camera.MoveSpeed = 0.02f;
            Camera.LookSpeed = 0.4f;
            GenerateEngineResources();
        }

        private void Application_Idle( object sender , EventArgs e ) {
            KeyboardState keyboardState = Keyboard.GetState();
            ProcessMovement( keyboardState );
            this.OpenGlLayer.Invalidate();
            _renderTimer.Restart();
        }



        private void ProcessMovement( KeyboardState keyboardState ) {
            if( !this.OpenGlLayer.Focused ) {
                return;
            }
            if( keyboardState[ Key.W ] ) {
                Camera.MoveForward( ( float )_renderTimer.Elapsed.TotalMilliseconds );
            }
            if( keyboardState[ Key.S ] ) {
                Camera.MoveBackward( ( float )_renderTimer.Elapsed.TotalMilliseconds );
            }
            if( keyboardState[ Key.A ] ) {
                Camera.StrafeLeft( ( float )_renderTimer.Elapsed.TotalMilliseconds );
            }
            if( keyboardState[ Key.D ] ) {
                Camera.StrafeRight( ( float )_renderTimer.Elapsed.TotalMilliseconds );
            }
            if( keyboardState[ Key.Space ] ) {
                Camera.FlyUp( ( float )_renderTimer.Elapsed.TotalMilliseconds );
            }
            if( keyboardState[ Key.LShift ] || keyboardState[ Key.RShift ] ) {
                Camera.FlyDown( ( float )_renderTimer.Elapsed.TotalMilliseconds );
            }
            if( keyboardState[ Key.AltLeft ] ) {
                this._isRotating = true;
                this.ActiveCameraMode = CameraMode.Arcball;
                this.OpenGlLayerStatusStrip.UpdateCameraModeDisplay( this.ActiveCameraMode );

            } else {
                this._isRotating = false;
                this.ActiveCameraMode = CameraMode.FirstPerson;
                this.OpenGlLayerStatusStrip.UpdateCameraModeDisplay( this.ActiveCameraMode );
            }
        }

        private void OpenGLLayer_Resize( object sender , EventArgs e ) {
            if( !this.OpenGlLayer.IsHandleCreated ) {
                return;
            }
            GL.Viewport( ClientRectangle.X , ClientRectangle.Y , ClientRectangle.Width , ClientRectangle.Height );
            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView( ( float )Math.PI / 4 , Width / ( float )Height , 0.1f , 1000.0f );
            GL.MatrixMode( MatrixMode.Projection );
            GL.LoadMatrix( ref projection );
        }

        private void OpenGLLayer_Paint( object sender , PaintEventArgs e ) {
            if( !this.OpenGlLayer.IsHandleCreated ) {
                return;
            }
            Render();
            this.OpenGlLayer.SwapBuffers();
        }

        private void OpenGLLayer_MouseUp( object sender , MouseEventArgs e ) {
            if( e.Button == MouseButtons.Right ) {
                _isLooking = false;
                Cursor.Position = _lastMousePosition;
                Cursor.Show();
            }
        }

        private void OpenGLLayer_MouseMove( object sender , MouseEventArgs e ) {
            _mouseLocation = new Point( e.X , e.Y );
        }

        private void OpenGLLayer_MouseDown( object sender , MouseEventArgs e ) {
            if( e.Button == MouseButtons.Right && e.Button != MouseButtons.Left ) {
                Point nativeMouse = Cursor.Position;
                _lastMousePosition = nativeMouse;
                Point windowCenter = WinFormsExtensions.FindCenterOfControl( this );
                Cursor.Position = windowCenter;
                _isLooking = true;
                Cursor.Hide();
            }
            if( e.Button == MouseButtons.Left && e.Button != MouseButtons.Right ) {
                PickGeometry( e.X , e.Y );
            }
        }
    }
}

And here is a download link for the executable itself, so that you guys can see the problem first-hand instead of through my vague, inadequate verbal descriptions.

Download Link

EDIT/Update

I think I just discovered the issue. The control itself is returning (Width 518, Height 537) during a resize. However, I measured this in GIMP and discovered that I am only getting a visual area of (518,512) and this means that the control is being partially covered up by the neighboring toolstrip, and lower displaystrip control. Thinking this was the case I tried to offset the y value of the picking routine by -25(the height of one of the controls) and it magically started working better. I think that actually solves the problem, I just need to orient the controls better and bring the opengl context to the front. This should fix it; if that is the case I will supply an answer below soon.


Solution

  • I figured it out! It was not an issue with the controls, but instead, how I was recreating the opengl viewport itself. The problem was nothing more than a minor oversight. I had to change:

    GL.Viewport( ClientRectangle.X , ClientRectangle.Y , ClientRectangle.Width , ClientRectangle.Height );
    

    To:

    GL.Viewport( this.OpenGlLayer.ClientRectangle.X , this.OpenGlLayer.ClientRectangle.Y , this.OpenGlLayer.ClientRectangle.Width , this.OpenGlLayer.ClientRectangle.Height );
    

    The picking is working like a charm now! Lesson learned, make sure you're not using the wrong/or mapping to the wrong control when using a GLControl!