so, i started doing two-week projects again.
this time they're more... this is, i guess, explicitly gamedev work for an actual game? instead of random noodling. so the list of prospective things to work on is like, "picking", "map generation", "interface", "timing and animations", "more efficient rendering", which will hopefully over the course of the next, uh, year-or-so, shade into actual gameplay and mechanics stuff rather than just mostly engine stuff.
this two week project was picking
screenshots on monsterpit:
screenshots on tumblr:
if you're not up to date on your programming lingo, 'picking' is the term for locating stuff under your cursor. so like, being able to click on something in a 3d environment. picking for ui is very simple, since like... you can get the pointer cursor position, and do a super-simple 2d collision vs. whatever rectangles or circles that provide interaction. simple. doing it in 3d is more complex, and it's in two parts: one is to figure out what in the environment the cursor ray hits, but before that you have to figure out what the cursor ray even is. i'd never really tried doing picking in its most general form before, because it involves a lot of confusing matrix math, so it was something really intimidating and i was kind of resigning myself to two weeks of struggling with math and concepts before eventually producing a super buggy and half-working prototype
so it was kind of a surprise when two days in i had written the unprojection code and could reliably hit a target on a grid.
the full pipeline here is: take the cursor position. turn that into a 4d vector in clip space, as a point on the camera near plane. the clipping frustum is always a cube, so you can calculate that pretty trivially.1 it's also very easy to get the exit point of the ray: that's just the same point but on the far clipping plane. then you need to 'unproject' the point by multiplying it by the inverse of your transformation matrix. this performs the inverse function as normal rendering: instead of transforming coordinates in world space into coordinates into opengl clip space, it transforms coordinates in opengl clip space into coordinates in world space. after you've done all that you have two points that represent 1. the point in world space that's closest to the camera and directly under the cursor, and 2. the point in world space that's furthest from the camera and directly under the cursor. assuming your space isn't curved (which it won't be unless you're doing super weird spatial geometries), if you subtract one from the other you get a starting point and a ray, which you can use to shoot through the world environment to see what the cursor could possibly be over.
i was kind of vaguely aware that that was possible, but it wasn't anything i'd ever come close to doing by myself before.
i ended up reading this tutorial and the opengl documentation for unproject several times.
things that stumped me for a while:
- the clipping frustum is centered on 0,0,0 and goes from -1 to 1. notably, this means that the near clipping plane is at -1, not at 0.
gluUnProject
takes z values from 0 through 1 but it shows you in the documentation math that it transforms them to the range-1
through1
. - 4d coordinates are 4d. one of the biggest issues i had was that my screen position and ray values seemed to be scaled weirdly, and i ended up taking several days tweaking a weird magical scaling factor that i didn't understand before ultimately realizing that i wasn't normalizing the vectors by dividing by w. after i did that all the math became very simple.2
- 3d perspective accounts for a lot. i was rendering the raycast output as projected onto the grid for a while, and the output always looked super weird and wrong until i started rendering it as the actual hex prism volume hit for each hex that the ray passed through, at which point i realized it had been right all along.
once i had the ray it was a fairly simple job to cast it through the grid, although that brought with it the usual imprecision issues that i still haven't totally fixed. something like bresenham's is nice because it's an algorithm; when you're doing fully general geometric collision through a grid there's always the potential to slip through some vertices due to imprecision, and that'll mess up the entire cast. that's one of the remaining problems, although uh looks like i have a whole six days to resolve the final few bugs. hopefully this won't be one of those "half the time spent working on 90% of the problem and the other half of the time working on the other 90% of the problem" situations.
more generally, i'm hoping these two-week projects help... focus me? like this time i guess i really am committing to working on an actual Game Project, instead of dozens of different libraries and diversions. i could see how that might amp up my natural tendency to get frustrated. what i found during the last year of projects was that that two weeks is just about exactly enough time for me to get frustrated and annoyed with something, before i run out of time and basically have scheduled permission to stop thinking about it entirely and not actually care that much if i didn't get much accomplished, and that really helped a lot in that it gave me permission to stop caring about a project. i frequently have a big problem of constantly obsessing over a project and the act of perpetually worrying exhausts all the energy i have to work on it in the first place, so i end up constantly worrying about something but never actually making progress.
but the other half of it, which i'm becoming more conscious of now, is that in addition to giving me an automatic escape hatch for when i get bored, is that they also focus me on a specific thing? with other game projects i've seen the vast spread of Stuff To Do and tried picking away at the margins slowly over months and made no real progress. but here i was just like "oh there's so much to do and there are so many different categories of stuff i need to work on" "oh i could make a two-week project out of each thing". picking is something that i've had looming over me as a Complex Math Thing Needed For Games That I Have No Clue How To Do Properly for years, and here i was just like, well, i'll work on it for two weeks and see how it goes. and it turns out that that was something i could make a big headway on in two weeks. that was a thing i made a big headway on in two days. but it never would've happened if i was trying to do picking and ui and better shaders and timing and animation and game mechanics all at once.
so i guess we'll see how i feel about all of this in another year, when i may or may not have a list of 24 items i worked on.
also i'm technically posting this a week early but that's mostly because uh i'm hoping the rest of the picking stuff is just mopping up some of the raycasting glitches and hex prism collision stuff, which will probably be pretty boring since that's all ground i've covered before. i may or may not write a follow-up post on the 14th/15th when it's actually 'due'.
- i got very used to thinking about the camera as a point, when this made me realize that of course it's not just a point; the camera is basically the quad of the near clipping plane. a lot of my thinking was predicated like "well all the rays are shot out of the camera so the starting position of the ray should always be the same unless the camera moves", when of course that's wrong: the position can be any point on the near clipping plane. sure, you could say the camera is a point and the near clipping plane is in front of it, but that's not really... useful. ↑
- when you have a 4x4 camera matrix, the fourth coord is called
w
and there are a bunch of talks about what that weird fourth dimensional value actually means. "it should be 1 for points and 0 for vectors" is generally true, but it wasn't until i did all this math that i really understood why that was true. all of this matrix math is to simulate the perspective effects of cameras, right? which includes the thing that we take very much for granted, which is "things that are further away are smaller". in the matrix math, this is done by literally scaling things down when they're far away. similarly, since the clipping frustum is always a cube, if you want anything other than a 45-degree view angle the perspective matrix also stretches out everything horizontally to give a 'correct' field of view. these are transforms encoded in the camera/perspective matrix that wildly squish and squash things around to mimic the perspective effects of 3d space. here, whatw
encodes is the degree of perspective scaling: a point will have a largew
if it's very close to the camera, and it'll have a smallw
if it's very far from the camera. those points will be in something like clipping space, which is usually not what you want, so "points should have aw
of 1" is just telling you that if you have a point in clip space you can normalize it with itsw
to get a point in world space, which is usually what you will want. ↑