├── .github └── workflows │ ├── build.yml │ └── docs.yml ├── .gitignore ├── .gitmodules ├── LICENSE.md ├── README.md ├── bin ├── config.nims ├── createPpm.nim ├── noise1d.nim ├── noise2d.nim └── private │ └── cli.nim ├── nim.cfg ├── perlin.nimble ├── src ├── perlin.nim └── perlin │ └── private │ ├── common.nim │ ├── perlin.nim │ └── simplex.nim └── tests ├── config.nims ├── t_noise.nim ├── t_perlin.nim └── t_simplex.nim /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [push, pull_request] 3 | jobs: 4 | 5 | test: 6 | runs-on: ubuntu-latest 7 | container: nimlang/choosenim 8 | strategy: 9 | matrix: 10 | threads: [on, off] 11 | nim: [ 2.0.0, 1.6.14 ] 12 | steps: 13 | - uses: actions/checkout@v1 14 | - name: Choose Nim 15 | run: choosenim update -y ${{ matrix.nim }} 16 | - name: Safe git directory 17 | run: git config --global --add safe.directory "$(pwd)" 18 | - name: Test 19 | run: nimble --threads:${{ matrix.threads }} test -y 20 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation Deploy 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | docs: 8 | concurrency: ci-${{ github.ref }} 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | container: nimlang/nim 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: nimble -y doc --index:on --out:docs --project src/perlin.nim 16 | - run: cp docs/perlin.html docs/index.html 17 | - name: Deploy documents 18 | uses: peaceiris/actions-gh-pages@v3 19 | with: 20 | github_token: ${{ secrets.GITHUB_TOKEN }} 21 | publish_dir: docs 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all 2 | * 3 | 4 | # Unignore all with extensions 5 | !*.* 6 | 7 | # Unignore all dirs 8 | !*/ 9 | 10 | build 11 | .DS_Store 12 | *.ppm 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nycto/PerlinNim/9a2f3b18e209ea98aac6b1aaac7ea63eff81dd62/.gitmodules -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright (c) 2015 James Frasca 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PerlinNim 2 | ========= 3 | 4 | [![Build](https://github.com/Nycto/PerlinNim/actions/workflows/build.yml/badge.svg)](https://github.com/Nycto/PerlinNim/actions/workflows/build.yml) 5 | [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/Nycto/PerlinNim/blob/main/LICENSE.md) 6 | 7 | A noise generation library for Nim, with support for both Perlin noise and 8 | Simplex noise. 9 | 10 | ![](http://nycto.github.io/PerlinNim/example.png) 11 | 12 | API Docs 13 | -------- 14 | 15 | http://nycto.github.io/PerlinNim/perlin.html 16 | 17 | A Quick Example 18 | --------------- 19 | 20 | ```nimrod 21 | import perlin, random, math 22 | 23 | # Call randomize from the 'math' module to ensure the seed is unique 24 | randomize() 25 | 26 | let noise = newNoise() 27 | 28 | # Output a 20x10 grid of noise 29 | for y in 0..10: 30 | for x in 0..20: 31 | let value = noise.simplex(x, y) 32 | # If you wanted to use Perlin noise, you would swap that line out with: 33 | # let value = noise.perlin(x, y) 34 | 35 | stdout.write( int(10 * value) ) 36 | stdout.write("\n") 37 | ``` 38 | 39 | License 40 | ------- 41 | 42 | This library is released under the MIT License, which is pretty spiffy. You 43 | should have received a copy of the MIT License along with this program. If 44 | not, see http://www.opensource.org/licenses/mit-license.php 45 | -------------------------------------------------------------------------------- /bin/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") -------------------------------------------------------------------------------- /bin/createPpm.nim: -------------------------------------------------------------------------------- 1 | ## 2 | ## Creates a PPM image file of perlin noise 3 | ## 4 | 5 | import perlin, math, random, parseopt2, os, strutils, private/cli 6 | 7 | # Seed the random number generator in Nim 8 | randomize() 9 | 10 | # The various config options to be filled from the CLI 11 | var filename: string 12 | var noiseType = NoiseType.perlin 13 | var width = 600 14 | var height = 600 15 | var octaves = 1 16 | var persistence = 1.0 17 | var seed = randomSeed() 18 | var zoom = 1.0 19 | 20 | # Parse the command line options 21 | parseOptions(opts): 22 | opts.parse(width, ["width", "w"], parseInt(it), it > 0) 23 | opts.parse(height, ["height", "h"], parseInt(it), it > 0) 24 | opts.parse(octaves, ["octaves", "o"], parseInt(it), it > 0) 25 | opts.parse(persistence, ["persistence", "p"], parseFloat(it), it > 0) 26 | opts.parse(seed, ["seed", "s"], uint32(parseInt(it))) 27 | opts.parse(zoom, ["zoom", "z"], parseFloat(it), it > 0) 28 | opts.parseFlag(noiseType, ["perlin"], NoiseType.perlin) 29 | opts.parseFlag(noiseType, ["simplex"], NoiseType.simplex) 30 | 31 | opts.parseArg(filename) 32 | if not filename.strip.toLowerAscii.endsWith(".ppm"): 33 | filename = filename.strip & ".ppm" 34 | 35 | 36 | type PpmImage = object 37 | ## Creates a PPM file 38 | file: File 39 | width, height: int 40 | 41 | proc newPPM( filename: string, width, height: int ): PpmImage = 42 | ## Creates a new PPM 43 | assert( width > 0 ) 44 | assert( height > 0 ) 45 | assert( filename.len > 0 ) 46 | 47 | var image = PpmImage( 48 | file: open(filename, fmwrite), 49 | width: width, height: height) 50 | 51 | image.file.write( "P3\n" ) 52 | image.file.write( width, " ", height, "\n" ) 53 | image.file.write( "255\n" ) 54 | return image 55 | 56 | proc draw( image: var PpmImage, r, g, b: int ) = 57 | ## Writes a pixel to a Ppm file 58 | assert( r >= 0 and g >= 0 and b >= 0 ) 59 | assert( r <= 255 and g <= 255 and b <= 255 ) 60 | image.file.write( r, " ", g, " ", b, "\n" ) 61 | 62 | iterator pixels( image: PpmImage ): tuple[x, y: int] = 63 | ## Presents each pixel in this image in the order it appears in the file 64 | for y in 0..image.height - 1: 65 | for x in 0..image.width - 1: 66 | yield (x: x, y: y) 67 | 68 | 69 | 70 | var noiseConf = newNoise( seed, octaves, persistence ) 71 | 72 | var image = newPPM( filename, width, height ) 73 | 74 | for point in image.pixels: 75 | let shade = int( 76 | 255 * noiseConf.get( 77 | noiseType, 78 | float(point.x) / zoom, 79 | float(point.y) / zoom) ) 80 | image.draw( shade, shade, shade ) 81 | 82 | 83 | -------------------------------------------------------------------------------- /bin/noise1d.nim: -------------------------------------------------------------------------------- 1 | ## 2 | ## Draws an 80 by 80 column of noise to the console 3 | ## 4 | 5 | import perlin, math, random, strutils, private/cli, sugar 6 | 7 | # Seed the random number generator in Nim 8 | randomize() 9 | 10 | # The various config options to be filled from the CLI 11 | var noiseType = NoiseType.perlin 12 | var columns = 80 13 | var rows = 40 14 | var octaves = 1 15 | var persistence = 1.0 16 | var seed = randomSeed() 17 | var zoom = 1.0 18 | 19 | # Parse the command line options 20 | parse( 21 | option(proc(it: int) = columns = it, ["width", "w", "cols", "columns"], (it) => parseInt(it), (it) => it > 0), 22 | option(proc(it: int) = rows = it, ["height", "h", "rows", "r"], (it) => parseInt(it), (it) => it > 0), 23 | option(proc(it: int) = octaves = it, ["octaves", "o"], (it) => parseInt(it), (it) => it > 0), 24 | option(proc(it: float) = persistence = it, ["persistence", "p"], (it) => parseFloat(it), (it) => it > 0), 25 | option(proc(it: uint32) = seed = it, ["seed", "s"], (it) => uint32(parseInt(it))), 26 | option(proc(it: float) = zoom = it, ["zoom", "z"], (it) => parseFloat(it), (it) => it > 0), 27 | flag(proc() = noiseType = NoiseType.perlin, ["perlin"]), 28 | flag(proc() = noiseType = NoiseType.simplex, ["simplex"]) 29 | ) 30 | 31 | let noiseConf = newNoise(seed, octaves, persistence) 32 | 33 | for y in 0..(rows - 1): 34 | let rand = noiseConf.get(noiseType, PI / zoom, float(y) / zoom) 35 | let offset = int(rand * float(columns - 1)) 36 | stdout.write(repeat(' ', offset)) 37 | stdout.write(".\n") 38 | 39 | -------------------------------------------------------------------------------- /bin/noise2d.nim: -------------------------------------------------------------------------------- 1 | ## 2 | ## Draws an 80 by 80 column of noise to the console 3 | ## 4 | 5 | import perlin, math, random, strutils, private/cli, sugar 6 | 7 | # Seed the random number generator in Nim 8 | randomize() 9 | 10 | # The various config options to be filled from the CLI 11 | var noiseType = NoiseType.perlin 12 | var columns = 80 13 | var rows = 40 14 | var octaves = 1 15 | var persistence = 1.0 16 | var seed = randomSeed() 17 | var zoom = 1.0 18 | 19 | # Parse the command line options 20 | parse( 21 | option(proc(it: int) = columns = it, ["width", "w", "cols", "columns"], (it) => parseInt(it), (it) => it > 0), 22 | option(proc(it: int) = rows = it, ["height", "h", "rows", "r"], (it) => parseInt(it), (it) => it > 0), 23 | option(proc(it: int) = octaves = it, ["octaves", "o"], (it) => parseInt(it), (it) => it > 0), 24 | option(proc(it: float) = persistence = it, ["persistence", "p"], (it) => parseFloat(it), (it) => it > 0), 25 | option(proc(it: uint32) = seed = it, ["seed", "s"], (it) => uint32(parseInt(it))), 26 | option(proc(it: float) = zoom = it, ["zoom", "z"], (it) => parseFloat(it), (it) => it > 0), 27 | flag(proc() = noiseType = NoiseType.perlin, ["perlin"]), 28 | flag(proc() = noiseType = NoiseType.simplex, ["simplex"]) 29 | ) 30 | 31 | let noiseConf = newNoise(seed, octaves, persistence) 32 | 33 | # The symbols to use for representing noise scale 34 | const symbols = [ " ", "░", "▒", "▓", "█", "█" ] 35 | 36 | for y in 0..(rows - 1): 37 | for x in 0..(columns - 1): 38 | let rand = noiseConf.get(noiseType, float(x) / zoom, float(y) / zoom) 39 | let index = int(floor(symbols.len * rand)) 40 | stdout.write( symbols[index] ) 41 | stdout.write("\n") 42 | 43 | -------------------------------------------------------------------------------- /bin/private/cli.nim: -------------------------------------------------------------------------------- 1 | ## 2 | ## Helper code for parsing CLI options 3 | ## 4 | 5 | import sugar, sequtils, tables, parseopt 6 | 7 | type 8 | OptionKind = enum 9 | ## The kind of flag represented 10 | WithValue, NoValue 11 | 12 | Option = object 13 | ## A single option definition 14 | keys: seq[string] 15 | case kind: OptionKind 16 | of WithValue: 17 | assign: (string, string) -> void 18 | of NoValue: 19 | enable: () -> void 20 | 21 | template failIf(condition: bool, msg: untyped) = 22 | ## Quits with a failure code and a message 23 | if condition: 24 | stderr.write("Error: ", msg, "\n") 25 | quit(1) 26 | 27 | func option*[T](assign: (T) -> void, keys: openArray[string], parse: (string) -> T, validate: (T) -> bool): Option = 28 | ## Creates an option with the given definition 29 | result = Option( 30 | keys: keys.toSeq, 31 | kind: WithValue, 32 | assign: proc(key: string, value: string): void = 33 | let parsed = parse(value) 34 | failIf(not validate(parsed), "Invalid value passed for " & key) 35 | assign(parsed) 36 | ) 37 | 38 | func option*[T](assign: (T) -> void, keys: openArray[string], parse: (string) -> T): Option = 39 | ## Creates an option with the given definition 40 | option(assign, keys, parse, (it) => true) 41 | 42 | func flag*(assign: () -> void, keys: openArray[string]): Option = 43 | ## Creates an option with the given definition 44 | result = Option(keys: keys.toSeq, kind: NoValue, enable: assign) 45 | 46 | func toTable(options: openarray[Option]): Table[string, Option] = 47 | ## Indexes a set of options by their flags 48 | for option in options: 49 | for key in option.keys: 50 | result[key] = option 51 | 52 | proc getOption(options: Table[string, Option], key: string): Option = 53 | ## Fetches an option given a flag 54 | failIf(not options.contains(key), "Unrecognized CLI flag: " & key) 55 | return options[key] 56 | 57 | proc parse*(options: varargs[Option]) = 58 | ## Parses the given options 59 | let lookup = options.toTable 60 | 61 | for kind, key, val in getopt(): 62 | case kind 63 | of cmdArgument: 64 | failIf(true, "Unexpected CLI argument: " & key) 65 | of cmdLongOption, cmdShortOption: 66 | let opt = lookup.getOption(key) 67 | case opt.kind 68 | of WithValue: 69 | failIf(val == "", "A value must be passed for CLI flag: " & key) 70 | opt.assign(key, val) 71 | of NoValue: 72 | failIf(val != "", "A value should not be passed for CLI flag: " & key) 73 | opt.enable() 74 | of cmdEnd: 75 | failIf(true, "Internal CLI parsing error") 76 | 77 | -------------------------------------------------------------------------------- /nim.cfg: -------------------------------------------------------------------------------- 1 | --hint[Processing]:off 2 | -------------------------------------------------------------------------------- /perlin.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.7.0" 4 | author = "Nycto" 5 | description = "A Perlin Noise Implementation" 6 | license = "MIT" 7 | srcDir = "src" 8 | skipDirs = @[] 9 | 10 | # Deps 11 | 12 | requires "nim >= 1.2.0" 13 | 14 | # Targets 15 | 16 | task demo, "Executes demo code": 17 | exec "nimble c ./bin/noise1d.nim" 18 | exec "nimble c ./bin/noise2d.nim" 19 | exec "./bin/noise2d --perlin" 20 | exec "./bin/noise1d --perlin" 21 | exec "./bin/noise2d --simplex" 22 | exec "./bin/noise1d --simplex --zoom=2" 23 | 24 | -------------------------------------------------------------------------------- /src/perlin.nim: -------------------------------------------------------------------------------- 1 | ## 2 | ## A Noise Generation Library with support for both Perlin noise and Simplex 3 | ## noise. 4 | ## 5 | ## Simplex Noise 6 | ## ------------- 7 | ## 8 | ## Take a look here: 9 | ## * http://webstaff.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf 10 | ## * http://stackoverflow.com/questions/18279456/any-simplex-noise-tutorials-or-resources 11 | ## 12 | ## Based on the implementation found here: 13 | ## * http://webstaff.itn.liu.se/~stegu/simplexnoise/SimplexNoise.java 14 | ## 15 | ## Perlin Noise 16 | ## ------------ 17 | ## 18 | ## Take a look at the following resources: 19 | ## * http://mrl.nyu.edu/~perlin/noise/ 20 | ## * http://flafla2.github.io/2014/08/09/perlinnoise.html 21 | ## * http://riven8192.blogspot.com/2010/08/calculate-perlinnoise-twice-as-fast.html 22 | ## 23 | 24 | import math, random, perlin/private/common 25 | 26 | type 27 | Noise* = object 28 | ## A noise instance 29 | ## * `perm` is a set of random numbers used to generate the results 30 | ## * `octaves` allows you to combine multiple layers of noise 31 | ## into a single result 32 | ## * `persistence` is how much impact each successive octave has on 33 | ## the result 34 | perm: array[0..511, int] 35 | octaves: int 36 | persistence: float 37 | 38 | NoiseType* {.pure.} = enum ## \ 39 | ## The types of noise available 40 | perlin, simplex 41 | 42 | proc randomSeed*(): uint32 {.inline.} = 43 | ## Returns a random seed that can be fed into a constructor 44 | uint32(rand(high(int32))) 45 | 46 | proc newNoise*(seed: uint32, octaves: int = 1, persistence: float = 0.5): Noise = 47 | ## Creates a new noise instance with the given seed 48 | ## * `octaves` allows you to combine multiple layers of noise 49 | ## into a single result 50 | ## * `persistence` is how much impact each successive octave has on 51 | ## the result 52 | assert(octaves >= 1) 53 | return Noise(perm: buildPermutations(seed), octaves: octaves, persistence: persistence) 54 | 55 | proc newNoise*(octaves: int, persistence: float): Noise = 56 | ## Creates a new noise instance with a random seed 57 | ## * `octaves` allows you to combine multiple layers of noise 58 | ## into a single result 59 | ## * `persistence` is how much impact each successive octave has on 60 | ## the result 61 | newNoise(randomSeed(), octaves, persistence) 62 | 63 | proc newNoise*(): Noise = 64 | ## Creates a new noise instance with a random seed 65 | newNoise(1, 1.0) 66 | 67 | template hash( 68 | self: Noise, 69 | unit: Point3D[int], ux, uy, uz: untyped, 70 | pos: Point3D[float], gx, gy, gz: untyped 71 | ): untyped = 72 | ## Generates the hash coordinate given three expressions 73 | let gIndex = self.perm[unit.x + ux + self.perm[unit.y + uy + self.perm[unit.z + uz]]] 74 | grad(gIndex, pos.x + gx, pos.y + gy, pos.z + gz) 75 | 76 | template hash( 77 | self: Noise, 78 | unit: Point2D[int], ux, uy: untyped, 79 | pos: Point2D[float], gx, gy: untyped 80 | ): untyped = 81 | ## Generates the hash coordinate given three expressions 82 | let gIndex = self.perm[unit.x + ux + self.perm[unit.y + uy]] 83 | grad(gIndex, pos.x + gx, pos.y + gy, 0) 84 | 85 | include perlin/private/perlin, perlin/private/simplex 86 | 87 | 88 | template applyOctaves(self: Noise, callback: untyped, point: Point): float = 89 | ## Applies the configured octaves to the request 90 | var total: float = 0 91 | var frequency: float = 1 92 | var amplitude: float = 1 93 | 94 | # Used for normalizing result to 0.0 - 1.0 95 | var maxValue: float = 0 96 | 97 | for i in 0..self.octaves: 98 | let noise = callback( 99 | self, 100 | when compiles(point.z): 101 | (point.x * frequency, point.y * frequency, point.z * frequency) 102 | else: 103 | (point.x * frequency, point.y * frequency) 104 | ) 105 | 106 | total = total + amplitude * noise 107 | 108 | maxValue = maxValue + amplitude 109 | amplitude = amplitude * self.persistence 110 | frequency = frequency * 2 111 | 112 | total / maxValue 113 | 114 | proc perlin* (self: Noise, x, y, z: int|float): float = 115 | ## Returns the noise at the given offset. Returns a value between 0 and 1 116 | ## 117 | ## Note: This method tweaks the input values by just a bit to make sure 118 | ## there are decimal points. If you don't want that, use the 'purePerlin' 119 | ## method instead 120 | applyOctaves(self, perlin3, (x: float(x) * 0.1, y: float(y) * 0.1, z: float(z) * 0.1)) 121 | 122 | proc perlin* (self: Noise, x, y: int|float): float = 123 | ## Returns the noise at the given offset. Returns a value between 0 and 1 124 | ## 125 | ## Note: This method tweaks the input values by just a bit to make sure 126 | ## there are decimal points. If you don't want that, use the 'purePerlin' 127 | ## method instead 128 | applyOctaves(self, perlin2, (x: float(x) * 0.1, y: float(y) * 0.1)) 129 | 130 | proc purePerlin* (self: Noise, x, y, z: int|float): float = 131 | ## Returns the noise at the given offset without modifying the input. 132 | ## Returns a value between 0 and 1 133 | applyOctaves(self, perlin3, (x: float(x), y: float(y), z: float(z))) 134 | 135 | proc purePerlin* (self: Noise, x, y: int|float): float = 136 | ## Returns the noise at the given offset without modifying the input. 137 | ## Returns a value between 0 and 1 138 | applyOctaves(self, perlin2, (x: float(x), y: float(y))) 139 | 140 | 141 | proc simplex* (self: Noise, x, y, z: int|float): float = 142 | ## Returns the noise at the given offset. Returns a value between 0 and 1 143 | ## 144 | ## Note: This method tweaks the input values by just a bit to make sure 145 | ## there are decimal points. If you don't want that, use the 'purePerlin' 146 | ## method instead 147 | applyOctaves(self, simplex3, (x: float(x) * 0.1, y: float(y) * 0.1, z: float(z) * 0.1)) 148 | 149 | proc simplex* (self: Noise, x, y: int|float): float = 150 | ## Returns the noise at the given offset. Returns a value between 0 and 1 151 | ## 152 | ## Note: This method tweaks the input values by just a bit to make sure 153 | ## there are decimal points. If you don't want that, use the 'purePerlin' 154 | ## method instead 155 | applyOctaves(self, simplex2, (x: float(x) * 0.1, y: float(y) * 0.1)) 156 | 157 | proc pureSimplex* (self: Noise, x, y, z: int|float): float = 158 | ## Returns the noise at the given offset without modifying the input. 159 | ## Returns a value between 0 and 1 160 | applyOctaves(self, simplex3, (x: float(x), y: float(y), z: float(z))) 161 | 162 | proc pureSimplex* (self: Noise, x, y: int|float): float = 163 | ## Returns the noise at the given offset without modifying the input. 164 | ## Returns a value between 0 and 1 165 | applyOctaves(self, simplex2, (x: float(x), y: float(y))) 166 | 167 | 168 | proc get* (self: Noise, typ: NoiseType, x, y, z: int|float): float = 169 | ## Returns the noise at the given offset. Returns a value between 0 and 1 170 | ## 171 | ## Note: This method tweaks the input values by just a bit to make sure 172 | ## there are decimal points. If you don't want that, use the 'purePerlin' 173 | ## method instead 174 | case typ 175 | of NoiseType.perlin: return perlin(self, x, y, z) 176 | of NoiseType.simplex: return simplex(self, x, y, z) 177 | 178 | proc get* (self: Noise, typ: NoiseType, x, y: int|float): float = 179 | ## Returns the noise at the given offset. Returns a value between 0 and 1 180 | ## 181 | ## Note: This method tweaks the input values by just a bit to make sure 182 | ## there are decimal points. If you don't want that, use the 'purePerlin' 183 | ## method instead 184 | case typ 185 | of NoiseType.perlin: return perlin(self, x, y) 186 | of NoiseType.simplex: return simplex(self, x, y) 187 | 188 | proc pureGet* (self: Noise, typ: NoiseType, x, y, z: int|float): float = 189 | ## Returns the noise at the given offset without modifying the input. 190 | ## Returns a value between 0 and 1 191 | case typ 192 | of NoiseType.perlin: return purePerlin(self, x, y, z) 193 | of NoiseType.simplex: return pureSimplex(self, x, y, z) 194 | 195 | proc pureGet* (self: Noise, typ: NoiseType, x, y: int|float): float = 196 | ## Returns the noise at the given offset without modifying the input. 197 | ## Returns a value between 0 and 1 198 | case typ 199 | of NoiseType.perlin: return purePerlin(self, x, y) 200 | of NoiseType.simplex: return pureSimplex(self, x, y) 201 | 202 | 203 | -------------------------------------------------------------------------------- /src/perlin/private/common.nim: -------------------------------------------------------------------------------- 1 | ## 2 | ## Shared methods for generating noise 3 | ## 4 | 5 | import sequtils, mersenne 6 | 7 | type 8 | Point3D*[U: float|int] = ## \ 9 | ## A helper definition for a 3d point 10 | tuple[x, y, z: U] 11 | 12 | Point2D*[U: float|int] = ## \ 13 | ## A helper definition for a 3d point 14 | tuple[x, y: U] 15 | 16 | PointND*[U: float|int] = ## \ 17 | ## a point of N dimensions with a specific precision 18 | Point3D[U]|Point2D[U] 19 | 20 | Point* = ## \ 21 | ## A 2d or 3d point with any kind of precision 22 | Point3D[float]|Point3D[int]|Point2D[float]|Point2D[int] 23 | 24 | proc shuffle[E](seed: uint32, values: var seq[E]) = 25 | ## Shuffles a sequence in place 26 | 27 | var prng = newMersenneTwister(seed) 28 | 29 | let max = uint32(values.high) 30 | 31 | # Shuffle the array of numbers 32 | for i in 0u32..(max - 1u32): 33 | let index = int(i + (prng.getNum() mod (max - i)) + 1u32) 34 | assert(index <= 255) 35 | assert(int(i) < index) 36 | swap values[int(i)], values[index] 37 | 38 | proc buildPermutations*(seed: uint32): array[0..511, int] = 39 | ## Returns a hash lookup table. It is all the numbers from 0 to 255 40 | ## (inclusive) in a randomly sorted array, twice over 41 | 42 | # Create and shuffle a random list of ints 43 | var base = toSeq(0..255) 44 | shuffle(seed, base) 45 | 46 | # Copy into the result 47 | for i in 0..511: 48 | result[i] = base[i mod 256] 49 | 50 | template map*(point: Point, apply: untyped): untyped = 51 | ## Applies a callback to all the values in a point 52 | when compiles(point.z): 53 | (x: apply(point.x), y: apply(point.y), z: apply(point.z)) 54 | else: 55 | (x: apply(point.x), y: apply(point.y)) 56 | 57 | template mapIt*(point: Point, kind: typedesc, apply: untyped): untyped = 58 | ## Applies a callback to all the values in a point 59 | var output: array[3, kind] 60 | block applyItBlock: 61 | let it {.inject.} = point.x 62 | output[0] = apply 63 | block applyItBlock: 64 | let it {.inject.} = point.y 65 | output[1] = apply 66 | when compiles(point.z): 67 | block applyItBlock: 68 | let it {.inject.} = point.z 69 | output[2] = apply 70 | (x: output[0], y: output[1], z: output[2]) 71 | else: 72 | (x: output[0], y: output[1]) 73 | 74 | proc grad*(hash: int, x, y, z: float): float {.inline.} = 75 | ## Calculate the dot product of a randomly selected gradient vector and the 76 | ## 8 location vectors 77 | case (hash and 0xF) 78 | of 0x0: return x + y 79 | of 0x1: return -x + y 80 | of 0x2: return x - y 81 | of 0x3: return -x - y 82 | of 0x4: return x + z 83 | of 0x5: return -x + z 84 | of 0x6: return x - z 85 | of 0x7: return -x - z 86 | of 0x8: return y + z 87 | of 0x9: return -y + z 88 | of 0xA: return y - z 89 | of 0xB: return -y - z 90 | of 0xC: return y + x 91 | of 0xD: return -y + z 92 | of 0xE: return y - x 93 | of 0xF: return -y - z 94 | else: raise newException(AssertionError, "Should not happen") 95 | 96 | -------------------------------------------------------------------------------- /src/perlin/private/perlin.nim: -------------------------------------------------------------------------------- 1 | ## 2 | ## Perlin noise generation 3 | ## 4 | 5 | import math 6 | 7 | proc lerp( a, b, x: float ): float {.inline.} = 8 | ## Linear interpolator. https://en.wikipedia.org/wiki/Linear_interpolation 9 | a + x * (b - a) 10 | 11 | template withPerlinSetup(point: Point, unit, pos, faded: untyped, body: untyped) = 12 | ## Sets up three standard variables needed to run the generation 13 | 14 | # Calculate the "unit cube" that the point asked will be located in 15 | let unit = point.mapIt(int, int(floor(it)) and 255) 16 | 17 | # Calculate the location within the cube 18 | let pos = point.mapIt(float, it - floor(it)) 19 | 20 | ## Fade function as defined by Ken Perlin. This eases coordinate values 21 | ## so that they will "ease" towards integral values. This ends up smoothing 22 | ## the final output. 23 | ## 6t^5 - 15t^4 + 10t^3 24 | let faded = pos.mapIt(float, it * it * it * (it * (it * 6 - 15) + 10)) 25 | 26 | # For convenience constrain to 0..1 (theoretical min/max before is -1 - 1) 27 | return (body + 1) / 2 28 | 29 | proc perlin3 ( self: Noise, point: Point3d[float] ): float {.inline.} = 30 | ## Returns the noise at the given offset 31 | 32 | withPerlinSetup(point, unit, pos, faded): 33 | 34 | # The hash coordinates of the 8 corners 35 | let aaa = hash(self, unit, 0, 0, 0, pos, 0, 0, 0) 36 | let aba = hash(self, unit, 0, 1, 0, pos, 0, -1, 0) 37 | let aab = hash(self, unit, 0, 0, 1, pos, 0, 0, -1) 38 | let abb = hash(self, unit, 0, 1, 1, pos, 0, -1, -1) 39 | let baa = hash(self, unit, 1, 0, 0, pos, -1, 0, 0) 40 | let bba = hash(self, unit, 1, 1, 0, pos, -1, -1, 0) 41 | let bab = hash(self, unit, 1, 0, 1, pos, -1, 0, -1) 42 | let bbb = hash(self, unit, 1, 1, 1, pos, -1, -1, -1) 43 | 44 | let y1 = lerp(lerp(aaa, baa, faded.x), lerp(aba, bba, faded.x), faded.y) 45 | let y2 = lerp(lerp(aab, bab, faded.x), lerp(abb, bbb, faded.x), faded.y) 46 | 47 | lerp(y1, y2, faded.z) 48 | 49 | proc perlin2 ( self: Noise, point: Point2D[float] ): float {.inline.} = 50 | ## Returns the noise at the given offset 51 | 52 | withPerlinSetup(point, unit, pos, faded): 53 | let aa = hash(self, unit, 0, 0, pos, 0, 0) 54 | let ab = hash(self, unit, 0, 1, pos, 0, -1) 55 | let ba = hash(self, unit, 1, 0, pos, -1, 0) 56 | let bb = hash(self, unit, 1, 1, pos, -1, -1) 57 | lerp(lerp(aa, ba, faded.x), lerp(ab, bb, faded.x), faded.y) 58 | 59 | -------------------------------------------------------------------------------- /src/perlin/private/simplex.nim: -------------------------------------------------------------------------------- 1 | ## 2 | ## 3D Simplex noise generation 3 | ## 4 | 5 | # 3D Skewing and unskewing factors 6 | const F3 = 1.0 / 3.0 7 | const G3 = 1.0 / 6.0 8 | 9 | # 2D Skewing and unskewing factors 10 | const F2 = 0.5 * (sqrt(3.0) - 1.0) 11 | const G2 = (3.0 - sqrt(3.0)) / 6.0 12 | 13 | proc getSimplexCorners( 14 | point: Point3D[float] 15 | ): tuple[second, third: tuple[i, j, k: int]] {.inline.} = 16 | ## Determine which simplex we are in and return the points for the corner 17 | if point.x >= point.y: 18 | if point.y >= point.z: 19 | # X Y Z order 20 | return (second: (1, 0, 0), third: (1, 1, 0)) 21 | elif point.x >= point.z: 22 | # X Z Y order 23 | return (second: (1, 0, 0), third: (1, 0, 1)) 24 | else: 25 | # Z X Y order 26 | return (second: (0, 0, 1), third: (1, 0, 1)) 27 | else: 28 | if point.y < point.z: 29 | # Z Y X order 30 | return (second: (0, 0, 1), third: (0, 1, 1)) 31 | elif point.x < point.z: 32 | # Y Z X order 33 | return (second: (0, 1, 0), third: (0, 1, 1)) 34 | else: 35 | # Y X Z order 36 | return (second: (0, 1, 0), third: (1, 1, 0)) 37 | 38 | proc getCornerOffset( 39 | point: Point2D[float], 40 | ijk: tuple[i, j: int], 41 | multiplier: float 42 | ): Point2D[float] {.inline.} = 43 | ## Calculates the offset for various corners 44 | ( 45 | x: point.x - float(ijk.i) + multiplier, 46 | y: point.y - float(ijk.j) + multiplier ) 47 | 48 | proc getCornerOffset( 49 | point: Point3D[float], 50 | ijk: tuple[i, j, k: int], 51 | multiplier: float 52 | ): Point3D[float] {.inline.} = 53 | ## Calculates the offset for various corners 54 | ( 55 | x: point.x - float(ijk.i) + multiplier, 56 | y: point.y - float(ijk.j) + multiplier, 57 | z: point.z - float(ijk.k) + multiplier ) 58 | 59 | proc contribution( 60 | self: Noise, 61 | point: Point3D[float], 62 | unit: Point3D[int], 63 | ijk: tuple[i, j, k: int] 64 | ): float {.inline.} = 65 | ## Noise contributions from a corners 66 | let t = 0.6 - point.x * point.x - point.y * point.y - point.z * point.z 67 | if t < 0: 68 | return 0.0 69 | else: 70 | let hash = self.hash(unit, ijk.i, ijk.j, ijk.k, point, 0, 0, 0) 71 | return t * t * t * t * hash 72 | 73 | template sum( point: Point ): untyped = 74 | ## Adds all the points in a tuple 75 | point.x + point.y + (when compiles(point.z): point.z else: 0) 76 | 77 | template subtract( a, b: Point ): untyped = 78 | ## Subtracts two points 79 | when compiles(a.z) or compiles(b.z): 80 | ( x: a.x - b.x, y: a.y - b.y, z: a.z - b.z ) 81 | else: 82 | ( x: a.x - b.x, y: a.y - b.y ) 83 | 84 | template withSimplexSetup( 85 | point: PointND[float], 86 | F, G, unit, origin: untyped, 87 | body: untyped 88 | ): untyped = 89 | 90 | # Skew the input space to determine which simplex cell we're in 91 | let skew = sum(point) * F 92 | 93 | let floored = point.mapIt(int, int(floor(it + skew)) ) 94 | 95 | let t = float(sum(floored)) * G 96 | 97 | # Unskew the cell origin back to (x,y,z) space 98 | let unskewed = floored.mapIt(float, float(it) - t) 99 | 100 | # The x,y,z distances from the cell origin 101 | let origin = subtract(point, unskewed) 102 | 103 | let unit = floored.mapIt(int, it and 255) 104 | 105 | # Restrict the range to 0 to 1 for convenience 106 | return (body + 1) / 2 107 | 108 | proc contribution( 109 | self: Noise, 110 | point: Point2D[float], 111 | unit: Point2D[int], 112 | ijk: tuple[i, j: int] 113 | ): float {.inline.} = 114 | ## Noise contributions from a corners 115 | let t = 0.5 - point.x * point.x - point.y * point.y 116 | if t < 0: 117 | return 0.0 118 | else: 119 | let hash = self.hash(unit, ijk.i, ijk.j, point, 0, 0) 120 | return t * t * t * t * hash 121 | 122 | 123 | proc simplex3 ( self: Noise, point: Point3D[float] ): float {.inline.} = 124 | ## 3D simplex noise 125 | 126 | withSimplexSetup(point, F3, G3, unit, origin): 127 | 128 | # Offsets for the second and third corner of simplex in (i, j, k) coords 129 | let (ijk1, ijk2) = getSimplexCorners(origin) 130 | 131 | # Offsets for second corner in (x,y,z) coords 132 | let pos1 = getCornerOffset(origin, ijk1, 1.0 * G3) 133 | let pos2 = getCornerOffset(origin, ijk2, 2.0 * G3) 134 | let pos3 = getCornerOffset(origin, (1, 1, 1), 3.0 * G3) 135 | 136 | # Add contributions from each corner to get the final noise value. 137 | 32.0 * ( 138 | self.contribution(origin, unit, (0, 0, 0)) + 139 | self.contribution(pos1, unit, ijk1) + 140 | self.contribution(pos2, unit, ijk2) + 141 | self.contribution(pos3, unit, (1, 1, 1)) ) 142 | 143 | proc simplex2 ( self: Noise, point: Point2D[float] ): float {.inline.} = 144 | ## 2D simplex noise 145 | 146 | withSimplexSetup(point, F2, G2, unit, origin): 147 | 148 | # Offsets for the second corner of simplex in (i, j) coords 149 | let ijk = if origin.x > origin.y: (i: 1, j: 0) else: (i: 0, j: 1) 150 | 151 | # Offsets for second corner in (x,y,z) coords 152 | let pos1 = getCornerOffset(origin, ijk , 1.0 * G2) 153 | let pos2 = getCornerOffset(origin, (1, 1), 2.0 * G2) 154 | 155 | # Add contributions from each corner to get the final noise value. 156 | 70.0 * ( 157 | self.contribution(origin, unit, (0, 0)) + 158 | self.contribution(pos1, unit, ijk) + 159 | self.contribution(pos2, unit, (1, 1)) ) 160 | 161 | 162 | -------------------------------------------------------------------------------- /tests/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") -------------------------------------------------------------------------------- /tests/t_noise.nim: -------------------------------------------------------------------------------- 1 | import unittest, perlin, math, random 2 | 3 | randomize() 4 | 5 | suite "General Noise should": 6 | 7 | test "Provide perlin noise": 8 | let noise = newNoise() 9 | check( noise.get(NoiseType.perlin, 1, 2, 3) >= 0 ) 10 | check( noise.get(NoiseType.perlin, 1, 2, 3) < 1 ) 11 | 12 | test "Provide simplex noise": 13 | let noise = newNoise() 14 | check( noise.get(NoiseType.simplex, 1, 2, 3) >= 0 ) 15 | check( noise.get(NoiseType.simplex, 1, 2, 3) < 1 ) 16 | 17 | test "Noise should be assignable at build time": 18 | const noise = newNoise(12345) 19 | const simplexVal = noise.get(NoiseType.simplex, 1, 2, 3) 20 | const noiseVal = noise.get(NoiseType.perlin, 1, 2, 3) 21 | 22 | check(simplexVal == 0.3310572195555556) 23 | check(noiseVal == 0.492089610128111) 24 | 25 | -------------------------------------------------------------------------------- /tests/t_perlin.nim: -------------------------------------------------------------------------------- 1 | import unittest, perlin, math, random 2 | 3 | randomize() 4 | 5 | suite "Perlin Noise should": 6 | 7 | let seedOne = randomSeed() 8 | test "Produce 3D values from 0 to 1 for seed " & $seedOne: 9 | let noise = newNoise( seedOne ) 10 | for x in -20..20: 11 | for y in -20..20: 12 | for z in -20..20: 13 | let val = noise.perlin(x, y, z) 14 | check( val >= 0 and val < 1 ) 15 | check( val == noise.perlin(x, y, z) ) 16 | 17 | let pure = noise.purePerlin(x, y, z) 18 | check( pure >= 0 and val < 1 ) 19 | check( pure == noise.purePerlin(x, y, z) ) 20 | 21 | let seedTwo = randomSeed() 22 | test "Produce 3D values from 0 to 1 with octaves for seed " & $seedTwo: 23 | let noise = newNoise( seedTwo, 4, 0.1 ) 24 | for x in -20..20: 25 | for y in -20..20: 26 | for z in -20..20: 27 | let val = noise.perlin(x, y, z) 28 | check( val >= 0 and val < 1 ) 29 | check( val == noise.perlin(x, y, z) ) 30 | 31 | let seedThree = randomSeed() 32 | test "Produce 2D values from 0 to 1 for seed " & $seedThree: 33 | let noise = newNoise( seedOne ) 34 | for x in -20..20: 35 | for y in -20..20: 36 | let val = noise.perlin(x, y) 37 | check( val >= 0 and val < 1 ) 38 | check( val == noise.perlin(x, y) ) 39 | 40 | let pure = noise.purePerlin(x, y) 41 | check( pure >= 0 and val < 1 ) 42 | check( pure == noise.purePerlin(x, y) ) 43 | 44 | -------------------------------------------------------------------------------- /tests/t_simplex.nim: -------------------------------------------------------------------------------- 1 | import unittest, perlin, math, random 2 | 3 | randomize() 4 | 5 | suite "Simplex Noise should": 6 | 7 | let seedOne = randomSeed() 8 | test "Produce values from 0 to 1 for seed " & $seedOne: 9 | let noise = newNoise( seedOne ) 10 | for x in -20..20: 11 | for y in -20..20: 12 | for z in -20..20: 13 | let val = noise.simplex(x, y, z) 14 | check( val >= 0 and val < 1 ) 15 | check( val == noise.simplex(x, y, z) ) 16 | 17 | let pure = noise.pureSimplex(x, y, z) 18 | check( pure >= 0 and val < 1 ) 19 | check( pure == noise.pureSimplex(x, y, z) ) 20 | 21 | let seedTwo = randomSeed() 22 | test "Produce values from 0 to 1 with octaves for seed " & $seedTwo: 23 | let noise = newNoise( seedTwo, 4, 0.1 ) 24 | for x in -20..20: 25 | for y in -20..20: 26 | for z in -20..20: 27 | let val = noise.simplex(x, y, z) 28 | check( val >= 0 and val < 1 ) 29 | check( val == noise.simplex(x, y, z) ) 30 | 31 | let seedThree = randomSeed() 32 | test "Produce 2D values from 0 to 1 for seed " & $seedThree: 33 | let noise = newNoise( seedOne ) 34 | for x in -20..20: 35 | for y in -20..20: 36 | let val = noise.simplex(x, y) 37 | check( val >= 0 and val < 1 ) 38 | check( val == noise.simplex(x, y) ) 39 | 40 | let pure = noise.pureSimplex(x, y) 41 | check( pure >= 0 and val < 1 ) 42 | check( pure == noise.pureSimplex(x, y) ) 43 | 44 | --------------------------------------------------------------------------------