using System; using System.IO; using System.Drawing; using System.Diagnostics; using OpenTK; using OpenTK.Graphics; using OpenTK.Graphics.OpenGL; using OpenTK.Input; using Examples.Shapes; namespace Examples.Tutorial { /// /// This demo shows over which triangle the cursor is, it does so by assigning all 3 vertices of a triangle the same Ids. /// Each Id is a uint, split into 4 bytes and used as triangle color. In an extra pass, the screen is cleared to uint.MaxValue, /// and then the mesh is drawn using color. Using GL.ReadPixels() the value under the mouse cursor is read and can be converted. /// [Example("Picking", ExampleCategory.OpenGL, "1.x", Documentation = "Picking")] class Picking : GameWindow { /// Creates a 800x600 window with the specified title. public Picking() : base(800, 600, GraphicsMode.Default, "Picking", GameWindowFlags.Default, DisplayDevice.Default, 1, 1, GraphicsContextFlags.Default) { VSync = VSyncMode.On; } struct Byte4 { public byte R, G, B, A; public Byte4(byte[] input) { R = input[0]; G = input[1]; B = input[2]; A = input[3]; } public uint ToUInt32() { byte[] temp = new byte[] { this.R, this.G, this.B, this.A }; return BitConverter.ToUInt32(temp, 0); } public override string ToString() { return this.R + ", " + this.G + ", " + this.B + ", " + this.A; } } struct Vertex { public Byte4 Color; // 4 bytes public Vector3 Position; // 12 bytes public const byte SizeInBytes = 16; } const TextureTarget Target = TextureTarget.TextureRectangleArb; float angle; BeginMode VBO_PrimMode; Vertex[] VBO_Array; uint VBO_Handle; uint SelectedTriangle; // int VertexShaderObject, FragmentShaderObject, ProgramObject; /// Load resources here. /// Not used. protected override void OnLoad(EventArgs e) { GL.Enable(EnableCap.DepthTest); GL.Enable(EnableCap.CullFace); #region prepare data for VBO from procedural object DrawableShape temp_obj = new SierpinskiTetrahedron(3f, SierpinskiTetrahedron.eSubdivisions.Five, false); VertexT2fN3fV3f[] temp_VBO; uint[] temp_IBO; temp_obj.GetArraysforVBO(out VBO_PrimMode, out temp_VBO, out temp_IBO); temp_obj.Dispose(); if (temp_IBO != null) throw new Exception("Expected data for GL.DrawArrays, but Element Array is not null."); // Convert from temp mesh to final object, copy position and add triangle Ids for the color attribute. VBO_Array = new Vertex[temp_VBO.Length]; int TriangleCounter = -1; for (int i = 0; i < temp_VBO.Length; i++) { // Position VBO_Array[i].Position = temp_VBO[i].Position; // Index if (i % 3 == 0) TriangleCounter++; VBO_Array[i].Color = new Byte4(BitConverter.GetBytes(TriangleCounter)); } #endregion prepare data for VBO from procedural object #region Setup VBO for drawing GL.GenBuffers(1, out VBO_Handle); GL.BindBuffer(BufferTarget.ArrayBuffer, VBO_Handle); GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(VBO_Array.Length * Vertex.SizeInBytes), VBO_Array, BufferUsageHint.StaticDraw); GL.InterleavedArrays(InterleavedArrayFormat.C4ubV3f, 0, IntPtr.Zero); ErrorCode err = GL.GetError(); if (err != ErrorCode.NoError) Trace.WriteLine("VBO Setup failed (Error: " + err + "). Attempting to continue."); #endregion Setup VBO for drawing #region Shader /* // Load&Compile Vertex Shader using (StreamReader sr = new StreamReader("Data/Shaders/Picking_VS.glsl")) { VertexShaderObject = GL.CreateShader(ShaderType.VertexShader); GL.ShaderSource(VertexShaderObject, sr.ReadToEnd()); GL.CompileShader(VertexShaderObject); } err = GL.GetError(); if (err != ErrorCode.NoError) Trace.WriteLine("Vertex Shader: " + err); string LogInfo; GL.GetShaderInfoLog(VertexShaderObject, out LogInfo); if (LogInfo.Length > 0 && !LogInfo.Contains("hardware")) Trace.WriteLine("Vertex Shader failed!\nLog:\n" + LogInfo); else Trace.WriteLine("Vertex Shader compiled without complaint."); // Load&Compile Fragment Shader using (StreamReader sr = new StreamReader("Data/Shaders/Picking_FS.glsl")) { FragmentShaderObject = GL.CreateShader(ShaderType.FragmentShader); GL.ShaderSource(FragmentShaderObject, sr.ReadToEnd()); GL.CompileShader(FragmentShaderObject); } GL.GetShaderInfoLog(FragmentShaderObject, out LogInfo); err = GL.GetError(); if (err != ErrorCode.NoError) Trace.WriteLine("Fragment Shader: " + err); if (LogInfo.Length > 0 && !LogInfo.Contains("hardware")) Trace.WriteLine("Fragment Shader failed!\nLog:\n" + LogInfo); else Trace.WriteLine("Fragment Shader compiled without complaint."); // Link the Shaders to a usable Program ProgramObject = GL.CreateProgram(); GL.AttachShader(ProgramObject, VertexShaderObject); GL.AttachShader(ProgramObject, FragmentShaderObject); // link it all together GL.LinkProgram(ProgramObject); err = GL.GetError(); if (err != ErrorCode.NoError) Trace.WriteLine("LinkProgram: " + err); GL.UseProgram(ProgramObject); err = GL.GetError(); if (err != ErrorCode.NoError) Trace.WriteLine("UseProgram: " + err); // flag ShaderObjects for delete when not used anymore GL.DeleteShader(VertexShaderObject); GL.DeleteShader(FragmentShaderObject); int temp; GL.GetProgram(ProgramObject, ProgramParameter.LinkStatus, out temp); Trace.WriteLine("Linking Program (" + ProgramObject + ") " + ((temp == 1) ? "succeeded." : "FAILED!")); if (temp != 1) { GL.GetProgramInfoLog(ProgramObject, out LogInfo); Trace.WriteLine("Program Log:\n" + LogInfo); } Trace.WriteLine("End of Shader build. GL Error: " + GL.GetError()); GL.UseProgram(0); */ #endregion Shader } protected override void OnUnload(EventArgs e) { GL.BindBuffer(BufferTarget.ArrayBuffer, 0); GL.DeleteBuffers(1, ref VBO_Handle); base.OnUnload(e); } /// /// Called when your window is resized. Set your viewport here. It is also /// a good place to set up your projection matrix (which probably changes /// along when the aspect ratio of your window). /// /// Contains information on the new Width and Size of the GameWindow. protected override void OnResize(EventArgs e) { GL.Viewport(ClientRectangle); Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, this.Width / (float)this.Height, 0.1f, 10.0f); GL.MatrixMode(MatrixMode.Projection); GL.LoadMatrix(ref projection); } /// /// Called when it is time to setup the next frame. Add you game logic here. /// /// Contains timing information for framerate independent logic. protected override void OnUpdateFrame(FrameEventArgs e) { if (Keyboard[Key.Escape]) Exit(); } /// /// Called when it is time to render the next frame. Add your rendering code here. /// /// Contains timing information. protected override void OnRenderFrame(FrameEventArgs e) { GL.Color3(Color.White); GL.Enable(EnableCap.ColorArray); #region Pass 1: Draw Object and pick Triangle GL.ClearColor(1f, 1f, 1f, 1f); // clears to uint.MaxValue GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); Matrix4 modelview = Matrix4.LookAt(Vector3.UnitZ, Vector3.Zero, Vector3.UnitY); GL.MatrixMode(MatrixMode.Modelview); GL.LoadMatrix(ref modelview); GL.Translate(0f, 0f, -3f); GL.Rotate(angle, Vector3.UnitX); GL.Rotate(angle, Vector3.UnitY); angle += (float)e.Time * 3.0f; // You may re-enable the shader, but it works perfectly without and will run on intel HW too // GL.UseProgram(ProgramObject); GL.DrawArrays(VBO_PrimMode, 0, VBO_Array.Length); // GL.UseProgram(0); // Read Pixel under mouse cursor Byte4 Pixel = new Byte4(); GL.ReadPixels(Mouse.X, this.Height - Mouse.Y, 1, 1, PixelFormat.Rgba, PixelType.UnsignedByte, ref Pixel); SelectedTriangle = Pixel.ToUInt32(); #endregion Pass 1: Draw Object and pick Triangle GL.Color3(Color.White); GL.Disable(EnableCap.ColorArray); #region Pass 2: Draw Shape if (SelectedTriangle == uint.MaxValue) GL.ClearColor(.2f, .1f, .3f, 1f); // purple else GL.ClearColor(0f, .2f, .3f, 1f); // cyan GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); GL.Color3(1f, 1f, 1f); GL.DrawArrays(VBO_PrimMode, 0, VBO_Array.Length); GL.PolygonMode(MaterialFace.Front, PolygonMode.Line); GL.Color3(Color.Red); GL.DrawArrays(VBO_PrimMode, 0, VBO_Array.Length); GL.PolygonMode(MaterialFace.Front, PolygonMode.Fill); if (SelectedTriangle != uint.MaxValue) { GL.Disable(EnableCap.DepthTest); GL.Color3(Color.Green); GL.DrawArrays(VBO_PrimMode, (int)SelectedTriangle * 3, 3); GL.Enable(EnableCap.DepthTest); } #endregion Pass 2: Draw Shape this.SwapBuffers(); ErrorCode err = GL.GetError(); if (err != ErrorCode.NoError) Trace.WriteLine("Error at Swapbuffers: " + err); } /// /// The main entry point for the application. /// [STAThread] static void Main() { // The 'using' idiom guarantees proper resource cleanup. // We request 30 UpdateFrame events per second, and unlimited // RenderFrame events (as fast as the computer can handle). using (Picking example = new Picking()) { // Get the title and category of this example using reflection. ExampleAttribute info = ((ExampleAttribute)example.GetType().GetCustomAttributes(false)[0]); example.Title = String.Format("OpenTK | {0} {1}: {2} (use the mouse to pick)", info.Category, info.Difficulty, info.Title); example.Run(30.0); } } } }