Today all we do is use the mapping table to look up the position in the texture atlas (which I'm calling the "texture code") for blocks from the same slice of the map as last time. I'm not going to discuss that in detail because it's not terribly exciting. Pull down the code from github, and copy your terrain.png file into the same directory before you run texture_demo.py. (I don't think I can legally include terrain.png in the repo unless I draw it all myself.) You'll see something like this:
Here you can see pink squares in areas of empty space, tree trunks, leaves in dark grey, grass in light grey, dirt in brown and stone in medium grey. The reason that the leaves and grass are grey is because the texture in terrain.png is greyscale and Minecraft colours it at run-time based on the biome. We might do that eventually, but it's probably easier just to use a custom terrain.png with green leaves and grass.
One thing that I would like to discuss is some of the stuff we're doing in numpy that's not really important just now, but will be much more important when we render more than just a flat horizontal slice of the world.
def get_cells_using_heightmap(source, heightmap): ''' Given a 3D array, and a 2D heightmap, select cells from the 3D array using indices from the 2D heightmap. ''' idx = [numpy.arange(dimension) for dimension in source.shape] idx = list(numpy.ix_(*idx)) idx[2] = numpy.expand_dims(heightmap, 2) return numpy.squeeze(source[idx])
- Supposing the input array "source" is 512×512×128, we first construct three arrays, [0, 1, 2 ... 510, 511], [0, 1, 2 ... 510, 511] and [0, 1, 2 ... 126, 127].
- We then feed them into numpy.ix_, which converts them into 3D arrays. The first has dimensions 512×1×1, the second 1×512×1 and the last 1×1×128, and each has the sequence of values that we previously fed in.
- We throw away the third one and replace it with our height-field. If our height-field has less than two dimensions, we bump it up to two dimensions. (This lets us pass in a single value if we want a uniform height-field.)
- The horrifying magic occurs when we use this bizarre collection of three arrays as the indices into source. First of all, numpy applies its broadcasting rules to expand all of index arrays to have matching dimension. The result of this is that all three of our indexing arrays end up with dimensions 512×512×1.
- The dimensions of the indexing arrays determine the dimensions of the result of the indexing operation. Each element of the output array is determined by looking up the source array with the coordinates from the indexing arrays at the corresponding position. The X and Z indexing arrays basically just pass through the X and Z coordinates unmodified. The Y indexing array is our height field.
- Finally, we use numpy.squeeze to discard the last dimension.
That's quite enough for today. I'm now undecided what I'll discuss next week. I'm really not sure who's reading and what sort of interests they have. Are you beginners or experts? Do you want tutorials and stuff that you can easily try at home, or would you prefer a faster paced tour of the fancy bits? Is it important to build things up a step at a time or would you prefer I skip over things to get to the good stuff? How painful is this numpy stuff? I could spend some time drawing up diagrams to better explain it, but that would probably mean it will be quite a while before we get to the interesting shader stuff. Let me know what you think in the comments.
Next time: using numpy to flatten the map.
No comments:
Post a Comment