├── .gitignore ├── helper.h ├── unity_base.cginc ├── custom_base.txt ├── unreal_base.txt ├── license.txt ├── Makefile ├── dont_modify.txt ├── README.md ├── shader_base.frag └── wang.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | wang_layout_array.* 3 | unity_wang.cginc 4 | unreal_snippet.txt 5 | custom_snippet.txt 6 | -------------------------------------------------------------------------------- /helper.h: -------------------------------------------------------------------------------- 1 | #ifndef WANG_SHADER_HELPER_H 2 | 3 | #define HASH_HELPER # 4 | #define HASH_HELPER_2(x) x 5 | #define ESCAPED_HASH(x) HASH_HELPER_2(HASH_HELPER)x 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /unity_base.cginc: -------------------------------------------------------------------------------- 1 | #include "dont_modify.txt" 2 | 3 | #include "helper.h" 4 | 5 | ESCAPED_HASH(ifndef) ARTOMATIX_HELPER_METHODS_CGINC_INCLUDED 6 | ESCAPED_HASH(define) ARTOMATIX_HELPER_METHODS_CGINC_INCLUDED 7 | 8 | #define wangSampler _WangTex 9 | #define PREDEFINED 10 | #include "shader_base.frag" 11 | 12 | ESCAPED_HASH(endif) 13 | -------------------------------------------------------------------------------- /custom_base.txt: -------------------------------------------------------------------------------- 1 | #include "dont_modify.txt" 2 | 3 | //#define FLIP_V // if the v component of your uvs is flipped, enable this 4 | //#define GLSL // enable this if you're using glsl (as opposed to hlsl) 5 | 6 | #define PREDEFINED // disable this if you want to use the hashing method 7 | 8 | #include "shader_base.frag" 9 | 10 | -------------------------------------------------------------------------------- /unreal_base.txt: -------------------------------------------------------------------------------- 1 | #include "dont_modify.txt" 2 | 3 | #include "helper.h" 4 | 5 | 6 | ESCAPED_HASH(if) COMPUTESHADER 7 | return float4(0, 1, 0, 1); 8 | ESCAPED_HASH(else) 9 | #define NO_FUNCTION 10 | #define FLIP_V 11 | #define PREDEFINED 12 | #define GRID_SAMPLE(uv) wang.Sample(wangSampler, float2(uv.x, uv.y)) 13 | #define FINAL_SAMPLE_OVERRIDE(coords) coords 14 | #include "shader_base.frag" 15 | ESCAPED_HASH(endif) 16 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Artomatix Ltd 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: unity unreal custom 2 | 3 | compile = cpp -C -nostdinc $(1) -o - | sed '/^\#/d' | sed 's/SED_NL/\n/g' > $(2) 4 | 5 | shader_base.frag: wang_layout_array.txt helper.h 6 | 7 | wang_layout_array.png: wang_layout_array.txt 8 | 9 | wang_layout_array.txt: wang.py 10 | ./wang.py --size 1024 --check_size 3 --print false -o wang_layout_array 11 | 12 | clean: 13 | -rm wang_layout_array.* 14 | -rm unity_wang.cginc 15 | -rm unreal_snippet.txt 16 | -rm custom_snippet.txt 17 | 18 | ######### 19 | # unity # 20 | ######### 21 | unity: unity_wang.cginc wang_layout_array.png 22 | 23 | unity_wang.cginc: unity_base.cginc shader_base.frag 24 | $(call compile, unity_base.cginc, unity_wang.cginc) 25 | 26 | ########## 27 | # unreal # 28 | ########## 29 | unreal: unreal_snippet.txt wang_layout_array.png 30 | 31 | unreal_snippet.txt: unreal_base.txt shader_base.frag 32 | $(call compile, unreal_base.txt, unreal_snippet.txt) 33 | 34 | ########## 35 | # custom # 36 | ########## 37 | custom: custom_snippet.txt wang_layout_array.png 38 | 39 | custom_snippet.txt: custom_base.txt shader_base.frag 40 | $(call compile, custom_base.txt, custom_snippet.txt) 41 | -------------------------------------------------------------------------------- /dont_modify.txt: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // DO NOT MODIFY // 3 | // This file was automatically generated. See https://github.com/Artomatix/infinity_tile_shader // 4 | ////////////////////////////////////////////////////////////////////////////////////////////////// 5 | 6 | /* 7 | Copyright (c) 2016 Artomatix Ltd 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | */ 15 | // 16 | // 17 | // 18 | // 19 | // 20 | // 21 | // 22 | // 23 | // 24 | // 25 | // 26 | // 27 | // 28 | // 29 | // 30 | // 31 | // 32 | // 33 | // 34 | // 35 | // 36 | // 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Infinity Tile Shader 2 | Shader snippets for Artomatix Infinity Tiles (Wang Tiles) 3 | 4 | Artomatix Infinity Tiles are automatically generated Wang Tiles. 5 | Artomatix generates the Wang tiles, which is normally a tedious and lengthy process (example here: http://www.pathofexile.com/forum/view-thread/55091). 6 | This repository contains some shader snippets for rendering these tiles in either HLSL or GLSL. 7 | 8 | ## What are Wang Tiles? 9 | (Chunks of this explanation are from http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter12.html) 10 | 11 | 12 | Wang tiles are square tiles in which each tile edge is encoded with a color. 13 | A valid tiling requires all shared edges between tiles to have matching colors. 14 | When a set of Wang tiles is filled with texture patterns that are continuous across matching tile edges, 15 | a valid tiling from such a set can produce an arbitrarily large texture without pattern discontinuity. 16 | A sample Wang tiling is shown below: 17 | 18 | ![](http://http.developer.nvidia.com/GPUGems2/elementLinks/12_tilebased_02a.jpg) 19 | 20 | We carefully pack the texture tiles into a single texture map so that we can perform texture fetching 21 | and filtering on this single texture via texturing hardware. 22 | 23 | ![](http://http.developer.nvidia.com/GPUGems2/elementLinks/12_tilebased_03.jpg) 24 | 25 | Because we use this arrangement, texture filtering and mipmapping will work as expected because each 26 | tile always tiles with the tiles beside it in our packed texture. 27 | 28 | ## How do we render them? 29 | We use an approach based on the one desribed in http://graphics.stanford.edu/papers/tile_mapping_gh2004/final/paper_final.pdf 30 | to render the wang tiles using a fragment shader. 31 | The basic algorithm is as follows: 32 | - Calculate which tile we are currently in 33 | - Generate a hash for each of the four edges of the current tile. Because the wang tile set we are using is complete, 34 | we are always able to render any combination of edge hashes 35 | - By carefully choosing the values we hash, we can make sure that the edge colours always match. Eg: we might hash the west side 36 | with hash(x), and the east side with hash(x-1). In this way, the west edge of tile (n, m) will always be the same as the east edge 37 | of tile (n+1, m) 38 | - We actually use both the x and y in our hashes with some multiplication and addition as it gives better results but we make sure 39 | not to violate the basic principle 40 | - The final step is to turn the 4 edge hashes into uv a tile index (x and y, from 0-4), and then turn that into a texture coordinate 41 | - Finally we do the texture sample. We do that here because we need to do a slightly specialised texture sample. Because our texture 42 | coords can vary a lot from one pixel to the next at a tile border, we can end up using bad mip levels just at the edges. This is 43 | obviously undesireable, so we use the built in mechanism of ddx and ddy to fix this (google ddx ddy hlsl if you don't know what 44 | this is) 45 | 46 | We also support a predefined chunk passed in as a texture. 47 | The texture is generated by wang.py, and just contains the xy grid coords in the 4x4 tile set in the red and green channels. 48 | The reason that we want this is that while the hashing method described above will produce decent results, there's no way to 49 | ensure that it doesn't place the same tile beside itself. The predefined section is generated sequentially, and tries to 50 | space tiles out so they aren't placed close to themselves, but will repeat over long distances. 51 | 52 | 53 | ## How do I integrate this? 54 | We have generators that create custom snippets for unity and unreal, and you can generate a snippet for your own custom engine too. 55 | To run the generators, just clone this repo to something with a unix shell, make, python and cpp (cpp comes with any or c++ compiler) 56 | installed (cygwin should do, or osx or linux), and run make. Also require the PIL python libary. 57 | You can download prebuilt versions of the unity and unreal snippets [here](https://github.com/Artomatix/infinity_tile_shader/releases/download/v0.1/precompiled_unity_unreal.zip). 58 | 59 | ### Unity 60 | You can get our unity plugin from our site [here](https://artomatix.com/?c=github) which handles all of this, but if you don't want to, you can use our shader snippet from here 61 | directly (it's the same code). 62 | running make will create unity_wang.cginc. You can then include this and use the wangSample function it defines, which takes two params, 63 | a texture sampler (which must contain a packed wang tile set), and the input uv coords. You must also define a sampler called wangSampler, 64 | into which you must pass the wang_layout_array.png file which is generated when you run make. This texture must have its Texture type set to 65 | advanced, and then disable generate mip maps, format to Automatic Truecolor, and filter mode to Point. 66 | Alternatively, you can omit the wangSampler and disable the pregenerated section in the centre by adding #define NO_PREDEFINED to 67 | unity_base.cginc. 68 | 69 | ### Unreal 70 | You can get our unreal plugin from our site [here](https://artomatix.com/?c=github) which handles all of this, but if you don't want to, you can use our shader snippet from here 71 | directly (it's the same code). 72 | To add in the snippet (which is generated into unreal_snippet.txt), you need to create a material in unreal, and add a custom node. 73 | Then paste the contents of unreal_snippet.txt into the code box in the custom node. On the custom node, set a few properties: Set 74 | Output Type to CMOT Float 2, create two inputs called uv, and wang. 75 | Create two Texture Objects (not Texture Samplers) and set their textures to the packed wang tile set, and the wang_layout_array.png 76 | that is generated by the makefile, then pass them into the tex and wang inputs accordingly. 77 | Pass the texture coords into the uv input (TexCoord node). 78 | The wang_layout_array texture needs a few import settings in unreal: Mip Gen Settings: NoMipmaps, Texture Group: 2D pixels(unfiltered), 79 | Compression Settings: VectorDisplacementmap (RGBA8), and untick SRGB. You might need to click reimport in the top right after setting these. 80 | The output from the custom node is our new UV coords, which we can pass into a TextureSample node. 81 | To fix any problems with mipmapping, set the MipValueMode on the TextureSample node to Derivative(explicit derivative to compute mip level), 82 | then pass in the original coords run through DDX and DDY nodes. 83 | 84 | ### Custom 85 | Have a look at custom_base.txt, and put in whatever settings you want in there, then run make. 86 | A snippet will be generated in custom_snippet.txt, which you can integrate into your game as you see fit. 87 | If you do use the pregenerated section, you'll need to define a sampler called wangSampler and pass in the wang_layout_array.png texture 88 | as described in the unreal and unity sections. 89 | Make sure to disable mipmapping and compression on this texture, and do no texture filtering (filtering set to point or nearest). 90 | -------------------------------------------------------------------------------- /shader_base.frag: -------------------------------------------------------------------------------- 1 | #ifdef GLSL 2 | #define float4 vec4 3 | #define float2 vec2 4 | #define frac fract 5 | #define tex2D texture2D 6 | #endif 7 | 8 | // just a sample that guarantees using the highest mip level 9 | #ifndef GRID_SAMPLE 10 | #ifdef GLSL 11 | #define GRID_SAMPLE(uv) texture2D(wangSampler, uv, -999.0) 12 | #else 13 | #define GRID_SAMPLE(uv) tex2Dlod(wangSampler, float4((uv.x), (uv.y), 0, 0)) 14 | #endif 15 | #endif 16 | 17 | #define HASH(in1, in2) (int(frac(sin(dot(float2(in1+2*in2, in1+in2),float2(12.9898,78.233))) * 43758.5453) > 0.5)) 18 | 19 | // This function will sample a packed wang tile set with two edge colours. This is pretty much the only kind of wang 20 | // tile you can even use. The presumption is that the input texture coordinates will exceed 1, as in a normal terrain 21 | // in a game engine, where each tile of the terrain will increase the texcoords by 1. 22 | // Because there are only two edge colours, the hash function just returns 0 or 1. 23 | // The basic algorithm is as follows: 24 | // - calculate which tile we are currently in 25 | // - generate a hash for each of the four edges of the current tile. Because the wang tile set we are using is complete, 26 | // we are always able to render any combination of edge hashes. 27 | // - by carefully choosing the values we hash, we can make sure that the edge colours always match. Eg: we might hash the west side 28 | // with hash(x), and the east side with hash(x-1). In this way, the west edge of tile (n, m) will always be the same as the east edge 29 | // of tile (n+1, m) 30 | // - we actually use both the x and y in our hashes with some multiplication and addition as it gives better results but we make sure 31 | // not to violate the basic principle 32 | // - The final step is to turn the 4 edge hashes into uv a tile index (x and y, from 0-4), and then turn that into a texture coordinate 33 | // - finally we do the texture sample. We do that here because we need to do a slightly specialised texture sample. Because our texture 34 | // coords can vary a lot from one pixel to the next at a tile border, we can end up using bad mip levels just at the edges. This is 35 | // obviously undesireable, so we use the built in mechanism of ddx and ddy to fix this (google ddx ddy hlsl if you don't know what 36 | // this is) 37 | // 38 | // Finally, this function also supports a predefined chunk passed in as a texture, when you define the macro PREDEFINED. 39 | // The texture is generated by wang.py, and just contains the xy grid coords in the 4x4 tile set in the red and green channels. 40 | // The reason that we want this is that while the hashing method described above will produce decent results, there's no way to 41 | // ensure that it doesn't place the same tile beside itself. The predefined section is generated sequentially, and tries to 42 | // space tiles out so they aren't placed close to themselves, but will repeat over long distances. 43 | #ifndef NO_FUNCTION 44 | float4 wangSample(sampler2D texSampler, float2 uv) 45 | { 46 | #endif 47 | #ifdef FLIP_V 48 | uv.y = 1.0-uv.y; 49 | #endif 50 | 51 | float2 t = floor(uv); 52 | 53 | int tileX = int(t.x); 54 | int tileY = int(t.y); 55 | 56 | float2 tile = float2(0.0, 0.0); 57 | 58 | #ifdef PREDEFINED 59 | { 60 | #include "wang_layout_array.txt" // defines grid_size 61 | 62 | int predef_tileX = tileX; 63 | int predef_tileY = tileY; 64 | 65 | #ifdef FLIP_V 66 | predef_tileY = grid_size - tileY - 1; 67 | #endif 68 | 69 | // grab from predefined 70 | float2 hashUv = float2(tileX, predef_tileY); 71 | hashUv += float2(0.5, 0.5); 72 | hashUv /= float2(grid_size, grid_size); 73 | int grid_entry = int(GRID_SAMPLE(hashUv).r * 255.0); 74 | 75 | tile = GRID_SAMPLE(hashUv).rg * 255.0; 76 | } 77 | #else // PREDEFINED 78 | { 79 | // use the hashing algorithm 80 | 81 | // senw - South, East, North, West 82 | int s = HASH(tileY-1, tileX); 83 | int e = HASH(tileX+1, tileY); 84 | int n = HASH(tileY, tileX); 85 | int w = HASH(tileX, tileY); 86 | 87 | // turn senw values into a tile specifier (x,y coord) of the tile to use (from the tileset) 88 | // in webgl we can't do lookup tables, so we use a big 'ol nasty if-else chain 89 | 90 | #ifndef NO_LOOKUP_TABLES 91 | { 92 | static const float2 tiles[16] = 93 | { 94 | float2(0.0, 3.0), // 0, 0, 0, 0 // 0 95 | float2(3.0, 3.0), // 0, 0, 0, 1 // 1 96 | float2(0.0, 2.0), // 0, 0, 1, 0 // 2 97 | float2(3.0, 2.0), // 0, 0, 1, 1 // 3 98 | float2(1.0, 3.0), // 0, 1, 0, 0 // 4 99 | float2(2.0, 3.0), // 0, 1, 0, 1 // 5 100 | float2(1.0, 2.0), // 0, 1, 1, 0 // 6 101 | float2(2.0, 2.0), // 0, 1, 1, 1 // 7 102 | float2(0.0, 0.0), // 1, 0, 0, 0 // 8 103 | float2(3.0, 0.0), // 1, 0, 0, 1 // 9 104 | float2(0.0, 1.0), // 1, 0, 1, 0 // 10 105 | float2(3.0, 1.0), // 1, 0, 1, 1 // 11 106 | float2(1.0, 0.0), // 1, 1, 0, 0 // 12 107 | float2(2.0, 0.0), // 1, 1, 0, 1 // 13 108 | float2(1.0, 1.0), // 1, 1, 1, 0 // 14 109 | float2(2.0, 1.0), // 1, 1, 1, 1 // 15 110 | }; 111 | 112 | tile = tiles[w + 2*n + 4*e + 8*s]; 113 | 114 | } 115 | #else // ndef NO_LOOKUP_TABLES 116 | { 117 | if(s == 0) 118 | { 119 | if(e == 0) 120 | { 121 | if(n == 0) 122 | { 123 | if(w == 0) 124 | tile = float2(0.0, 3.0); // 0, 0, 0, 0 125 | else // w == 1.0 126 | tile = float2(3.0, 3.0); // 0, 0, 0, 1 127 | } 128 | else // n == 1.0 129 | { 130 | if(w == 0) 131 | tile = float2(0.0, 2.0); // 0, 0, 1, 0 132 | else // w == 1.0 133 | tile = float2(3.0, 2.0); // 0, 0, 1, 1 134 | } 135 | } 136 | else // e == 1.0 137 | { 138 | if(n == 0) 139 | { 140 | if(w == 0) 141 | tile = float2(1.0, 3.0); // 0, 1, 0, 0 142 | else // w == 1.0 143 | tile = float2(2.0, 3.0); // 0, 1, 0, 1 144 | } 145 | else // n == 1.0 146 | { 147 | if(w == 0) 148 | tile = float2(1.0, 2.0); // 0, 1, 1, 0 149 | else // w == 1.0 150 | tile = float2(2.0, 2.0); // 0, 1, 1, 1 151 | } 152 | } 153 | } 154 | else // s == 1.0 155 | { 156 | if(e == 0) 157 | { 158 | if(n == 0) 159 | { 160 | if(w == 0) 161 | tile = float2(0.0, 0.0); // 1, 0, 0, 0 162 | else // w == 1.0 163 | tile = float2(3.0, 0.0); // 1, 0, 0, 1 164 | } 165 | else // n == 1.0 166 | { 167 | if(w == 0) 168 | tile = float2(0.0, 1.0); // 1, 0, 1, 0 169 | else // w == 1.0 170 | tile = float2(3.0, 1.0); // 1, 0, 1, 1 171 | } 172 | } 173 | else // e == 1.0 174 | { 175 | if(n == 0) 176 | { 177 | if(w == 0) 178 | tile = float2(1.0, 0.0); // 1, 1, 0, 0 179 | else // w == 1.0 180 | tile = float2(2.0, 0.0); // 1, 1, 0, 1 181 | } 182 | else // n == 1.0 183 | { 184 | if(w == 0) 185 | tile = float2(1.0, 1.0); // 1, 1, 1, 0 186 | else // w == 1.0 187 | tile = float2(2.0, 1.0); // 1, 1, 1, 1 188 | } 189 | } 190 | } 191 | } 192 | #endif // ndef NO_LOOKUP_TABLES 193 | } 194 | #endif // PREDEFINED 195 | 196 | // calculate the final texture coordinates from the computed tile value 197 | float2 finalCoords; 198 | finalCoords.x = (tile.x/4.0) + frac(uv.x)/4.0; 199 | finalCoords.y = ((3.0-tile.y)/4.0) + frac(uv.y)/4.0; 200 | 201 | #ifdef FLIP_V 202 | finalCoords.y = 1.0-finalCoords.y; 203 | #endif 204 | 205 | 206 | // do the final texture sample 207 | #ifdef FINAL_SAMPLE_OVERRIDE 208 | return FINAL_SAMPLE_OVERRIDE(finalCoords); 209 | #else 210 | #ifdef GLSL 211 | ESCAPED_HASH(ifdef) GL_ES 212 | return texture2D(texSampler, finalCoords, -999.0);// GL_ES doesn't support textureGrad, so just force it to use the highest mip level 213 | ESCAPED_HASH(else) 214 | return textureGrad(texSampler, finalCoords, dFdx(uv/4.0), dFdy(uv/4.0)); 215 | ESCAPED_HASH(endif) 216 | #else 217 | return tex2D(texSampler, finalCoords, ddx(uv/4), ddy(uv/4)); 218 | #endif 219 | #endif 220 | 221 | #ifndef NO_FUNCTION 222 | } 223 | #endif 224 | -------------------------------------------------------------------------------- /wang.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import random 5 | import optparse 6 | import PIL.Image as Image 7 | 8 | class Tile(object): 9 | def __init__(self, s=None, e=None, n=None, w=None, senw=None): 10 | self.dirs = [s, e, n, w] 11 | 12 | if senw != None: 13 | if len(senw) > 4: 14 | raise Exception("bad senw") 15 | 16 | self.dirs = senw 17 | 18 | self.set_dirs = [d is not None for d in self.dirs] 19 | 20 | def get_perms(self): 21 | perms = set() 22 | allSet = True 23 | for i in range(0, 4): 24 | if not self.set_dirs[i]: 25 | allSet = False 26 | for j in range(2): 27 | newVals = self.dirs[:] 28 | newVals[i] = j 29 | t = Tile(senw=newVals) 30 | perms=perms.union(t.get_perms()) 31 | if allSet: 32 | perms.add(self) 33 | 34 | return perms 35 | 36 | def get_s(self): 37 | return self.dirs[0] 38 | def get_e(self): 39 | return self.dirs[1] 40 | def get_n(self): 41 | return self.dirs[2] 42 | def get_w(self): 43 | return self.dirs[3] 44 | 45 | def set_s(self, val): 46 | self.dirs[0] = val 47 | def set_e(self, val): 48 | self.dirs[1] = val 49 | def set_n(self, val): 50 | self.dirs[2] = val 51 | def set_w(self, val): 52 | self.dirs[3] = val 53 | 54 | 55 | def get_id(self): 56 | if self.get_s() == 0: 57 | if self.get_e() == 0: 58 | if self.get_n() == 0: 59 | if self.get_w() == 0: 60 | return 0 61 | else: 62 | return 1 63 | else: 64 | if self.get_w() == 0: 65 | return 2 66 | else: 67 | return 3 68 | else: 69 | if self.get_n() == 0: 70 | if self.get_w() == 0: 71 | return 4 72 | else: 73 | return 5 74 | else: 75 | if self.get_w() == 0: 76 | return 6 77 | else: 78 | return 7 79 | else: 80 | if self.get_e() == 0: 81 | if self.get_n() == 0: 82 | if self.get_w() == 0: 83 | return 8 84 | else: 85 | return 9 86 | else: 87 | if self.get_w() == 0: 88 | return 10 89 | else: 90 | return 11 91 | else: 92 | if self.get_n() == 0: 93 | if self.get_w() == 0: 94 | return 12 95 | else: 96 | return 13 97 | else: 98 | if self.get_w() == 0: 99 | return 14 100 | else: 101 | return 15 102 | 103 | def get_xy(self): 104 | if self.get_s() == 0: 105 | if self.get_e() == 0: 106 | if self.get_n() == 0: 107 | if self.get_w() == 0: 108 | return (0, 3) 109 | else: 110 | return (3, 3) 111 | else: 112 | if self.get_w() == 0: 113 | return (0, 2) 114 | else: 115 | return (3, 2) 116 | else: 117 | if self.get_n() == 0: 118 | if self.get_w() == 0: 119 | return (1, 3) 120 | else: 121 | return (2, 3) 122 | else: 123 | if self.get_w() == 0: 124 | return (1, 2) 125 | else: 126 | return (2, 2) 127 | else: 128 | if self.get_e() == 0: 129 | if self.get_n() == 0: 130 | if self.get_w() == 0: 131 | return (0, 0) 132 | else: 133 | return (3, 0) 134 | else: 135 | if self.get_w() == 0: 136 | return (0, 1) 137 | else: 138 | return (3, 1) 139 | else: 140 | if self.get_n() == 0: 141 | if self.get_w() == 0: 142 | return (1, 0) 143 | else: 144 | return (2, 0) 145 | else: 146 | if self.get_w() == 0: 147 | return (1, 1) 148 | else: 149 | return (2, 1) 150 | 151 | 152 | 153 | 154 | 155 | def __eq__(self, other): 156 | return self.get_id() == other.get_id() 157 | 158 | def __ne__(self, other): 159 | return not self.__eq__(other) 160 | 161 | def __hash__(self): 162 | return hash(self.__str__()) 163 | 164 | def __repr__(self): 165 | return "Tile(senw=%s)" % str(self.dirs) 166 | 167 | __str__ = __repr__ 168 | 169 | 170 | def get_possibilities(t, grid, x, y, size_out_to_check_for_duplicates): 171 | perms = {k: 0 for k in t.get_perms()} 172 | weights = [1] * len(perms) 173 | 174 | size = len(grid) 175 | 176 | def helper_add(coords): 177 | t = grid[coords[1]][coords[0]] 178 | if t in perms: 179 | perms[t] += 1 180 | 181 | 182 | for dist in range(size_out_to_check_for_duplicates): 183 | dist += 1 184 | 185 | curr = [x-dist-1,y+1] 186 | 187 | if curr[0] < 0: 188 | break 189 | 190 | doContinue = False 191 | 192 | for i in range(dist+1): 193 | curr[0] += 1 194 | curr[1] -= 1 195 | 196 | if curr[0] >= size or curr[1] >= size or curr[1] < 0: 197 | doContinue = True 198 | break 199 | 200 | helper_add(curr) 201 | 202 | curr[0] += 1 203 | 204 | if doContinue or curr[0] >= size: 205 | continue 206 | 207 | helper_add(curr) 208 | 209 | for i in range(dist-1): 210 | curr[0] += 1 211 | curr[1] += 1 212 | 213 | if curr[0] >= size or curr[1] >= size or curr[1] < 0: 214 | break 215 | 216 | helper_add(curr) 217 | 218 | return perms 219 | 220 | def get_best_from_perms(perms): 221 | best = [] 222 | bestVal = 999999999999999999999999 223 | for t in perms.keys(): 224 | if perms[t] < bestVal: 225 | bestVal = perms[t] 226 | best = [t] 227 | elif perms[t] == bestVal: 228 | best.append(t) 229 | 230 | return random.choice(best) 231 | 232 | def grid_to_ascii_art(grid): 233 | size = len(grid) 234 | 235 | out = "" 236 | out += "-" 237 | for x in range(size): 238 | out += "----" 239 | out += "\n" 240 | 241 | for y in range(size): 242 | for x in range(size): 243 | out += "| " + str(grid[y][x].get_n()) + " " 244 | out += "|\n" 245 | 246 | for x in range(size): 247 | out += "|" + str(grid[y][x].get_w()) + " " + str(grid[y][x].get_e()) 248 | out += "|\n" 249 | 250 | for x in range(size): 251 | out += "| " + str(grid[y][x].get_s()) + " " 252 | out += "|\n" 253 | 254 | out += "-" 255 | for x in range(size): 256 | out += "----" 257 | out += "\n" 258 | 259 | return out 260 | 261 | def check_grid(grid): 262 | size = len(grid) 263 | 264 | for y in range(1, size): 265 | for x in range(1, size): 266 | if grid[y][x].get_n() != grid[y-1][x].get_s() or grid[y][x].get_w() != grid[y][x-1].get_e(): 267 | raise Exception("Invalid grid!") 268 | 269 | def generate_grid(size, size_out_to_check_for_duplicates): 270 | grid = [[None for i in range(size)] for j in range(size)] 271 | 272 | for y in range(size): 273 | if y % 10 == 0: 274 | print >> sys.stderr, "generating row %d" % y 275 | for x in range(size): 276 | s = None 277 | e = None 278 | 279 | if x == 0: 280 | w = None 281 | else: 282 | w = grid[y][x-1].get_e() 283 | 284 | 285 | if y == 0: 286 | n = None 287 | else: 288 | n = grid[y-1][x].get_s() 289 | 290 | perms = get_possibilities(Tile(s, e, n, w), grid, x, y, size_out_to_check_for_duplicates) 291 | grid[y][x] = get_best_from_perms(perms) 292 | 293 | for i in range(size): 294 | grid[i][0].set_w(grid[i][size-1].get_e()) 295 | grid[0][i].set_n(grid[size-1][i].get_s()) 296 | 297 | return grid 298 | 299 | def grid_to_shader_image(grid): 300 | size = len(grid) 301 | 302 | data = [] 303 | 304 | for y in range(size): 305 | for x in range(size): 306 | val = grid[y][x].get_xy() 307 | data.append((val[0], val[1], 0)) 308 | 309 | img = Image.new('RGB', (size, size)) 310 | img.putdata(data) 311 | 312 | return img 313 | 314 | def __main__(): 315 | parser = optparse.OptionParser() 316 | parser.add_option("-s", "--size", dest="size", default=100) 317 | parser.add_option("-c", "--check_size", dest="check_size", default=3, help="distance out from each tile to check for duplicates") 318 | parser.add_option("-p", "--print", dest="do_print", default='true') 319 | parser.add_option("-o", "--output_filename", dest="output_filename", default="output") 320 | (options, args) = parser.parse_args() 321 | 322 | size = int(options.size) 323 | size_out_to_check_for_duplicates = int(options.check_size) 324 | do_print = options.do_print in ['y', 'yes', 'true'] 325 | 326 | grid = generate_grid(size, size_out_to_check_for_duplicates) 327 | check_grid(grid) 328 | 329 | with open(options.output_filename + ".txt", "w") as f: 330 | f.write("int grid_size = %d;" % size) 331 | 332 | grid_to_shader_image(grid).save(options.output_filename + ".png") 333 | 334 | if do_print: 335 | print grid_to_ascii_art(grid) 336 | 337 | if __name__ == "__main__": 338 | __main__() 339 | --------------------------------------------------------------------------------