19 Feb, 2010

WxWidgets is a beautiful thing. Coupled with wxFormBuilder, you can make relatively complex GUI applications in a fraction of the time a traditional C++ app would take... still not as quick to build as C#, but then again, if you have a huge library of useful C++ code lying around, the relative value of C# is diminished. The MSVC GUI builder is fast and simple, but the amount of extra binding code to use our C++ libs would make it far less than ideal.

 

So, wxWidgets is the best alternative I've found. Of particular interest is wxGrid. It's a beast that warrants a moment of understanding, because the design is actually kind of beautiful once you grok it. It's incredibly useful to have a grid of stuff. But I wrote an entire translation tool the WRONG WAY using wxGrid, because it seemed like the right way to do things according to the (admittedly minimal) documentation. Do not use a vanilla wxGrid except in toy apps where you don't have many elements and don't care if the contents are duplicated into the grid itself--which sucks in so many, many ways. Let me state this plainly and clearly: wxGrid should always be used in conjunction with your own custom-derivation of wxGridTableBase. Sounds complicated, but really it's just a handful of functions that convert a row/column pair into a string and back again when it's been modified.

 

What are the benefits? For one, you don't have to go in and stuff the grid full of data that you no doubt already have somewhere else in a perfectly reasonable structure. Instead, the grid asks you for the data it needs to display--only the data it displays--not every cell in the grid. This added efficiency saves a lot of memory and dramatically improves a full refresh.

 

More important than the above, from what I can tell, deriving your own data provider is the only way to add custom controls inside the grid for editing the contents. I had no luck simply registering my own data types with the default wxGrid until I added my table type to it. Then, suddenly, everything started working! That means you can put file browser controls, or drop-down choosers, or whatever in the grid trivially by calling RegisterDataType(), and on specific cells or even whole columns, SetColFormatCustom() with the same type names.

 

Then it Just Works. The grid is no longer a separate control, but a direct view of your data structure, and you write about 10x less code dealing with it. In fact, it's a damn shame more controls don't work like this. Good job wxWidgets coders!


09 Feb, 2010

Yes, it's another episode of Can't Google My Way Out, with your host Jason Hughes!

 

While working on our editor, we just recently got to the point where we needed to do mouse selection of objects.  There were two methods I was considering for accomplishing this: 1) the obvious method that involves ray casting from the near-plane out to the far-plane and selecting the nearest object  the ray hits, and 2) rendering all objects to a render target that has object IDs stored per-pixel, and simply scrape through the render target to figure out what was clicked on.

 

The obvious method seemed simplest, so I went with that, for now.  Long-term, I'd really like to do method #2, since you can trivially do lasso selection and additive/subtractive regions in a single swipe.  Very cool.  The second method also has the extremely nice property that, if you can't see it, you won't select it by accident.  I hate editors that let you pick things you can't see, since it's very rarely what you wanted in the first place.

 

Well, the way I approached casting a ray in 3D based on a 2D point is not extremely complicated and can be found in bits and pieces on the web if you look.  The key is that I wanted to generate a ray in homogeneous clip space, not in camera or world space.  Why?  It's simpler and works whether you're using perspective OR orthographic projectiong matrices.  Of course, you could calculate things directly, but there's at least two cases involved, and it may prevent computing wacky projection matrices if you do it the forward route.  So don't.

 

The idea is your mouse cursor is floating along the near plane in clip space, which goes from -1 to 1 along X and -1 to 1 along Y and fixed at 0 or -1 on Z (depending on your graphics system, OpenGl and D3D disagree).  So take your 2D XY coordinate and scale and bias them based on the width of the viewport to be in that range.  Once you have that, your far plane point is the exact same XY position, but at a different fixed Z (again, depends on your graphics system). 

 

Since this is in homogeneous  clip space, you now have a Vector4(xn,yn,zn,1) that represents your near point, and Vector4(xf,yf,zf,1) that represents your far point.  Multiply these vectors against the (4x4) inverse projection matrix.  Now you have camera-space points, but their XYZ are all weird and the W component is not 1 anymore.  That's because it's still a homogeneous vector.  You have to convert it to cartesian space, which is simply dividing each vector by its W component... ta-da!  Camera-space near and far plane points.

 

Oh, and one more thing: if for whatever reason you are modifying your projection matrix by negating certain axes, I found that I needed to perform the same negations when forming the near/far clip space vector, so it corresponds with my projection matrix, and not the graphics hardware's clip space description. This is because you're using the projection matrix you created, not the graphics driver itself.  Thus, take care that your orthographic and perspective matrices have the same clip space coordinates, or you'll have to take that into account here.

 

Why post this? There are plenty of explanations for how to do some parts of this, but I haven't found a place where all of the steps were explained in simple terms.

 

 

JH