June 12th, 2010
You drop every programming project you have for six months and when you come back nothing makes any sense.
If my memory serves me, I gave up because of a bizarre bug where SDL would think every key was constantly pressed on the keyboard; this made debugging the input code impossible. I suspected the problematic code was elsewhere, and that it was probably memory corruption. This assumption has still not been validated.
I violently tore out a lot of the config code so I could merge the tree-based configElement traversal functions with the more widely-used tree-based entity traversal functions. This also, conveniently, gives me a data tree for config data, allowing me to both alter them and write them as human-readable human-editable files. None of it works yet and the actual "traversal" part is incredibly tedious, but I've been making headway. I just now got it to compile again. After which it immediately quits because it thinks the quit button is constantly pressed. Still. I had been hoping the memory corruption would be in the config code, but no. Now I suspect it's in the entity code, except most likely it'll end up being somewhere entirely different.
234 addresses allocated.
(on quit)
Fuck.
entity_detachChild (0x8057038): 0x8057038->prevSibling is (nil), but 0x8057038->parent->firstChild is (nil)! Something has gone horribly wrong here.
Fuuuuuuck.
June 15th, 2010
71 addresses allocated.
(on quit)
Better. Part of the problem (and what caused that entity_detachChild
bug above) was that I was calling entity_detachChild when I destroyed an entity... because, well, if you don't do that you end up with a dangling pointer. It's not an issue in this case because I'm destroying the entire tree. I don't know specifically why it caused huge issues; I just had the vague realization that 1) I've had problems with traversal code when it modifies the structure it's traversing and 2) detaching child entities modifies the entity tree being traversed.
Now it seems like freeing config elements doesn't actually free all of their data; I hope that's what the final 74 entries are. All of them are either parts of a configElement structure or a configElement entity... but that kind of implies that some of the entities aren't visited when traversing the tree. bleh. And the memory corruption is still there! If it even is memory corruption.
Okay, down to 37 addresses allocated on quit, all of which are one part of a configElement structure. So that means config_destroyElement just doesn't free that memory. Also, unsettlingly, I (tentatively) fixed the issue with entity_detachChild only to find that if I uncomment the line there is an new, unrelated memory leak. sigh.
No memory allocated.
Score! The final 37 entries were because of a memory leak in the config-loading state machine; memory was allocated to store the data twice, because I forgot that config_attachAttribute allocates memory.
So now... I work on the mysterious new memory leak that has to do with entity_detachChild and hope that it has something to do with the mysterious SDL keyboard presses.
Note to self: the new memory leak has something to do with the input system. apparently. Look for a place where an entity is destroyed (and thus detatched) before more entities are added to its parent. Or something like that. There's an inputFrame with a child inputHandler that aren't properly attached to the input entity (or perhaps they are detached at some point) for some reason, so their memory leaks out.
June 16th, 2010
Okay. Let's try this again. The design I'm using in this latest code iteration is from Game Programming Gems 4, and it involves creating things called "entities" by the article. Part of the idea is that it creates a distinction between front-facing code and private code, something otherwise hard to have in C.
But while rewriting the config system I repeatedly wrote horrible code that called various private functions all over the place, which means that the "input" code and the "config" code are now tethered together, and I can't change any functions without worrying that it might break something in an unrelated code file. This is bad.
(before, configElements didn't have a corresponding entity, so they were basically free-floating code that could be called anywhere, like, say, list code. Upon rewriting, I gave them their own entity, except to do a simple rewrite I had to bypass the proper entity messaging system and just call their functions all over the place.)
So I think as part of the further fixing-up of the system, I'm going to create a config
entity, to match the input
, video
, and world
entities. configElement entities will be children of the config entity, and for the most part input will get its data by requesting it from config. There's even already a message for that; EM_GETATTR
, and then I can leave the intricacies of how exactly the config loading and checking works to the config entity. All the input entity knows is that it can ask the config entity for keybind data and either the value from the config file or the default will be returned. And that means the two systems will no longer be tied together. the input system shouldn't care, or even know about config files and how they're loaded.
So anyway, that's what I'm going to be working on. Hopefully it will work. Although I still have to resolve a few entity bugs, not even to mention the mysterious "memory corruption" bug. bleh.
June 22th, 2010
And then instead of doing that, I did something else, instead! For the longest time I have been managing all my projects just by keeping all my code in one directory and making my Makefile
s by hand. Not-all-that-recently, I realized that part of the reason my code becomes such a buggy tangle is because, well, I'm doing all this stuff by hand and not testing it at all.
When this thought first occured to me I looked around for testing suites in C, and found Check, which is a unit testing framework for C. Except it requires you to use the autotools applications to manage your project, and the Check documentation doesn't explain how to set that up, rather rightly, because the documentation that does explain how to set that up are the various autotools manuals, totalling at the size of a rather large book. So that was really intimididating and I never got either Check nor the autotools working because I could produce was a cascade of errors and warnings.
Until now, that is! It's not even on this project, but I decided to start off with a very small, simple program with not much code for my first uses of autotooks and check and soforth. So now it's all set up and it compiles and makes the tests and all that, and all I have to do is write the tests. ...and also figure out how libtool fits into things. But. It's progress!
June 23th, 2010
/* this code uses the Dark Arts of type-punning and relies on specific results
* occurring from an explicitly undefined operation. One might assume that the
* *(int*)&float cast allows you to read float's bits as an integer value; but
* that is not the case always, and as I have found out, it is not a technique
* you want to rely on unless you enjoy the results changing depending on your
* compiler edition, your CPU, the number of printf debugging statements used,
* the system time, the local weather and humidity, and the phase of the moon.
* READ THIS AND BEWARE, FOR IF THESE COMMENTS ARE EVER UNDONE THE GREAT BEAST
* SHALL AWAKEN AND LOPE THROUGH THE CODE LEAVING NON-DETERMINISM IN ITS WAKE!
*/
The less said about this, the better. Although that said, as it turns out if I had type-punned in a slightly different way it would have been guaranteed to work with GCC. But after having the wonderful experience of the compiler optimizing away the critical lines of a function, I think I'm going to use a slightly less clever version of the code, just in case.
July 6th, 2010
base: "X2▒2▒@"; expansion: "�1▒@�1▒@checX" (-56)
So two strings walk into a bar and one says "Hey, I'll have a drinkýsÌ¡_H". The other string turns to the bartender and says, "You'll have to excuse my friend; he's not nul-terminated."
full expansion: "FFFFFFFF[[-FFFF[[-FF[[-F[[-X]+X]]+F[[-X]+X]]]+FF[[-F[[-X]+X]]+F[[-X]+X]]]]+FFFF[[-FF[[-F[[-X]+X]]+F[[-X]+X]]]+FF[[-F[[-X]+X]]+F[[-X]+X]]]]"
Victory!
July 9th, 2010
In retrospect, perhaps attempting to write an object system in C was not the best of ideas. The thing is, C is so... scattered. Unless you take significant steps to organize your code it's pretty inevitable that it all becomes one gigantic mutually-dependant tangle. And C doesn't have any built-in systems for organizing you mode, really. I know the thing to say here is "well, so switch to C++", except, um, I don't want to. As I have so frequently said to my friends who want me to learn C++ so I can teach it to them, if I ever decided to learn an object oriented language, it would be one that was designed from the start to be object oriented, and not one that had it tacked on afterwards as an afterthought. This is also part of why I refuse to learn the PHP 5 object system. Maybe I'm just obstinate.
At any rate, since the only object systems I'm familar with are those from LambdaMOO, uh, I find myself at something of an impasse when trying to assemble my own system. How important is polymorphism? How should I deal with inheritance? I dunno.
The turtle-running code (incidentally, by the way, I'm working on turtle-running code) is all working, as far as I can tell without actually using it outside my unit tests. It's possible to move a turtle around, and to define symbols and then feed it a string of symbols, of which it will act in the way dictated by those symbols, and to establish iterated function system-style variable expansions (see previous update) that allow you to easily create fractal shapes. You can add a certain limited amount of randomness to the expansion by defining multiple replacements for the same character. And in addition to all of that, it's possible to run a "cycle", in which the turtle follows a certain set of actions over and over again until its path closes (or once, if it never closes). Things you can't do, yet: create spirals, or curves, or run multiple turtles at the same time.
And since I've finished the turtle-specific code, what I next turned my eye to was the structural glue that would hold the entire program together. In other words, the object system. There's a rudimentry version of it in phantomnation, but only the most simple parts of it work and there is a whole lot of mysterious, unexplained behavior. I would like to fix it up and recode it with tests and an actual internal structure, but... it's kind of an undertaking. Once again, the "make games, not engines" motto is something I disregard completely.
The thing is, I would like to know how object systems work, and a good way to do that is to write my own. Also, since part of my eventual goal is to write a complete language parser, starting with writing my own langauge constructs seems like a decent plan. Once this is finished, if it ever is finished, it would only take an extremely simple parser to let me send object messages at runtime.
Hopefully within the next few updates I should be able to post some pretty screenshots. Still, given that I've been back programming for less than a month, I consider writing a bunch of geometry code and turtle turing machines and setting up a unit-testing system and comprehending the basics of autotools pretty decent coverage.
July 15th, 2010
100%: Checks: 27, Failures: 0, Errors: 0
HELL FUCKING YEAH!
...excuse me. Man, and those unit tests aren't even complete. I know for sure there's at least one thing I ought to have a test for and don,t and I know it would fail that test. But man. progress. I wrote a fucking CALL STACK, okay, and now I can do Lambda-style pass() calls inside entity handlers. Which means entities have actual inheritence!
Now to try actually writing an entity that does something other than debug the entity system. I'm sure there will be plenty of awful bugs I will have to write tests for.
July 28th, 2010
lol.
Okay. So. Progress. I wrote up a video entity and some turtle line drawing functions, and also some unit tests for both. My unit tests for entities has been rather lacking; writing their tests is a little tricky because you have to basically design a dummy interface into the entity itself for things like video resolution or user input. The program now renders as you'd expect it to:
There were a few tangles because I had forgotten a lot about matrices and view transforms, but I worked it all out in the end. The video entity is merrily processing messages that tell it to set itself up and start rendering, the turtle... still is entity-less for the time being, since I haven't put together the "design" entity it'll probably be a part of, but as you can see, it's quite capable of expansion and cycles.
However, right now you have to kill -9 the process to get it to quit, since it completely ignores any input. So to fix this, I decided to not slapdash things and instead put together the complete input entity with all its subsystems, so that it'd be easier for me to set up loading keybinds and stuff from a config file later (another big plus for the entity object system: it makes it really easy to load up config data into the entities). This is somewhat intimidating, as you might recall the last time I tried to put together an input system I revealed a bug that made testing or using input impossible, and that drove me away from programming for six months and befuddled me enormously, and in fact never was fixed or understood, and was still lurking in the code I would be using as a reference.
So I was somewhat leery of this, especially since I hadn't (and still haven't) put together any unit tests for the input entity, since that requires setting up a mess of function pointers and writing functions that fake arbitrary SDL events and soforther. So I was heading in blind, as it were.
The basic flow of the input entity is as follows: there's the one Input
entity, which is regularly told to update itself. Its update routine consists of getting the SDL keystate, once, and then polling for SDL events until they run out. All the events are messaged to its children, the Input Frames
, along with the keystate, and then lastly any Input Responses
that are set up to "repeat" (i.e., fire repeatedly when a key is held down) and currently firing are messaged directly with only the keystate. The Input Frames
just relay the message to their children, which are all Input Responses
, and potentially halt messaging their children if the frame is deactivated.
This is probably more complicated than it needs to be. It's a work in progress, mostly because I've never gotten it working for long enough to figure out which parts are completely unnecessary and which parts are lacking, so the entire system is somewhat clunky and limited in weird places.
The problem I was having was that, for some reason, any event which was set up to check the keystate constantly returned that its keys were pressed, leading to it instantly and perpetually sending off its response message. This was extremely vexing, not the least in part because the "quit" message was mapped to the Escape key. I was extremely alarmed when I finished the first iteration, compiled, and saw that yet again in the exact same manner the system was reading all keys as constantly pressed down.
However, evidently six months away from the code and a month and a half's worth of new practice and problems let me spot the problem instantly. No, it was not memory corruption from the entity system, or memory corruption from the config code or the memory code or anything like that. It was because I was dereferencing the keystate pointer wrong.
Kind of the obvious place to look, if all your keystate values are going haywire and absolutely nothing else is, but apparently I had not thought of looking there.
SDL_PollEvent
expects as its single argument a blank event structure it can fill with event data; this is usually accomplished by defining an SDL_Event variable as a part of some structure or declaring one on the stack somewhere. Meanwhile, SDL_GetKeyState
returns a pointer to SDL's existing keyboard state array. I had declared a SDL_Event and a Uint8 * as part of the input struct, and I was passing the addresses of both of them to its children so they could be checked against the various triggers. If you're expecting SDL_Event * event
then passing &(SDL_Event)event
is perfectly fine, but if you are expecting Uint8 * keys
then passing &(Uint8 *)keys
is distinctly the wrong thing to be doing. You end up with (Uint **)keys
and then if you try, say, k[SDLK_WHATEVER]
you end up looking at whatever gibberish happens to be past the end of the input structure (in my case) instead of the actual keystate.
So that was my completely boneheaded mistake for the month. The issue is less the mistake itself as it was my response to it: diving into other subsystems looking for memory corruption, putting down programming for six months, etc. I'm... going to try to avoid that in the future.
So the things still left to do here are as follows:
- Finish the input code and link up responses to some, so that pressing quit will actually quit, instead of basically just triggering a message that says "this would have quit if it could quit"
- work on free typing. It's kind of a special case as keyboard input goes; you definitely do not want to have to write out "and if the user presses 'a' you want to record 'a' or 'A' depending on if either of the two shift keys were pressed, and if the user presses 'b'...", and of course you don't have to. SDL has this entire subsystem that does that for you, so that you can just say, hey, SDL, since these keys are pressed what's the printing character they'd generate? The issue I'm having is fitting that into my existing input structure, and also making space for a buffer to store recently typed letters.
- wire up the input system to a fake source of SDL Events (I can probably just use
SDL_PushEvent
or the like for events, but I might have to do something more clever to fake a keystate) so I can write some unit tests for dealing with input, and incidentally help pin down exactly what a useful input interface, code-wise, looks like.
blah blah blah get back to work >:E
52 addresses allocated.
oh goddammit why did I even turn off memory debugging in the first place now I have to hunt down each and every one of these stupid leaks and they could be anywhere >:(
July 30th, 2010
So instead of working on any of that, I turned on memory debugging and realized I had actually never used the new memory debugging code, since it didn't compile. So I fixed it up and subsequently discovered that it caused crashes. Well. Probably it was the thing causing crashes, since I got none without it and one with it, but as always I was paranoid about going down the wrong alley and wasting a lot of time looking over perfectly fine code. (The issues with memory corruption errors is that there's a distinct difference between "code that creates memory corruption" and "code that reveals memory corruption", and the latter can be absolutely anything.)
But anyway this morning I was messing with it and I managed to get a new breed of crash with it enabled that pretty conclusively shows that it really is the memory code causing corruption.
These two shots were created with the exact same generator, only one of them had memory debugging turned on and the other did not. Note that one of them not only is missing a line near the bottom-right, but has a malformed line in the center, and oh incidentally also has half the shape missing. (The screen autoscales any shape to fit the screen, but you can tell just by comparing the kinks in the curve between the two.)
This is, once again, pretty definitively the memory code's fault. Which is actually good news, because now there is no ambiguity left: where is this memory corruption bug? in the memory code and nowhere else. And the memory code isn't even very long; it's like five functions.
so anyway I should get to work on that instead of typing up this update post
Okay, so that's all fixed now. As it turned out the problem was in my realloc code: after re-allocating memory it's entirely possible for the memory to have a new address. I updated the address in the memory tracker, but I didn't re-sort the tracker, which meant that each time it just so happened that a realloc switched addresses, the memory list would get unordered, and that meant... something. But it's fixed now, although I have a lurking worry that the actual memory corruption is lurking somewhere in the failure branches of the memory code, just no longer called for the time being.
(I have no reason why the screenshot to the left looks the way it does, but it's basically what happened when I didn't NULL terminate a bunch of lists in a effort to see if the problem was in the realloc code or the code that used the realloc code.)
Here are some notes-to-self about the entity code:
-
"registers" still don't work right. The idea here is that when you send a message that flows through the entity hierarchy (messages a bunch of entities in an order based on their relation) you sometimes need to have them pass data between them, or determine which entities have been messaged already or whatever. So there are "registers" which should have their own scope-- only usable inside an entity handler, wiped at the end of a message flow, and (here is the important part) capable of storing multiple instances, in case an entity handler itself triggers another entity message flow-- we want the new flow to have its own cleared registers, but we still want to be able to use the old registers after that flow terminates and the first one continues. Right now they're just wiped clear every time a new flow starts, instead they should have a stack structure and push/pop on flow start/end.
Also, do I really need them? all I've used them for so far has been checking the order entities are messaged in, and it's not really super important when you can generally assume all yr parents/children (depending on the type of message) have already been messaged.
- on a somewhat similar note, you can do some weird stuff inside an entity handler. Most notably, doing something like
entclass_destroy (e->class)
will wreak absolute havoc, likely ending in a segfault. This is annoying, because sometimes you really do want to have a class destroy itself based on a message sent to an instance of it. Let's not even get into callingentclass_destroyAll
(which is further complicated by DESTROYING THE CALLSTACK). - Messages that move around the entity tree also wreak havoc when sent as a message flow, since the flow determines which entities to message by looking at the entity tree. The most annoying culprit would be
entity_messagePre (..., EM_DESTROY)
, since destroying an entity unlinks it from the entity tree. If the root entity you're messaging doesn't have a parent, its kids will lose their parent and the entity flow will be unable to message all of them, causing memory leaks. Currently the workaround is to useentity_messagePost
to destroy entities, since it hits leaves on the tree first. But what really needs to be done is that internally all the entity messaging functions need to traverse the entity tree to figure out what entities they'll be messaging before sending a single message. Then messaging will be guaranteed to hit all the entities it should given the way the hierarchy was organized at the moment the message was sent. - Message halting and skipping children probably doesn't work right if it's immediately (i.e., within the same entity handler message block) followed by manually starting a new flow. e.g.,
entity_skipchildren();
won't work right (it'll skip the children of
entity_messagePre (f, ...);f
in the new flow instead of skippinge
's children in the existing flow) even thoughentity_messagePre (f, ...)
would work right. There's a similar issue with
entity_skipchildren();entity_halt()
-- if called before a new entity flow it'll prevent messaging in that flow as well as in any currently executing ones, and in fact if called anywhere it'll prevent messaging in, well, any currently executing flows, not just the one it's a part of. Basically this is the same problem as the registers problem; there needs to be more of a concept of the "scope" of halt/skipchildren. -
Saving the most important for last, there's the further issue of
entity_destroy
. Here is the long and short of the problem: when an entity destroys itself, it needs to destroy all its class data so it doesn't leak out. This means that for each class it's an instance of needs to have itsEM_DESTROY
/EM_SHUTDOWN
message sent. There's usually just the one class, but if it's an inherited class then its parent class needs to be messaged as well, and so forth and so on up the line.The problem is when to call
entity_destroy
. We can just callentity_pass
when an entity destroys itself, except... if its parent class handler callsentity_destroy
too then the program will probably explode when the pass returns and we try to callentity_destroy
again. You might say "only call entity_destroy on the lowermost handler, after passing the message to all parent handlers" but there's not really an easy way to check if it any random handler is "the lowermost handler".This is further complicated because certain entity messages might cause some classes to destroy themselves without all classes the entity is an instance of wanting to destroy themselves.
EM_SHUTDOWN
, most notably, is a "shutdown gracefully" message, so some entities may persist for a while after getting it, only to sendEM_DESTROY
to themselves later. But most entities will do the exact same thing for both messages.The incredibly vague idea I have is to tie
entity_destroy
into the previously mentioned scope. Calling entity_destroy flags an entity for deletion, but wouldn't really destroy it until it leaves scope and isn't processing any messages and won't recieve any more messages from the current flow. That doesn't solve the entire problem, but it's a start.(This is completely academic right now, since there are no classes that inherit from other classes yet. But eventually there will be.)
god writing unit tests for all of these is going to be so fun, let me tell you. I— I think I might just ignore this for the time being, since it works fine in most cases, and do something like working on the world structure or cameras; something fun instead of delving deep into more object-oriented message-passing gibberish.
August 3rd, 2010
So okay. Progress: progressed, for certain values of "progress".
As it turns out, the "any input triggers exactly one message, which is created when the program first starts and very hard to change afterwards" mechanism I had going was... not very useful. For the time being it's been swapped out for a more hardcoded approach— there's a big enum of things input can do (moving the camera in various ways, moving the avatar in various ways, switching camera modes, and quitting, right now) and the vast majority of key input cases are handled directly inside a switch in the input entity (now renamed the control entity) in its EM_INPUT case.
What I became aware of while doing this recode though is how weird and tacked-together my object system is. I mean, they are called "entities" but that's not because of any similarity to an entity system, it's just because the code I was originally working off of (from Game Programming Gems 4; "A System for Managing Game Entities") called its objects entities and so I followed in its path.
This is actually kind of vexing, because while yes it has been an experience attempting to code an object system in straight C, I would probably get more practical use from the modularity an actual entity system provides. Plus it is a lot more intuitive to be using entity systems rather than objects, since I'm pretty used to DB programming and it's easy to map "entity" to "primary key" and "component" to "table", with "component data" being the columns of its table. This is further complicated by the fact that when I wrote object inheritence I unconsciously framed the "entities" as actual entities, hence the problems with destroying objects: if you have an object, you don't want to destroy it until you're actually finished with all of it, but since an entity's components are decoupled it makes perfect sense to destroy some of them but not others. Hence my EM_DESTROY problems.
Also, my entity objects were more inspired by, say, Smalltalk-style message-passing encapsulated objects, than C++ style objects (well, ostensibly. Really I have only the vaguest idea how C++ does objects). What I am saying here is my object system: kind of confused.
I am probably going to take this as an excuse to finally start using my version control (which, yes, I had not being doing previous. yeah yeah yeah say 50 hail marys to absolve yr sin, whatever) by forking the project: on one fork, I'll work on "fun" things like the camera and the world and stuff like that, on the other I will slowly clean up the object system (and probably rename them to be called "objects") and rework it with a more coherant model. When that's done I'll merge the forks and start working on an actual entity system, probably with a design geared towards putting together and altering entities on the fly at runtime.
So uh, that's the plan! NOW SIMPLY TO LEARN EXACTLY HOW TO USE GIT FROM SCRATCH. I'm sure this will not take long. Also, probably I will just work some on visual stuff now, although that would require doing physics integration, which is yet another thing I've generally never gotten far enough to work out how I actually need to do it.
I promise next update will have some pretty pictures, okay.
August 10th, 2010
THIS IS AS PRETTY A PICTURE AS YOU'RE GONNA GET, SORRY.
Man. Hexagons, huh? The plan is to use hexagonal tiles for the world geometry, although right now you can see the world is basically a flat plain with visible edges. That'll... probably change. I'm actually using a lot of code revised from phantomnation, and I'll probably be able to straight up copy and paste a bunch of code from there to this project later on. Internally I might end up using the exact same map storage system as in phantomnation, with some slight modifications to the tile structure.
(the new tile rule here that wasn't in phantomnation is that the surface [and bottom] of a hex must be planar. This means that instead of storing a center height value and six corner height values and drawing a hexagon as six triangles in a fan from the center, like I did in phantomnation, I can store a center height value as well as two slope values that all together define the plane of the tile surface, and I can draw hexagons as five triangles from any edge. this is not actually much of a savings [ooh, two or four less triangles rendered and seven less floats in memory per tile, amazing] but it is of interest to me, so shut up)
This last week has been rather complicated by the fact that 1) there is a summer heatwave going on and 2) I live in an attic with no air conditioning. In fact, I think the heat has caused either my wireless card or my memory to fail, and this means I'm becoming all-too-familiar with exciting random crashes. Not very much fun. So I haven't really made as much progress on anything as I'd have liked, but oh well, murderous stifling heat is a pretty reasonable reason for slacking off a bit.
So now that I have "world geometry" rendering it's time to hook up the input system, and then start iterating across updating the input controls, the physics system, and the world representation. Let's hope I can get something you can actually play before much longer.
August 13th, 2010
I am so not up to writing an engaging and humorously mock-deprecating update. Here are some screenshots. All of them are wrong. I may keep the wrongness in the final shot, simply because I don't know how to do it the right way and this way is probably easier to calculate.
hex (n) = 3 * n * (n + 1) + 1 {r k i} = hex (r - 1) + (k * r) + i ({0 0 0} is a special case) _______ / \ _______/ {3 5 0} \_______ / \ 34 / \ _______/ {3 4 2} \_______/ {3 5 1} \_______ / \ 33 / \ 35 / \ _______/ {3 4 1} \_______/ {2 5 0} \_______/ {3 5 2} \_______ / \ 32 / \ 17 / \ 36 / \ / {3 4 0} \_______/ {2 4 1} \_______/ {2 5 1} \_______/ {3 0 0} \ \ 31 / \ 16 / \ 18 / \ 19 / \_______/ {2 4 0} \_______/ {1 5 0} \_______/ {2 0 0} \_______/ / \ 15 / \ 6 / \ 7 / \ / {3 3 2} \_______/ {1 4 0} \_______/ {1 0 0} \_______/ {3 0 1} \ \ 30 / \ 5 / \ 1 / \ 20 / \_______/ {2 3 1} \_______/ {0 0 0} \_______/ {2 0 1} \_______/ / \ 14 / \ 0 / \ 8 / \ / {3 3 1} \_______/ {1 3 0} \_______/ {1 1 0} \_______/ {3 0 2} \ \ 29 / \ 4 / \ 2 / \ 21 / \_______/ {2 3 0} \_______/ {1 2 0} \_______/ {2 1 0} \_______/ / \ 13 / \ 3 / \ 9 / \ / {3 3 0} \_______/ {2 2 1} \_______/ {2 1 1} \_______/ {3 1 0} \ \ 28 / \ 12 / \ 10 / \ 22 / \_______/ {3 2 2} \_______/ {2 2 0} \_______/ {3 1 1} \_______/ \ 27 / \ 11 / \ 23 / \_______/ {3 2 1} \_______/ {3 1 2} \_______/ \ 26 / \ 24 / \_______/ {3 2 0} \_______/ \ 25 / \_______/
August 15th, 2010
The most extreme rudiments of maps are in. This is like phantomnation all over again, seriously. awful. And, in fact, to that note I'm going to do just a few more things with rendering (mostly flagging the edges of tiles, so there is some visual depth going on, or maybe just making a 8x8 texture I can draw on everything) before I start making the physics integrator actually integrate. Right now it exists and runs, but there's absolutely no objects set up to integrate over. Probably I will kludge together part of an entity system so I can attach the camera to a physical object to allow for a reasonable first-person view.
After that, hopefully I will update the input format to work better. Most entertaining bug: the keystate is evaluated every pass through the main loop, but it's also evaluated with events. So spamming the SDL event queue with useless events [like, say, moving the mouse pointer across the screen] makes all key controls respond much faster. Most annoying bug: only one key input response can be triggered at a time, so you can't do things like move forward and look left.
I so do not want to get caught up in the kind of perpetual reworking morass of phantomnation, and I think part of avoiding that is to try to get a many of the real "game systems" working at first, and then go around fixing them up to work better, instead of trying to get them finished and well-coded and shiny one by one and inevitably bouncing back and forth between two of them while the game remains completely unplayable.
TO THAT END, well, see above. Physics, then entity system, then input. Hopefully after that I'll try my hand at interaction with the environment, by which I mean map editing. I never actually got "map regions" working in phantomnation, but I still think it's a good idea, so I'll maybe try that out too, once moving and looking around isn't a complete chore.
August 19th, 2010
Well, a few days in and the physics integrator is integrating, and the entity/component system is... componenting. The physics system is not, however, colliding, so when you start up you immediately fall through the floor into the dark abyss below the level. This is something I should work on. After that, input, especially since I had to take out the current camera control code since it didn't fit with the component system. It's something of a worry, since the component system is bolted on top of the homemade object system, and I suspect I will pretty easily reach the limits of them both when I try to add in physical controls and camera controls.
Also, here is a screenshot of what it looks like when you start to fall through the floor! You may note that I have changed the aspect ratio and generally made the player/camera much smaller. For the time being, while I'm still working out the basics of unit control and integration and collision, I've sized down the camera to make the level geometry rather Cyclopean. In the long term each hex tile ought to be roughly the size of a unit footprint, but right now they are much larger than that.
I'm also deliberating on maybe taking a little more effort when I put together update posts, now that they are on a blog and actually visible to the internet. Although conversely, I don't really want to make such a production of each post that I'd feel bad slapping up the occasional short "today I worked on [x] and now it works right, horray" note.
August 24th, 2010
Still working on collision. Since I have never ever before done any kind of physics integration or collision detection/response, things are going kind of slow. But I've grasped the basics of it and am currently puzzling out the specific types of primitives and their collisions and stuff like that. Also... I fixed an issue with the physics integrator that made it run incorrectly, and I messed around with the timestep and accumulator so that it should run reasonably fast most of the time, which is an extremely subjective measure.
The collision architecture is all in place-- the collision system is updated and does its calculations and detects nearby objects and does collision tests against them and all that, it's just missing three incredibly critical pieces yet: first, it doesn't yet generate the tile footprint of an object yet; second, it doesn't detect collisions between objects and tiles; third, even if a collision is detected there are no response actions. A single one of those not working would mean physics would be useless. So... those are the things I'm still working on. All that's left for the first problem is that I need to finish a function that converts x,y coordinates to r,k,i polar coordinates (and I already have the inverse function written); the other two problems are more significantly unfinished. I don't really comprehend plane intersection code to begin with, and my planes, since I am using hexagons, tend to be defined with non-orthographic axes. Conceptually it seems like collision response will be fairly easy, since I'd already have the physical data of the two objects colliding and their point of contact, but in practice it might turn out to be a little more complex than that.
And lastly, even if I got collision all working I still would not really be able to test it until I get control/input working again. Obviously if the camera stops falling through the floor on startup that will be some evidence, but there's all sorts of things having to do with tile walls and sloped tiles that can't be tested until I can move the camera around again.
But I wanted to make an update anyway so I could remind myself I was still actually making progress, even if it was going slow. And hey, once I get this working I can deal with input pretty easily, since I have done it so many times before, and then I will have the absolute basics of the system (world geometry, physical objects, cameras, physics integration and collision, player input) all working! And then... probably text and menus and in-game debug text. I kind of want to go and add more primitives to the physics system (current primitives: spheres. and tiles, by the time I'll be calling it "working") and add in angular momentum, but I know that will take forever and not be very useful.
August 26th, 2010
Yeaaaaaaaaaaah collision detection! And collision response! Both are just unbelievably awful in their implementation— okay, no, the parts of the collision detection that I have working are great, it's more that collisions aren't tested against walls at all yet, so you could (hypothetically) walk straight through those walls and then fall through the world. Hypothetically because there's still no input. But now that I no longer fall through the world, I will work on input! yaaaaay!
Those green lines are the tile surface normals, by the way. For a while I wasn't generating them properly.
Also I'm thinking of putting together a "collision detection for people who never had any formal math education past the sixth grade" thing, because boy I could have used something like that. And I might as well put the blog part of this to a useful use.
September 7th, 2010
Was away for a few days and then wiped and without energy on account of being away for a few more, but now I am back! woo. hooray.
Anyway so I finished the input code (mostly just uncommenting and bugfixing the code I had written before I left, which, on that note, reminds me that I really ought to be writing documentation for this whole thing as I go along so I'll know what I was attempting to do when I come back to all of the half-finished and half-broken components I am leaving in my wake) and tested it out and fixed a few glaringly obvious crash bugs.
Now the obvious question to ask myself is: how will I couple the input component to, oh, everything else? How will a SDL event being received wind its way through the code to have actual affects in the game world? Actually that is a rhetorical question; I have already decided how: firstly the keystate is converted into a game event index (there are already a dozen or so of them and there will probably be a few hundred before the game is "done"-- these are things like IR_MOVE_AVATAR_LEFT or IR_PAN_CAMERA_UP or IR_QUIT or IR_TOGGLE_CONSOLE, etc etc etc) and then that game event index is messaged to each entity that has control or focus with an appropriate CONTROL_INPUT or FOCUS_INPUT message. A component associated with an entity can send messages to all the other components associated with that same entity, and this capacity is sofar unused (and thus untested, since I haven't even finished the unit tests for this aspect of the entity/component structure-- don't do this) but handling input seems like a good use of this capacity.
This hinges on a few design choices. One of them is that component messages have to come from a certain component, so part of having control or giving focus to an entity would have to be instantiating an input component on them. Another is a little more technical-- with all these layers of indirection (input system is updated in the entity systems batch update, input system sends component messages to specific entities via an input component, component messages iterate over all components on an entity) I am starting to get a little worried about just how practical all of this is. Already the key mapping "solution" I have is going to require some kind of index to make it fast enough (well, I assume, given that there's a few O(n*k) loops to even figure out which game events are triggered by a given key configuration) and when I add on iterating over every component of a few entities per game event, I suspect the cycles spent just resolving all of this so that the right code is called at the right time are going to be a not-insignificant percentage of the total.
All this to say that I intend to write a new component, one called 'Movement'. Or, actually, one probably called something like 'walking'. Since there will probably be several movement types-- walking, in an actual vehicle with wheels, on something like a bike, etc-- it seems prudent to start making it easy to decouple movement types from each other. How this will actually work out, I don't know. (and of course there are questions about how it would work if, say, you gave an entity both a 'walking' and a 'flying' component, much less what would explode if you give an entity both a 'car' and a 'skating' component.) So. That's probably what I'm going to do! So that the IR_MOVE_AVATAR_FORWARD game event messages the controlled entity with CONTROL_INPUT: MOVE_FORWARD, which is picked up by the entity's 'walking' component and that is where the actual calculations about walk speed and soforth are decided.
(And then I'm going to have to figure out how specifically to resolve how the 'walking' component will alter the entity's position or velocity and blah blah blah, the horrors of dependancy. So. There's that.)
ANYWAY SO THAT'S THAT.
September 13th, 2010
Movement kind of works. It works well enough to reveal glaring issues with integration and collision, and that's good enough for now! The most notable problem is that since I never actually did get around to writing that cartesian-to-polar-coordinate function, all of the collision is done against the tile at the origin. Which makes collision basically useless! The integration may or may not be a bit more nuanced than that; the symptom of the problem is that sometimes when moving entities may suddenly stop short or accelerate wildly. (Also, once I get collision working over all tiles I'll have to fix up the movement code to deal with walking over slopes properly and flipping the onSolidGround bool on and off.)
Part of the problem is assuredly with the interface between the movement component and the integration component: it's not reasonable to model walking around as applying forces to a rigid body, there has to be some way to directly mess with the velocity of the entity in a way that doesn't break the actual physics integration, and as of right now I have not done that properly. Still, it's definitely possible that there are more fundamental issues with the integrator have that only been revealed now that I am actually doing some non-trivial (by which I mean not-gravity-only) integration. Also once I fell through the floor (which was not entirely unexpected) only to stop abruptly for no reason. So as it turns out even the simple parts of collision I have working aren't being handled well!
My focus on the project has been kind of flagging since the start of the month. I want to start working on maps and spatial geometry (since I am actually reasonably good at that part) but I can't really do that until I have movement and collision (and cameras) working well enough to let me walk around the world without it being this huge hassle. (Plus once I get the maps and spatial geometry working, I'm going to have to revise a lot of position code since the current slapdash implementation is just "vector distance from origin", and we all know how that ends.)
September 15th, 2010
Fixed the issue with movement— as it turns out, what was happening was a complex set of interactions between the movement, integration, and collision code. When the movement code was working "right", what was actually happening was that the collision code was registering a entity/hex collision every frame and thus cancelling out the velocity of the entity. When it was working "wrong", there was not a collision (due to the incorrect testing of tiles) and so the extra velocity added by walking about was piling up, leading to acceleration. The random dead stops that occured somewhat randomly happened when the collision code detected a collision.
So to solve the problem, or at least make it less bad, I fixed up the following things: Firstly, when an entity collides with the surface of a tile its "onStableGround" bool gets flipped on, which turns off gravity integration. This happened already; what I fixed was now, tile collisions are skipped as well, so collisions aren't reported for every step in which an entity is touching against a surface. This isn't actually correct-- a sphere hitting a sloped surface should roll; ignoring tile collisions entirely means an entity that's "on stable ground" could walk through walls-- but since there's no angular momentum or wall collision in the first place, this doesn't add any problems. But with only that fix, it means walking always builds up momentum, like the world is completely frictionless. So I changed the way extra velocity was integrated: now instead of being added to the 'target velocity', which creates momentum, it's added directly to the entity's 'target position'. This is a hack, but fundamentally, to the integration system, any kind of magical velocity-altering without an associated force is a hack, it will just have to do.
So now movement "works"! On to:
- fixing a crash problem when you hold down a lot of keys at once, due to my awful "vector" (as in C++-style) implementation
- fixing a memory leak due to the same issue above
- collision detection with walls
- collision detection/response that does sweep tests or at least handles interpenetrations
- collision response that does something other than "halt object", e.g. slide against walls if the direction of movement isn't head on
The last point in particular is probably going to be hard, since it's something that requires coupling together the collision and movement systems, I think. Obviously the main issue is to figure out a way to do that without coupling together all of the components into one big tangle, but really I'm perfectly fine with, say, position and integration and collision all being tightly coupled together. When other components start getting coupled to those, though, that's a problem.
(this is mostly me talking to myself puzzling at that last point, feel free to ignore everything below this:)
The problem is hitting a wall due to walking into it involves four seperate components. The velocity of the movement is being added to the integration component via the walking component, and the collision component is checking the location of the entity vs. a hex prism with the position component's values. If the movement from integration was just a rigid body force, you might want the entity to rebound against the wall, but if the movement is instead due to intentional movement then the proper response would be something like... slide the entity along the wall at a speed that's, like, entity's velocity vector · vector of the wall. Or the inverse of that. Something probably involving dot products, let's just say. Except there is no way for the collision component to respond in the right way, unless it is aware of what type of movement it is. I suppose having it discern between "extra velocity", e.g. faked up movement from an unknown source, and actual velocity from a rigid body force would be a solution, but that seems rife with its own problems, notably about what happens if something other than movement ever uses extra velocity. Except the entire point of extra velocity is for things to move around in ways that don't imply it's a collection of external forces, so w/e I guess that's the solution.
September 27th, 2010
oh god how did this get here i am not good with computer
November 10th, 2010
First of all: still here, still alive.
October was supposed to be a month full of programming on this, except I got sidetracked thinking about how the coordinate system would work, and I got all kinds of hung up on tiling and curvature and stuff, and I only really figured out a workable way of doing things halfway through the month, and by that point my drive was pretty much shot. Also, I got sick.
I started work on the coordinate system finally after I figured everything out, but since what I was doing was mostly small, fiddly code there wasn't much in the way of screenshots or updates or anything like that. So here, have a big screencap dump:
Everything is working exactly as planned in the above screenshot. Long story short, there are clusters of tiles (which are called grounds internally) and they are connected to other clusters on their edges (which are called edges internally, unsurprisingly), and that is how the world is built. Each ground has six edges, and while hypothetically they don't have to be connected to anything, in practice they all probably will be. This setup ought to be completely obscured in the daily life of the tiles themselves; if a tile is at the edge of a ground it thinks of the abutting tiles on the neighboring ground as its neighbors in exactly the same way as its intra-ground tile neighbors.
The entire point of the grounds system is to avoid losing resolution as the player travels away from the origin; in addition to the unstructured mess of ground-to-ground edge connections, there's also a more easily accessible cache of labeled grounds, which are used for things like rendering. There is always a ground centered on the world origin (which is called the origin ground, also unsurprisingly) and as the player moves around (specifically, away from the origin ground) then the origin ground is updated and everything is seamlessly translated with respect to the new origin ground. This is basically my poor version of the way Dungeon Seige does things, only not really at all. Let's call it "inspired by". "Loosely".
As it turns out the system is also good for something else: namely, rendering. It's easy to tell how far away any particular ground is from the origin if it's close enough to be in the cache in the first place, and this would be a great place to put in level of detail stuff. As it is right now, any grounds within two steps of the origin are rendered. Not the best way to do things, but it works for now.
What I'd like to do next is... well, actually, probably making collision detection work right before I turn gravity back on, since intermittently falling through the floor or walls was no fun at all. Also, now that I have grounds "working" I want to see if my raycast skybox idea is in any way feasible. ...since it involves casting infinite-length rays, probably not.
And then I changed the color of the clusters, because bright red, dull green, and muddy orange are really ugly.
November 14th, 2010
Grounds are now drawn slightly better. It's possible to actually discern sloped tiles or tiles with height differences! kind of. Most immediately what I'd like to do is finally write the mouse look-around code, since I have been using this awful keyboard-only control scheme since forever and it really sucks. Once that's done, testing out a lot of stuff (like the column collision code) will be easier, because I will be able to look around. But before I do that, I should fix the position system to actually do the fancy ground-updating thing. Right now you can just walk out past the edge of the world because the origin ground never updates, because the entity position component is only a dumb x,y,z vector.
November 18th, 2010
Got sidetracked bugfixing. And also dealing with a lingering sickness. Bleh. The origin ground still doesn't update, but now there are 100% less memory leaks when updating the grounds cache, so that's a definite improvement. Also I made the tiles very slightly variegated to make it more obvious how they were laid out even on a flat plane.
(There were issues with the cache not actually freeing all of its stored labels when it was reset; the issue turned out to be with the resizable array ['vectors', if you are familiar with C++] code, which I apparently wrote while completely out of it. Parts of it were totally overly complex for what they needed to do, parts of it did not actually do what they needed to do; it was a real mess. At one point I was &-ing values together instead of |-ing them. It was just horrific. I'm actually kind of surprised the error only showed up as misrepresenting the length of certain arrays and not my computer spontaniously combusting.)
So the to-do list remains the same: get the new position system working with grounds, and use that to make the origin update as the player travels around the world. Then work on camera control. Then work on raycasting and collision. All of those (except for camera control, that is) are pretty tightly related, since they're all about resolving where tiles are and what their boundaries are and that kind of stuff. And camera control, once it's done, will definitely help me with rendering things efficiently, which will make it easier for me to render mode stuff. (My computer is a low-end desktop PC from around 2005; if it can run a game at a playable framerate then I'm betting any computer can.)
November 19th, 2010
OH I GUESS IT IS TECHNICALLY THE 19TH NOW
I still think screenshots of ridiculous bugs are much more interesting than screenshots of everything working perfectly. In this case, I discovered that the math I was using to place grounds in world space was wrong: it worked correctly only for the specific size of ground I had been using when I put together the code. Any smaller and the tiles overlapped, any larger and gaps formed between grounds. So I fixed that up after realizing that proper calculation of tile position across grounds is pretty much exactly what the position-resolving code has to be about. And now my job getting that working ought to be slightly easier.
Still working on position-resolving code. It turned out to be conceptually much more simple than I had thought, (it's basically vector translation, and a fairly easy to compute one at that) but in practice I've had to remember what it is specifically that all the hex and ground vector functions calculate so that I can write functions that I'll use more than once. Also it's that hour of night where I can't actually do math anymore, which poses a problem.
Also, there are certain practical issues with the way the position code works, mostly dealing with the potential of the world to wrap back around on itself, that means that while each position in space is uniquely define, those unique positions might be rendered more than once in different places, and it's important to have the camera in the right place if it happens to be at one of those locations. Probably I will solve all this stuff early tomorrow, though, and then move on to camera control. Camera control! With the mouse! I'm sure it'll be an all new horrific slog.
(oh yeah, and this screenshot is when I fell through the floor. it shows off part of why the rendering setup is so awful: tile edges are always drawn, even on the inside of flat grounds where they're completely invisible. that roughly triples the polygons used in any scene, even though every one of them is occluded.)
November 20th, 2010
Grounds-based position movement almost works now. The hard parts (where I am defining "hard parts" as the parts dealing with resolving hexagon locations in world space coordinates) are all done, and all that's really left is to make the camera respect the new position system. As of right now the camera only calculates position from the local offset and ignores the ground parts of the position entirely, which means that it always looks like you are stuck on the same ground perpetually. Also not functional is the rotation at ground boundaries. Since grounds can be rotated relative to each other, crossing an edge might entail rotating anything that passes over it. This is actually a pretty simple calculation, (rotate the position and view axes by the rotation index * 60 around the y axis) I just haven't gotten around to actually doing it, since it requires actually using message passing for something.
The only real issue has to deal with how the camera is going to work ultimately. It's got to know about the grounds cache, because cameras are the only thing that realizes positions as rendered aren't always unique, and to get a truly unique position reference you've got to have a cache label. I mean, that's it, problem solved: the camera has to get a cache label instead of a ground pointer, the end. But it will be slightly trickier to do that without introducing dumb dependancies.
November 21st, 2010
It's this kind of thing that makes me feel like a failure at math. uuuugh. And on top of everything else, I've introduced an intermittent memory corruption bug that shows up as an infinite loop on quit.
Where to even start? Okay, so, I was constructing the camera matrix incorrectly, and because of that I had at some point inverted the axes when dealing with position — and only with position. This caused problems when the position had to be compared with anything else, since I didn't realize the issue was that the position was inverted and so instead I tried to fix it in other ways, usually with bizarre math. To add to that... I'm not going to explain exactly how the grounds system works, but for the camera there are many coordinate systems. The actual hex tiles use both x,y coordinates and polar coordinates, and in storing the cache data grounds themselves are effectively assigned x,y coordinates too. Everything else only knows that certain grounds are adjacent to each other in certain directions, but the camera can assign coordinate offsets to those directions. To aid confusion, they use the same coordinate enum as the hex tiles. There are actually legitimate reasons to do that too, because then it's easier to map tiles along the border of a ground to their adjacent tiles on another ground, but the grounds coordinates/hex coordinates dual usage is definitely the most confusing part about resolving the mess I have gotten myself into.
Everything is still broken in unusual ways, but now it is broken because I have gone through and fixed a lot of the underlying bad math and I haven't yet gone through and fixed all the calculations that depended on the math being a very specific kind of wrong. In all hope, fixing things won't make a huge mess again, because with the fundamental issues resolved the calculations require will no longer be absurdly overly-complex and still not work right.
November 22nd, 2010
And grounds work1! It is a contingent "works", since they only work 1) when the grounds are not rotated relative to each other and 2) when all their edges are filled. With the former the camera still stutters and jerks and teleports and with the latter there are frequent invisible walls at the edges of grounds. The latter is something I'd like to fix; it's caused by a mysterious calculation that places a moving object on the wrong ground but at the right offset, so that it is already off the wrong ground. And then any further movement makes it recalculate again, which sets it on the right ground. Visually there is no difference, but if all the ground edges aren't filled sometimes the mysterious calculation fails because the wrong ground it would move the object to doesn't exist, and so the whole move fails. Very mysterious. But not actually a problem if I stick to boring old connected worlds. It's good enough for now; I'll go back and fix it, hopefully, when I start working on world generation or something.
Next up is camera control! This will probably end up being another huge slog through matrix math. Oh well. I am just so fed up with keyboard controls at all times. And with camera control I think I'm going to try and fix up the rendering setup; I know I could at least double the draw distance if only I was more actively culling polygons that didn't need to be drawn in the first place.
(I also made a bunch of other small changes, like rearranging functions so that all of the grounds drawing-related code was in the *_draw.c
files, which means that I could remove a bunch of includes and make more of my pointers opaque. Which was very nice, because the less dependant on each other the source files are the easier it is for me.)
1 The math did turn out to be dead simple now that I had fixed the underlying issues. Literally all I had to do was uncomment some lines of code I had written earlier, ones that I was certain ought to work but for some reason didn't. So that was pretty satisfying. I am actually fairly certain that the mysterious stuttering calculation mentioned above is caused by some of the various other old bad math I haven't removed yet. [↑]
November 24th, 2010
And mouse cursor pointing works. Barely. Not only is it hypersensitive, but it also has a tendancy to roll around wildly. The screen to the left, where the camera has flipped entirely upside-down while listing to one side, is entirely typical of its usual performance. The fundamental issue here has to do with the way I'm constructing the matrix from the view quaternion, or maybe the way I'm constructing the alteration quaternion from mouse input. Actually I would say the real fundamental issue is that I totally do not understand quaternions. Stupid fourth-dimensional geometry. There are also some issues that exist solely because I use a tablet as my pointing device, and those are really hard to use to control a first-person camera in any case. But the actual control part ended up being surprisingly easy. Now what's left (and there is a lot left) is tweaking the camera until it handles naturally. Since I have no clue how to do this, this is going to be something of a problem.
But being able to look around, even if it is with a camera that has a tendancy to roll over at the drop of a hat, goes a long way in making this feel like a real game. Even the cursor goes a long way, even though it's an 8x8 square of white pixels. Who knows what will be possible soon! Probably a better camera. And better rendering. And better input. But on top of that I'm sure I'll manage to get something more done!
November 25th, 2010
Reviewed my matrix math and corrected the calculation of the cameraview matrix (again). That had the side-effect of revealing that the position orientation wasn't being updated when the camera rotated, which I already knew but wasn't really inclined to fix without any symptoms. Featured to the left: symptoms. Camera still rolls around and upside-down, since it's still fundamentally a 6dof camera.
I fixed the ground edge stuttering bug, although I haven't tested to make sure this fixes the invisible-walls-when-ground-not-fully-connected bug. It ought to, though, and I'm sufficiently certain (since I can see the ground_bridgeConnections
function is only getting called once per ground edge traversal, instead of twice on the problem edges as before) that it's fixed that I don't feel the need to check. Famous last words, I know.
I also fixed an issue with regenerating the origin ground. It still doesn't update until you walk past the edge of the world, which is a problem, but now when it updates it places the camera entity on the right ground — previously it would skip them back a ground due to the way the new origin was calculated. So that is all nice! You can now just walk and walk and walk forever in any direction and there will never be any form of numerical instability or visual stuttering (...excepting the part where you have to walk out past the visible edge of the world).
I don't actually know why this screen happened, but it looks nice enough that I might try to emulate the effect later on, if I decide to stick with using visible ground borders. I'm really liking these muted per-ground colors with patterns of lighter tiles. But a less hexagonal spread would be nicer.
(And then I made it so that the origin regenerated whenever you get more than a certain percentage of the draw distance away from the origin!)
Remaining bugs I'm aware of: rotated grounds probably still break everything. Also the input system has a really annoying memory leak. The input system as a whole is really annoying, actually.
November 26th, 2010
Finished rewriting input system! Or at least finished rewriting most of the input system. It no longer leaks memory like a sieve, and it's more compact and simpler. Less bit arithmatic, less internal structs that were used only to organize the overly-complex system, more centralization, and, of course, support for non-key SDL events that's more than a bolted-on afterthought. So that is all good.
Walking around is still broken, though. And the camera still sucks.
November 27th, 2010
Fatal signal: Segmentation Fault (SDL Parachute Deployed)
And we were doing so well.
Walking works right, finally: point the camera in a direction and press down and you move along the world plane in that direction. It is a testament to how much I've learned about matrix and vector math that my train of thought was something along the lines of "oh, use the orientation quaternion to generate a 3x3 rotation matrix, and then read its rows to get the front/side/up view axes, and then get the front and side's cross product with the world up vector to get the side and front, respectively, movement axes. Easy!" instead of "aaah how do I do this? :(". Once I get the camera actually working right I think I might write something more technical about matrix math and first person cameras and stuff like that, because most of the tutorials I read were pretty opaque.
Meanwhile, now whenever I enlarge the camera rendering cache past a certain amount the entire program crashes. The good news is that it's almost certainly a bug with the resizing array code. The bad new is that it's almost certainly a bug with the resizing array code. It's old code; there's lots of pointer arithmatic; there are bit arrays to count indices; it's a mess. And half the functions don't really support non-sequentially filled arrays. So I'm probably going to spend some time getting that working right, again.
Although I'm not sure what I'll work on after that. The camera has been downgraded from "incredibly annoying" to "technically incorrect" after I made a few slight changes in the way it handles. Obviously at some point I gotta get the rendering fixed up to be more efficient. And I should probably make sure rotated grounds work. But that's all bug fixes or efficiency things. Soon I will have to add new features! New features will probably be plants or map generation. Or UI stuff. We'll see how it goes.
November 29th, 2010
I wonder if I can get this done before the end of the month.
Still working on the new dynamic array code. Or rather, working on replacing the old dynamic array code with the new code. There are a few bugs left in it, and there are some parts of the code that are sufficently different that I definitely did not convert them correctly. Right now all my unit tests spit out pages of error gibberish. I managed to get one passing so far. The next one on the list is the object/entity system. This is going to get messy. Or at least "annoying".
objClass_search: names: "�R��@¡ �ȷ� 0� 0� �R꿥" and "debug"
holy shit but then i fixed two search/sort functions and suddenly the entire object suite passes!
sys_search: got "� � �g��V�� Pg�� " vs. "COMPONENT_NAME"
(and then i fixed those too and absolutely everything started working. that took literally ten minutes. fixing the first set of tests took about six hours.)
Now, of course, I have to turn to fixing all the code that doesnt have unit tests. Ugh.
AND THEN I FINISHED IT ALL. Go me! I am great!
And now to get back to work fixing the rendering system and making grounds transparent. I am laying the foundation here for GAME ENTITIES WHICH AFFECT THE WORLD, which... I really have only the vaguest clues on how to do this. But I'm sure I'll figure it out. First thing on the to-do list is plants. After that, who knows.
November 30th, 2010
/*** * Here are the things that depend on these arrays being in this exact order: * label_distanceFromOrigin in ground_draw.c uses the XY array to calculating * spacing distance. It requires the indices of the {1,0} and {0,1} entries on * the array, for use as X and Y axes. * hex_draw in hex_draw.c uses the H array to draw hexes. More importantly, it * calculates the camera heading to do some basic hex edge culling, and those * calculations will fail if the order of H is changed at all. * hex_coordinateAtSpace requires a specific mapping between the H and the XY * values, although at this point I forget what it is. * * Furthermore, ground_bridgeConnections in component_ground.c depends on * the grounds themselves having a certain linkage spin (clockwise). * "Linkage spin" is defined as the way the grounds themselves are drawn; e.g., * if you are standing on the center of a ground and proceed straight along * the edges of the key hexes, when you hit the edge of the ground you will * either end up on the rightmost column of the ground to your left (clockwise) * or the leftmost column of the ground to your right (counter-clockwise). * This is an important property of the grounds and I have no clue where it is * calculated. * * What I am trying to say here is don't change any of these values unless you * are very sure of what you are doing. */
I wrote this and then very promptly broke things. Yes, it was because I didn't remember the specific mapping. I then fixed things and updated the comment to specify which mapping was the proper one.
Also, somewhat disappointingly, the field of view in-game is so wide that I can't cull the half of the tile walls that face away from the camera, since you can still see them on the periphery. The only one I can cull without issues is the one that is literally on the opposide side. This may be something I can fix by messing with the screen resolution, though.
...Now I remember why I resolved to never mess with the screen resolution ever again. That's a nice fisheye lens effect going on there, though.
Wireframe! Which really just shows off how inefficient the rendering is, still. The major issue is that tile walls are drawn all wrong: even when they are below the level of the tiles they're still rendered. All five of them, (only five because one is now always culled, which is probably a useless savings) and that means ten extra polys per hex that are usually completely useless. If I can cull eight of them on average then I could extend the draw distance by two or three steps while still drawing roughly the same number of polygons. So let's try that. First things first: if I want to draw tile edges correctly, tiles need to be able to look at their neighbors. This would be dead simple if I had one large map, but the use of grounds complicates this slightly.
My idea so far is to have each tile calculate, on creation, its six neighbors. This would be done just by calling something like ground_getAdjacentTiles (g, r, k, i)
, so that I can put all the grounds calculations into one place. Then the tiles would calculate and remember the relative heights of their neighbors, so it stores the values it needs to draw its visible joins each frame. If tiles are actually shifted around, or grounds are shifted around, this would send a message that would cause the affected tiles/grounds to recalculate their edge data. This way, all the calculations happen when a ground is loaded (instead of "every frame", which is how things were done in phantomnation) and outdated data shouldn't be a problem. I hope.
First problem: it looks like the adjacency calculations are all wrong. Tiles closer to the ground origin don't show up as highlighted, and tiles past the ground boundary don't show up at all. It did not exactly take a lot of thought to realize that since my method of "highlighting" tiles was just redrawing tiles in a different color and hoping for the best it was entirely reasonable to expect that they wouldn't show up sometimes. So I fixed that issue, and promptly another issue was revealed:
The cross-ground tile neighbor code appears to be incorrect. The tile highlighted is in the right place, but it doesn't correspond to the tile that is actually on that coordinate. Instead, there are highlights floating or embedded through the side of other tiles or completely invisible because they're flat when the tile actually there is raised. This is even more confusing. Highlights showing up in the wrong place, okay, but the wrong tiles showing up in the right place?
As it turned out, that is the kind of weird error that happens if you draw a tile with an incorrect camera label. Since this "highlighting" code is a quick and dirty test to see if the neighbors were calculating correctly, I didn't really bother to place it correctly in the larger scheme of things. Instead, I just added a footer in hex_draw
that highlighted its neighbors under certain conditions. And to draw the highlights I needed a camera label, and I just figured that I could pass the label that was passed to hex_draw
in the first place and everything would be fine. But that is not how things work!
With the wrong ground, the coordinates of the tile are calculated into real space relative to the origin of the wrong ground, and that means any neighbor tiles that crossed ground edges would be drawn in the wrong corner. Except, because of the way I was highlighting the neighbors in the first place, one tile from each visible ground had its neighbors lit up, so the incorrectly placed highlights ended up showing up in just the right place to make it look like they were part of a different cluster of highlit tiles.
That probably made no sense to anyone.
The point is, the neighbor calculations were always working right, I was just drawing them incorrectly. Now to actually use that information to cull tile edges!
This problem looks oddly familiar. If you go through the phantomnation logs you will see I have had this issue five or six separate times. Thankfully it's been getting easier and easier to fix each time. Still, bleugh. But now it's working right! And there is a vast improvement in the responsiveness of the program! Time to increase the draw distance until it starts lagging again!
The old draw distance was three or four, which drew 31 or 61 grounds, respectively. Now I can set it to seven and still get a playable response, even though now it's drawing 169 grounds. Next up: let's see if we can cull all grounds that are off the screen entirely. Best case scenario is that it thirds the polys drawn. Worse case scenario would be more like maybe a twenty or thirty percent reduction, if you stand near the edge of a ground and look across the rest of it.
Another option would be to try and simplify planar geometry. If there are a block of hexagons all together that all have the same slope, it's not important that they are all drawn as separate hexagons. Instead they could be all chunked together into a blob and drawn as fewer, larger polygons. This is complexified somewhat by the fact that they are hexagons and not something more reasonable like squares or triangles, but it's still doable and it would still save a lot of polys on flat terrain. The downside would be that I would have to completely retool the way I render the ground.
Another another option would be try level-of-detail rendering. The further away from the camera anything is, the less space it takes up, in terms of sheer pixels. So I could try to see if I could simplify the geometry there. There is already a simple and well-defined way to get a ground label's distance from the camera, and if I had level-of-detail whatevers I could add them in very easily.
...But also, I don't actually have much knowledge of how any more advanced culling would work, and I'd rather not get hung up on making things render faster and better at this point. Rendering is now officially Good Enough, and it's time to start working on flowers and junk.
December 7th, 2010
All of this stuff happened, like, on the first few days of December; I just didn't write an update then.
The very first attempt to render flower shapes draws them along the wrong axis and also absurdly huge, turning them into some kind of bizarre borealis across the sky. I will have to remember this for when I want to draw actual borealises. ..."an actual borealis", let's say.
Further issues with the "draw on the plane defined by its normal passed" code. Or really just the one issue. So, drawing flat designs works right, although they are still one-sided since I haven't found an easy way to calculate the camera's facing from its position and orientation. (The idea is to only try to draw the side that is facing towards the camera.)
I think here I drew the shapes huge again just for kicks. I also gotta remember how this works for when I get around to drawing ley lines and stuff like that.
Flower "shapes" that aren't flat. These are also defined by a single normal that defines the plane of their "top" face; the other axes are pretty much arbitrary. Presumably I'd like to work up to being able to draw all the Platonic solids, but for now I'm just going to settle for cubes, since the other platonic solids are... more complicated. I mean, really what I want is for all the shapes code to be capable of generating all sorts of regular polyhedra, but the steps up to that are getting the Platonic solids drawing right.
And finally, slightly more complex "cutout" shapes work. Generating them is sure to be a stupid hassle, but they work at least in regards to drawing correctly. These were something I really wanted to put into phantomnation but never really figured out a good way to do it, so... I'm hoping I will find a good way to put the idea into this game!
AND THEN, IN SLIGHTLY MORE RECENT NEWS, yesterday and today I fixed up the camera some. It is no longer fundamentally a 6dof camera: rotation of heading takes place around the world up axis; rotation of pitch takes place around the local side view axis. (rotation of roll, hypothetically, would take place around the local front movement axis.) Pitch rotation isn't clamped1, still, so you can still flip the camera upside-down by looking up or down past the zenith or nadir, but at least the camera no longer rolls over just from looking around. Also, I had made an error in the code that converted the orientation quaternion into a rotation matrix (I had flipped the sign of one value) and that had been leading to a weird bug where the world appeared to flatten down into a plane whenever you looked up or down. That is fixed now! That also fixed the issue where the camera would clip through the surface of the world if you looked up or down. So basically the camera works almost as hoped now.
But I've been kind of swamped by other stuff so far this month, so I may not get a whole lot of coding done. I've resolved a fairly concrete to-do list: get flowers generating and growing, probably w/o mutations and hybridization unless that turns out to be really easy; get the world map automatically generating, probably in the form of a completely flat closed surface with flowers growing at the poles; get enough of a UI working (probably cannibalized from my phantomnation UI code + freetype) so that people can get a title screen + menu and a quit confirmation dialog. After that I'll see if I can release an extremely boring and unstable linux-only (probably even debian-only) test. From there I'll work on logging and debug stuff and rooting out the linux-specific remnants from the code, so that it could actually be something that could be cross-platform.
Basically I'm just tired of toiling in loneliness and isolation and I'd like to get this thing up to the point where I can tell people to check out the latest release and have them be able to do that, even if all you can do in-game is glide around looking at flowers.
Also, oh my fucking god, I turned off my terrible memory debugging code and performance got something in the range of 4000% faster. I didn't think it was terrible before; it is now retroactively terrible because of this. I can push the grounds distance to something like 10 before it starts to slow down at all, and to 12 or so before it becomes the kind of slideshow I usually got with 7 or 8. (And keep in mind the numbers here are in a geometric sequence; each number adds (n - 1) * 6 grounds to the render cache, so 12 is to 10 as 8 is to around 4.5)
Presumably the reason for the speed-up is that a lot of the updating routines allocate temporary arrays when fetching lists of components to iterate over, and that requires a lot of interactions with my admittedly old and crufty memory system. (The specific thing I'm assuming causes 90% of the slowdown would be that it completely resorts the list of memory addresses each time one is added. Which uses the built-in qsort
, which may not actually be a quick sort [and anyway that'd still require nlogn comparisons, which isn't trivial with several hundred addresses allocated], even though only the newest entry needs to be sorted.) With all my memory code removed and substituted in the preprocessor with straight malloc/free calls, there is none of that overhead and things go a lot smoother. A lot smoother.
The only problem: the program segfaults on quit. Not that big a deal, since hey it was quitting anyway, but presumably what this means is somewhere I'm freeing something in such a way that my own code doesn't mind, but that isn't permissable with the actual library functions. So that is added to the list of things to fix.
Also, I'm going to add "reasonable allocation functions that also tabulate memory footprint and stability", mostly because I have been reading "Cross-Platform Game Programming" and it's making me realize just how many things I take for granted are really just artifacts of gcc or linux development. And also that while computer games are often written to take advantage of effectively infinite memory, it's often a good idea to, you know, not do that. And especially not to actually allocate and free a whole ton of memory per frame. I mean, all those dynamic arrays are getting created and freed each frame still, it's just that the library functions are a lot more efficient at what they do, so they're nowhere near as slow.
1 And then I made it clamp too, after some confusion about which specific rotation matrix value you need to check pitch. It is the front axis' y component. That is the specific rotation matrix value you need to check pitch. Assuming your up vector is invariant and also happens to be [0, 1, 0]. If it's not you have a much more complex problem on your hands. [↑]
December 11th, 2010
Fixed crash bug, it was some outdated code in the input handler that tried to free values that were not actually pointers. My memory code just ignored it, but free (0x0000b1)
made it crash. So. Fixed now.
Here's my todo list:
flowers:
basic genome:
- total lifespan (e.g., 100 updates)
- total lifespan multiplier (e.g., 20)
- time spent in each stage of life
- can flower/flower type
- seeds dropped
^ GET THESE WORKING FIRST
(and then expressions of all of those, ofc, enough so that flowers can actually spread)
world:
consolidate map generation code somewhere and write a createWorld function that makes an entire world. Also, make it not suck when generating huge worlds: while the world may be a few thousand grounds in size, not all of them have to be generated. I would happy with something that "generates" grounds as they hit the render cache via being passed the world object/component and a planet coordinate. right now it can return a flat ground, properly linked, but in the future that would be the cue to generate or load a map chunk
- then make that code easily called in practice from the world object somehow
general:
rename "camera labels" or whatever to be renderLabels and put them in their own file, not in ground_draw.h (since anything that needs to be rendered has to include that now)
consolidate position and ground code somehow. Grounds are how a position is expressed; their code is really interrelated; there's no particular reason for them to be in two separate files except for how they are two seperate components
make the memory system suck less
make the entity/component/object system suck less
add logging (which implies add log files, which implies add file i/o)
shift around the main loop and the way the system object/world object interact, because the "world object" as it is right now is this outdated relic from when I didn't have any world data to track.
Once flowers work I'll try to make a world generator, and once those both work I'll sort code around and try to condense it, and then add some error logging instead of just relying on debugging with printf statements all the time.
January 1st, 2011
HAPPY NEW YEAR. I have been busy. Kind of.
I am pretty sure this is just a vanity screenshot taken to show off those kind of... nightmarish spiral things I made revolve out from the center of each ground. I think "nightmarish spiral" is kind of overdoing it, really. It's hard to tell if they're not really concentric circles.
So what have I really been up to? Rewriting the map system. Previously, grounds were linked together as a sort of abstract graph network: each ground had six possible connections, one for each edge, and it could be linked up with any other ground. This is a very modular approach, but as I diagrammed out what kinds of maps I'd actually be using, I realized I really didn't need all that flexibility.
Since I want to have closed maps (i.e., ones that wrap around in some way or another) there's not much point in having some freeform graph network. Eventually if you walk across the entire world, every ground will have all of its edges filled up, and the way they're all connected will be regular. Furthermore, if you stick to using a freeform graph there are some disadvantages. Most important to me are that it's a lot harder to draw a line from any arbitrary point to any other arbitrary point. So I officially deprecated a lot of the old graph code and replaced it with a global positioning system.
I'm not going to get into the vagarities of how this system works, but suffice to say every ground now has a unique global coordinate, and instead of painstakingly making a cache by traversing a graph and tossing away repeated grounds, now I can make a cache by flood filling out from the global coordinate location of the current ground.
This rewrite is close to being done. The camera functions have been updated to use the new system, but the movement functions haven't. What this results in is a perfectly rendered world that you can't explore: you're confined within the origin ground, because none of its adjacent neighbors are filled.
I don't actually know what the upper limits are in terms of world size. I mean, I did some really loose calculations and came up with "an area roughly equivalent to a thousand times the surface area of the planet Earth" as a tentative lower bound for the upper bound of map size, but there are two values in the size which can vary (ground size and pole size) and only one can be adjusted freely (pole size — ground size can't be set too high, because that's the number of tiles in each ground, and you'll always need to have at least 19 of them in memory at all times, so if they're set too high that game would rapidly become unplayable on even the most advanced computer) there are limits to practical size. That being said, that calculation was made assuming I only had two bytes per int, and I know in actual fact the real value is twice that.
But anyway, I'm not really interested in making huge maps. That works very well for games like Minecraft or Dwarf Fortress because the map is essentially raw material, and players are expected to build things of interest wherever they end up. But this isn't going to be as much of a building game, so I have to worry a lot more about generating maps which are interesting in their own right. This is clearly not anywhere near a trivial task. So I'll be keeping maps small as I start working on my map generation algorithm.
(But I'm not actually going to be working on that; I'm going to be finishing up the map architecture recode and then making flowers display as something other than black Xes, and then I'm going to see about releasing a .deb or something, just to see if this will run on absolutely anyone else's computer.)
January 2nd, 2011
Okay, and that's done: grounds no longer have a list of edges; all their connections are calculated on the fly using global positioning. ...I should actually stop calling it "global", since the shape of the worlds is definitely not a globe.
The downside to this is now there's some very noticable loading when the origin ground is recalculated. This is because, on the most obvious level, I am doing everything in one pass and thus forcing the cache to entirely rebuild itself between frames, which causes everything else to lock up until that finishes. Obviously in the long run I need to stop doing that, but at this point I don't think I'm dealing with enough data to reasonably assume it can't be finished in under a frame. My rough guess is that this has much more to do with my really awful cache design and ground loading techniques than anything more fundamentally about doing loading in the background across multiple frames.
I'm going to finish up the rewrite to make grounds actually unload as well as reload, and that might help some of the lag (but probably not much of it), and if any simple optimizations occur to me I'll try them out. Otherwise, it's on to making flowers actually do things. Like grow.
January 7th, 2011
oh huh it's already the seventh is it. very early on the seventh.
the problems i've been having with this code are mostly that i'm not really sure where it should go. the ground loading code has to go somewhere, but there's not really an elegant place to put it, since when i wrote the main control loop and everything i was mostly thinking about input and rendering and i kind of bypassed the parts in between that involve world objects being represented as data.
so i reorganized the main system a little to streamline it and reduce dependancy, and i even got rid of a few code files that were almost completely useless by folding what little functionality they had (keeping track of the system time in accumulated form) into the system object itself. also, now the system object updates everything instead of the world object, which frees it up to deal with grounds. except really the more i think about it the more i think the ground loading code should go in ground_update
instead of its own object. ditto with the world_position
struct; it has to do with position and grounds so it should be in the same place as the rest of the code that deals with position and grounds.
i'm really tired :(
January 14th, 2011
Now it's very early on the 14th. I've got to stop staying up so late.
Where do I even start. Okay.
- Grounds are now loaded in the background while the player walks around. The weighing mechanism is still something of a work-in-progress though, and it's not unusual to see really distant grounds loading up while the ground at your feet stubbornly remains unloaded.
- The camera cache is also regenerated in the background. This is still dog slow; I'm still using a kind of placeholder-y regeneration of the cache, instead of just translating the parts of the cache still apply and only generating the new parts. Fundamentally this is because I made the unwise choice to use polar coordinates, and in fact the next thing on the to-do list is replace at least these polar coordinates with Cartesian.
- There's now a worldgen component. Right now it does absolutely nothing except serve as a placeholder between a ground being created and its tiles being created, but now I have a perfect place to put all the fancy generation code instead of awkwardly shoehorning it in to the world creation code.
- There's now a mildly robust section of code that handles all this background loading. I need to make it suck less, but right now it's there and it works, so I'm not going to complain much.
I'm just going to say that these are all chunk errors and be done with it.
(Unloaded chunks are rendered as single hexagons, colored depending on how awful the consequences will be if you step on it.)
So, what now? Well, making the camera cache not suck is high up there on the list of things to fix. After that comes fixing the loading errors at the pole edges — there are only a few places where it's safe to step over the pole edge; most places are perpetually red or orange because of a problem in one of the distance functions. Or because of at least a problem in one of the distance functions.
After that: flowers. Maybe? What seems slightly more pressing is a loading screen; right now the player is placed onto the half-generated world in a manner which is extremely fragile, and it would be nice to fix that so the player is definitely only placed after the ground it starts on is loaded.
Honestly, I'm itching to rewrite the entire entity/component system. There are so many ways I could make it work better and it's currently so bad. But that will have to wait at least until I get a few more things working.
Also I'm itching to actually get this to the point where I can release it, even if it's just a really boring "let's walk around this featureless map" release.
Morning: first thing I do is fix the loading errors at the pole edges. Hooray. All together I don't think there's a single thing stopping me from generating just unspeakably vast worlds now. Aside from how they're still flat and boring. Still gotta make the camera cache faster.
And then I made the camera cache... well, not really "faster", but it's now a little less inefficient in terms of memory allocation.
I... started on getting only the grounds the camera was pointed at the render, except the thing is, I wrote most of the code for that a week ago and then forgot to save it. So trying to rewrite it has been an exercise in frustration. I'll try that again later.
So... what's left? Hex edge calculating at the edge of grounds has been broken since I added the world system, so that would be good to fix. Aside from that I think this system is fairly robust! Or at least robust enough for me to start working on something else. Something else in this case being flowers.
January 19th, 2011
To the left: why glClear is a good idea. Randomly, my rendering code 1) pushed and popped matrices each frame and 2) called glClear twice with the same arguments. I think removing the extra sped up performance somewhat, but as you can see there, calling glClear (GL_COLOR_BUFFER_BIT) at least once is a good idea if you don't draw across the entire view. Hypothetically, if I was using an opaque skybox I'd never have to clear the color buffer at all, since I'd always be overwriting it.
Blah blah blah, tile edge problems. As usual. Fixed them.
There are still two fairly major rendering improvements I could make: tiles at the edge of a ground don't have their cross-ground edges properly calculated, since the code I wrote there depended on the grounds having that whole graph network setup instead of world coordinates. This is something I can definitely fix, but it'll involve some fiddly geometrical calculations and it's not particularly important yet — it's impossible to tell they're not calculating correctly unless you turn on wireframe, and compared to the next issue the number of extra polygons drawn is pretty minor.
The other major rendering improvement I could make would be to update the camera cache to only render grounds the camera is looking at. This would be pretty trivial to do once I write up a rotation comparison metric, and even without caching the results at all I suspect there would be a really substantial improvement — each ground has to be iterated over to draw it anyway, and since at least half of them would be culled in all cases the overhead of actually doing the rotation check would be miniscule in comparison.
(In case you haven't noticed yet, these two paragraphs detail the same things I was talking about at the end of last update.)
Anyway, I really am moving on to artificial life now. Except this is something I actually have to think about, so going has been a little slow. Hopefully I should have really ugly trees growing within a week.