├── LICENSE ├── README.md └── heightmap.lua /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011 Marc Lepage 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Heightmap 2 | ========= 3 | 4 | Overview 5 | -------- 6 | 7 | A Lua module by Marc Lepage for producing heightmaps. 8 | 9 | The heightmap module uses the diamond-square algorithm to generate cloud or plasma fractal heightmaps which can be used for terrain. 10 | 11 | Usage 12 | ----- 13 | 14 | -- import module 15 | require "heightmap" 16 | 17 | -- create 32x32 heightmap 18 | map = heightmap.create(32, 32) 19 | 20 | -- examine each height value 21 | for x = 0, map.w do 22 | for y = 0, map.h do 23 | print(map[x][y]) 24 | end 25 | end 26 | 27 | -- define a custom height function 28 | -- (reusing the default but scaling it) 29 | function f(map, x, y, d, h) 30 | return 2 * heightmap.defaultf(map, x, y, d, h) 31 | end 32 | 33 | -- use it to create a larger non-square heightmap 34 | map = heightmap.create(100, 200, f) 35 | 36 | How it Works 37 | ------------ 38 | 39 | The heightmap must be a square the size of a power of two, plus one, so that it can be evenly divided. For example, 4x4 cells will require 5x5 vertices. If another size is specified, a sufficiently large power of two square will be used, and the result clipped to the desired size. 40 | 41 | First the four corners are seeded with a random value (C). 42 | 43 | Then each square is used to set the value of its center (S) based on the average of its four corners (plus some randomness). 44 | 45 | Then each diamond is used to set the value of its center (D) based on the average of its four points (plus some randomness). 46 | 47 | The square and diamond steps continue until all values have been set: 48 | 49 | 4 S 2 D 2 S 1 D 1 50 | C...C c...c c.D.c c.d.c cDdDc 51 | ..... ..... ..... .S.S. DsDsD 52 | ..... ..S.. D.s.D d.s.d dDsDd 53 | ..... ..... ..... .S.S. DsDsD 54 | C...C c...c c.D.c c.d.c cDdDc 55 | 56 | The default height function randomly displaces values by up to +/- 0.5 of the step size. So above, the corners will be from -2 to +2, the center will be the mean of the corners randomly displaced from -1 to +1, and so on. 57 | 58 | Resources 59 | --------- 60 | 61 | * [Wikipedia: heightmap](http://en.wikipedia.org/wiki/Heightmap) 62 | * [Wikipedia: diamond-square algorithm](http://en.wikipedia.org/wiki/Diamond-square_algorithm) 63 | * [Wikipedia: fractal landscape](http://en.wikipedia.org/wiki/Fractal_landscape) 64 | -------------------------------------------------------------------------------- /heightmap.lua: -------------------------------------------------------------------------------- 1 | -- Heightmap module 2 | -- Copyright (C) 2011 Marc Lepage 3 | 4 | local max, random = math.max, math.random 5 | 6 | module(...) 7 | 8 | -- Find power of two sufficient for size 9 | local function pot(size) 10 | local pot = 2 11 | while true do 12 | if size <= pot then return pot end 13 | pot = 2*pot 14 | end 15 | end 16 | 17 | -- Create a table with 0 to n zero values 18 | local function tcreate(n) 19 | local t = {} 20 | for i = 0, n do t[i] = 0 end 21 | return t 22 | end 23 | 24 | -- Square step 25 | -- Sets map[x][y] from square of radius d using height function f 26 | local function square(map, x, y, d, f) 27 | local sum, num = 0, 0 28 | if 0 <= x-d then 29 | if 0 <= y-d then sum, num = sum + map[x-d][y-d], num + 1 end 30 | if y+d <= map.h then sum, num = sum + map[x-d][y+d], num + 1 end 31 | end 32 | if x+d <= map.w then 33 | if 0 <= y-d then sum, num = sum + map[x+d][y-d], num + 1 end 34 | if y+d <= map.h then sum, num = sum + map[x+d][y+d], num + 1 end 35 | end 36 | map[x][y] = f(map, x, y, d, sum/num) 37 | end 38 | 39 | -- Diamond step 40 | -- Sets map[x][y] from diamond of radius d using height function f 41 | local function diamond(map, x, y, d, f) 42 | local sum, num = 0, 0 43 | if 0 <= x-d then sum, num = sum + map[x-d][y], num + 1 end 44 | if x+d <= map.w then sum, num = sum + map[x+d][y], num + 1 end 45 | if 0 <= y-d then sum, num = sum + map[x][y-d], num + 1 end 46 | if y+d <= map.h then sum, num = sum + map[x][y+d], num + 1 end 47 | map[x][y] = f(map, x, y, d, sum/num) 48 | end 49 | 50 | -- Diamond square algorithm generates cloud/plasma fractal heightmap 51 | -- http://en.wikipedia.org/wiki/Diamond-square_algorithm 52 | -- Size must be power of two 53 | -- Height function f must look like f(map, x, y, d, h) and return h' 54 | local function diamondsquare(size, f) 55 | -- create map 56 | local map = { w = size, h = size } 57 | for c = 0, size do map[c] = tcreate(size) end 58 | -- seed four corners 59 | local d = size 60 | map[0][0] = f(map, 0, 0, d, 0) 61 | map[0][d] = f(map, 0, d, d, 0) 62 | map[d][0] = f(map, d, 0, d, 0) 63 | map[d][d] = f(map, d, d, d, 0) 64 | d = d/2 65 | -- perform square and diamond steps 66 | while 1 <= d do 67 | for x = d, map.w-1, 2*d do 68 | for y = d, map.h-1, 2*d do 69 | square(map, x, y, d, f) 70 | end 71 | end 72 | for x = d, map.w-1, 2*d do 73 | for y = 0, map.h, 2*d do 74 | diamond(map, x, y, d, f) 75 | end 76 | end 77 | for x = 0, map.w, 2*d do 78 | for y = d, map.h-1, 2*d do 79 | diamond(map, x, y, d, f) 80 | end 81 | end 82 | d = d/2 83 | end 84 | return map 85 | end 86 | 87 | -- Default height function 88 | -- d is depth (from size to 1 by powers of two) 89 | -- h is mean height at map[x][y] (from square/diamond of radius d) 90 | -- returns h' which is used to set map[x][y] 91 | function defaultf(map, x, y, d, h) 92 | return h + (random()-0.5)*d 93 | end 94 | 95 | -- Create a heightmap using the specified height function (or default) 96 | -- map[x][y] where x from 0 to map.w and y from 0 to map.h 97 | function create(width, height, f) 98 | f = f and f or defaultf 99 | -- make heightmap 100 | local map = diamondsquare(pot(max(width, height)), f) 101 | -- clip heightmap to desired size 102 | for x = 0, map.w do for y = height+1, map.h do map[x][y] = nil end end 103 | for x = width+1, map.w do map[x] = nil end 104 | map.w, map.h = width, height 105 | return map 106 | end 107 | --------------------------------------------------------------------------------