Generic pattern maps

posted in Gamedev info
Published January 26, 2015
Advertisement
generic pattern maps

often times you need a pattern for something in a game.
for example: what texture tile to use with a given ground quad.

i used to create randomly generated pattern maps for different things:
plants, rocks, bushes, trees, ground quad textures, etc.

then it occurred to me that you could use a generic random pattern map,
and write functions to return whatever you needed.

a generic pattern map is an array of ints with random values.

some examples (from Caveman 3.0)...

Caveman uses a 100x100 generic random pattern map with values from 1
through 100.

// ####### GENERIC RANDOM PATTERN MAP ############ int randmap[100][100]; void init_randmap(){int a,b;for (a=0; a<100; a++) { for (b=0; b<100; b++) { randmap[a]=dice(100); } } } // ####### END GENERIC RANDOM PATTERN MAP ######## // used by hm_canyon2// x and z get divded by 20 to scale them down to the pattern map.// then they get modded by 100 to map them into the pattern map // range of 0..99 by 0..99// the value at that location in the pattern map is then returned. int get_randmap_value(float x,float z){int x2,z2;x2=(int)x;x2/=20;x2=x2%100;z2=(int)z;z2/=20;z2=z2%100;return(randmap[x2][z2]);} // heightmap formula for canyon floors// the heightmap repeats every 2000 units.// 20 units in the heightmap = one unit in the generic // pattern map.// the formula does a double interpolation.// first it determines the height of the four closest // pattern map cells.// then it does a lerp from cell x,z to x+1,z // and x,z+1, to x+1,z+1.// it then lerps between these two results to get the // final interpolated height at the desired location.// 100 is then subtracted from this value, so the results// range from -100 (canyon floor) to 0 (even with canyon rim). float hm_canyon2(float x,float z){int y0,y1,y2,y3;// rem is remainderfloat rem,y4,y5,y6;rem=x;while (rem>=2000.0f) { rem-=2000.0f; }while (rem>=20.0f) { rem-=20.0f; }y0=get_randmap_value(x,z);y1=get_randmap_value(x+20.0f,z);y2=get_randmap_value(x,z+20.0f);y3=get_randmap_value(x+20.0f,z+20.0f);y4=f_lerp(rem,0.0f,20.0f,(float)y0,(float)y1);y5=f_lerp(rem,0.0f,20.0f,(float)y2,(float)y3);rem=z;while (rem>=2000.0f) { rem-=2000.0f; }while (rem>=20.0f) { rem-=20.0f; }y6=f_lerp(rem,0.0f,20.0f,y4,y5);// / y6 2.0fy6-=100.0f;return(y6);} // returns tall grass plant texture ID number to use int grasstex(int x,int z){x=x%100;z=z%100;if (randmap[x][z] < 51) { return(21); }return(121);} // returns prairie grass plant texture ID number to use int prairie_grasstex(int x,int z){x=x%100;z=z%100;if (randmap[x][z] < 31) { return(348); }if (randmap[x][z] < 61) { return(349); }if (randmap[x][z] < 81) { return(346); } return(347);} // returns savanna grass plant texture ID number to use int savanna_grasstex(int x,int z){x=x%100;z=z%100;if (randmap[x][z] < 31) { return(359); }if (randmap[x][z] < 61) { return(360); }if (randmap[x][z] < 81) { return(361); } return(362);} // returns grass plant mesh scale to use int grass_scale(int x,int z){int scale;x=x%100;z=z%100;scale=250+randmap[x][z];scale/=10;return(scale);} // returns grass plant mesh y rotation to use float grass_rotation(int x,int z){float rot;x=x%100;z=z%100;// rot = 0 thru 99% * 2pirot=(float)(randmap[x][z]-1);rot*=pi*2.0f;rot/=100.0f;return(rot);} // returns grass plant mesh offset (jitter) to use float grass_offset(int x,int z){float ofs;x=x%100;z=z%100;// grass step size is 5. offset is -2 thru 2.// offset = -2 + 4 * rand / 100ofs=(float)randmap[x][z];ofs*=4.0f;ofs/=100.0f;ofs-=2.0f;return(ofs);} // adds grass plant meshes to a terrain chunk// grass_stepsize is #defined as 5 void generate_terrain_grass(int c) // c is chunk #{int x1,z1, // SW corner of area to draw x2,z2, // NE corner of area to draw x,z, // un-normalized grass coords scale;Zdrawinfo a;get_chunk_area(c,&x1,&z1,&x2,&z2);ZeroMemory(&a,sizeof(Zdrawinfo));a.materialID=2;a.meshID=27; // 27 = plant2.x a.cull=0;a.alphatest=1;a.clamp=1;a.rad=6;a.cliprng=grass_cliprng;for (z=z1; z<=z2; z+=grass_stepsize) { for (x=x1; x<=x2; x+=grass_stepsize) {if (waterinway(CL[c].mx,CL[c].mz,x,z)) continue;if (storepit_in_way(CL[c].mx,CL[c].mz,x,z)) continue; scale=grass_scale(x,z); a.sx=(float)scale*0.1f; a.sy=(float)scale*0.09f; a.sz=(float)scale*0.1f; a.x=(float)x+grass_offset(x,z); a.z=(float)z+grass_offset(x,z); a.y=heightmap(CL[c].mx,CL[c].mz,(float)x,(float)z); a.ry=grass_rotation(x,z); a.texID=grasstex(x,z); add_zdrawinfo(c,&a); } }}
0 likes 1 comments

Comments

Waterlimon

Seems like what you have is essentially a random hash. You take a value (lets say x,y coords into your 'randmap') and get a deterministic random value back (same value every time for given input)

Thus, all you need is something like randvalue = randhash(input) (where input could be some combination of x,y,seed or just x,y if you dont need world seeds). There was a related article on gamasutra recently I found interesting:

http://www.gamasutra.com/blogs/RuneSkovboJohansen/20150105/233505/Primer_on_Repeatable_Random_Numbers.php

The article also compares some hash functions so I guess you could grab one.

Using such random hashes/sequences is how everyone seems to do procedural generation today.

Im not sure whether a lookup table like you use is faster performance wise (on one hand the hash functions do more, but on the other hand lookup tables are bad for cache). But not using lookup tables has the benefit that you dont get the repeating patterns that you seem to face.

January 28, 2015 02:40 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement