djk's picture

Selection/Picking

Background:
Over the last 20 years I have been using the Hoops3d graphics system from TechSoft to provide rendering and graphical selection of objects in a large engineering application. My last foray into OpenGl was 15 years ago on an SGI Onyx using the GLUT framework to render a simulation there was no picking required.

I have recently started with a new company that is chartered to create a similar engineering system as open source using the .Net framework. The requirements have lead us to OpenGL and the OpenTK GlControl has risen to the top of the list from the 3-4 open source C# OpenGl controls evaluated.

In the Hoops3d system one would apply a filter to the segment tree (scene graph) to specify what you were going to select, ie whole object, face of a shell, line/edge, or vertex. Every graphical item had a key which could be remapped to a reference to the object that generated it. Thus when you selected an item you accessed the key to get the underlying object to operate on.

Question:
I have tried to port several picking implementations from other C/C++ projects to the OpenTK GlControl without success, I am sure the issue is with my inability to get my head wrapped around the OpenGL picking model. Does anyone have an example app built on OpenTk's GlControl that implements picking that they would be willing to share?

thanks
djk

AttachmentSize
SelectionExample.zip87.83 KB

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture

It's well known that documentation is lacking in open source projects, especially in programming libraries. Now, there are shining counter-examples, but in my experience it's safe to bet that docs will suck on any given project.

Now, I'd say that's due to two things: a) lack of time and laziness (writing docs is more boring than writing features) and b) libraries are directed to other programmers/technical users, which means they ship technical documentation (changelogs, build docs, function references) rather than end-user documentation (i.e. *how* to use it).

I don't know if there is a career opening here, but producing decent docs is a useful skill anyway. In most programming related jobs, two things matter a lot: getting the job done and producing maintainable code. Doc-writing skills help in both.

Regarding events, start a new topic and we can help explain the concept. In reality, it is simple (deceptively so), but also very powerful.

Technolithic's picture

Thanks for your input. What you say makes a lot of sense and I agree. I like the theories of a more agile software development paradigm to sync requirements with users as progress is generated, but strong documentation seems to go the other way toward the waterfall theory. There is probably a theoretically perfect ratio of features/documentation, but I like to err on the side of a bit too much documentation for my own sake. With a distributed development environment, it seems to me that it may actually facilitate greater efficiency if everyone can more quickly tie into what is happening with a project just by reading the current documentation. Of course, I have no real-world experience... I just work on school projects that stress certain aspects pertaining to the topic of the day.

This kinda goes off on a tangent, since I just wrote about documentation for a specific project, rather than for learning concepts and implementation of a language. My thoughts are that more people could make use of a doc that covers easy-to-learn language concepts and implementations than what is posted for documentation about a specific project. This might make it worth-while to invest in good documentation with a language/platform. Historically, over the past 50 years, if it's easy to use it takes over the market regardless of the fact that it's really inferior in many aspects.

Anyhow, every seasoned programmer, and 3rd year CS student for that matter, probably knows what I'm rambling about already, so these keystrokes are most likely destined for the bit-bucket.

Inertia's picture

Well, if you want to make money writing about OpenTK the best course of action might be writing a book (which will have to get printed) about using OpenGL/AL in the managed world. If your definition of "career" is not related to money, there sure is room in pretty much every open-source project for someone writing quality programmer guides.

Most programmers know it's more efficient to use [insert name of your favorite search engine here] to find an answer, rather than looking inside a book or making a forum post. There's very few answers you cannot find on the net, and most likely when writing about GL|AL you'll end up reading C documentation|specifications|tutorials and rephrase that for C#.

Edit: If you want to give it a try, write a FBO tutorial for OpenTK. :) It will be a valuable experience, there's a huge difference between knowing something and teaching something.

Technolithic's picture

Does FBO stand for Frame Buffer Object? Sounds like that would take some serious clock-ticks for me to stamp out a tutorial for that. I'd have to delve into some straight-faced, screaming, burning, and twisting OpenGL before I could even start. #=) The hash, sharp, tick-tac-toe/whatever sign is what my hair looks like after the ride. =)

I put a nice intro on an Nitro-RC Monster-Truck site about my own truck. Someone called me Charles Dickens... I take that as a compliment, LOL. Anyhow, I hope the stuff that sings out of my fingertips on onto this keyboard is almost worth the time it takes to read. My tuts wouldn't be boring!

I guess my biggest prob with other-folk's tuts is that I'm inexperienced, but this stuff is fun so it will come to me... the more I learn, the more fun it becomes, similar to compounding interest on greenbacks, only I'm still trying to figure out how to calculate technological inflation.

Inertia's picture

I hope nobody bothers that this discussion has gone a little offtopic from the initial post, but I guess it's ok since the question has been covered.

Yep, FBO refers to the Framebuffer Object extension used for off-screen rendering. It is neither covered in the red nor orange book, but is for example useful for shadow mapping or rendering to a g-buffer. There seem to be no quality tutorials around which cover the topic and reading OpenGL extension specs isn't really easy to digest.

Not sure if a tutorial has to be funny, personally I do prefer the 'dry' texts that stay ontopic rather than trying to entertain the reader and lose focus. (If you're looking for entertainment there are better things to do than reading a page about pushing bytes around)

If you want a hint how to gather experience quickly: do several small projects after the other rather than a single huge one. You will make mistakes (which are inevitable to build a judgement what's good or bad choices) and a small project is easier to abandon than something you spent months working on. Nothing in the world is perfect.

Except OpenTK. It simply gets even more perfect with every version number increase. :P

ToolmakerSteve's picture

Fiddler, did this picking example ever get written? If not, can you point me to some source that includes picking?

CommuSoft's picture

This is perhaps a very simple question, but I don't find the Glu-class in the OpenTK class library, further the namespace in my classlibrary isn't OpenTK.OpenGL but OpenTK.Graphics.OpenGL. What am I doing wrong? I'm using Mono.Net/C#, and added the OpenTK.dll assemby as a reference to my Mono.Net project. I know the answer will propably be very simple, but I don't find it at the moment.
-----------------------------------------
my apologies for my bad english.
The box said: "Requires Windows XP or better."
So i installed Linux.

the Fiddler's picture

Glu has been deprecated upstream and lives in OpenTK.Compatibility.dll now.

Also, do consider opening a new thread instead of hijacking an existing one. :)

CommuSoft's picture

When I use this code (and the previous versions of it) something strange happens: When I click on a certain area in my GameWindow it selects an object, but it's like the object are mirrored around an axis parallel with the x-axis. That means that when I select a far object in the scene, it sometimes returns a near object. I don't say that there is any problem with this code, but It seems my overriden methods of my GameWindow aren't compatible with the code posted by JTalton. To find a solution, I will put the most important pieces of my GameWindow here:

using OpenTK;
using Glu = OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
 
namespace TestGame {
 
	public class MainWindow : GameWindow {
 
		private double zoom = 0.4d, toZoom = 0.4d, rotateX = 0.0d, rotateY = 45.0d, toRotateX = 0.0d, toRotateY = 45.0d;
		private double drX = 0.0d, drY = 0.0d; 
		private bool mouseDown = false, paused = false;
		private int[] selectBuffer = new int[0x80*0x04];
		private TestGame testGame = new TestGame();
		private System.Drawing.Point oldPoint = System.Drawing.Point.Empty;
		private StandardPiece selected;
		private Glu.TextPrinter textPrinter;
 
		public MainWindow () : base(800,600) {
			this.textPrinter = new Glu.TextPrinter();
			this.Keyboard.KeyDown += new EventHandler<KeyboardKeyEventArgs>(keyboard_keyDown);
			this.Mouse.WheelChanged += new EventHandler<MouseWheelEventArgs>(mouse_wheel);
			this.Mouse.Move += new EventHandler<MouseMoveEventArgs>(mouse_move);
			this.Mouse.ButtonDown += new EventHandler<MouseButtonEventArgs>(mouse_buttonDown);
			this.Mouse.ButtonUp += new EventHandler<MouseButtonEventArgs>(mouse_buttonUp);
		}
 
		private void moveToView (double rotateX, double rotateY, double zoom) {
			this.toRotateX = GetRealAngleDegrees(rotateX);
			this.toRotateY = GetRealAngleDegrees(rotateY);
			this.toZoom = zoom;
		}
		private void mouse_buttonDown (object s, MouseButtonEventArgs e) {
			if(e.Button == MouseButton.Left) {
				Piece piece = this.GetPieceAt(e.X,e.Y);
				if(piece != null) {
					if(this.selected != null)
						this.selected.Selected = false;
					this.selected = (StandardPiece) piece;
					this.selected.Selected = true;
					//Console.WriteLine("found: {0} {1} @{2}",piece.Owner,piece.PieceName,DateTime.Now.ToString());
				}
			}
			else if(e.Button == MouseButton.Right) {
				this.mouseDown = true;
				this.oldPoint = e.Position;
				this.toRotateX = this.rotateX;
				this.toRotateY = this.rotateY;
			}
		}
		private void mouse_buttonUp (object s, MouseButtonEventArgs e) {
			if(e.Button == MouseButton.Right) {
				this.mouseDown = false;
				this.rotateX = GetRealAngleDegrees(this.rotateX+this.drX);
				this.rotateY = GetRealAngleDegrees(this.rotateY+this.drY);
				this.toRotateX = this.rotateX;
				this.toRotateY = this.rotateY;
				this.drX = 0.0d;
				this.drY = 0.0d;
			}
		}
		private void mouse_move (object s, MouseMoveEventArgs e) {
			if(this.mouseDown) {
				this.drX = e.X-this.oldPoint.X;
				this.drY = this.oldPoint.Y-e.Y;
			}
		}
		private void mouse_wheel (object s, MouseWheelEventArgs e) {
			this.toZoom *= Math.Pow(1.1,2*e.Delta-1);
		}
		private void keyboard_keyDown (object s, KeyboardKeyEventArgs e) {
			if(e.Key == Key.F1)
				this.moveToView(0.0d,45.0d,0.4d);
			else if(e.Key == Key.F2)
				this.moveToView(180.0d,45.0d,0.4d);
			else if(e.Key == Key.F3)
				this.moveToView(45.0d,45.0d,0.3d);
		}
		protected override void OnLoad (EventArgs e) {
			//basic loading
			base.OnLoad(e);
			//basic settings
			GL.ClearColor(0.0f, 0.0f, 0.0f, 0.0f);
			GL.ShadeModel(ShadingModel.Flat);
			//material loading
			float[] mat_diffuse = {0.5f,0.5f,0.5f};
			GL.Material(MaterialFace.Front,MaterialParameter.Diffuse,mat_diffuse);
			//light loading
			float[] light_position1 = {0.0f,0.0f,-3.0f,1.0f};
			float[] light_ambient = {0.3f,0.3f,0.3f,0.3f};
			GL.Light(LightName.Light0,LightParameter.Position,light_position1);
			GL.Light(LightName.Light0,LightParameter.Ambient,light_ambient);
			GL.Light(LightName.Light0,LightParameter.Diffuse,light_ambient);
 
			//enabling GL-options
			GL.Enable(EnableCap.Lighting);
			GL.Enable(EnableCap.Light0);
			GL.Enable(EnableCap.DepthTest);
			GL.Enable(EnableCap.ColorMaterial);
			GL.Enable(EnableCap.CullFace);
		}
		private Piece GetPieceAt (int x, int y) {
			int selectedId = -0x01;
			GL.SelectBuffer(128*4,this.selectBuffer);
			GL.RenderMode(RenderingMode.Select);
			GL.InitNames();
			GL.PushName(-0x01);
			GL.MatrixMode(MatrixMode.Projection);
			GL.PushMatrix();
			int[] viewport = new int[0x04];
			GL.GetInteger(GetPName.Viewport,viewport);
			double[] doubleArray = new double[0x10];
			GL.GetDouble(GetPName.ProjectionMatrix,doubleArray);
			GL.LoadIdentity();
			Glu.Glu.PickMatrix(x,y,0.001d,0.001d,viewport);
			GL.MultMatrix(doubleArray);
			GL.MatrixMode(MatrixMode.Modelview);
			GL.Translate(0.0f,0.0f,-6.0f);
			GL.Rotate(rotateY+this.drY,-1.0f,0.0f,0.0f);
			GL.Rotate(rotateX+this.drX,0.0f,0.0f,1.0f);
			GL.Scale(this.zoom,this.zoom,this.zoom);
			this.testGame.Render();
			GL.Flush();
			GL.MatrixMode(MatrixMode.Projection);
			GL.PopMatrix();
			GL.MatrixMode(MatrixMode.Modelview);
			GL.Flush();
			int hits = GL.RenderMode(RenderingMode.Render);
			uint closest = uint.MaxValue;
			for (int i = 0; i < hits; i++) {
				uint distance = (uint)selectBuffer[i*0x04+0x01];
				if (closest >= distance) {
				    closest = distance;
				    selectedId = (int)selectBuffer[i*0x04+0x03];
				}
			}
			return Piece.GetPieceFromId(selectedId);
		}
		protected override void OnRenderFrame (FrameEventArgs e) {
			base.OnRenderFrame(e);
			GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
			GL.RenderMode(RenderingMode.Render);
			GL.MatrixMode(MatrixMode.Modelview);
			GL.LoadIdentity();
			this.textPrinter.Begin();
			this.textPrinter.Print("FPS: "+Math.Round(this.RenderFrequency).ToString(),new Font("Arial",12),Color.White);
			this.textPrinter.End();
			double factor = Math.Min(10.0d*e.Time,1.0d);
			double factor2 = Math.Min(3.5d*e.Time,1.0d);
			this.zoom = (1.0d-factor)*this.zoom+factor*this.toZoom;
			this.rotateX = (1.0d-factor2)*this.rotateX+factor2*this.toRotateX;
			this.rotateY = (1.0d-factor2)*this.rotateY+factor2*this.toRotateY;
			GL.Translate(0.0f,0.0f,-6.0f);
			GL.Rotate(rotateY+this.drY,-1.0f,0.0f,0.0f);
			GL.Rotate(rotateX+this.drX,0.0f,0.0f,1.0f);
			GL.Scale(this.zoom,this.zoom,this.zoom);
			this.testGame.Render();
			double invzoom = 1.0d/this.zoom;
			GL.Scale(invzoom,invzoom,invzoom);
			GL.Rotate(-rotateX-this.drX,0.0f,0.0f,1.0f);
			GL.Rotate(-rotateY-this.drY,-1.0f,0.0f,0.0f);
			GL.Translate(0.0f,0.0f,6.0f);
			if(this.paused) {
				GL.Color3(System.Drawing.Color.Gold);
				GL.Begin(BeginMode.Quads);
				GL.Vertex3(-1.0,-1.0,-3.0);
				GL.Vertex3(1.0,-1.0,-3.0);
				GL.Vertex3(1.0,1.0,-3.0);
				GL.Vertex3(-1.0,1.0,-3.0);
				GL.End();
			}
			this.SwapBuffers();
		}
		protected override void OnResize (EventArgs e) {
			base.OnResize(e);
			GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height);
			Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI/4, Width/(float)Height,1.0f,64.0f);
			GL.MatrixMode(MatrixMode.Projection);
			GL.LoadMatrix(ref projection);
		}
 
		public static void Main () {
			using(MainWindow window = new MainWindow()) {
				window.Run();
			}
		}
 
	}
 
}

-----------------------------------------
my apologies for my bad english.
The box said: "Requires Windows XP or better."
So i installed Linux.

rathan001's picture

Could you please post the Zip sample code?