// This code was written for the OpenTK library and has been released
// to the Public Domain.
// It is provided "as is" without express or implied warranty of any kind.
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);
}
}
}
}