June 13th, 2011
This was a very productive use of my time.
I do wish I could get the colors closer to the orange-and-blue of the prior iteration, but everything I try now just ends up murky or really bright. The design code is nice, at least.
Now to see if I can get movement working. And then loading. And then unloading. And then maybe I can finally get to what was the point of this entire map redesign in the first place. Sigh.
The definitive to-do list is as follows: write the remaining map functions (at this point mostly limited to mapRelativeSubhexWithSubhex
, which is the function that generates a displacement struct given two existant platters), then fix whatever remaining bugs there are with cross-platter movement (ideally just writing the function should fix them, but I'm not that optimistic). Then add priority queue support to the dynamic array code. Then hook the map code together so that nearby platters are loaded. Then make it unload platters that are no longer nearby.
I'm mostly writing this list again because I don't want to actually code right now. I should stop that.
June 14th, 2011
I messed around with the map/position/camera code until it started crashing in a different way, at least. I wrote this code like a week ago, but already I have no idea what most of it does or how it works. That's never a good thing.
Movement across platters 'works' in that it no longer crashes, but now the camera bounces around and movement is quanticized to the size of each platter and the direction of movement doesn't actually determine the platter the camera ends up on. This is all very confusing.
Oh, and I changed the clear color to white, but I think I might change it back, or at least to something less bright.
I kind of want to just say "w/e fuck the map code for the time being; let's add a visible avatar and some map generation stuff and a ui and system loading", since I am so fed up with this right now and I'd like to make some visible progress instead of picking at my really confusing code for ages. (This is probably a bad idea.)
June 15th, 2011
What.
So I imported a bunch of phantomnation code en masse — font code, the spritesheet code that requires, the texture code that requires, and the pathing code that requires — and then tried to run it after blindly updating a lot of the code. (My favorite: a 400-line state machine that handles the irregularly-sized sprite sheet loading process.)
Evidently there's memory corruption somewhere? That sure looks a lot like memory corruption.
And the font code isn't even drawing anything; I mean, I can't see any —
Oh.
Okay, there are several things wrong here.
The memory corruption has something to do with the font creation, but aside from that it's hard to tell what's up. For some reason it's worse if the sprite sheet isn't initialized — the central platter is entirely vacant/very tall and it crashes almost immediately — which is weird, since all the allocations the aforementioned huge state machine does are, as far as I can tell, valid. Also it uses the same dynamic array code that's used a billion other places. The map isn't even in memory when the font's loaded. So... I got no clue. Even if the texture or font code was wildly overwriting addresses, by the time the map loads it's the font data that should be overwritten, if there's an overlap or overrun happening. And it's always very specifically the first platters to be loaded that are corrupted.
As it turns out, the problem is specifically in the texture code, not the sprite code. And specifically in the texture loading code; the problem doesn't occur if an invalid file fails to load. The problem appears to be with any invocation of the glpng
library; everything works fine if all instances of those functions are commented out. Of course, without those functions there's no textures either. This is really obnoxious; I'd rather not have to write my own image loading code. There's one critical invocation of pngLoadRaw
(in the sprite code) and one critical invocation of pngBind
(in the texture code), and either call somehow corrupts the later map data, and if those functions don't work there's very little I can do to correct things. I'm going to check the map code now and see if the problem is somehow on its end.
It was :|
Or rather, certain values weren't set to zero on hex creation; this worked fine so long as the data happened to be set to zero already but evidently the png code uses and frees enough memory that the hexes were getting reallocated memory at first (hence only the first platter being corrupted) which, as it had been used, was not set to all zeroes, and those values ended up sitting on the part of the hex struct that determined height.
Once again, I'm dumb. sigh. Where was I?
Oh right, that. So the font text is 1) not being drawn with the right matrix (because it's within the world coordinate space), 2) not being stenciled properly (because we can see the pink border), 3) not being drawn in the right position, 4) not being drawn with the right vertical offset, and finally 5) being drawn upside-down. Also, that font is ugly.
2. is the easiest to fix; that's happening because I don't have blending enabled. The rest are all part of the same fundamental problem, which, distilled to its essense, is "aaah matrices".
Given how everyone in practical purposes will be looking at this on Tumblr, and given how it resizes images, the weird pixelated blurring across the top will probably be rendered completely invisible. But take my word for it: there's some weird pixelated blurring across the top of the screen. It's actually the tell-tale sign of polygons being drawn backwards; I turned off back-face culling and just made them draw as outlines, because one of the most obnoxious rendering problems is "augh why isn't this rendering why why why why oh wait i'm drawing them backwards so they're invisible"
I love how I go through this whole song-and-dance of "oh the fonts are drawing upside down again" every time I revise the font code. Very ill-advisedly back when I first wrote it, I spun some of the coordinate axes around so the vectors would face in directions I considered at the time intuitive, and since then it's been nothing but problems, since more recently I've just been using the identity matrix, because, you know, that's reasonable.
Okay, getting there. The main problem is that this line is positioned at 16,16; e.g., that while the left margin of the line is properly offset, the top margin is all wrong. This has been a long-standing problem with my font code; to get the correct top margin the height of the tallest sprite relative to the baseline must be known, and last time I tried to calculate that things went haywire. There's the problem of ascendors and descendors, which always seems so intuitive until you see fonts drawn without them — the font is drawn aligned to the baseline, which is roughly around the bottom of the lower-case characters. The 'h', 'y', and 'g' (as well as many other characters; part of the reason this font is awful is because the baseline is all messed up) distinctly fall below that line. Ideally, I'd want a line drawn at 16,16 to have the top margin be aligned with the height of the letter (whichever it ends up being) that rises the most above the baseline — 'y' is a very tall letter, but since a lot of it is below the baseline its height above the baseline is only a pixel more than most other lower-case letters. It seems like the calculation would be easy; something like the total height of the sprite minus the baseline pixel, but... probably I tried that already. Anyway, time to try and figure this one out again.
Oh...
That was easy! All the values were even calculated already; it was just required to offset the font by the maximum letter-offset-from-baseline. Why did I think that was complicated? Oh, past me.
(Also it turned out that the letters-drawing-backwards-and-upsidedown problem was the fault of an bug in a pixel-to-screen-coordinate function; with that fixed the old calculations are exactly correct. Oh, slightly less past me.)
NOW IT'S TIME TO DRAW A NEW FONT; holy shit, i cannot put up with that blackletter monstrosity any longer.
And I've replaced the vertically-uneven blackletter with some kind of horizontally-uneven oblique font. (Although I admit the 'y' and 'z' were close to copied from the blackletter; I really like that style of z.) I just now realized this doesn't have a space character; no wonder it looks so cramped. The curves from all those "h b" parts makes it look like it does, but no, it's just unevenly spaced. Okay, look, there's no way I'm going to handle kerning in my homemade pixel font library. Deal with it.
(This current iteration of the font code only handles single lines, left-aligned, for simplicity's sake. Once I need word-wrapping and aligning and junk I'll put it back it. The old code was kind of hilarious; it supported left, right, centered, and justified alignment. It did this by having a switch based on the alignment type. The 'left' case started at the left margin and drew the string to the right; the 'right' case started at the right margin and drew the string from the end to the left; the 'center' case calculated the line's total width and changed the left margin so that it would be centered given the original margins, and then fell through to the 'left' case; the 'justify' case calculated the line's total width and changed the word spacing so it would be justified, and then fell through to the 'right' case. It did that because, obviously, it couldn't fall through to the 'left' case because 'center' was already falling through there. Maybe that should have made me realize a switch might not be the best code structure for that code flow. Maybe! But evidently not.)
But now that there's some trivial text rendering I should probably work on putting that to use. Like debug output for tile coordinates or something. Or, you know, menus. Menus menus menus. Some time soon I should write up priority queues and revise the system code to use them. Honestly the entire entity/component system needs to be overhauled, but this time... I'm going to do it piece by piece. Add better ways of doing things, make new things, slowly revise the things that depend on the old terrible ways, and only remove them when nothing uses them.
Or maybe I should fix the outstandingly unfinished/broken map code! Since it still becomes unplayable if the player crosses any platter edges. I should write more tests to cover the parts that aren't covered and coincidentally happen to still be broken.
June 16th, 2011
I wrote some tests to test the most trivial distance calculations and they all failed looking something like this: Got distance vector of 0.00,0.00,416.00; expected 0.00,0.00,52.00
, so that's kind of good news. If the distance calculations are all messed up, that does explain some of the issues I was having with movement. Not sure if I'm going to try and tackle all of that right this moment, though... I still want to get priority queues working and mess with the system/loading code more, so that it's actually a viable option to use for background loading and explicit loading screens. While I'm at it I can remove some of the disused and broken component background loading, (the code wherein the priority queue technically neither had priorities nor was a queue) under the assumption that it can use the newer code.
I'd just like to state for the record that I'm really bad at searching and sorting algorithms. But I've got a faux-y insertion sort and mergesort working for the dynamic array code, with enough tests (read: three) that I'm fairly sure it's not completely broken.
ha ha, spoke too soon. Actually it's completely broken. My tests covered essentially half of the functions and as it turns out the half I didn't test were broken! There was some issue with the practical applications of void **
vs. void *
pointers (leading first to sorting memory addresses instead of the values stored in them and then trying to dereference the values stored in them instead of the memory addresses) and there was an off-by-one error that meant in unusual cases the array didn't end up sorted, but it's all sorted out now. Now I have five tests! Certainly that should be enough for anything.
Hopefully tomorrow I'll elaborate on either the map code or the system/loading code (or both), but I think that's all for today. Not every day can be a flood of screenshots, okay.
June 17th, 2011
Loading screens are incredibly interesting. Also this is a very boring loading screen. It pops up for a half-second at the start of the game and finally resolves the "the camera incorrectly renders for a handful of frames at program start" bug, since the cause of that was, well, the camera and the world around it not being fully loaded. (I just realized I could have fixed that just as well by forcing the timer to start with enough time to cycle through one tick, since that would initialize that stuff before the rendering code was called at all. Maybe I'll still do that.) There's still a tiny flicker of a black screen between when SDL is initialized and when the render code runs for the first time, but come on who cares.
The intent is to have this screen be a more full-fledged "things are being explicitly loaded or generated; here's some info about that; here's how far along we are" page, to be used for things like pre-game worldgen. But for now there isn't even a loading bar, since really there's nothing that needs to be loaded or generated yet. I'll work on that more (let me tell you about my very involved ideas for loading screens) once there's anything that needs to be actively loaded. I figure most of the time while actually playing the loading will take place entirely in the background, but there are a bunch of situations (generating a new world, saving data to files on quit, loading data from files on start, etc) where control should probably be interrupted.
map.c:1436: Can't get offset 638 from subhex; maximum offset is 169
map.c:1436: Can't get offset 476 from subhex; maximum offset is 169
map.c:1093: Using relativehex 0x97a2b28; couldn't get span-1 target (lowest target: 2)
map.c:1380: subhexSpanLevel: Passed NULL subhex!
map.c:1380: subhexSpanLevel: Passed NULL subhex!
map.c:1380: subhexSpanLevel: Passed NULL subhex!
component_position.c:157: Entity #1 has moved from span level 1 to 0 and GAINED resolution in the process.
map.c:938: Can't calculate subhex offset: passed NULL subhex! (got 0x97309b8 and (nil))
component_position.c:48: CAN'T HANDLE THIS; NEED TO GENERATE A RELATIVEHEX FROM SCRATCH BUT CAN'T AAA (one that connects 0x97309b8 to (nil)
map.c:1380: subhexSpanLevel: Passed NULL subhex!
map.c:1380: subhexSpanLevel: Passed NULL subhex!
map.c:1380: subhexSpanLevel: Passed NULL subhex!
Fatal signal: Segmentation Fault (SDL Parachute Deployed)
Movement is still broken. I hadn't even noticed those "Can't get offset [] from subhex" lines; that just further affirms my "movement is broken because vector distances are messed up so platter traversal instantly overruns" theory, since those warnings only show up when there's a platter coordinate overrun.
I guess it's time to work on movement again? And background map loading? I can't really think of anything important I can do before I get that working again. Well, menus. But those aren't that important right now. I mean, I could spend some time getting config files working and then make more useful UI and make them editable in-game and all that. But. I think I should do map stuff first.
(I should really stop using this to talk about what I'm going to do and stick to talking about what I have done)
June 18th, 2011
Did just enough poking around in my 300-line traversal function to realize a lot of it is totally broken. You know it's bad when a single assertion for reasonableness makes 80+ tests fail. But the most trivial possible tests succeed now, so I'm kind of making headway. Part of the issue: I had written the test wrong. There were still problems with the code above and beyond the test being wrong, though, so fixing that didn't actually make anything start passing. It did help explain some of the more bizarre results I was getting; in a lot of cases the vector result wasn't just too large, it was in the wrong direction, and that was the fault of the broken test.
The problem I'm definitely noticing is that even with most of the tests passing, well... the tests don't cover that much of the expected behavior. So, okay, the "step one tile over a span 1 platter edge" test works. But everything else is broken in new and exciting ways because certain cross-span values are completely out of whack.
Like so. I'm pretty sure the other platters are generated properly, it's just that they're being rendered off way beyond the max draw distance. This is not an improvement. Now movement and rendering are visibly broken! And I don't even think they're both broken for the same reasons. At least I know specifically what lines of code are responsible for this. Amusingly (and alarmingly) there's four lines of code that do it1: commented out, the trivial distance tests pass, the rest fail, and rendering breaks; left in, the non-trivial tests pass, the trivial ones fail, and rendering works right.
And all the tests pass! This has not in any way fixed the movement problem. Now it just breaks in a new way: upon leaving the initial platter the player is instantly teleported to the centre hex of the new platter (which doesn't seem to be the one they actually stepped on, and in fact frequently [but not always] appears to be the platter in the diametrically opposite direction from where they stepped) and spasmodically jerks and teleports around if they try to leave that hex. Things do not get better if they return to the initial platter, and I haven't yet managed to get teleported into the unloaded abyss and thus crash the game.
There are a few clues here. One is that movement appears to be constrained to the platters ajacent to the first platter, which implies that either the initial platter isn't being cleared from the entity's position struct on move or the movement code is calculating from the 3D origin instead of from the centre of the current platter. Another is that since the jerking movement routine triggers if they move a single hex from their initial position that means the hex position ifcheck that triggers the platter movement code is succeeding, and that means the entity's stored position is out of bounds for whatever platter they're on.
component_position.c:150: position vector out of bounds (at -8, 17); changing entity #1's platter
component_position.c:150: position vector out of bounds (at 8, -17); changing entity #1's platter
Yes indeed that appears to show that the position update doesn't actually update the platter correctly. Where exactly the code goes wrong is entirely another thing. But I think that's enough coding for today; (honestly there was not much coding today) it's late and I'm tired. But hopefully tomorrow I can solve more of THE MYSTERY OF THE PLATTERS.
stupid platters.
1 I have since replaced those four lines with one line and a comment:
/* FIXME: this won't work in all cases. specifically when crossing a pole this will overrun and then no one will be happy (except it won't, because i is set to spanRange and that's always < mapSpan, but spanRange isn't the right value to use AT ALL, like, categorically, it's just an arbitrary vaue that happens to be large enough to work in most cases; the right value would have something to do with the number of up-traversals required. also i don't really know how well this whole scheme works if we're not trying to calculate a span 0 position, since it seems like a lot of this code will fail if i doesn't match the current span level OH GOD EVERYTHING IS SO COMPLICATED) */
mapScaleCoordinates (-1, goalX[i + 1], goalY[i + 1], &lX, &lY, NULL, NULL);
(The initial four lines were an ifcheck to check if it was at pole level and do something else in that case, except that ifcheck never worked right and was kind of backwards anyway; I don't really know off the top of my head how to deal with that situation at this very moment. But like I said, late and tired.) [↑]
June 19th, 2011
Did more poking around. Here's how it goes: vector distance calculations where any coordinate overruns the platter size aren't calculated correctly. If the platter limit is eight hexes and the coordinate tried is e.g., 9,0, then that's up-traversed to the platter at 1,0 one span level up with a remainder of 0,-8. That's correct so far. The incorrect part is that when the distance vector is calculated both the 9,0 span-0 distance and the 1,0 span-1 distance are applied, so the vector is roughly twice as big as it ought to be. Since this vector is used for updating the entity's position in the position code, it ends up on entirely the wrong platter and then is forced to jump around wildly forever after since any further movement will be onto a new platter, which is calculated incorrectly for the same reason.
There might be further issues with my code (I mean, there certainly are) but this might be the critical one. NOW: TO FIX IT.
Further issues with my code: The distance vector is used incorrectly in the position code. What the vector is (or ought to be) is the distance from the current platter centre to the centre of the newly-calculated platter. This calculation is precise down to the individual hex (or span-0 platter), since hexes themselves are considered platters. But that's not what we want: we want a vector that's the distance between the current platter centre and the centre of the new span-1 platter, because that's what we need to update the entity's position correctly. And that's not calculated anywhere. Or to copy the comment I wrote in the code:
// this /isn't/ the distance to the centre of the new subhex; the vector offset is done with fidelity 0 and so it's accurate down to the individual hex but what we want is, well, the vector distance to the centre of the adjacent subhex. the funny thing is we can calculate /that/ easily from the position vector (i think??? convert to hex and then upscale one step); we only need to do the traversal to get the new subhex struct. but this should be as a map function, not part of the position code, because come on that is just too much map calculation to have to do externally
The thing about this is that it makes the distance vector as calculated by the traversal function pretty much useless. Like, it would come in handy if you had two entities and wanted to get the distance between them (which would come up a lot in the actual game) but in terms of low-level map operations it's completely extraneous information. So at this point it doesn't matter how badly it's calculated; it won't be used for anything. That being said, I should still write some more tests, just to remind myself to fix it at some point.
This screenshot doesn't show anything that haven't already posted a dozen times, it's just that these brightly colored hex platters are kind of hypnotic. They remind me of the bright squeaky toy area in LSD.
As usual, fixing one problem just introduces so many more. Movement 'works' in a very tentative way; movement is calculated correctly across platters and the entity's position is updated, but the camera isn't drawn in the right place — visually it looks like we're retreading over the initial platter over and over again. Additionally, when the entity steps out past the edge of the world they end up back at 0,0. I think this isn't a "bug" in the strict sense so much as an unintended consequence of the way the still-kind-of-broken position update code handles an entity moving out to partly-unloaded areas (which is something that will happen a lot in the game in practice)... it's not bad, but it's not really the way it ought to work. It's definitely better than just crashing, though.
So: time to check out the camera code. Also time to wonder if it would be possible to encapsulate the map code to the point where the only functions visible to the outside are mapMove
, a hypothetical mapCompare
, and the map data access/setting functions. That would be really nice.
As it turns out the problem with the camera code was that I had commented out the "update the render cache" line at some point when the map code was even less well-behaved than it is now.
So here it is: the view from the end of the world. I suppose I should fix that issue with the out-of-bounds movement (you should lose resolution but otherwise continue perfectly fine) and try to condense the header file a little before working on the background loading/unloading code.
Also currently the render cache updates with every platter transition; this is strictly unnecessary even though it works right. As long as it works that way, there's a worse-case update, which is that if you stand directly over a platter boundary (which will be pretty much invisible in the real game so you'll never be able to tell if you are or not) and step back and forth over the edge you'll send a constant stream of render cache updates, which in certain contexts (e.g., the platter has a high-span edge) will cause a lot of really inefficent traversal and recalculating. Better would be to only update when the player gets two or three platters from the origin, since then the worst-case update is caused by walking in a 90 metre circle instead of stepping a centimetre back and forth.
(the 'canonical' hex size btw is half a metre across at the widest point [or to restate that in different terms, each edge is a quarter of a metre long]; this makes each hex something like .16 square metres. this isn't really obvious yet since there's no landscape or objects and the camera is a set distance above the bottom of the world)
June 20th, 2011
Map generation! Is a work in progress. I figured, well, even if I haven't recoded the map background-loading code, I know how to, but right now I can force generate as much space as I want and I still haven't gotten map generation working despite that being the goal of this entire recode. So I'm working on that. So far, though, I'm just constructing the map data layer and adding the functions to interact with it. After that... patterns. Finally. KIND OF WORRIED ABOUT THIS STILL, NOT GONNA LIE. But let's see where it's going.
(This isn't much of an update; deal w/ it)
July 28th, 2011
THIS LOOKS PROMISING
Wait, where was I?
It's, uh, been a while. Let's just say it was a month fraught with feelings of inadequacy and failure, in which I had to confront some of my rather depressingly-avoidant habits. Or, really, more like I had to futher avoid them. But whatever this isn't my tumblr (okay, well, yes, I will be posting this to tumblr eventually, at which point it will be my tumblr, but my point is— nevermind) so instead of talking about my ~feelings~ I'm going to talk about MAP GENERATION.
This is what the above looks like when the camera isn't shoved inside a hex. Note to self: calculate camera height from the hex it's standing on, don't just set it to 90.0 and forget about it. The generation rule here is painfully simple: on every platter, place a seed point randomly and pick a random height value, then linearly scale outwards and step the hexes down until the height is 0. The disadvantages are already obvious: there's a problem with the pyramid/hills cutting into each other and replacing the values. I expect this problem ("map rules from one section overwriting the terrain generated by another map rule elsewhere" to put it in the general form) is going to be the new "hex joins calculating/rendering incorrectly".
For those of you that aren't following this log obsessively that means we're going to be seeing a lot of it.
The big thing to show off in this screenshot, though, is: hey, look at how the hills are in no way bound to the platter structure? All my messing around with map systems is finally starting to pay off; it's super easy to pick a platter at any order of magnitude and use it as an origin, even when looking for tiles that are outside its boundary. I'll still have to worry a little bit about platter storage — if the platters aren't loaded period the various functions return NULL pointers — but for the most part I can completely ignore the massively-complicated map system and build up a new massively-complicated map generation system on top of that! In time, not even I will understand how my code works.
Actually I'm pretty sure I've reached that point already.
Here's a better shot of the "hills overcutting each other" issue. I'll... probably try and fix this particular instance of it, but I have no idea what I'm going to do in the general case. (Also I have no idea what's going on with the joins in the lower half of this shot, aside from "some kind of depth-sorting bug".
Oh yeah, and speaking of hex joins guess which function I never recoded when I rewrote the map system, on account of always dealing with a flat plane of hexagons anyway?
A large number of my commit notes end with "sorry future self" and that's really all you need to know about the state of my repository.
(also hey, my actual email address. well if there's ever a place to post it online that's safe from spambots, in an image full of other text is probably the best you can get)
((I, uh, have since fixed the name/email in my commit messages I really don't know why I set it that way in the first place.))
It's fiiiiine. But hey, this will probably be the very last "joins not working properly" screenshot ever, so savor it while you can.
It's actually pretty amazing what a speed improvement I get from having the joins calculated; I think in all cases four out of six joins are still getting drawn1, but apparently the difference is enough to take response from "laggy" to "snappy". (Those are the technical terms.) Also worth mentioning: that mention of messing around with map systems and relative origin points and soforth mentioned above is already paying off again; the old versions of the join calculation function had all these special cases and forks when dealing with the outer edge of a platter (or, well, they were "grounds" back then) since it had to figure out the direction of the adjacent platter and do a lookup and blah blah blah w/e. It still has to do a lookup but I don't care; all the extra calculations are done automatically and I don't have to know about it. Hooray abstraction!
sprite.c:619: 2 (4 - 2) > -3: FALSE
sprite.c:688: -5 (1 - 6) > -3: FALSE
sprite.c:619: 1 (3 - 2) > -3: FALSE
sprite.c:708: 5 (10 - 5) > -3: FALSE
sprite.c:619: 2 (9 - 7) > -3: FALSE
sprite.c:619: 3 (10 - 7) > -3: FALSE
sprite.c:619: 2 (13 - 11) > -3: FALSE
This is one of the times when you have to stop to check that first order predicate logic is still in effect, and also that you haven't suddenly gotten brain damage and forgotten what < and > mean. (I was actually in remedial math as a kid because I couldn't grasp the concept of operators and I still have to think back to "think of them as hungry alligators, which open their mouths in the direction of the larger number".) I think I've already covered this particular unintuitive corner of C before: if you happen to operate on an unsigned [type] and a signed [type], the signed value undergoes type promotion to an unsigned value. Once you understand that, the above results look perfectly reasonable and printf's defaulting to signed representation looks like a foul lie. One (signed int)
cast later:
sprite.c:619: 2 (4 - 2) > -3: TRUE
sprite.c:688: -5 (1 - 6) > -3: FALSE
sprite.c:619: 1 (3 - 2) > -3: TRUE
sprite.c:708: 5 (10 - 5) > -3: TRUE
sprite.c:619: 2 (9 - 7) > -3: TRUE
sprite.c:619: 3 (10 - 7) > -3: TRUE
sprite.c:619: 2 (13 - 11) > -3: TRUE
Thank you.
Oh this is actually quite nice. I mean, it's not exactly what you'd call "rolling hills"; it's too uniform and flat and the tiers really need to go, but it's definitely better than just a flat plain forever. Sadly, this is as placeholder as the hex coloring: I still haven't totally worked out how to store map generation cues, (which are called "arches" in the code because I like the way it sounds; you can assume it's short for "architecture" if you'd like) and I haven't actually coded the mechanism through which the actual world generation junk is going to make its data accessible to the map system (although it's just gonna be a slight header alteration) so right now there are some substantial unfixable problems with even the most trivial generation. But: tomorrow, since it's getting late.
1 On further reflection that's wrong; in the average case four joins are undrawn (and in almost all of the non-average cases even fewer joins are drawn): since most hexes will have two neighbors lower (and thus draw those joins), two neighbors at an equal height (which won't be drawn), and two neighbors above (which also won't be drawn — previously each hex would draw all six of its joins so every edge was covered twice) so the total reduction is that around 40% of the polygons that were in the scene are just not drawn at all. [↑]
July 29th, 2011
There's some kind of bug with fetching certain tiles across platter boundaries when the tile coordinates have a certain k-value, which... okay, that doesn't make sense, but it's happening, so. (It might have something to do with the coordinates not being converted properly; there's been a recurring console warning that involves attempting to get the 224-th offset of a list with only 169 entries, that kind of thing. wait hold on a sec. oh no that wasn't the cause of the bug nvm i had an idea. Anyway I'm certain that's what the end result of the bug is: attempts to get values past the end of a list, which returns NULL. Why that's happening is still up in the air.)
This current pyramidal hill is actually stored in an arch and is imprinted more-or-less properly, so that's an improvement. I'm gonna try and make it look less regular, then try placing a few around, maybe add a generation pattern that's not just a lumpy hill — maybe something that uses the map data layer, even — and then I'm going to take the big dive into actual generation and try to put together an arch graph with sub-arches as vertices. aaah.
/* okay i don't really know the right way to code this. the wrong way would be
* to convert the x,y coord to a vector and subtract 52 from its magnitude,
* then convert it back to a x,y coord. but i'm sure there's a better way of
* doing this.
* - xph 2011 07 29
* (of course this doesn't actually work right because there's no guarantee
* that blindly subtracting the average diameter of a hex will line up with
* the actual hex layout on any specific vector line)
* - still xph 2011 07 29
*/
Guess how I ended up doing it.
Ah.
wow I'm bad at this.
this is equally broken and bizarre but at least it looks interesting.
God this is going to be the phantomnation sloping problem all over again, only even more complicated. I think I'm going to: 1) stop writing updates as I code, and 2) stop coding for a while because this is annoying.
Okay part of it was that this code was wrong. See if you can spot the bug:
#define GETCORNER(p, n) (__n = (n), __v = (__n % 2 ? p[__n/2] & 0x0f : (p[__n/2] & 0xf0) >> 4), (__v & 8) ? 0 - ((__v & 7)) : __v)
#define SETCORNER(p, n, v) (__n = (n), __v = (v), __v = (__v < 0 ? ((~__v & 15) + 1) | 8 : __v & 7), p[__n/2] = (__n % 2 ? (p[__n/2] & 0x0f) | (__v << 4) : (p[__n/2] & 0xf0) | __v))
This is such a glaring error that should be completely obvious to anyone with even half a brain; I won't even bother to explain, but the code should quite obviously be as follows, instead:
#define GETCORNER(p, n) (__n = (n), __v = (__n % 2 ? p[__n/2] & 0x0f : (p[__n/2] & 0xf0) >> 4), (__v & 8) ? 0 - ((__v & 7)) : __v)
#define SETCORNER(p, n, v) (__n = (n), __v = (v), __v = (__v < 0 ? ((~__v & 15) + 1) | 8 : __v & 7), p[__n/2] = (__n % 2 ? (p[__n/2] & 0xf0) | __v : (p[__n/2] & 0x0f) | (__v << 4)))
God I can't believe I ever wrote that. Yes that monstrosity is totally worth the whole three bytes of space per hex it saves. And I gripe at other people about premature optimization.
Uh anyway the issue was that when getting corner values they were operated on in a big-endian way (i.e., the bytes were read as 00001111 22223333 44445555) but when they were set they were operated on in a little-endian way (i.e., the bytes were set as 11110000 33332222 55554444) and that lead to uneven hex edges. Or, at least, there was an endian-ness conflict, I honestly don't remember which is which and no one cares unless they design system architectures in their free time.
The generated 'hills' still look bad but it's a slightly more smooth-sided bad.
With a little more tweaking they are totally smooth except on the side where the distance metric is completely messed up. I think I'm gonna try to fix this and the coordinate bug before I do any more worldgen stuff, since when I get around to actually messing with real generation rules stuff like this would make it impossible for me to figure out if the rules are doing what I want them to.
July 30th, 2011
Didn't do much today— fixed the more minor of the two hex bugs, the one involving improper distance metrics leading to upthrust patterns instead of smooth slopes.
I also tried to set more arches to see how much work it took and how valid the resultant landscape was. It took a few tweaks of the single generative rule before everything stopped looking incredibly broken. Right now it's a major hassle to work out if a hex has been affected by any arch imprinting, and if so how that should change the current imprinting. I... kind of suspect this is not going to be a problem that's easily solved. Sadly.
Also there are still some issues with setting joins. I don't think this is a calculation or rendering problem, I think it's the result of just not re-calculating joins after an imprinting has changed what they should be. This is another thing I'm finding it hard to work out when to do, especially since I still haven't gotten platter background loading working, and since that's when imprinting and join calculating should occur everything I have now is jury-rigged and incorrect. Maybe I should work on that.
Also there are some join rendering issues. Or rather, there's one join rendering issue: 'Cross' joins don't draw right, as defined by two adjacent hexes which each have one edge higher than the other. (e.g., two hexes with an edge 4 – 3 running in opposite directions, so for both hexes its 4-high corner is higher than the adjacent 3-high corner and its 3-high corner is lower than the adjacent 4-high corner. You can see an instance of this in the above screenshot, there's a red hex in the foreground with a purple hex behind it but the join visible there is red instead of purple since as you can see they slope against each other in a way that results in both their joins being drawn. ...this makes a lot more sense why you can actually rotate the camera to see how everything fits together, okay.) This leads to a situation where both joins are drawn and they're both drawn incorrectly, leading to ugly z-fighting and spindled polygons. phantomnation had a special case for these (and also incidentally for joins which are triangles instead of quads); I should maybe look at its code to see if I can just copy across the fix.
But, I mean, aside from all those problems this whole arch thing is going pretty smoothly.
July 31st, 2011
Awww yeeee.
Actually this kind of deserves an explanation. Basically I fixed the cross join bug mentioned last post; this incidentally covers all the cases when the join is a triangle instead of a full quad too. (Thus saving a whole one polygon per join.) There are two special cases; in this screenshot one of them is colored white and the other is colored black. You... can't really see because of the false coloring but they're drawn properly and not spindled.
And here's a good shot of that "joins not being re-calculated after imprinting changes the landscape" bug. There are always more bugs to fix. Sigh.
August 1st, 2011
I decided to bite the bullet and get map loading working. As I write this it's still a work in progress (really need to start writing these after I finish coding) but, eh. The extremely naive implementation is very simple: every time the render cache updates, force load all the platters within [x] steps of the new origin before updating the actual cache. This will load everything fine, but it will firstly lock up until all the platters have been loaded and generated — not a problem yet, but when the generation arches are more everpresent instead of in a cluster by the pole, this will cause unacceptable hitches in the game flow. Secondly, since there's no code to unload platters as they become distant, eventually any computer running this will either run out of memory and crash or end with the entire planet loaded into memory. And since the planet is currently 43 times the size of the earth, I'm betting more on the former.
Right now I'm writing a proper distance metric between two arbitrary subhexes. Augh. This is not as easy as it sounds due to the whole recursive scaling; the vast majority of cases would result in a distance of MAX_INT, but all the likely cases are for subhexes that are extremely close together.
/* FIXME: this won't work in all cases. specifically when crossing a pole this will overrun and then no one will be happy (except it won't, because i is set to spanRange and that's always < MapSpan, but spanRange isn't the right value to use AT ALL, like, categorically, it's just an arbitrary vaue that happens to be large enough to work in most cases; the right value would have something to do with the number of up-traversals required. also i don't really know how well this whole scheme works if we're not trying to calculate a span 0 position, since it seems like a lot of this code will fail if i doesn't match the current span level OH GOD EVERYTHING IS SO COMPLICATED)
* - xph 2011 06 18
*/
This is a comment in the current distance metric function (that I've actually already mentioned in a prior update; it's kind of a memorable comment okay), which isn't so much a "function" as a loop inside the traversal code. I'm going to break it out and fix it, and in doing so hopefully reduce the complexity of the traversal code, because seriously it's monstrous right now. Right before that loop are the following comments:
/* we're using a seperate distX/Y instead of rel->x/y because if we change the rel values it becomes impossible to generate the target subhex from a low-precision RELATIVEHEX, since the rel-> values map directly to subhex local coordinates
* also we're doing this calculation seperately from the actual goal traversal step, above, because while we don't care if the goal is at low-resolution, it's pretty vitally important that we always have the coordinate and distance information set for every RELATIVEHEX
* - xph 2011 06 04
*/
/* mentally replace every instance of 'distX/Y' in the above with 'goalX/Y'; also this code never worked right so what i wrote before is mostly meaningless
* (the intent is for all the code below to be spun off into its own function so it can be reused for SubhexWithSubhex, below. but before that will work I need to isolate the variables needed-- ideally it should just require the start/goalX/Y pointers, but i still don't know what's the deal with the i-value and whatever is needed to get that if/else involving mapScaleCoordinates to work right)
* - xph 2011 06 18
*/
Actually I kind of wonder how much of this function's length is just rambling comments about how complicated and broken it is. Also, maybe I should start spell-checking my comments.
And we have our first weird map rendering bug! That tiny five-pixel-wide smear on the upper left is a bunch of platters being drawn way off in the distance, at or around the openGL max draw distance — way past the max platter draw distance. This happened as I walked away from the pole, and as it happened all the drawn platters behind me vanished1. I don't have position readouts (I should really work on a debug overlay now that I've got text working) but my suspicion was that I crossed a high-span (in this case span-2) platter boundary and the math involving placing them relative to the rendering origin is messed up.
/* i feel like this function is ignoring the fact that subhexes will
* frequently be high-span (not just span-1 at all times) and i /also/ feel
* like that will doubtless lead to problems when you accidentally unload a
* pole or something like that
* - xph 2011 08 01
*/
I started coding late today so I don't think I'm going to finish this tonight, but I wrote a (still completely untested) distance metric that probably isn't totally awful, and an unloading function that is mostly complete even though the "loaded platters" list it's iterating over isn't populated ever yet.
This whole recursive platter thing is really hard to code for. But it thankfully allows for a lot of abstraction away; in practice once I've written all the internal code all I'll need is a platter, a coordinate, and a scale level (which is usually 1, 0, or the span of the platter) to wander freely around the entire world without ever worrying about the math behind it all. I'm... really looking forward to that day. Until then: writing more internals code.
1 I was looking back towards the origin as I walked back and the sudden disappearance of the ground right behind me actually caused me to yell and jerk back from the computer. If I am going to have trouble debugging all my map (and eventually physics) code due to my fear of the unending abyss there are going to be problems. Sigh. Maybe I'll end up desensitizing myself. One can only hope. [↑]
August 2nd, 2011
Didn't do much aside from fire up the game and walk around a little. Well, I also added a really ugly debug overlay. With it I was able to confirm that yes, the rendering bug is only triggered when a span-2 platter edge is crossed, and thus presumably is exactly what I thought it was; something's wrong with the vector calculations when the rendering origin isn't extremely close to a pole.
I also found a crashing bug when you walk in certain directions, but that's no surprise — since the code that does platter lookup is the same code as does tile lookup, and I already know there's a problem with tile lookup in certain directions past a certain distance, well, it wasn't really much of a surprise.
August 6th, 2011
I did nothing for a few days. And then I fixed the more drastic bug I discovered; the one that caused platter lookup to fail in certain directions. There were two distinct problems caused by this one bug: hex imprinting would fail in certain places, leading to incomplete patterns; and moving across platters in certain directions would crash the game since it'd try to store the player's position on a NULL platter.
The ultimate cause was the way I was calculating distance and remainders in the coordinate scaling function. One of the extremely frequent calculations I need to do is, given an x,y coordinate (in hex notation not standard cartesean; this complicates things) is it within [x] steps from the 0,0 hex (and thus on the current platter; this is the easy part to calculate since it's just magnitude) and if not, in which direction does the coordinate lie. Like, I have conceptually divided the coordinate grid up into hexagonal clusters of hexes, and I need to find out which of those clusters an arbitrary coordinate lies on.
(Nothing makes me realize just how convoluted this system is than trying to explain why I'm doing something in this specific way to other people; suffice to say there are reasons and it totally makes sense once you understand the entire system)
There's a systemic and constant-time way to do this, I know, but I haven't worked out how to actually put it into code yet. The actual code basically just iterates over and over testing the directions and translating the coordinate when a step brings it closer. But I'd written the loop wrong, and in one specific case when it needed to step in the direction mapped to 5 and then in the direction mapped to 0, it didn't do the latter step because it had broken from the loop. This resulted in a coordinate remainder that was actually out of bounds from what was expected, which lead ultimately to attempting to get a hex past the end of the hex store and consequently returned a NULL pointer that was handled in more or less safe ways.
The actual code, in the event that makes all this more explicable (it won't):
(in both cases centres
is an array of... okay all you have to know about it is that the way it's used here results in the x,y coordinate of the centre of an adjacent hex platter, and XY
is a constant that returns all the straight-line directional vectors in order: 1,0; 0,1; -1,1; -1,0; 0,-1; 1,-1.
Hexagonal geometry is complicated, okay.
At least it is the way I do it.)
Old code that failed in a certain specific case:
while (i < 6)
{
while (hexMagnitude (x - centres[i * 2], y - centres[i * 2 + 1]) < hexMagnitude (x, y))
{
x -= centres[i * 2];
y -= centres[i * 2 + 1];
scaledX += XY[i][X];
scaledY += XY[i][Y];
}
i++;
}
And the 'fixed' code, which is worse wrt time-complexity but does at least work properly in all cases:
while (failedDirections < 6)
{
failedDirections = 0;
i = 0;
while (i < 6)
{
if (hexMagnitude (x - centres[i * 2], y - centres[i * 2 + 1]) < hexMagnitude (x, y))
{
x -= centres[i * 2];
y -= centres[i * 2 + 1];
scaledX += XY[i][X];
scaledY += XY[i][Y];
}
else
failedDirections++;
i++;
}
}
There's actually a comment about this, which is probably accurate but really I have no idea:
/* there's a slight simplification that i'm not using here since idk how
* to put it in a way that actually simplifies the code: if index i can be
* subtracted, then either index i + 1 % 6 or index i - 1 % 6 can also be
* subtracted, and no other values can. since we iterate from 0..5, if 0
* hits the other value may be 1 OR 5; in all other cases if i is a hit
* then i + 1 % 6 is the other hit
* - xph 2011-05-29
*/
Ideally at some point I'd like to cut out the double loops and just do the coordinate math this way, but like I said, I don't really know how to write that math simply.
Given that this bug only showed up in certain contexts depending on the position and size of the hills it's hard to point to any one screenshot and say "yes, this is something that wouldn't have worked before", but whatever, it's definitely fixed.
After I fixed this I decided to walk all the way to a span-3 platter boundary and, firstly, I discovered just how long that takes. I should have timed it, really. Secondly, I discovered that everything crashed in a completely new way right as I got to the point where the adjacent span-3 platter would become visible. There are always more bugs to fix. Although I suspect this one is going to be trivial; it came from a part of the traversal code that is completely jury-rigged and awful, so once I replace that with code that actually makes sense hopefully there will be no problem.
August 9th, 2011
Augh. I have decided to un-bite the bullet and ignore map loading again for a while. I just spent two hours trying to comprehend why my traversal code works and came up empty-handed and frustrated. On the upside I did fix the two lingering bugs: high-span platter movement is seamless (instead of leading to nothing past the edges of the platter being drawn) and the entire program no longer crashes past the span-2 platter boundary around 0,0. The latter was just a spurious assert; there are some values in the traversal code that don't do wht I thought they did when I wrote the code, so what I assumed was an assertion for reasonableness turned out to just be an assertion that... the ratio of the platter draw distance to the world coordinates relative to the pole wasn't above a certain value. Which is a completely pointless thing to have an assertion for in the first place.
Here's an example of the rendering bug. The top screenshot was taken on the span-2 0,0 platter, and the bottom one was taken a tiny step back, on the span-2 1,-1 platter. Past the span-2 0,0 platter, every span-2 platter was floating in a sea of nothingness, although you could walk across them fine.
While trying to fix this bug I created several new problems for a while.
help.
The problem turned out to be with the way distance vectors were calculated between platters, and the way I'm calculating them is so outstandingly ideosyncratic I can't really figure out how to simplify the code to the point where it's something I can fully comprehend. Thankfully, I still managed to fix the problem.
So here's me walking all the way to a span-3 platter boundary, woo. I should specify that it takes at the bare minimum eight times as long to cross a span-(n+1) platter compared to a span-n one. Also, it takes like five minutes to get to this point, which is halfway between the 0,0 and -1,0 span-3 platters. I think this means it would take ten and a half hours to walk in a straight line from the centre of a span-5 platter to the centre of the adjacent span-5 platter. And I currently have the span hierarchy set to seven levels. It can go up to 255. I'm not going to try that.
But now that I've got really inefficient and worrisome map loading working (worrisome since it never unloads everything so it's just a matter of time before it'll crash from E_NOMEM
) and I've fixed all the currently-existing bugs with platter movement (that I know of) I'm gonna go back to working on world generation, since that is the entire point of this exercise.
I'm very strongly ambivalent about world generation, if you haven't been able to tell from all the updates I've made about it. On the one hand, the aspect of design and algorithmic generation of landscapes and stuff is really interesting to me, because it's all about fractals and identifying structure and generating coherent objects (out of hexagonal prisms even, which aren't really the easiest volume to work with) and all that's very interesting and I want to see what I can do with it; on the other hand though it's all those things and that's really intimidating since it's complicated and not exactly something a lot of people have done so I'm basically forging my own path and I'm not exactly what you'd call good at programming.
Anyway I'll worry about that tomorrow when I actually start bashing my head against patterns again.
August 12th, 2011
Okay, here's the idea: the map data layer will store a height value, and between platters that value will be linearly interpolated. This is slightly more complicated than it seems, because 1) the centres of the platters aren't along any coordinate lines and 3) in order to do the interpolation I'm going to have to calculate barycentric coordinates, which I've never done before.
The first attempt goes wildly wrong.
There was an issue that resulted in the centre height being less than zero; since the height values are unsigned ints this was a problem and caused the by-now standard underflowing height gigantic pillars issue.
I recently upgraded my RAM and I guess that means attempting to move the camera around when this happens no longer entirely crashes X? So here's a shot of what happens when you look up.
More directly worrisome than the underflow issue, though, was the fact that the entire terrain is varied like this. The map data layer only had a few height values set in the platters right around the origin, but all the platters had the lumpy, ugly terrain, even though most of them should still be perfectly flat.
Also worth mentioning was around this point there was this incredibly strange bug that broke movement across platters; sometimes it'd regen the entire world around the platter (so everything would change color) and sometimes it'd wrap the player around the platter if they tried to leave in a certain direction... it was worrisome, and ultimately caused by me not realizing that when all the subhexes in the render cache were sent over to be imprinted, in certain cases (i.e., not all platters in the view range were loaded) the platters wouldn't be span-1 platters with hexes stored; they'd be span-2 platters with span-1 platters stored. Why that issue resulted in broken movement of that specific variety I don't know, (well, I mean, aside from realizing that I was rewriting semirandom chunks of platter data) but whatever, fixed now.
Restricting the map data points to the origin and a point adjacent to the origin is extremely helpful in diagnosing the other problem, though: all other platters get the data value of the non-origin platter as adjacent. It is at this point I realize my coordinates are wrong when I look up the adjacent data points: instead of getting the six points adjacent to the current platter they get the six points adjacent to the centre of their parent platter, since I forgot to add the current platter's coordinates to the calculation.
Fixing that brings me to where I am now: extremely lumpy, uneven hills mk. II. The major issue is that I'm not iterating over the correct hexes; as far as I can tell I'm doing the barycentric coordinate math right, it's just I'm not constructing the proper triangles or only applying the height transforms to hexes within the current triangle. Which was the cause of the underflow issue, incidentally: if a coordinate is outside the triangle one or more of its barycentric coordinates will be negative, and with the height math as it was that could lead to underflow in certain situations.
So now to get that fixed and attempt to make actual smooth-sloped hills. After that I'll try adding in another data point, like the smoothness of the slope, and see if I can get that working right.
Oh yeah, and I also did two minor things: added support for right-aligned text (although as you can see right now it doesn't work right when the text includes newlines; I'll have to fix that at some point), and increased the fidelity of the debug coordinates, so now you can uniquely identify where you're standing in the world down to the very hex by checking the coordinate stack.
(I should really draw the capital letters for my font at some point, too)
Okay, so my first thought was to actually do the barycentric coordinate validation and not imprint any hexes with height if their coordinate is outside the triangle formed by the three platter centres. This lead to what you see here: only half of each platter is imprinted, but all those tiles have the correct height. Now, there's a very simple way I could fix this issue: I was trying to save cycles and only calculate the coordinates for the hexes that were inside the various triangles, but instead of doing that I could just calculate the coordinates for each hex on each platter relative to all six triangle areas, and only imprint the sole accurate height resulting from the proper coordinates.
That's not actually what I did, though. Well, I mean, that was what I did at first, but then I tightened the scope of the hexes being iterated over until the only hexes being visited were the ones within the triangle. It was ultimately pretty simple to do; since hex coordinates and platter coordinates have certain isomorphisms it's fairly easy to discern the pattern of hex coordinates that fell within the area of whichever triangle.
So that made me feel kind of smart, and now finally I have map data → hex imprinting working the correct way, instead of as a dumb kludge using patterns. The landscape is back to being stepped, though. Sigh. I'm conflicted as to whether I should start adding more nuanced imprinting — map data to control the smoothness of the terrain, or the evenness of the terrain, or the slope, or whatever else — or try to fix the camera so it no longer spawns inside the world geometry—
— like so, which is what I see every time I start up the game these days. But that means collision detection and response, even of the most trivial kind, and that's scary.
Also I still haven't put together any proper arches. I should do that.
There we go. Very fancy. Okay this isn't a 'proper' arch either; there's no internal structure aside from the size of the thing. But it's doing something arches ought to do (generate objects with small-scale detail) even if the way it does it is bad. I should make an actual pattern one of these days. Multiple actual patterns. I'm actually pretty close to doing that, I think, since I've got the map layer and imprinting working. Huh.
This is actually bringing me alarmingly close to the final major hex data alteration: storing overlapping hexes! Again! I'm gonna have to think a little about how to do it; I'd really rather not write a shoddy implementation that I have to totally recode in a month when I first try to actually start using it. But in the mean time, uh, more map data stuff, camera collision, and real patterns. Maybe actual background loading and unloading. aaah.
August 19th, 2011
I wasn't really feeling ~map generation~, so I decided to try and clean up the entity/component code in preparation of making some new components dealing with UI or whatever. This was kind of a success; the entity code is monstrous but I managed to prune a few of the functions that were literally never used, and rewrite things so that some of the functions that were almost never used became never used, and then I removed those too. I also reorganized the code; it's now divided into sections more-or-less approximating the aspects of the entity system. ("Entities", "Messaging", "Components", "the Entity system", and "Loading (old and terrible; don't use this and try to remove it)", if you're wondering.)
At some point I'd like to completely remove the entity code's dependancy on the "object" code, since the object code is even more of a complex mess and it's only still around because it's enmeshed in a few complex ways with the entity code. Doing that would involve rewriting all the old components, though — components used to define their message responses in a huge monolithic switch statement, and the newer iteration of the code defines message responses as stored function pointers, and so I've been writing new component message responses using the newer system there's still a lot of older code (like, say, everything in the position and camera components) that's done in the monolithic format.
Reorganised and condensed the component functions.
Function names are shorter (component_instantiateOnEntity is now just component_instantiate) and more precise (entitySubsystem_registerMessageResponse is now component_registerResponse), related code is now closer together in the code file instead of scattered around in roughly the order it was written, and one internal function is no longer publicly visible.
(There are like four commits along these lines)
I'd like to get things like loading options from a config file — and in-game editing of those options — working, since that'd require me to get the UI and menu code in a state where it's fairly easy to specify a bunch of complex UI and menu elements. But, uh, not right now, since before I do that I'd really like to get the map generation structure completely finished, to the point where I'm generating arches from patterns and placing them using graphs. Once that's happening I figure I can goof off finishing up some other aspects of the code while I fiddle around with the generation rules, but there are still some substantial pieces of code left to write and some important bugs to fix.
Like: I set the world's span level down to two because it occured to me that the code dealing with pole-level traversal was probably totally broken, and yes as it turns out when you try to cross a pole edge everything crashes. This will be the next bug I fix. Although, and I can't really be sure until I can stand on them and check the position reading, (the debug overlay: super useful, even if it's still intensely ugly) but it looks like platters load correctly across the poles.
...All in all, though, that was one of the dumbest bugs I've ever had to fix. Or, maybe I'm saying that just because this is so recent in my memory. Since this was not one of the nice crashes where the crash was accompanied by an "Assertion failed: (whatever)" line on the console, I had to actually figure out where the problem was. All I really had to go on was that movement over the poles crashed things but platter generation over the poles didn't. My first thought was the movement code, since, y'know, that would kind of make sense.
The first thing I discovered was that the position_move
code worked completely differently from how I remember it working— at one point it generated some values in advance and cached them for a different position update function that was usually called afterwards (called position_messageGroundChange
, which is called whenever something moves over a platter edge), and while position_messageGroundChange
still checked that cache for values, they were never actually set in the first place. So that was very confusing and I expurgated the outdated code once I realized what was going on.
Since the cache was never being used, though, position_messageGroundChange
was calculating the map movement junk itself, and since it had limited information about the original move it had to do it in a less efficient way (hence the point of the cache in the first place; position_move
knows about the movement as a starting position and a vector addition, but position_messageGroundChange
only knows about the platters where the movement started and ended, and some values have to be recalculated and... AND YES OKAY IT'S INEFFICENT TANGENT OVER) and specifically it was using the traversal function that I never actually really finished properly. So that was where it was crashing, inside that function that I already knew was half-finished and kind of broken.
Looking inside the function for what was wrong had a distinct feeling of stepping inside a crumbling abandoned building, where it's only a matter of time before it all comes crashing down and you'd rather be out of there before that happens. This is a function that contains the line ERROR ("%s: traversal hit pole level; it's trivial to make this work right but i haven't yet; sorry (got poles %c and %c)", subhexPoleName (sTrav), subhexPoleName (gTrav))
and DEBUG ("returning probably-broken relativehex %p", rel)
It is a classy piece of work, is what I am saying.
So there's a pretty glaring issue here, namely that I had left myself that error message basically saying "this doesn't work across poles", so it wasn't really surprising that it wasn't working. Except that message wasn't actually getting displayed before the crash, so the issue had to be somewhere before that, or it must not have been being triggered properly, or something like that.
I added a bunch of debugging printf statements, and they pretty much told me what I could have figured out on my own: the function got to around the point when the traversal would hit the pole level and then around when functions would start returning NULL pointers and arrays would start overrunning if it continued it crashed. It shouldn't have crashed, since there was an ifcheck that broke the loop, but that same block also printed the error message so clearly that wasn't working right.
So as it turns out it was the error message itself that was causing the crash. You may have already noticed something wrong with it if you know your printf
statements: there are three variable substitutions but only two extra arguments, and while usually that just gives you gibberish due to type mismatches, in this case the missing argument is the first one and that lead to an attempt to cast a char
to a char *
and then dereference it, which is literally guaranteed to cause a segfault.
So that made me feel dumb. On the other hand, I did find and fix an ultimately unrelated bug where subhexPoleName
wouldn't work right if it was passed one of the pole subhexes, so there's that, I guess.
Once that was fixed the function stopped crashing and pole traversal magically started working. I don't know why and I don't really get why an obviously incorrectly-calculated hex position still works, but I'm not really interested in looking at it too hard right now.
This isn't a rendering bug, this is me circumnavigating the world. (The max render distance is substantially larger than the force load radius around the player, so already-loaded platters become visible in the distance before the ground between the current location and their location has been generated.)
Trans-pole movement still causes a long string of pointless warnings, which has something to do with coordinate scaling doing... something. Not even something wrong; I think the issue might just be I wrote the warning thinking the case it occurs in is something to get alarmed about instead of something that happens whenever you get close to a pole edge, but I'm not really sure. It might also have something to do with the perpetually-incorrect distance metric I'm using, or maybe it's due to the lack of actual loading. Who knows. I'll try to figure that out at some point.
But now that movement is finally actually working correctly in all cases it's probably time to try to hook the map back up and see if I can make it more useful for world generation. Yay. Which means I'm going to have to work on UI stuff anyway, since I'm going to want to print all sorts of map data layer information.
...and then, just to make today's log entry a little more lengthy and tedious I updated the camera component to use this new stored function pointer message architecture. There were some major bugs, the most dramatic of which was that in the deallocation code I had written a line that tried to free a series of function pointers. That didn't go well.
Now the camera processes message updates through independant functions instead of having a gigantic if/else strcmp
block in the camera "object"! This is definitely an improvement. It's occurred to me that this means I can now mess around with the way components respond to messages during runtime, assuming I have the alternate functions written, although I have to admit I don't see a practical application for that. I'd rather not get into messages-that-change-message-responses metaprogramming, however appealing it seems.
August 20th, 2011
lt-beyond: malloc.c:3097: sYSMALLOc: Assertion `(old_top == (((mbinptr) (((char *) &((av)->bins[((1) - 1) * 2])) - __builtin_offsetof (struct malloc_chunk, fd)))) && old_size == 0) || ((unsigned long) (old_size) >= (unsigned long)((((__builtin_offsetof (struct malloc_chunk, fd_nextsize))+((2 * (sizeof(size_t))) - 1)) & ~((2 * (sizeof(size_t))) - 1))) && ((old_top)->size & 0x1) && ((unsigned long)old_end & pagemask) == 0)' failed.
Aborted
Nice to know I'm not the only person guilty of horrific #define
s, though. Now to figure out how I managed to break things so thouroughly that I broke gcc malloc.
(It turns out it's actually pretty simple; there was a memory overrun that messed with the gcc malloc memory tracking space, and it was in such a specific place that it caused that failed assertion instead of the more standard glibc memory corruption error crash.)
I did that while I was further rewriting the camera component messaging code — now the only old-style messages it handles are the built-in class init/class destroy messages, and to change those... the issue is that you can't register message responses until the component is built, and if I wanted an automatic "class init" message to be registered I'd have to pass it in to the entity_registerComponentAndSystem
function (which is yet another function that needs a rename) and that'd require changing the prototype, which would break every component ever. While I'll do eventually, but not until all the existing components have their messages changed over to the new system. This is unfortunate, since it would be really useful to pass an initialization function that itself declares all the rest of the message responses.
What would be really nice is to fully encapsulate components. Only expose their message-response functions, and require any interaction from the rest of the program to happen through message passing. Oh that would be so sweet. For the most part the components have their actual data as hidden internals, but there's a messy spread of functions that mess around with the component's state in more-or-less well-advised ways— for example, it's possible to manually call the camera_update function or switch its mode, even though there's no good reason to do so outside the context of the various input or update messages it handles, so they really ought to be statically declared inside the code file instead of available in the header.
I know, I know, this is like BABBY'S FIRST ENCAPSULATION, but I feel like I've finally managed to wrap my mind around the intent of encapsulation as opposed to it being a bunch of rules that I half-understood and tried to emulate without really comprehending.
Like, I was talking to a friend recently and he was all "oh yes so I have properly encapsulated everything and everything requests data through helper functions instead of directly accessing the object itself" and I was like "okay, unless you change what kind of data is stored in the object, and also what are you trying to use that data you get for, since if it does any calculation whatsoever using it it's gonna break if you change the way the object handles its state internally substantially" and he was like "hm, wait, uh"
It made me feel really smart and kind of mean!
Although I'm sure I'll be hoist by my own petard when I next realize I've messed up and have to rewrite a bunch of code because I changed one component's internals. But hopefully that won't be happening as much.
And as much fun and as useful as all these updates to the entity code have been, I should really get back to the point, which is MAP CODE. so much map code. I'm gonna try to generate textures for it this time, instead of drawing a billion hexagons as polygons for the UI element. This may or may not be super easy, depending on how well I remember the way OpenGL stores its texture data. So. Expect that for the next several updates.
August 24th, 2011
This is going to be a short and boring update. Mostly I've just been picking at replacing the current UI code (which is used in like two places) with something more general that can actually handle, you know, arbitrary text and panels. At some point I'm going to have to update the font code to handle word wrapping, but right now that's not the biggest concern.
The new UI code isn't actually in yet; it's mostly finished but not totally finished, and after that I'll have to pull out the current code and replace it with this fancy new UI component. Which means that UI panels will be entities in the same way objects in the game world are entities; I'm not really sure what any real advantages of that would be aside from a more uniform system interface. I might push and have things like the video and system structure be special-case components in the same vein as the input component (where there's really just a single instantiation of the component data and adding the component to an entity just serves as a "pay attention to this" flag) so I can make the main loop message driven, but, uh, maybe not quite yet.
Anyway that's it. Very exciting, I know.
August 27th, 2011
Yeahhhhh, I haven't done much this past week.
Well, I did totally rewrite all my texture code. That was... imposing. Like, I had been using this glpng library to do all my texture stuff, like I'd just be all "load this image path" and it'd give me an OpenGL texture name and it'd all work right, but now that I'm attempting to get a map working...
Let me step back a few steps.
I'm trying to get a map working. That was the point of the UI recode, obviously, but while I was doing that I thought about my dumb issues even getting poles to draw on the map, and I realized that, quite obviously, actually drawing hexagonal 3d primitives on top of the map was not the way to do it. I should be generating textures from the map data and using them. This was alarming, since I have no clue how to use textures.
Texture loading in OpenGL is kind of, uh. My physical reference book is for literally OpenGL 1.0, and this is bad because a lot of the API that made textures actually usable — like, for example, the ability to bind a texture — came along in 1.1, and I only have some incomplete function references for the various texture functions. But that's not the issue, really, the issue is a lot more basic: when you're generating your own textures, like from scratch, you have to do a lot of things. You have to start caring about bit-packing and mipmaps and the difference between alpha and luminence and the difference between telling OpenGL you're using alpha versus luminence and how large your bytes are and how they're divided between your color channels and if you're using powers of two for your dimensions and, just, generally a lot of very fiddly things. Glpng was handholding me through a lot of this, but as a result my texture data was all strctured with the idea of loading them from images and there was no possible way to actually make a texture from scratch during the game. So I totally rewrote it (I mean, the old texture code was old) and added some geometric drawing code and some stenciling code and threw in the loading-textures-from-files code almost as an afterthought. (Which, incidentally, means that I could hypothetically post-process images, but to get an effect worth generating I'd have to know a lot more about this whole thing than I do right now.)
So it was kind of alarming to remove all the old texture code, entirely replace it with the new code, fix all the compile errors, and run the game to get this on the first try.
Then I was like "oh, right, I have to do my own stenciling now, so I'm drawing the primitive as black with blending on but there's nothing to blend since it's all totally opaque.
...So then I turned the color to white and got this. Totally legible.
Long story short I fixed it.
I'm enthused, okay. Yes, it's functionally equivalent to the old code, and probably slower since I don't know what I'm doing, but I stenciled that all by myself. Next: actually trying out the "draw a hexagon of an arbitrary size and rotation onto a texture" code.
So, first try: when the map is brought up there's an infinite loop due to faulty line code; in certain cases, due to floating-point rounding error, lines kept attempting to imprint their pixels off the edges of the texture forever and ever.
Second try: when the map is brought up there's an crash from glibc; memory corruption (double free) and backtrace, for reasons that even now I don't understand caused by attempting to draw off the edge of the texture when certain geometrical calculations are wrong — keep the image within the bounds of the texture OR fix the geometry and the bug stops happening. This is actually kind of ominous since it implies that my "don't actually mess with texture data when the coordinate is not within the bounds of the texture" code doesn't work right, and that means I'm going to see this exact same problem recur on certain literal edge cases.
Third try: it doesn't crash but why isn't the texture appearing why am i getting a blank white polygon what aaah
Sadly this problem took the longest amount of time to solve. I checked the actual UI-drawing code and made sure it bound a texture when it was passed a texture, I checked the way the map was established to make sure it was actually generating a texture and binding it, etc etc. What I did not check was if I was actually passing the map texture to the ui drawing code, because, well, I'm dumb.
T-This is the most beautiful thing I've ever seen.
Things to fix: a lot.
The tentative second iteration is a lot better. Kind of.
Obvious issue: I need to write the flood fill code. I also... okay, the big question is how I want the map to handle; if it's always centered on the player and can't be scrolled, then it makes sense to generate a texture that's just the landscape around the player, but if it can be scrolled I'd have to 1) calculate the right texture coords to centre the map on the plater and possibly 2) divide the map up into multiple textures. Aaah. Maybe, perhaps, instead of focusing on that I should focus on getting the map working, and I can figure out the details of its UI EXPERIENCE later. That's gonna involve a lot of hex math. I think I'd be able to reuse a lot of the hex utility functions that I use for the actual world geometry code, though, which is good.
So, coming soon: an actual explanation of how the world coordinate system is put together, in pictoral format as I try to make the map work right.
Oh yeah, and it can do this. It took me a worryingly long time to actually calculate an angle that wasn't a multiple of 30 and thus that would generate a hex that wasn't either pointing up or pointing to the side.
August 30th, 2011
Map texturing continues to be a work in progress. I have a flood-fill working in an incredibly inefficient manner, and I fixed that mysterious memory corruption / geometry bug — my out-of-bounds check was < 0 || > width/height
when, of course, it should have been >= width/height
. So in literally one case that lead to altering memory outside the bounds of the texture, and that lead to crashes.
The issue with getting seamless hexes drawn onto the texture is... uh, basically all the hex utility code I wrote I wrote with the assumption that the size of the hexes would be definitely fixed— there are precise width and height values stored within the utility function that get used whenever there's a calculation regarding the size of hexes in coordinate space. This works very well for the actual "placing hexes within the view frustum" part of the geometry, which is all I've ever used it for, so far, but when it comes to something like this it's all wrong. I have to normalize and then multiply the vectors every time I want to get a useful coordinate, and even then there are gaps due to (lack of) coordinate rounding. And this problem would, I suspect, be totally exacerbated by attempting a hex map where the hexes are actually rotated.
After a little work I can get something that looks okay, although I'm certain that the reason why it looks okay is that now there's not enough space between the hexes, so they're getting drawn on top of each other. I'd verify this to make sure, but that would require getting blending working right, and, uhhhh.
Not even to mention that the "map" code is just a long list of hexagonal coordinates to draw on the texture; it is in no way hooked up to the real map yet. I don't think that would actually be that hard to do in theory— when we draw each hex we could look up the actual in-game hex at that coordinate offset from the hex the player is standing on, and store a reference to that hex so when the time comes to do things like "look up map system data" (or hex data, like, say, color) or whatever it can do that. But that's actually kind of complicated and I'm kind of worried that this will rapidly outgrow its conceptual space in the UI code.
I'm already having issues — or more like the issues are continuing — with blocking input, like, "when the map is up keep the player from moving around", since right now you can still move, look around, and switch camera modes with the map screen up. To fix that I'd have to... I guess group the game command codes together in blocks and check the system state before sending any of the messages off. So there's a chunk of code in the input component that's basically "resolve whether or not to open or close or ignore a particular UI trigger", and I worry that that kind of boundary-crossing code is gonna spread through the entire codebase and destroy everything.
Already I can't really run my unit tests since they're trapped in #include hell; there are some cross-dependancies that make it impossible to test the world generation code without including literally every code file in the test. This is... really unfortunate.
But enough whining about my organizatonal problems; they're not just going to fix themselves.
Wow, I totally don't know how blending works.
This is only marginally better. I'm going to... put this code aside for when I have net access again, since, you know, I'm pretty sure I could look this up in an instant online.
OKAY YES DEFINITELY PUTTING THIS ASIDE
Oh it was actually really simple. Like the color channel is straight up linear interpolation1; that wasn't the hard part, the hard part was figuring out how to apply the alpha when neither color was fully opaque. But then it came to me in a flash that of course what you'd want to do is increase the alpha by one minus the background alpha times the foreground alpha2. Obviously. And then I rewrote it all using fixed point int math, since, you know, code being executed tens of thousands of times per texture generation.
1 assuming the color channels are from 0x00–0xff, bgColor * (0xff - alpha) + fgColor * alpha
; evidently there's an even more efficient way to write it but I don't really care; maybe I'll change it if it turns out the speed increase actually matters. [↑]
2 see above, bgAlpha += (0xff - bgAlpha) * fgAlpha / 0xff
[↑]
September 1st, 2011
First things first: I slightly tweaked the code that generated hex colors to make it possible to actually get the color outside of the function that drew the hexes. Of course, I didn't do it quite right on the first try. (All hex joins got turned pure white because of some misapplied casting.)
But, hey:
There are still some major issues relating to really mysterious crashes or freezes, so I'm going to... fix those. But the simplest possible implementation is working!
The crashing issue turned out to be yet another issue with overrunning the bounds of the texture — there was an off-by-one issue when mapping coordinates, so when anything wrote to the topmost row they'd write past the end of the memory block.
The freezing issue turned out to be an issue with the flood fill algorithm entering an infinite loop, caused in part by inefficiencies of the algorithm and exacerbated by the fix of the mapping code, since it was in the code in two places and I only fixed it in one. (Afterwards I changed the code around so it was only in the code in one place, since, you know, this is basically the perfect example of code duplication causing problems.)
There are still some major issues. Firstly, given that the map currently only covers the area within a dozen square metres it's pretty useless. Secondly, the flood fill doesn't always work on hexes at the edge of the map texture (which you can see along the bottom row of the above screenshot). Thirdly, the map is generated irrespective of the direction the player is actually facing, which 1) is really disorienting and 2) improperly constructs an absolute global frame of reference. (And fourth, the map doesn't update as the player walks around, but since ultimately I'll probably freeze movement when the map is up this isn't an actual issue.)
The second is a minor graphical glitch I don't really care that much about fixing, but the first and the third... The first requires doing some span-level scaling, which implicitly deals with rotation given the way the span hierarchy is defined, so that leaves the third, which is all about rotation.
So. Time to actually get around to calculating and storing the direction the player is facing in terms of hex alignment. This is something I've been meaning to do for ages; knowing where the player is looking is pretty important for rendering efficiency and map loading. (It is also time to stop talking about what I'm about to do and instead to report back once I've done all that.)
I had to turn off flood filling again because it kept going into an infinite loop. I really, really need to make it stop doing that. The process I'm using to do flood fills is outstandingly terrible.
Also, wow, somehow I suspect the rotation isn't being calculated properly. Somehow.
Anyway eventually I got it working, hurrah, the end. For a while the coordinate rotations and the hexagonal angle rotations didn't line up, which is why the maps there look all scattered. But now the map aligns in the direction the player is facing, and this means that once again there's no global frame of reference.1
1 One of the big reasons I'm trying to avoid any hint of a global frame is because, well, given that it's a closed world it doesn't really make sense to make the maps all point "north" since that's not even a coherent term in this context; any global representation would have to be something like "up is always the y coordinate axis" and that'd be an unacceptable intrusion of the underlying code structure into the gameplay. [↑]
September 2nd, 2011
I got super tired of spawning inside the world geometry so I finally got around to making an entity's position update correctly for height as it moves. It's the most trivial possible way to do it — on a hex edge update it gets the hex's height + 90 and sets that as the new height — but at least it's no longer possible to walk straight through everything. This is totally a kludge until I get physics working, but, well, don't hold your breath for that.
(To celebrate I added a tall spire to the fake landscape around the pole so I could walk up it.)
Anyway back to the regularly scheduled map updates: Now that I have rotation working, the obvious next thing (well, if you're me) is to make the map scale. And the obvious scaling method is by span level, since it is after all a built-in spatial subdivision hierarchy. So I'll do that, and while I'm doing so actually explain the whole span hierarchy thing for once.
I also got a little tired of the camera fisheyeing so I changed it a little; I'm not really sure how much I like this though, but I'll probably keep it for now at least.
Okay, this doesn't look very impressive but the map texture now responds to input; hitting up or down will scale the map out or in, respectively. Only not really, since I haven't written the code that generates the higher-level maps yet. Right now it cycles through the actual map, red, green, blue, and then solid white after that because I didn't bother making enough dummy textures. And, it's capped on both ends, so the lowest level will always be individual hexes and the highest level will be the world's poles.
Due to the lack of input blocking and the overloaded keys, you move around when you flip through the map. I should really fix that.
September 4th, 2011
Fixed an issue with the input code that had never come up before: when an entity was destroyed it wasn't removed from the list of entities to message with input, so this caused some crashing issues if any input was sent after opening and then closing the world map. The remaining issue is that you technically don't have to register an input component on an entity that receives input, so if that happens the problem will still recur. Probably I'll just make it so that any entity that gets added to the input lists will also have an input component instantiated on it if it doesn't already have one.
Okay, so, span levels. As you can see here, I've changed the coloring again to make the separate platters more distinct.
Some important mechanical features of platters: within each platter, its subhexes are stored with their own coordinate system; at the centre of each platter there's a 0,0 subhex, and all the coordinates for the subhexes (which in this case are individual hexes) are relative to that centre hex. Bridging across any two platters involves converting between the coordinate systems, and that's why I even need to have 'traversal code' at all.
This is one map level up, and it shows the span-1 platters themselves, and not the span-0 hexes they contain. They each map to the same coordinates as the 0,0 span-0 hex at the centre of each span-1 platter, but the span-1 platters themselves are distinct from the centre hex, and in fact they're all offset from a 0,0 span-1 subhex at the centre of a higher span-2 platter. (They're also not colored, because color is a quality only individual hexes have.)
There's a brief intermission while I attempt to get the higher-level map hexes to draw properly:
(The spacing has to change in a certain way on each scale upwards, but on top of that the hex rotation has to change too, since there's a slight spin due to the way the platters link together, and all this aligning needs to be done while still keeping the map centered on the player's position and making the 'up' on the map point forward. I still haven't got it right.)
September 5th, 2011
I actually got distracted and then never got back around to finishing up the map stuff. In part this is because when it ultimately comes down to it, getting all the map hexes drawing perfectly correctly isn't that important; as long as the information is there and visible it works, and taking forever getting it working exactly right wouldn't be the best use of my time as this point.
If you recall I was having issues with a bunch of inexplicable warnings on cross-platter movement. Well, those same warnings have been coming up since then, and there was finally an actual problem caused by them, so I had to figure out what exactly they meant and how to fix them. You see, in the screenshots from yesterday all the high-span maps were drawn in two colors: grey meant that platter was loaded and black meant that platter wasn't. In certain places the map would load as totally black, despite the fact that the platters obviously were loaded; I was standing on them. And at that same time, those same warnings popped up, since the same traversal code is called to generate the map textures as is called to load the real landscape.
As it turns out, in a lot of cases the traversal was failing because a higher-level platter wasn't loaded— to traverse across the span hierarchy the 0,0 subhex of whatever platter you're traversing down needs to be loaded, since that's used as the root platter on a lower span level. If it wasn't loaded, traversal couldn't continue all the way to the lowest level and all lookup attempts failed. Thius meant that when the player was walking on the outskirts of a platter, far enough away so that the centre wasn't loaded, all the map traversal attempts would fail. This is also why it only started happening past the pole: the pole itself is the relevant 0,0 platter, and it's forcibly loaded when the game starts.
Why this caused visible problems for the map but not for the real world geometry, I don't know, but for the time being I simply made it force load the centre subhex while traversing. This might have been a bad idea since it kind of violates the hypothetical independance of the traversal code and the loading code, but it fixed the map. Once I write the real loading code we'll see what, if anything, I have to change.
Okay, so the map is really tentatively giving information. Sometime soon I'll have to fix it a little more so I can actually query map data layer information (like "landscape height") and thus provide actually informational overlays encompassing most of the world, but for now, uh, it doesn't do that. What I want to do now is complete the pattern/arch world generation system, so that I can start laying down generation rules that actually do things and give some non-trivial information to the map data layer. Finally. World generation: still intimidating, even if I've made massive inroads.
Just so I have a screenshot for this update, hey, here's the current coloring. It's two pretty-much random colors in the same pattern on each platter.
September 6th, 2011
So I started on landscape generation. It's a work in progress. To put it lightly. The biggest issue here is that map data values don't interpolate when thy're subdivided, so if I, for example, set the poles to wildly different height values and then generate the world as usual each pole is the same height all across, and then there are very abrupt cliffs at the edges. Ideally there would be sloping hills or valleys that reach their maximal value directly at the pole centre.
Boring pseudo-perlin-noise 'rolling' hills as far as the eye can see. I've become everything I hate.
There's a problem with seams along the edges of poles, for some reason. There's actually a helpful error message:
map.c:954: mapRelativeSubhexWithSubhex: traversal hit pole level; it's trivial to make this work right but i haven't yet; sorry (got poles r and g)
Gee thanks, past self.
Obvious things do to:
- Add a height reading to the debug screen, since right now there's no way to tell how high up you are in an absolute sense.
- Interpolate map data values. The problem with this is that to interpolate you need to know the values of the adjacent platters, and in a lot of cases those won't exist. At that point there are a few options: assume the value is zero (it probably won't be anything near zero), assume the value is equal to whatever the current value is (marginally better but still likely to be wildly wrong), or step up a level and attempt to interpolate the value of the unloaded platter by checking its parent-and-adjacent platter values. Which leads to the same problem, only this is guaranteed to terminate since ultimately it'll reach the pole level and those are always loaded. This option is by far the most complicated and difficult to implement and it might be more accurate.
- Change the way the height value is applied to get different slopes, or
- Add a second map data value that determines the way the ground is sloped
- Maybe get rid of false coloring altogether and use a height-based spectrum
Less obvious things I could do:
- Finally get around to hex stacking and start making huge mushroom-cap formations
The interpolation doesn't go quite exactly as planned. This is actually a larger scale version of the barycentric triangle issue from August — the interpolation is happening, but not necessarily with the right triangle for each platter, and so the values are wildly incorrect.
And as usual, my method for solving things is to get things working in exactly the wrongest way and then to flip two operations around. However, there's still a problem:
The values are interpolated correctly, except for sometimes they're not. This is actually the "when you can't look up the adjacent platter use the current platter's map data value" code that I ended up using in action, and... I admit I wasn't expecting it to come up so quickly. The incorrect values make a very distinct shape, though, so...
Yeah, this doesn't look bugged at all.
I have to admit, that was... not what I was expecting. I don't really have a clue as to why the height values could end up looking like that. It looks like absolutely nothing is interpolating the right values, and I'm not really sure what could cause any part of this. I'm gonna give it a rest for tonight and hopefully come up with something tomorrow.
September 7th, 2011
Okay, so: What's happening here is obviously something to do with the interpolation not getting the right values. The edge of the red pole trends downward to zero no matter the height of the adjacent poles, and... and I have no clue if the interpolation is correct on the other poles, but I'm willing to bet probably not. There's also the issue of the strange crossbars, which... if I had to hazard a guess I'd say it was something to do with lookup failing, but as it turns out that's not it at all: it's because I wrote an ifcheck badly and that meant it didn't even try to interpolate over a fair chunk of the sub-platters.
Once that's fixed, the problem becomes a lot more clear-cut. The right values aren't being used for the interpolation, or the lookup is failing or something like that. As it turns out, the problem is, as usual, really obvious: the code I was using to do the initial subdivision went:
pole = mapPole ('r');
mapDataAdd (pole, "height", rand () & ((1 << 10) - 1));
mapForceSubdivide (pole);
pole = mapPole ('g');
mapDataAdd (pole, "height", rand () & ((1 << 10) - 1));
mapForceSubdivide (pole);
pole = mapPole ('b');
mapDataAdd (pole, "height", rand () & ((1 << 10) - 1));
mapForceSubdivide (pole);
And with a little thought it's obvious what the problem is. The red pole, for example, is being subdivided (and thus all its sub-platters are being interpolated) before the other poles have their value set, so of course it's sloping down to 0, because at the time of the interpolation that's what those values were!
So I re-ordered those operations.
Okay, well, the interpolation works perfectly. The landscape still needs a lot of tweaking. Incidentally, if you like what the landscape looks like you might want to check out this person's log of building a raycasting renderer from scratch (in one day, weep weep weep), which has a fairly similar landscape generator.
This, however, only resolves one of the problems. Lower-scale height stuff doesn't work right along the pole edges, because of that one issue I mentioned earlier. It's not very surprising; pole-level platter traversal is a special case, and in a lot of cases I'd never bothered to test to make sure it actually worked right. It's probable that the whole of the problem is just because I never got around to writing the bulk of some or another function, or actually testing my pole traversal code.
September 8th, 2011
This is just going to be a listing of bugs.
- As previously noted, height values near the pole borders don't seem to be set correctly. This might be another order-of-operations issue, or it may be a problem with looking up adjacent platters when that platter is on a pole edge.
- The game crashes when you get within visible range of the pole edge AND the pole span level is > 2. This is almost certainly related to the prior bug
- Like a billion things with the way maps are drawn; also the final pole-level map isn't yet generated at all since there's no parent to reference and as usual it has to be a special case
- There are some issues with height interpolation at the visible edges; I think what's happening is that when a visible platter is loaded any barycentric interpolation that requires values from the adjacent unloaded platters fails to work right. This has alarming implications for world generation as a whole.
Here's an example of the last one, although it's kind of hard to make out. The triangular divots that would usually be hidden behind the terrain are parts of the landscape interpolating down from zero; the blobby spire shape in the centre is the centre of a span-2 platter, which has map data height set, interpolating down in all directions to zero.
And here's me fixing the first problem; it was not actually related to the second problem at all. It was because I had set the loop that iterates over the pole's sub-platters incorrectly. I am great at programming. Sadly, this means the second bug is even more mysterious and annoying.
September 9th, 2011
Yeahhhh didn't get much done yesterday. So now today I get to try and fix all these annoying loading issues. Here's a better shot of just what I'm trying to fix: as platters are loaded, their imprinting goes all wrong because their adjacent platters aren't loaded and thus don't have map data to check, and so they interpolate with 0 and that leads to this. The only reason this is fixed as the player keeps walking is because on every platter traversal every loaded platter is re-imprinted and recalculated. This is not a good plan, not the least because already there's a noticable hitch in movement along platter boundaries.
The obvious solution is to have a margin: one set of platters that are loaded and have their data values calculated, but that are not rendered and thus don't have to look up the adjacent data values. They can be refered to by the closer platters so they don't have bizarre graphics glitches, and then platters can be marked "fully loaded" and then they never have to be updated again (until they're altered by an entity or their pattern updating but uh let's not go there quite yet).
The first attempt... doesn't go so well. I mean, yes, it fixed the issue with visible bad interpolation, but it replaced it with a new problem, namely that platter edges were all set to 0. Now that platters can be partly loaded it's possible to get a non-null hex that's nevertheless invalid, (because all its values are wrong because it hasn't had its data set properly) and now I need to add an additional check to handle that.
The second problem is that because the imprinting is now being done once on a per-platter basis, there's an issue with patterns overcutting. This is actually kind of the fault of a ~leaky abstraction~; I've made it so tile lookup disregards the whole platter structure, but some times it's actually important if the hex we're getting is on the active platter or not: if it's not, we've crossed an edge to a platter that could be in who-knows-what kind of state, and we don't want to mess with those values; we want to wait until that adjacent platter is loaded and then mess with its values or else we run the risk of, well, this happening.
Oh yeah, and there was a moment while fixing those issues that I accidentally set it so that (almost) every hex was considered invalid, so none of the hex edges got set.
But even with those problems still happening there's already a huge increase in responsiveness: crossing a platter edge feels like crossing over any other tile; there's no lag at all. (The problem here is that if a platter is loaded it's marked "loaded" and never altered again, but the vast majority of platters are going to be adjacent to an unloaded platter while they're being loaded, and that edge can't have its edge values set until that unloaded platter is itself loaded. What needs to happen is that every newly-loaded platter should look over its adjacent already-loaded platters and tell them to update their shared edge, and this is a place where the abstraction of the platter system is working against me.)
And even beyond that, there's the matter of patterns. Currently patterns are attached to a single platter, and they're "imprinted" onto the landscape after that platter has its map data applied to its hexes. However, patterns can affect tiles beyond the edges of that platter, and in that case things go wrong. My first thought is to have a "footprint" calculation that checks all the platters the pattern will cover, and then each of them will imprint their part of the pattern seperately when they get loaded. The main issue with that is that patterns can be weirdly shaped. I mean, right, now the only one is a fairly small perfect hexagon, but in the future anything goes.
The first attempt to get pattern footprints doesn't exactly go according to plan. The imprinting code was written assuming that the platter the pattern was being imprinted to was always the same as the pattern's centre.
Once that little issue is taken care of things are back to normal. The downside to the code as it's written currently is that the footprints are really, really vague (this one is "the base platter and all six adjacent platters" even though it's impossible for it to actually touch more than three) and each platter in the footprint imprints the entire pattern over and over, until they're all loaded and set up properly. This is very much not the best way to do things and may in fact have alarming reprecussions in the future.
On the other hand, platter movement is a lot more responsive and loading is closer to what it'll have to actually be when I get real loading working. Also, while it's technically inefficient and of dubious future use it does work properly in all cases currently, so this is another bug fixed. Now to tackle the big "everything crashes near the pole edge" bug. Sigh. And it's due to memory corruption, too, which means... I don't know what it means. But I've gotten invalid free, I've gotten invalid next pointer, and I've gotten that same failed assertion inside malloc.c, so whatever the problem is it's apparently fairly flexible in terms of what memory it's overwriting.
Allocating slightly more memory fixes the memory corruption crash (I am pretty sure the issue was that I was allocating MapSpan
spaces in an array when a pole-level transition requires MapSpan + 1
spaces, but I'm not totally sure that that is the whole of the memory corruption issue) but then there's a issue coming up slightly later in the pole traversal where the program just segfaults.
As it turns out it was just that there was another function that only allocated MapSpan
spaces instead of MapSpan + 1
. Off-by-one errors: in fact one of the most common types of bugs!
So hooray, bugs fixed, things working in a reasonable manner again. Time to think about what to do next. I'm thinking overlapping terrain, since the longer I put it off the more painful it'll be to reconcile with the existing hex/platter/etc/whatever code. But already I'm already going to have to do some thinking about how to get it to work in a reasonable way, so, uh, I'm going to do that now.
Also I'm going to make a bunch of commits, because I've changed a bunch of stuff and I really ought to commit it all.
September 10th, 2011
Overlapping hexes are complicated and I don't want to think about that right now. So instead I'm finally trying to figure out something that I've had on the to-do list since forever: culling platters outside of the view frustum. Now that there's some semblance of a landscape, the incredibly-close draw distance is kind of annoying, even when the landscape has been fully loaded. But to push that back I need to be drawing less polygons, and that means it's time for efficiency improvements.
The concept here is pretty simple: get the angle of the camera and the platter it's on, and calculate the angle between that platter and every other platter, and only draw the platters which are within a certain angle of the view angle. In practice, it's a little more complicated, not the least because I have no clue how to calculate difference on a modular scale.
Eventually, as usual, I can mess around with the values enough to roughly figure out what I'm doing — since I know this is a modular scale from -π–π then there's no possible way for a valid difference to be larger than that — the maximum angle difference is π radians (otherwise known as 180°) — so anything that turns up larger than that (or smaller than -π) must be invalid or incorrectly calculated. So, for example, the difference in radians between -π and π is 0; they're effectively the same value. But a straight-up subtraction yields a difference of π2 (or -π2), and so blah blah blah, long story short:
diff = facing - platterAngle;
if (diff > M_PI)
diff = (M_PI * -2) + diff;
if (diff < -M_PI)
diff = (M_PI * 2) + diff;
Although given that this yields a 'difference' value of π/2 for platters the cursor is directly centered on, there's probably something wrong with some of these heading calculations. But it's all wrong in the same way, so, uh, whatever. (Probably since atan2
expects its arguments to be in y, x form I transposed them out of habit somewhere.)
So now I can double the view distance and the nearby force-loading, to 12 and 6 platters, respectively, and... actually, testing with it off and on the difference isn't super noticible. Part of this is likely due to how there's now new overhead; all these angle and facing checks are done on all platters every frame instead of cached; if I changed that there'd probably be another slight speed increase. And, sadly, the framerate still drops to shutter-speed on really hilly landscapes due to all the extra joins getting drawn, which is unfortunate. But there's not really any getting around that without some level-of-detail rendering, and I don't exactly know how to do that right now or at all, especially since that's one of the things that'll make stacked hexes a lot more complex.
September 14th, 2011
And then I did nothing for a few days.
The first attempt to do something with overlapping hexes, uh. Let's go with "in some extremely tentative ways, works". The stacked hexes are rendered, but their joins haven't and can't be set correctly given the way the data is currently stored.
You know how I talked about how all that re-imprinting of patterns could lead to bad things happening in the future when patterns were slightly more complex? Well, this is slightly more complex enough. The floating hexes can have their joins calculated really tentatively if they do it in the right order, but due to the repeated imprinting of the pattern there are actually six hexes stacked right on top of each other, on if the hexes cross a platter boundary they are in all sorts of wrong states.
Also even when that's fixed it really just makes it really clear that I need to be drawing the underside of hexes now.
Even when that's done there's the new problem, of, well.
When you're just dealing with a planar heightmap joins are pretty simple; every hex has six adjacent hexes and those hexes are either above, below, or equal to the hex. This means there's always either zero one a single quad join that needs to be drawn between any two adjacent tiles. When hexes can overlap, that all changes.
For example, for the floating platform, not only do all the usual 'surface' joins on the top, but in a few cases it needs to draw more joins on the underside of the platform where the ceiling heights don't line up.
Now instead of having only one quad to deal with per hex side, there are potentially innumerable joins depending on the state of the world, and instead of a single adjacent tile there can be a lot more. This in addition to needing to alter the hex structure to store the bottom surface of each hex in addition to the top and there are actually some fairly radical alterations to the hex structure that I haven't gotten around to really reworking.
Soooo that's what I'm going to need to work on. This has the potential to really mess with pretty much all my existing hex-level code, and... okay, I'm kind of regretting not doing this earlier, but, well, I'm doing it now. Hopefully it won't be too much of a ridiculous challenge.
September 16th, 2011
Okay, so, the thing is. As it turns out the whole "joins have to be calculated differently" thing was actually a fairly major concern, and I had to revise the hex structure again to get it working roughly correctly.
Along the way I decided to finally push in material types, since this seemed like a good time for it. Whether a certain hex prism chunk is opaque or not determines quite a lot about how it and its joins (and the joins of adjacent hexes) are drawn, and while the current implementation isn't right it's certainly better than putting off that particular determination again. As a result, the entire world is now a very dull purple.
I hope you like purple. Purple and grey.
Purple and grey and dark purple.
The first attempt to get joins doesn't work right. Note that the second screenshot there is drawing wireframes; they're so thick underneath the ground they are literally opaque.
Let's just go with "there were problems".
But then I fixed things! This isn't perfect; the internal joins for 'floating' hexes are still drawn on the insides in a lot of places, and there are certain configurations of adjacent tiles that will lead to all sorts of warped joins currently. I'll fix those as floating hexes become more widespread and the issues because actually visible.
I'm feeling pretty good about this; I've finally gotten the real hex structure mostly-finalized, although obviously it's going to need a lot of polishing as I try to actually do things during the world generation step. But this is by no means a small step, even if it's pretty much a really complex implentation of something that ought to be simple, and it still doesn't work in most non-trivial cases. (The code to handle this is 55 lines, which admittedly isn't that bad given that the full hexDraw
function is 154 lines total, but it's already a confusing mess of if/elses and while loops with weird break conditions, and I fear it's going to get a lot longer.)
Also the pattern I'm using has started breaking across platter edges again, for various reasons that are fundamentally because patterns imprint strangely when the platter they're centered on isn't loaded before all the other platters. In this case it is, so fixing the problem would be trivial, but it's much more difficult in the general sense.
ANYWAY THAT'S WHAT I DID TODAY.
September 18th, 2011
Blah blah blah blah, I fiddled with some of the interpolation code and I forgot to update some of the values, so it interpolated between completely the wrong values. This was one of the results.
base height values + shear: [463, 459, 473] + 152
interpolated values: [5567098, 463, 11829565]
base height values + shear: [463, 459, 473] + 152
interpolated values: [6185612, 462, 13143908]
base height values + shear: [463, 459, 473] + 152
interpolated values: [6804126, 462, 14458252]
base height values + shear: [463, 459, 473] + 152
interpolated values: [7422641, 461, 15772595]
Okay, there are several things wrong here. What I want to do here is change the way height interpolates to include a new value: the amount of crumpling in the landscape, which will on occasion cause overhangs or columns that bulge outward, things like that. What I actually got was a claustrophobic area that was open in all directions but always had a ceiling just above the player's head.
Additionally, the way the hex heights were interpolated became substantially more... bad. But the former issue was caused by yet another implicit-signed-to-unsigned type conversion, and once I fixed that, well.
Holy shit.
The landscape here is just ridiculous. The framerate is something like one every five seconds due to the huge mass of hidden surfaces that are still getting drawn, and given the way movement works on the stacked hexes (you teleport to the lowestmost hex) it's extremely difficult to navigate. This really makes me want to fix the rendering issue and add actual physics-based movement, since there's finally a landscape you could move around. Even if it's incredibly chaotic.
But sadly, those generation rules are completely unplayable with the game in its current state, so I simplified a bit to focus on one of the multiple bugs with landscape generation. Part of the reason the landscape is so abrupt is because of some new interpolation issues; because of the way values are interpolated there are currently distinct jumps around all the platter boundaries. This is part of the reason the crumpled landscape is so chaotic; ideally it should be a little more regular.
But then I stopped coding for the evening, so, tomorrow.
September 26th, 2011
And then I did nothing for a few days. Patterns, even the bare basics of them, are still beyond me. But I finally decided that rather than fret and accomplish nothing I should work on something I have a reasonable expectation of accomplishing.
And that something is UI and picking. It's currently astoundingly ugly and non-functional, but hey, this is the first time I've actually attempted to put together any UI visuals since I stopped working on phantomnation. And now that I know what I'm doing when it comes to assembling textures I can put together a background that's not just a black box. ...I'd still like a non-bitmap font, also. Maybe I should work on adding freetype2 support, so I can use fonts made by people who know what they're doing.
In the mean time I'm going to be assembling some rough menu and status screens, and trying to get selection with both the keyboard and the mouse working. Keyboard is incredibly simple, but never ever in my entire life have I written a program that responds to mouse input. I'm starting with 2D detection because, well, that's a lot simpler to handle than 3D raycasting.
September 27th, 2011
Off-by-one errors are one of the most common sources of bugs. At least in this case it was obvious.
I added some code to color the options as they're selected/unselected. Like, I know this is a small thing, but all the menus in phantomnation were these static things with abrupt cursors and absolutely no selection response, and the sine-animated cursor and text fading add a lot of ~UI feel~ in my opinion, making it seem more advanced compared to the phantomnation menus. Also in the phantomnation menus I had to do all the line height calculation by hand, so it was always off. (Although the phantomnation menus were textured. Kind of.)
The fade here was exaggerated so I could actually get a screenshot of it; right now it fades six times as fast, and also between grey and white instead of white and red.
Next: maybe index and frame textures, maybe more sine animation (although really, it's very easy to overdo it), maybe making it actually possible to have menus that trigger events, maybe mouse picking. Well, all of the above, just, eventually.
In which I forget to clear out the title menu when I add the "New Game" option (technically "ew Game" since I haven't drawn most of the capitals). And still, due to the complete lack of input-blocking from game system, moving forwards or back also moves the menu index. I really should fix that. But first I fixed the menu not being removed thing.
Currently the menu actions are passed through something of a hack; each option has a input code associated with it (currently IR_NOTHING, which I will probably change to IR_MENU_DUMMY or something so I can make it make a buzz noise, when I get around to adding sound) and when the menu itself gets the IR_CONFIRM input code it goes and checks the index and sends off another input event with whatever the stored code was. Due to the vagarities of the input system, though, it's possible to wreak absolute havoc with some of the keycodes. Not ones you'd ever want to actually set in a menu — the move forward/back/left/right event codes in particular would start up movement that could never be stopped — but it's still worrisome. Once I've got actual practical input more worked out I've definitely got to revamp the input code. It's some of the oldest code in the project, and it really shows.
So I spend ages trying to work out why the texture code isn't working — I had to write a new function that copies rectangular chunks from texture to texture, and it's not working right. I finally marked up the UI texture to try and figure out where it was copying its data from, because it sure wasn't copying from the right place. The result is as you see here, and if you check against the texture I was using you might be able to spot the specific issue. In short, it's upside down. Now, this sounded like a familiar problem, kind of — OpenGL stores texture data starting from the bottom left corner to the right and then up, and for a lot of programmers it's intuitive to think of them as starting from the top left corner. I know this specifically because it was the cause of some of my texturing bugs, before I fixed it to work in the proper way. Which is why it was so annoying. But then I think about how I'm loading these pngs with glpng, and that it has a very specific setting that determines whether or not pngs are loaded the OpenGL way or the intuitive but wrong way, that of course defaults to intuitive but wrong.
Okay so I should just have to switch that off and everything should work right —
Jesus fucking christ
I had, of course, forgotten that there's a great deal of legacy code that requires the texture loading be set to the "wrong" option, so that when it's flipped around suddenly nothing works.
I was able to rig up a working system, that among other things involves loading up two copies of one texture, one flipped (so it can be turned into a sprite sheet, which only works with the option set wrong) and one unflipped (so it works with my more recent OpenGL-correct texture painting). Joy.
Anyway, menus work now. They draw all fancy, they react well, and they can actually do things. There's a lot more to do, but this is pretty good to start. And for once, the big annoying problem was totally not my fault! Well, excepting in how it was caused by my prior ignorance, but really if we called all the issues caused by— okay nevermind, moving on.
September 29th, 2011
And then I made the menus react to mouse movement, so the index changes as you mouse over each option, and also so clicking sends a IR_CONFIRM event if it's actually over an option. I'd take screenshots, but, uh...
Oh yeah, and I added a skybox. Currently it is the most gaudy colors possible; in the future it'll be something more, uh. understated. This is hypothetically the base of a more reasonable, textured skybox, but until I get around to making some skybox textures it's just as you see here, a gigantic ugly cube.
It also doesn't work right with isometric rendering, but hey, what else is new.
At first I had written the code that would prevent this from happening but I forgot it when I copied the skybox code from the main render code to its own function. (This is when you don't unbind textures before drawing the skybox, and it's kind of alarming to see the sky turn black and green when it's usually gaudy toy colors.)
Picking in 3D is a completely different challenge that I'm still not very clear on how to do. Like, the most common case could be raycasting with the front view vector to see what the cursor is focused on, but even that involves collision detection with a bunch of stuff. But, hey, I guess I've got to work out collision at some point.
September 30th, 2011
~On Hiatus~ for October; I'm doing Ludum Dare's challenge.
December 2nd, 2011
Well that certainly was a nice break! I attempted the Ludum Dare challenge in October (failed) and then tried another one-off text-based game thing in November. Neither project got very far off the ground before 1. I lost interest and 2. the month ended, but I learned a whole lot about the process of making a game that I... hadn't really learned while making this game.
There were some major code revisions I'm going to try and carry over to this project, and I'm going to list them here in order of importance (descending):
- The entity/component system is... different. Since the other games I was programming were a lot more lightweight, firstly I got to the point where I need to create component agglomerations for in-game objects, and secondly and following the first, I ended up using entities/components in a much more overarching way, and both of those made me revise the code repeatedly so that how it's used and how it fits into the system is markedly different than the current beyond code.
- The generic system code is substantially different too — when I started the first project I had to rip out the more generic parts of the system initialization and flow code, and I tried to keep game- or project-specific code from polluting the system code too much. When I started the second project I had to do that all over again, and at that point I decided it was worth it to make something entirely generic, which resulted in a substantial restructuring of all the system code.
- I decided on a format for all my human-readable config files: OGDL. The C library... does not comply with the current spec, to put things politely, but it's a parser that works and that I didn't have to write, so it's already been massively useful.
- I'm now using freetype2 for font rendering instead of using a homespun bitmap font library. The current setup might actually be a temporary thing too; in the future it might be more useful to use freetype2 + pango for layout if I ever need to render text seriously (e.g., with clipping planes) but for the time being it works exactly the same as the bitmap code only the text looks a not nicer.
- The input component works in a markedly different way. This I'm not too sure about reusing; the old code was badly-coded and broken, and while the new code is much, much simpler, and also less buggy, it's still badly written in its own ways. I suspect I'll end up rewriting chunks of it as I step back into updating beyond.
Which brings me to: working on this code again! I have kind of forgotten where I was at, and given the vast differences in the stuff I'm using to code now vs. two months ago just trying to refit the system code such that I can code in the way I've grown used to is itself going to be a huge challenge. Let's recap what I was doing:
I'd just gotten selection boxes and menus working with both mouse and keyboard input.
And I'd added a huge ugly-looking skybox.
More generally, I had finished up the map code for the most part and was moving on to areas that would make the game more playable: either the framework for user interaction with the world (the menus) or some preliminary code to make the world more interesting (the skybox, and also some abortive attempts at landscape generation). I think the very last thing I was doing before I stopped was attempting to get collision detection (but not response) working right.
I'm not entirely sure where to pick up at — I really want to start working on world generation, and between my new understanding of what entities and components and entity systems are for and a slightly better comprehension of how world generation algorithms can work I think I'll be able to put something more interesting than perlin noise hills together.
December 3rd, 2011
Oh my god, I had forgotten what a horrific state the code was in. I think... for the good of my pride I'm not going to detail exactly what that horrible state is, but I suspect you'll be able to figure the rough outlines of it out given that many of the upcoming updates are going to be about fixing it.
What I did today: fixed the repository so that i wasn't perpetually stuck updating a branch; wrote a .gitignore
file to ignore all the automatically-created autotools/libtool files so that the staging area wasn't a huge mess of system files; removed my homespun bool.h
file and made everything use <stdbool.h>
; and messed with the beyond.c
and system.c
files to better separate game code from system code.
The latter maybe requires some explanation. As mentioned in the last update, there's a new system structure that's wholly general, and as such shouldn't need to be changed at all when I switch over to other projects — all the game-specific code is in the game-specific code file (beyond.c
), and all the more generic system operations like running the main loop are in the system code file. This is opposed to the way it was, where there were game things in the system file and system things in the game file and it was a huge mess.
It's not entirely better now, but it's improved.
December 4th, 2011
Mostly what I did was further separate the beyond/system code and slowly add more pieces to the entity code in the hopes of getting it to the point where I can make a "modern" component/entity system setup for world generation.
The only real program-specific code was updating the position component — in the code curently there are four different data structures that can specify a position, all of which are used in certain ways in different contexts. It's a huge mess and it's really annoying, and to solve it I've introduced a fifth position data structure, which I'm gonna use for everything new I code and hopefully go back and slow update the other position code to use it too, so I can remove the other structures and all the code that they use.
I completely removed all the existing worldgen code except for the loaders themselves. I haven't fully decided on what the final-draft worldgen code is gonna look like, but it's gonna involve entities and entity systems and at least one worldgen component.
December 19th, 2011
Tumblr use has really demolished my interest in writing these updates, since I can just write a dozen short updates about the state of the code daily.
I've been working on this on and off for a while, generally just fixing up parts of the world generation system and trying to fix various bugs.
A listing of bugs found and fixed:
- certain coordinates would be generated outside of the map radius bounds and not be normalized into valid coordinates, which lead to missing tiles when imprinting hexes (this is actually still happening in some other way now, though)
- a million crashes having to do with memory allocation and not zeroing-out the data structures so that they're full of garbage data. eventually i just changed the memory allocation function i'm using from malloc to calloc.
- now that the starting position is random i was getting random crashes some times; long story short there was an array overrun in a coordinate function that caused memory corruption due to bad math
- arches didn't get placed on the right coordinates as higher-fidelity platters were created because the list of arches wasn't being iterated through correctly
- arches didn't get imprinted correctly because i had a loop inside a loop that both used the same indexing variable (because i am dumb)
After fixing all those bugs there's still one major issue left — arches aren't getting imprinted properly across high-span platter boundaries, so they cut off abruptly in some cases. I know what the issue is, (see the first entry in the list there) I just don't know exactly where in the code it's happening or what I'd need to change to fix it.
More importantly from a system-level perspective, though, I expurgated so much old terrible code, including all of the object code and several components that were never or had never been used in a long time. After trying to make three separate side games I've learned so much about system code and how to actually use my entity/component/system structure to, you know, make the code flow more apparently and more discrete, instead of helping it turn into a big mess of tubing patched into each other until everything is confusing. All the component code has been updated, and although it's still pretty bad there's enough space now to start writing new code using my new tools instead of struggling to use my old, terrible tools.
Here's the code line breakdown from the commits removing old code, which I am pretty proud of:
src/Makefile.am | 29 +-
src/beyond.c | 30 +--
src/comp_worldarch.c | 2 -
src/component_camera.h | 1 -
src/component_input.c | 96 ++---
src/component_input.h | 8 +-
src/component_integrate.c | 169 --------
src/component_integrate.h | 34 --
src/component_plant.c | 706 ---------------------------------
src/component_plant.h | 29 --
src/component_position.c | 161 ++------
src/component_position.h | 4 -
src/component_walking.c | 172 ++++-----
src/component_walking.h | 10 +-
src/entity.c | 122 +-----
src/entity.h | 10 +-
src/map.c | 140 -------
src/map.h | 12 +-
src/map_internal.h | 13 -
src/object.c | 942 ---------------------------------------------
src/object.h | 145 -------
src/system.c | 25 +-
src/system.h | 9 +-
src/video.c | 191 ++++------
src/video.h | 11 +-
src/worldgen.c | 4 -
src/xph_memory.h | 4 +-
27 files changed, 290 insertions(+), 2789 deletions(-)
Some of that code is stuff I'm sad to see go — I really do want to get plants working at some point in the near-ish future — but it was all either terrible or never used, and it was all adding to the perceptual complexity of the code and making everything a mess to think about. So I'm glad it's gone!
Now to stop recapping and get back to work, probably by first heavily revising the entity code to allow for a distinction between components and systems and then by shifting a bunch of update code into various systems.
December 20th, 2011
I added all the entity system code from the further ahead iteration of entity.c
and then rewrote most of the components to do their work as systems instead of as __update message responses. In most cases this was as simple as just rewriting the entity message function header to be a entity system function header and then registering it in the proper place, but in a few places it was a little more involved than that.
UI, for example. There is, for reasons I don't really want to get into, a list of UI entities in the system struct. This was used to determine what things to render as UI objects, and in that sense it's now useless, but it's also used in the input code to either create or destroy UI frames depending on the top entry in the stack. It's a mess, and I haven't yet rewritten it to not be a mess.
But movement is now entirely system-driven, and UI is halfway there, and given that the only components that I'm using are those two plus input and position, I think that's pretty good. What I'd like to do is mess around with the position/map code so that it's wholly abstracted away in the eyes of the entire rest of the code, but I think I should go and add some new features now, instead of continually fix up the existing code.
December 21st, 2011
Added npcs and started actually rendering the player avatar. NPCs do absolutely nothing: they cannot move, don't collide, and have no state whatsoever aside from a position.
Also rendering things as hexagonal prisms seems like an ill-advised idea.
December 22nd, 2011
The first thing I attempt today is getting the hexagonal prisms that are bodies to face in the direction of the front view vector. This is really some pretty easy... trigonometry? I guess? but I mess it up at first since it's been ages since I've had to use cos and sin and I didn't remember if I had any code already written to make things easier on me.
So the first time I tried it came out looking like this!
I also fixed up some of the camera "rendering" code that draws the cursor and stuff like that. It's actually exactly that "stuff like that" I'm working on right now — the big thing the camera should be drawing is what the camera's looking at, as in, it should draw highlights on any hex or hex wall that they camera cursor is directly over.
This is collision detection at its most primitive, and it's just about outside of my ability since I really don't know what I'm doing. I have a position in the world (the camera's position) and a vector to check along (the forwards view vector), so I guess I... project the vector into the 2d space of the hex grid (which is just dropping the y component) and loop through the hexes that 2d line enters, checking their heights vs. the (linearly interpolated) height of the vector as it passes through the hex? Something like that. There are probably ways of doing it that are natively 3d, but I don't know them. Really, I don't even know if the above plan will work, it just seems plausible.
There are two problems here. One is that the only way to reliably render highlit hexes without messing with the render coordinates is to disable depth testing, which leads to the highlights not being occluded when they ought to. The other is my "get all the platters adjacent to [x] platter to [y] steps outward" function doesn't work right. I mean, I knew the latter was happening; it's listed as a bug in that December 19th post, it's just this is another symptom, and one that's a lot more visible.
(The collision function is written, although it doesn't do any actual collision testing yet, so I decided to make it obvious what it was returning by actually rendering the results. Right now it returns the hex the player is standing on. And in fact that reveals a third issue, that sometimes, rarely, the hex calculated as being the one the player is on is wrong.)
Even after I get something vaguely like a "hexes crossed by this ray" function working, there are some substantial problems. Sometimes the hex-crossing code goes backwards because it hits the wrong line intersection first and it ends up oscillating between two hexes. Sometimes — half the time — the entire line projection is backwards. Sometimes it's just wrong in a really weird way.
Part of the problem is that I don't really know what a correct result would look like. But what I'm calculating currently now is definitely not right most of the time, I can tell that much.
So I fixed a bug or two and now it's mostly correct, but the fundamental issue is not resolved: half the time the intersection check hits the wrong intersection first. Once there's been one correct step I can catch that and keep going, but it's still bad code. More importantly, there's no way to make sure the first step is correct, so half the time the line tested is generated behind the player. There are ways to solve this, just none that I can do without recoding a big chunk of the function. Which, I mean, isn't a huge deal, it's just it's late and i'm tired so I'd rather not try it right now.
I think tomorrow I'm gonna work on player placement of patterns/arches and just tie that to the right picking code once it's actually finished, since on the whole patterns and arches are more important than getting picking working correctly (or at all).
Also happy solstice.
December 23rd, 2011
Well, I had a flash of insight and realized how I could stop the rays from tracing backwards— long story short since the cross product is directional there's a certain way to test if an intersection is in the right direction or not that I hadn't really realized at first glance.
It still doesn't work right in all cases, because the "object position is calculated as being on a tile adjacent to the one it's actually on" bug is substantially more pervasive than I had first thought. Also tile lookup, like pretty much everything else, fails at the edge of high-span platters. I'm pretty sure that problem is in my new hex position code, since movement doesn't have any problem and it uses the old code, so that limits the problem to basically one or two functions.
Anyway, still planning on working on patterns and arches and actually instantiating them in the world today. Picking is nice, but it's not really important when there's nothing to do in the world.
Actually getting new arches existing in the world on-demand is surprisingly tricky, and this is where leaving parts of the code that are hugely important as junky temporary code really comes back to bite me. There's just flat-out not a way (or a reason) to get platters to regenerate after new arches are added without a lot of incredibly inefficient trickery. This is a problem.
Upside: it's possible to get new arches to exist in the world on demand!
Still, before I elaborate on all this I'm going to rip out the old world loading code and try to replace it with something worthwhile. Maybe get some debug rendering for arch layouts while I'm at it, since I suspect that's about to become critically important.
The big issues I keep running in to:
- The new platter lookup code fails when looking up across more than one platter edge, leading to npcs that fail to render, arches that fail to imprint, and picking that stops short
- There's some hex math code that's wrong in a bunch of cases, leading to the wrong hex being calculated as the hex an object is on.
- There's no good way to figure out or reset the imprinting state of a platter, since the platter loading code was never designed to have to deal with arches being altered after the initial load, even though that's going to be a huge part of the game.
- All the new position code sets the
hexPos
part of the position struct and uses that solely; all the old position code uses rawSUBHEX
es orRELATIVEHEX
es and doesn't bother to set thehexPos
at all; this leads to some scrambling to calculate a roughly-accurate hexPos when it needs to be used. - Both patterns and arches are still very half-formed as data structures; patterns aren't used for much of anything at all and arches are essentially just a marker that says "there's an arch here" without any detail about what it is; it's going to be impossible to progress with this design unless I start making some content for what arches and patterns are.
Just in general, a lot of the comments I've been writing recently have been along the lines of "because there's no real loading infrastructure or pattern/arch content we can't write a permanent solution to this problem yet, so here's a stopgap to get this part of the code working" and it's getting more and more obvious just what the loading infrastructure will have to be, so I'm going to try my hand at that first before moving on to fixing various bugs and fleshing out the pattern/arch code.
Still, I think that was enough code for today. Tomorrow, with a fresh head, I'm gonna try to fix up some of the above problems.