├── .github ├── FUNDING.yml └── workflows │ ├── Benchmarks.yml │ ├── Documentation.yml │ ├── Tests.yml │ ├── iOS.yml │ ├── tvOS.yml │ ├── visionOS.yml │ └── watchOS.yml ├── .gitignore ├── .mailmap ├── ExternalBenchmarks ├── .benchmarkBaselines │ └── ExternalBenchmarks │ │ ├── bdb4ef08.aarch64 │ │ └── results.json │ │ └── bdb4ef08.x86_64 │ │ └── results.json ├── .gitignore ├── Benchmarks │ └── ExternalBenchmarks │ │ └── ExternalBenchmarks.swift ├── Package.resolved ├── Package.swift └── README.md ├── LICENSE ├── NOTICE ├── Package.resolved ├── Package.swift ├── README.md ├── Scripts └── TestAll ├── Sources ├── GenNoise │ ├── banners.swift │ ├── calibrate.blend │ ├── calibrate.blend1 │ ├── calibrate.swift │ └── main.swift └── Noise │ ├── cell.swift │ ├── compounds.swift │ ├── disk.swift │ ├── docs.docc │ ├── CellNoise2D.md │ ├── CellNoise3D.md │ ├── README.md │ └── png │ │ ├── banner_FBM.png │ │ ├── banner_cell2d.png │ │ ├── banner_cell3d.png │ │ ├── banner_disk2d.png │ │ ├── banner_simplex2d.png │ │ ├── banner_supersimplex2d.png │ │ ├── banner_supersimplex3d.png │ │ └── banner_voronoi2d.png │ ├── gradient.swift │ ├── hash.swift │ └── noise.swift ├── Tests └── NoiseTests │ └── ConsistentPRNGTests.swift └── examples ├── banner_FBM.png ├── banner_cell2d.png ├── banner_cell3d.png ├── banner_classic3d.png ├── banner_disk2d.png ├── banner_supersimplex2d.png ├── banner_supersimplex3d.png ├── banner_voronoi2d.png ├── calibrate_cell2d.png ├── calibrate_cell3d.png ├── calibrate_classic-distortion.png ├── calibrate_gradient2d.png └── calibrate_gradient3d.png /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [tayloraswift] 2 | -------------------------------------------------------------------------------- /.github/workflows/Benchmarks.yml: -------------------------------------------------------------------------------- 1 | name: benchmark 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | benchmark-macos: 11 | runs-on: macos-15 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v3 15 | 16 | - name: Homebrew Mac 17 | if: ${{ runner.os == 'Macos' }} 18 | run: | 19 | echo "/opt/homebrew/bin:/usr/local/bin" >> $GITHUB_PATH 20 | brew install jemalloc 21 | - name: Ubuntu deps 22 | if: ${{ runner.os == 'Linux' }} 23 | run: | 24 | sudo apt-get install -y libjemalloc-dev 25 | - name: benchmark 26 | run: | 27 | cd ExternalBenchmarks 28 | swift package benchmark 29 | -------------------------------------------------------------------------------- /.github/workflows/Documentation.yml: -------------------------------------------------------------------------------- 1 | # This workflow validates the package’s documentation. Because documentation building involves 2 | # compiling the package, this also checks that the package itself compiles successfully on each 3 | # supported platform. 4 | name: documentation 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | linux: 14 | runs-on: ubuntu-24.04 15 | name: Ubuntu 24.04 16 | 17 | steps: 18 | - name: Install Swift 19 | uses: tayloraswift/swift-install-action@master 20 | with: 21 | swift-prefix: "swift-6.0.3-release/ubuntu2404/swift-6.0.3-RELEASE" 22 | swift-id: "swift-6.0.3-RELEASE-ubuntu24.04" 23 | 24 | - name: Install Unidoc 25 | uses: tayloraswift/swift-unidoc-action@master 26 | 27 | # This clobbers everything in the current directory! 28 | - name: Checkout repository 29 | uses: actions/checkout@v3 30 | 31 | - name: Validate documentation 32 | run: | 33 | unidoc compile \ 34 | --swift-toolchain $SWIFT_INSTALLATION \ 35 | --ci fail-on-errors \ 36 | --project-path . 37 | 38 | macos: 39 | runs-on: macos-15 40 | name: macOS 41 | steps: 42 | - name: Install Unidoc 43 | uses: tayloraswift/swift-unidoc-action@master 44 | 45 | - name: Checkout repository 46 | uses: actions/checkout@v3 47 | 48 | - name: Validate documentation 49 | run: | 50 | unidoc compile \ 51 | --ci fail-on-errors \ 52 | --project-path . 53 | -------------------------------------------------------------------------------- /.github/workflows/Tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | linux: 11 | runs-on: ubuntu-24.04 12 | name: Ubuntu 24.04 13 | steps: 14 | - name: Install Swift 15 | uses: tayloraswift/swift-install-action@master 16 | with: 17 | swift-prefix: "swift-6.0.3-release/ubuntu2404/swift-6.0.3-RELEASE" 18 | swift-id: "swift-6.0.3-RELEASE-ubuntu24.04" 19 | 20 | - name: Checkout repository 21 | uses: actions/checkout@v3 22 | 23 | - name: Run tests 24 | run: Scripts/TestAll 25 | 26 | macos: 27 | runs-on: macos-15 28 | name: macOS 29 | steps: 30 | - name: Checkout repository 31 | uses: actions/checkout@v3 32 | 33 | - name: Run tests 34 | run: Scripts/TestAll 35 | -------------------------------------------------------------------------------- /.github/workflows/iOS.yml: -------------------------------------------------------------------------------- 1 | name: iOS 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | uses: tayloraswift/swift-device-action/.github/workflows/build.yml@master 12 | with: 13 | xcode-scheme: 'swift-noise-Package' 14 | destination: ${{ github.workflow }} 15 | -------------------------------------------------------------------------------- /.github/workflows/tvOS.yml: -------------------------------------------------------------------------------- 1 | name: tvOS 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | uses: tayloraswift/swift-device-action/.github/workflows/build.yml@master 12 | with: 13 | xcode-scheme: 'swift-noise-Package' 14 | destination: ${{ github.workflow }} 15 | -------------------------------------------------------------------------------- /.github/workflows/visionOS.yml: -------------------------------------------------------------------------------- 1 | name: visionOS 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | uses: tayloraswift/swift-device-action/.github/workflows/build.yml@master 12 | with: 13 | xcode-scheme: 'swift-noise-Package' 14 | destination: ${{ github.workflow }} 15 | -------------------------------------------------------------------------------- /.github/workflows/watchOS.yml: -------------------------------------------------------------------------------- 1 | name: watchOS 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | uses: tayloraswift/swift-device-action/.github/workflows/build.yml@master 12 | with: 13 | xcode-scheme: 'swift-noise-Package' 14 | destination: ${{ github.workflow }} 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.build 2 | /.build.ssgc 3 | /.vscode 4 | /Packages 5 | .swiftpm 6 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Dianna 2 | Dianna 3 | Joseph Heck 4 | -------------------------------------------------------------------------------- /ExternalBenchmarks/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/config/registries.json 8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 9 | .netrc 10 | .vscode 11 | -------------------------------------------------------------------------------- /ExternalBenchmarks/Benchmarks/ExternalBenchmarks/ExternalBenchmarks.swift: -------------------------------------------------------------------------------- 1 | import Benchmark 2 | import Noise 3 | import Foundation 4 | 5 | let viewer_size: Int = 1024 6 | let offset: Double = 0 7 | 8 | // running at roughly 500ms for a pass, so maxDuration will limit far before iteration count for this 9 | //func cell2d() async -> Data { 10 | // var pixbuf:[UInt8] = [UInt8](repeating: 0, count: viewer_size * viewer_size) 11 | // for (i, (x, y)) in Domain2D(samples_x: viewer_size, samples_y: viewer_size).enumerated() { 12 | // pixbuf[i] = UInt8(max(0, min(255, CellNoise2D(amplitude: 255, frequency: 0.01).evaluate(x, y) + offset))) 13 | // } 14 | // return Data(pixbuf) 15 | //} 16 | 17 | let benchmarks = { 18 | Benchmark.defaultConfiguration.maxIterations = .count(1024 * 1024) // Default = 100_000 19 | Benchmark.defaultConfiguration.maxDuration = .seconds(1) // Default = 1 second 20 | Benchmark.defaultConfiguration.metrics = [.throughput, .wallClock] 21 | 22 | Benchmark("disk2d") { benchmark in 23 | var poisson = DiskSampler2D(seed: 0) 24 | for _ in benchmark.scaledIterations { 25 | blackHole(poisson.generate(radius: 10, width: viewer_size, height: viewer_size)) 26 | } 27 | } 28 | 29 | Benchmark("cell2d") { benchmark in 30 | for _ in benchmark.scaledIterations { 31 | blackHole(CellNoise2D(amplitude: 255, frequency: 0.01)) 32 | } 33 | } 34 | 35 | Benchmark("cell3d") { benchmark in 36 | for _ in benchmark.scaledIterations { 37 | blackHole(CellNoise3D(amplitude: 255, frequency: 0.01)) 38 | } 39 | } 40 | 41 | Benchmark("cell_tiling3d") { benchmark in 42 | for _ in benchmark.scaledIterations { 43 | blackHole( 44 | TilingCellNoise3D(amplitude: 255, 45 | frequency: 16 / Double(viewer_size), 46 | wavelengths: 16) 47 | ) 48 | } 49 | } 50 | 51 | Benchmark("classic_tiling_fbm3d") { benchmark in 52 | for _ in benchmark.scaledIterations { 53 | blackHole( 54 | FBM( 55 | tiling: TilingClassicNoise3D(amplitude: 255, 56 | frequency: 16 / Double(viewer_size), 57 | wavelengths: 16), 58 | octaves: 10, 59 | persistence: 0.62) 60 | ) 61 | } 62 | } 63 | 64 | Benchmark("classic3d") { benchmark in 65 | for _ in benchmark.scaledIterations { 66 | blackHole( 67 | FBM(ClassicNoise3D(amplitude: 255, frequency: 0.001), 68 | octaves: 10, persistence: 0.62) 69 | ) 70 | } 71 | } 72 | 73 | 74 | Benchmark("classic_tiling3d") { benchmark in 75 | for _ in benchmark.scaledIterations { 76 | blackHole( 77 | TilingClassicNoise3D(amplitude: 255, frequency: 16 / Double(viewer_size), wavelengths: 16) 78 | ) 79 | } 80 | } 81 | Benchmark("gradient2d") { benchmark in 82 | for _ in benchmark.scaledIterations { 83 | blackHole( 84 | FBM(GradientNoise2D(amplitude: 180, frequency: 0.001), octaves: 10, persistence: 0.62) 85 | ) 86 | } 87 | } 88 | 89 | Benchmark("gradient3d") { benchmark in 90 | for _ in benchmark.scaledIterations { 91 | blackHole( 92 | FBM(GradientNoise3D(amplitude: 180, frequency: 0.001), octaves: 10, persistence: 0.62) 93 | ) 94 | } 95 | } 96 | 97 | 98 | 99 | 100 | 101 | } 102 | 103 | -------------------------------------------------------------------------------- /ExternalBenchmarks/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "hdrhistogram-swift", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/HdrHistogram/hdrhistogram-swift", 7 | "state" : { 8 | "revision" : "a69fa24d7b70421870cafa86340ece900489e17e", 9 | "version" : "0.1.2" 10 | } 11 | }, 12 | { 13 | "identity" : "package-benchmark", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/ordo-one/package-benchmark", 16 | "state" : { 17 | "revision" : "4dcbab2d23fb0f42c6859ac278746287e6a79980", 18 | "version" : "1.23.4" 19 | } 20 | }, 21 | { 22 | "identity" : "package-jemalloc", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/ordo-one/package-jemalloc", 25 | "state" : { 26 | "revision" : "e8a5db026963f5bfeac842d9d3f2cc8cde323b49", 27 | "version" : "1.0.0" 28 | } 29 | }, 30 | { 31 | "identity" : "swift-argument-parser", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/apple/swift-argument-parser", 34 | "state" : { 35 | "revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", 36 | "version" : "1.4.0" 37 | } 38 | }, 39 | { 40 | "identity" : "swift-atomics", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/apple/swift-atomics", 43 | "state" : { 44 | "revision" : "cd142fd2f64be2100422d658e7411e39489da985", 45 | "version" : "1.2.0" 46 | } 47 | }, 48 | { 49 | "identity" : "swift-hash", 50 | "kind" : "remoteSourceControl", 51 | "location" : "https://github.com/tayloraswift/swift-hash", 52 | "state" : { 53 | "revision" : "c7ba0cde5eb63042c2196b02b65a770101c1ac11", 54 | "version" : "0.5.0" 55 | } 56 | }, 57 | { 58 | "identity" : "swift-numerics", 59 | "kind" : "remoteSourceControl", 60 | "location" : "https://github.com/apple/swift-numerics", 61 | "state" : { 62 | "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", 63 | "version" : "1.0.2" 64 | } 65 | }, 66 | { 67 | "identity" : "swift-png", 68 | "kind" : "remoteSourceControl", 69 | "location" : "https://github.com/tayloraswift/swift-png", 70 | "state" : { 71 | "revision" : "14a720bfcf1b0660dc63979d55d218babc8d651f", 72 | "version" : "4.4.2" 73 | } 74 | }, 75 | { 76 | "identity" : "swift-system", 77 | "kind" : "remoteSourceControl", 78 | "location" : "https://github.com/apple/swift-system", 79 | "state" : { 80 | "revision" : "f9266c85189c2751589a50ea5aec72799797e471", 81 | "version" : "1.3.0" 82 | } 83 | }, 84 | { 85 | "identity" : "texttable", 86 | "kind" : "remoteSourceControl", 87 | "location" : "https://github.com/ordo-one/TextTable", 88 | "state" : { 89 | "revision" : "a27a07300cf4ae322e0079ca0a475c5583dd575f", 90 | "version" : "0.0.2" 91 | } 92 | } 93 | ], 94 | "version" : 2 95 | } 96 | -------------------------------------------------------------------------------- /ExternalBenchmarks/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "ExternalBenchmarks", 7 | platforms: [ 8 | .macOS("13.3"), 9 | ], 10 | dependencies: [ 11 | .package(url: "https://github.com/ordo-one/package-benchmark", .upToNextMajor(from: "1.0.0")), 12 | .package(path: "../"), 13 | ], 14 | targets: [ 15 | .executableTarget( 16 | name: "ExternalBenchmarks", 17 | dependencies: [ 18 | .product(name: "Benchmark", package: "package-benchmark"), 19 | .product(name: "BenchmarkPlugin", package: "package-benchmark"), 20 | .product(name: "Noise", package: "swift-noise"), 21 | ], 22 | path: "Benchmarks/ExternalBenchmarks" 23 | ) 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /ExternalBenchmarks/README.md: -------------------------------------------------------------------------------- 1 | # ExternalBenchmarks 2 | 3 | Additional benchmarks that utilize https://github.com/ordo-one/package-benchmark. 4 | 5 | This code is explicitly in a subdirectory with a local reference to swift-noise 6 | in order avoid adding transitive dependencies. 7 | 8 | To run the benchmarks, invoke the following command: 9 | 10 | swift package benchmark 11 | 12 | ## Using Benchmarks 13 | 14 | - [Documentation for the package](https://swiftinit.org/docs/package-benchmark/benchmark/gettingstarted) 15 | - [Creating and Comparing Baselines](https://swiftinit.org/docs/package-benchmark/benchmark/creatingandcomparingbaselines) 16 | - [Exporting Benchmark Results](https://swiftinit.org/docs/package-benchmark/benchmark/exportingbenchmarks) 17 | 18 | ## Baselines 19 | 20 | The baselines are set by commit hash (first 8 hex characters) or (in the future) tag with this work in place. 21 | The initial baseline is `bdb4ef08.{aarch64, x86_64}`. 22 | It builds on the tag `1.0.0` with updates to dependencies and Swift versions and no functional code changes. 23 | 24 | ```bash 25 | swift package --allow-writing-to-package-directory benchmark baseline update bdb4ef08.aarch64 26 | ``` 27 | 28 | To compare current development branch against this baseline, run the following command from this sub-project in the terminal: 29 | 30 | ```bash 31 | swift package benchmark baseline compare bdb4ef08.aarch64 32 | ``` 33 | 34 | The output defaults to plain text. If you'd like markdown output, for pasting into a pull request, add `--format markdown` to the command. 35 | 36 | For a summary comparison, use the command: 37 | 38 | ```bash 39 | swift package benchmark baseline check --check-absolute 40 | ``` 41 | 42 | ### Stored Baselines 43 | 44 | `bdb4ef08.aarch64`: 8 'arm64' processors with 16 GB memory (M1 macbook pro) 45 | 46 | `bdb4ef08.x86_64`: 12 'x86_64' processors with 30 GB memory (Dell precision 5530) 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2022 Kelvin Ma (@taylorswift) 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | the `swift-noise` package was created by Dianna (@taylorswift). 2 | 3 | you are welcome to contribute to this project at: 4 | 5 | https://github.com/tayloraswift/swift-noise 6 | 7 | we do not maintain any other mirrors of this repository, and such 8 | forks of this repository may not carry the most up-to-date code. 9 | 10 | contributors: 11 | 12 | 1. Dianna (@taylorswift, 2017–22) 13 | 2. Joseph Heck (@heckj) 14 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "swift-hash", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/tayloraswift/swift-hash", 7 | "state" : { 8 | "revision" : "4d70a941b7039358f2ec8565f6c3b53c05f6c6ef", 9 | "version" : "0.6.3" 10 | } 11 | }, 12 | { 13 | "identity" : "swift-png", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/tayloraswift/swift-png", 16 | "state" : { 17 | "revision" : "e677ba0728150c41118c52fcbebdfa74b071ab17", 18 | "version" : "4.4.7" 19 | } 20 | } 21 | ], 22 | "version" : 2 23 | } 24 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.8 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "swift-noise", 7 | platforms: [.macOS("13.3"), .iOS("16.4"), .tvOS("16.4"), .watchOS("9.4")], 8 | products: 9 | [ 10 | .library(name: "Noise", targets: ["Noise"]), 11 | .executable(name: "generate-noise", targets: ["GenNoise"]) 12 | ], 13 | dependencies: 14 | [ 15 | .package(url: "https://github.com/tayloraswift/swift-png", from: "4.4.0") 16 | ], 17 | targets: 18 | [ 19 | .target( 20 | name: "Noise" 21 | ), 22 | .testTarget(name: "NoiseTests", dependencies: ["Noise"]), 23 | .executableTarget(name: "GenNoise", 24 | dependencies: [ 25 | .target(name: "Noise"), 26 | .product(name: "PNG", package: "swift-png"), 27 | ], 28 | exclude:[ 29 | "calibrate.blend", 30 | "calibrate.blend1" 31 | ]) 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ***`noise`***
`2.0` 4 | 5 | [![Tests](https://github.com/tayloraswift/swift-noise/actions/workflows/Tests.yml/badge.svg)](https://github.com/tayloraswift/swift-noise/actions/workflows/Tests.yml) 6 | [![Documentation](https://github.com/tayloraswift/swift-noise/actions/workflows/Documentation.yml/badge.svg)](https://github.com/tayloraswift/swift-noise/actions/workflows/Documentation.yml) 7 | 8 | ![](Sources/Noise/docs.docc/png/banner_FBM.png) 9 | 10 |
11 | 12 | **`swift-noise`** is a free, pure Swift procedural noise generation library. The library product has no dependencies and does not import Foundation or any system frameworks. 13 | 14 | All popular types of procedural noise are supported, including three [gradient noises](https://en.wikipedia.org/wiki/Perlin_noise) (often called Perlin or simplex noises), and two [cellular noises](https://en.wikipedia.org/wiki/Worley_noise) (sometimes called Worley or Voronoi noises). 15 | 16 | `swift-noise` includes a [fractal brownian motion](https://thebookofshaders.com/13/) (FBM) noise composition framework, and a [disk point sampler](https://en.wikipedia.org/wiki/Supersampling#Poisson_disc) (often called a Poisson sampler), for generating visually even point distributions in the plane. `swift-noise` also includes pseudo-random number generation and hashing tools. 17 | 18 | `swift-noise`’s entire public API is [documented](https://swiftinit.org/docs/swift-noise/noise). 19 | 20 | 21 | ## Requirements 22 | 23 | The swift-noise library requires Swift 5.10 or later. 24 | 25 | 26 | | Platform | Status | 27 | | -------- | ------ | 28 | | 🐧 Linux | [![Tests](https://github.com/tayloraswift/swift-noise/actions/workflows/Tests.yml/badge.svg)](https://github.com/tayloraswift/swift-noise/actions/workflows/Tests.yml) | 29 | | 🍏 Darwin | [![Tests](https://github.com/tayloraswift/swift-noise/actions/workflows/Tests.yml/badge.svg)](https://github.com/tayloraswift/swift-noise/actions/workflows/Tests.yml) | 30 | | 🍏 Darwin (iOS) | [![iOS](https://github.com/tayloraswift/swift-noise/actions/workflows/iOS.yml/badge.svg)](https://github.com/tayloraswift/swift-noise/actions/workflows/iOS.yml) | 31 | | 🍏 Darwin (tvOS) | [![tvOS](https://github.com/tayloraswift/swift-noise/actions/workflows/tvOS.yml/badge.svg)](https://github.com/tayloraswift/swift-noise/actions/workflows/tvOS.yml) | 32 | | 🍏 Darwin (visionOS) | [![visionOS](https://github.com/tayloraswift/swift-noise/actions/workflows/visionOS.yml/badge.svg)](https://github.com/tayloraswift/swift-noise/actions/workflows/visionOS.yml) | 33 | | 🍏 Darwin (watchOS) | [![watchOS](https://github.com/tayloraswift/swift-noise/actions/workflows/watchOS.yml/badge.svg)](https://github.com/tayloraswift/swift-noise/actions/workflows/watchOS.yml) | 34 | 35 | 36 | [Check deployment minimums](https://swiftinit.org/docs/swift-noise#ss:platform-requirements) 37 | 38 | 39 | ## Building 40 | 41 | Build *Noise* with the Swift Package Manager. *Noise* itself has no dependencies, but the tests use 42 | 43 | The package includes an executable, `generate-noise`, that depends on [`swift-png`](https://github.com/tayloraswift/swift-png). 44 | It generates noise locally for visual inspection. 45 | To regenerate example images, run the following command in the terminal: 46 | 47 | ``` 48 | swift run -c release generate-noise 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- /Scripts/TestAll: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | swift --version 4 | swift build -c release --build-tests 5 | swift test -c release --skip-build 6 | -------------------------------------------------------------------------------- /Sources/GenNoise/banners.swift: -------------------------------------------------------------------------------- 1 | import Noise 2 | import PNG 3 | 4 | extension UInt8 5 | { 6 | init(clamping value:T) where T:BinaryFloatingPoint 7 | { 8 | self.init(Swift.max(0, Swift.min(255, value))) 9 | } 10 | } 11 | 12 | func color_noise_png(r_noise:Noise, g_noise:Noise, b_noise:Noise, 13 | width:Int, height:Int, value_offset:(r:Double, g:Double, b:Double), invert:Bool = false, path:String) 14 | { 15 | let domain:Domain2D = .init(samples_x: width, samples_y: height) 16 | let rgba:[PNG.RGBA] = domain.map 17 | { 18 | (p:(x:Double, y:Double)) in 19 | 20 | let r:UInt8 = .init(clamping: r_noise.evaluate(p.x, p.y) + value_offset.r), 21 | g:UInt8 = .init(clamping: g_noise.evaluate(p.x, p.y) + value_offset.g), 22 | b:UInt8 = .init(clamping: b_noise.evaluate(p.x, p.y) + value_offset.b) 23 | if invert 24 | { 25 | return .init(.max - r, .max - g, .max - b) 26 | } 27 | else 28 | { 29 | return .init(r, g, b) 30 | } 31 | } 32 | 33 | do 34 | { 35 | let image:PNG.Image = .init(packing: rgba, size: (width, height), 36 | layout: .init(format: .rgb8(palette: [], fill: nil, key: nil))) 37 | try image.compress(path: path, level: 9) 38 | } 39 | catch 40 | { 41 | print(error) 42 | } 43 | } 44 | 45 | func banner_classic3d(width:Int, height:Int, seed:Int) 46 | { 47 | color_noise_png(r_noise: ClassicNoise3D(amplitude: 1.6 * 0.5*255, frequency: 0.01, seed: seed), 48 | g_noise: ClassicNoise3D(amplitude: 1.6 * 0.5*255, frequency: 0.005, seed: seed + 1), 49 | b_noise: ClassicNoise3D(amplitude: 1.6 * 0.5*255, frequency: 0.0025, seed: seed + 2), 50 | width: width, 51 | height: height, 52 | value_offset: (0.65*255, 0.65*255, 0.65*255), 53 | path: "examples/banner_classic3d.png") 54 | } 55 | 56 | /* 57 | func banner_simplex2d(width:Int, height:Int, seed:Int) 58 | { 59 | color_noise_png(r_noise: SimplexNoise2D(amplitude: 0.5*255, frequency: 0.015, seed: seed), 60 | g_noise: SimplexNoise2D(amplitude: 0.5*255, frequency: 0.0075, seed: seed + 1), 61 | b_noise: SimplexNoise2D(amplitude: 0.5*255, frequency: 0.00375, seed: seed + 2), 62 | width: width, 63 | height: height, 64 | value_offset: (0.65*255, 0.65*255, 0.65*255), 65 | path: "tests/banner_simplex2d.png") 66 | } 67 | */ 68 | 69 | func banner_supersimplex2d(width:Int, height:Int, seed:Int) 70 | { 71 | color_noise_png(r_noise: GradientNoise2D(amplitude: 0.5*255, frequency: 0.01, seed: seed), 72 | g_noise: GradientNoise2D(amplitude: 0.5*255, frequency: 0.005, seed: seed + 1), 73 | b_noise: GradientNoise2D(amplitude: 0.5*255, frequency: 0.0025, seed: seed + 2), 74 | width: width, 75 | height: height, 76 | value_offset: (0.65*255, 0.65*255, 0.65*255), 77 | path: "examples/banner_supersimplex2d.png") 78 | } 79 | 80 | func banner_supersimplex3d(width:Int, height:Int, seed:Int) 81 | { 82 | color_noise_png(r_noise: GradientNoise3D(amplitude: 0.5*255, frequency: 0.01, seed: seed), 83 | g_noise: GradientNoise3D(amplitude: 0.5*255, frequency: 0.005, seed: seed + 1), 84 | b_noise: GradientNoise3D(amplitude: 0.5*255, frequency: 0.0025, seed: seed + 2), 85 | width: width, 86 | height: height, 87 | value_offset: (0.65*255, 0.65*255, 0.65*255), 88 | path: "examples/banner_supersimplex3d.png") 89 | } 90 | 91 | func banner_cell2d(width:Int, height:Int, seed:Int) 92 | { 93 | color_noise_png(r_noise: CellNoise2D(amplitude: 3*255, frequency: 0.03, seed: seed), 94 | g_noise: CellNoise2D(amplitude: 3*255, frequency: 0.015, seed: seed + 1), 95 | b_noise: CellNoise2D(amplitude: 3*255, frequency: 0.0075, seed: seed + 2), 96 | width: width, 97 | height: height, 98 | value_offset: (0, 0, 0), 99 | invert: true, 100 | path: "examples/banner_cell2d.png") 101 | } 102 | 103 | func banner_cell3d(width:Int, height:Int, seed:Int) 104 | { 105 | color_noise_png(r_noise: CellNoise3D(amplitude: 3*255, frequency: 0.03, seed: seed), 106 | g_noise: CellNoise3D(amplitude: 3*255, frequency: 0.015, seed: seed + 1), 107 | b_noise: CellNoise3D(amplitude: 3*255, frequency: 0.0075, seed: seed + 2), 108 | width: width, 109 | height: height, 110 | value_offset: (0, 0, 0), 111 | invert: true, 112 | path: "examples/banner_cell3d.png") 113 | } 114 | 115 | func banner_FBM(width:Int, height:Int, seed:Int) 116 | { 117 | color_noise_png(r_noise: FBM (CellNoise3D(amplitude: 10*255, frequency: 0.01, seed: seed + 2), octaves: 7, persistence: 0.75), 118 | g_noise: FBM(GradientNoise3D(amplitude: 300, frequency: 0.005, seed: seed + 3), octaves: 7, persistence: 0.75), 119 | b_noise: FBM(GradientNoise2D(amplitude: 300, frequency: 0.005), octaves: 7, persistence: 0.75), 120 | width: width, 121 | height: height, 122 | value_offset: (0, 150, 150), 123 | invert: false, 124 | path: "examples/banner_FBM.png") 125 | } 126 | 127 | func circle_at(cx:Double, cy:Double, r:Double, width:Int, height:Int, _ f:(Int, Int, Double) -> ()) 128 | { 129 | // get bounding box 130 | let x1:Int = max(0 , Int(cx - r)), 131 | x2:Int = min(width - 1 , Int((cx + r).rounded(.up))), 132 | y1:Int = max(0 , Int(cy - r)), 133 | y2:Int = min(height - 1, Int((cy + r).rounded(.up))) 134 | 135 | for y in y1 ... y2 136 | { 137 | let dy:Double = Double(y) - cy 138 | for x in x1 ... x2 139 | { 140 | let dx:Double = Double(x) - cx, 141 | dr:Double = (dx*dx + dy*dy).squareRoot() 142 | 143 | f(x, y, min(1, max(0, 1 - dr + r - 0.5))) 144 | } 145 | } 146 | } 147 | 148 | func banner_disk2d(width:Int, height:Int, seed:Int) 149 | { 150 | var poisson:DiskSampler2D = .init(seed: seed) 151 | var rgba:[PNG.RGBA] = .init(repeating: .init(.max), count: width * height) 152 | 153 | //let points = poisson.generate(radius: 20, width: width, height: height, k: 80) 154 | 155 | @inline(__always) 156 | func _dots(_ points:[(x:Double, y:Double)], color: (r:UInt8, g:UInt8, b:UInt8)) 157 | { 158 | for point:(x:Double, y:Double) in points 159 | { 160 | circle_at(cx: point.x, cy: point.y, r: 10, width: width, height: height) 161 | { 162 | (x:Int, y:Int, v:Double) in 163 | 164 | let i:Int = y * width + x 165 | rgba[i].r = .init(clamping: Int(rgba[i].r) - Int(Double(color.r) * v)) 166 | rgba[i].g = .init(clamping: Int(rgba[i].g) - Int(Double(color.g) * v)) 167 | rgba[i].b = .init(clamping: Int(rgba[i].b) - Int(Double(color.b) * v)) 168 | } 169 | } 170 | } 171 | 172 | _dots(poisson.generate(radius: 35, width: width, height: height, k: 80, seed: (10, 10)), color: (0, 210, 70)) 173 | _dots(poisson.generate(radius: 25, width: width, height: height, k: 80, seed: (45, 15)), color: (0, 10, 235)) 174 | _dots(poisson.generate(radius: 30, width: width, height: height, k: 80, seed: (15, 45)), color: (225, 20, 0)) 175 | 176 | do 177 | { 178 | let image:PNG.Image = .init(packing: rgba, size: (width, height), 179 | layout: .init(format: .rgb8(palette: [], fill: nil, key: nil))) 180 | try image.compress(path: "examples/banner_disk2d.png", level: 9) 181 | } 182 | catch 183 | { 184 | print(error) 185 | } 186 | } 187 | 188 | func banner_voronoi2d(width:Int, height:Int, seed:Int) 189 | { 190 | let voronoi:CellNoise2D = CellNoise2D(amplitude: 255, frequency: 0.025, seed: seed) 191 | 192 | let r:PermutationTable = PermutationTable(seed: seed), 193 | g:PermutationTable = PermutationTable(seed: seed + 1), 194 | b:PermutationTable = PermutationTable(seed: seed + 2) 195 | 196 | let domain:Domain2D = .init(samples_x: width, samples_y: height) 197 | let rgba:[PNG.RGBA] = domain.map 198 | { 199 | (p:(x:Double, y:Double)) in 200 | 201 | @inline(__always) 202 | func _supersample(_ x:Double, _ y:Double) -> (r:UInt8, g:UInt8, b:UInt8) 203 | { 204 | let (point, _):((Int, Int), Double) = voronoi.closest_point(Double(x), Double(y)) 205 | let r:UInt8 = r.hash(point.0, point.1), 206 | g:UInt8 = g.hash(point.0, point.1), 207 | b:UInt8 = b.hash(point.0, point.1), 208 | peak:UInt8 = max(r, max(g, b)), 209 | saturate:Double = .init(UInt8.max) / .init(peak) 210 | return (.init(.init(r) * saturate), .init(.init(g) * saturate), .init(.init(b) * saturate)) 211 | } 212 | 213 | var r:Int = 0, 214 | g:Int = 0, 215 | b:Int = 0 216 | let supersamples:[(Double, Double)] = [(0, 0), (0, 0.4), (0.4, 0), (-0.4, 0), (0, -0.4), 217 | (-0.25, -0.25), (0.25, 0.25), (-0.25, 0.25), (0.25, -0.25)] 218 | for (dx, dy):(Double, Double) in supersamples 219 | { 220 | let contribution:(r:UInt8, g:UInt8, b:UInt8) = _supersample(p.x + dx, p.y + dy) 221 | r += .init(contribution.r) 222 | g += .init(contribution.g) 223 | b += .init(contribution.b) 224 | } 225 | 226 | return .init( .init(r / supersamples.count), 227 | .init(g / supersamples.count), 228 | .init(b / supersamples.count)) 229 | } 230 | 231 | do 232 | { 233 | let image:PNG.Image = .init(packing: rgba, size: (width, height), 234 | layout: .init(format: .rgb8(palette: [], fill: nil, key: nil))) 235 | try image.compress(path: "examples/banner_voronoi2d.png", level: 9) 236 | } 237 | catch 238 | { 239 | print(error) 240 | } 241 | } 242 | 243 | public 244 | func banners(width:Int, ratio:Double) 245 | { 246 | let height:Int = Int(Double(width) / ratio) 247 | print("banner_classic3d") 248 | banner_classic3d (width: width, height: height, seed: 6) 249 | //print("banner_simplex2d") 250 | //banner_simplex2d (width: width, height: height, seed: 6) 251 | print("banner_supersimplex2d") 252 | banner_supersimplex2d(width: width, height: height, seed: 8) 253 | print("banner_supersimplex3d") 254 | banner_supersimplex3d(width: width, height: height, seed: 2) 255 | print("banner_cell2d") 256 | banner_cell2d (width: width, height: height, seed: 0) 257 | print("banner_cell3d") 258 | banner_cell3d (width: width, height: height, seed: 0) 259 | print("banner_voronoi2d") 260 | banner_voronoi2d (width: width, height: height, seed: 3) 261 | print("banner_FBM") 262 | banner_FBM (width: width, height: height, seed: 2) 263 | print("banner_disk2d") 264 | banner_disk2d (width: width, height: height, seed: 0) 265 | } 266 | -------------------------------------------------------------------------------- /Sources/GenNoise/calibrate.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/Sources/GenNoise/calibrate.blend -------------------------------------------------------------------------------- /Sources/GenNoise/calibrate.blend1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/Sources/GenNoise/calibrate.blend1 -------------------------------------------------------------------------------- /Sources/GenNoise/calibrate.swift: -------------------------------------------------------------------------------- 1 | import Noise 2 | import PNG 3 | 4 | func grayscale_noise_png(noise:Noise, width:Int, height:Int, value_offset:Double, path:String) 5 | { 6 | let domain:Domain2D = .init(-2 ... 2, -2 ... 2, samples_x: width, samples_y: height) 7 | let v:[UInt8] = domain.enumerated().map 8 | { 9 | (element:(Int, (x:Double, y:Double))) in 10 | 11 | return .init(clamping: noise.evaluate(element.1.x, element.1.y) + value_offset) 12 | } 13 | 14 | do 15 | { 16 | let image:PNG.Image = .init(packing: v, size: (width, height), 17 | layout: .init(format: .v8(fill: nil, key: nil))) 18 | try image.compress(path: path, level: 9) 19 | } 20 | catch 21 | { 22 | print(error) 23 | } 24 | } 25 | 26 | public 27 | func calibrate_noise(width:Int, height:Int, seed:Int = 0) 28 | { 29 | print("calibrate-classic-distortion") 30 | grayscale_noise_png(noise: DistortedNoise(FBM(ClassicNoise3D(amplitude: 0.5*255, frequency: 2, seed: seed), octaves: 4), strength: 0.003), 31 | width: width, 32 | height: height, 33 | value_offset: 0.5*255, 34 | path: "examples/calibrate_classic-distortion.png") 35 | 36 | print("calibrate-gradient2d") 37 | grayscale_noise_png(noise: GradientNoise2D(amplitude: 0.5*255, frequency: 4, seed: seed), 38 | width: width, 39 | height: height, 40 | value_offset: 0.5*255, 41 | path: "examples/calibrate_gradient2d.png") 42 | 43 | print("calibrate-gradient3d") 44 | grayscale_noise_png(noise: GradientNoise3D(amplitude: 0.5*255, frequency: 4, seed: seed), 45 | width: width, 46 | height: height, 47 | value_offset: 0.5*255, 48 | path: "examples/calibrate_gradient3d.png") 49 | 50 | print("calibrate-cell2d") 51 | grayscale_noise_png(noise: CellNoise2D(amplitude: 255, frequency: 4, seed: seed), 52 | width: width, 53 | height: height, 54 | value_offset: 0, 55 | path: "examples/calibrate_cell2d.png") 56 | 57 | print("calibrate-cell3d") 58 | grayscale_noise_png(noise: CellNoise3D(amplitude: 255, frequency: 4, seed: seed), 59 | width: width, 60 | height: height, 61 | value_offset: 0, 62 | path: "examples/calibrate_cell3d.png") 63 | } 64 | -------------------------------------------------------------------------------- /Sources/GenNoise/main.swift: -------------------------------------------------------------------------------- 1 | import Noise 2 | import PNG 3 | 4 | // TODO: migrate the stdlib Clock APIs 5 | #if os(Linux) 6 | import func Glibc.clock 7 | #else 8 | import func Darwin.clock 9 | 10 | func clock() -> Int 11 | { 12 | .init(Darwin.clock()) 13 | } 14 | #endif 15 | 16 | print("Generating banner examples") 17 | banners(width: 700, ratio: 5) 18 | 19 | print("Generated calibration examples ") 20 | calibrate_noise(width: 256, height: 256) 21 | 22 | //let viewer_size:Int = 1024 23 | //var pixbuf:[UInt8] = [UInt8](repeating: 0, count: viewer_size * viewer_size) 24 | // 25 | //func benchmark(noise generator:Generator, name:String, offset:Double = 0) throws 26 | // where Generator:Noise 27 | //{ 28 | // let t0:Int = clock() 29 | // for (i, (x, y)) in Domain2D(samples_x: viewer_size, samples_y: viewer_size).enumerated() 30 | // { 31 | // pixbuf[i] = UInt8(max(0, min(255, generator.evaluate(x, y) + offset))) 32 | // } 33 | // print("\(name): \(clock() - t0)") 34 | // 35 | // let image:PNG.Image = .init(packing: pixbuf, size: (viewer_size, viewer_size), 36 | // layout: .init(format: .v8(fill: nil, key: nil))) 37 | // try image.compress(path: "tests/\(name).png", level: 9) 38 | //} 39 | // 40 | //var poisson = DiskSampler2D(seed: 0) 41 | //let t0:Int = clock() 42 | //for point:(x:Double, y:Double) in poisson.generate(radius: 10, width: viewer_size, height: viewer_size) 43 | //{ 44 | // pixbuf[Int(point.y) * viewer_size + Int(point.x)] = 255 45 | //} 46 | //print("disk2d: \(clock() - t0)") 47 | //let image:PNG.Image = .init(packing: pixbuf, size: (viewer_size, viewer_size), 48 | // layout: .init(format: .v8(fill: nil, key: nil))) 49 | //try image.compress(path: "tests/disk2d.png", level: 9) 50 | // 51 | //try benchmark(noise: CellNoise2D(amplitude: 255, frequency: 0.01), name: "cell2d") 52 | //try benchmark(noise: CellNoise3D(amplitude: 255, frequency: 0.01), name: "cell3d") 53 | //try benchmark(noise: TilingCellNoise3D(amplitude: 255, frequency: 16 / Double(viewer_size), 54 | // wavelengths: 16), 55 | // name: "cell_tiling3d") 56 | //try benchmark(noise: FBM(tiling: TilingClassicNoise3D(amplitude: 255, 57 | // frequency: 16 / Double(viewer_size), 58 | // wavelengths: 16), 59 | // octaves: 10, persistence: 0.62), 60 | // name: "classic_tiling_fbm3d", 61 | // offset: 127.5) 62 | //try benchmark(noise: FBM(ClassicNoise3D(amplitude: 255, frequency: 0.001), 63 | // octaves: 10, persistence: 0.62), 64 | // name: "classic3d", 65 | // offset: 127.5) 66 | //try benchmark( noise: TilingClassicNoise3D(amplitude: 255, frequency: 16 / Double(viewer_size), wavelengths: 16), 67 | // name: "classic_tiling3d", 68 | // offset: 127.5) 69 | //try benchmark(noise: FBM(GradientNoise2D(amplitude: 180, frequency: 0.001), octaves: 10, persistence: 0.62), 70 | // name: "gradient2d", 71 | // offset: 127.5) 72 | //try benchmark(noise: FBM(GradientNoise3D(amplitude: 180, frequency: 0.001), octaves: 10, persistence: 0.62), 73 | // name: "gradient3d", 74 | // offset: 127.5) 75 | -------------------------------------------------------------------------------- /Sources/Noise/cell.swift: -------------------------------------------------------------------------------- 1 | fileprivate 2 | protocol _CellNoise2D 3 | { 4 | var frequency:Double { get } 5 | var amplitude:Double { get } 6 | 7 | func hash(point:Math.IntV2) -> Int 8 | } 9 | 10 | extension _CellNoise2D 11 | { 12 | @inline(__always) 13 | private 14 | func distance2(from sample_point:Math.DoubleV2, generating_point:Math.IntV2) -> Double 15 | { 16 | let hash:Int = self.hash(point: generating_point) 17 | // hash is within 0 ... 255, take it to 0 ... 0.5 18 | 19 | // Notice that we have 256 possible hashes, and therefore 8 bits of entropy, 20 | // to be divided up between 2 axes. We can assign 4 bits to the x and y 21 | // axes each (16 levels each) 22 | 23 | // 0b XXXX YYYY 24 | 25 | let dp:Math.DoubleV2 = ((Double(hash >> 4 ) - 15 / 2) * 1 / 16, 26 | (Double(hash & 0b1111) - 15 / 2) * 1 / 16) 27 | 28 | let dv:Math.DoubleV2 = Math.sub(Math.add(Math.cast_double(generating_point), dp), sample_point) 29 | return Math.dot(dv, dv) 30 | } 31 | 32 | // ugly hack to get around compiler linker bug 33 | @inline(__always) 34 | func _closest_point(_ x:Double, _ y:Double) -> (point:(Int, Int), r2:Double) 35 | { 36 | let sample:Math.DoubleV2 = (x * self.frequency, y * self.frequency) 37 | 38 | let (bin, sample_rel):(Math.IntV2, Math.DoubleV2) = Math.fraction(sample) 39 | 40 | // determine kernel 41 | 42 | // The feature points do not live within the grid cells, rather they float 43 | // around the *corners* of the grid cells (the ‘O’s in the diagram). 44 | // The grid cell that the sample point has been binned into is shaded. 45 | 46 | // xb xb + 1 47 | // +------------------+ 48 | // | | | | 49 | // yb |---- O//////O ----| 50 | // | |//////| | 51 | // yb + 1 |---- O//////O ----| 52 | // | | | | 53 | // +------------------+ 54 | 55 | // The bin itself is divided into quadrants to classify the four corners 56 | // as “near” and “far” points. We call these points the *generating points*. 57 | // The sample point (example) has been marked with an ‘*’. 58 | 59 | // A —————— far 60 | // | | | 61 | // |----+----| 62 | // | * | | 63 | // near —————— A ← quadrant 64 | // ↓ 65 | 66 | // The actual feature points never spawn outside of the unit square surrounding 67 | // their generating points. Therefore, the boundaries of the generating 68 | // squares serve as a useful means of early exit — if the square is farther 69 | // away than a point already found, then there is no point checking that 70 | // square since it cannot produce a feature point closer than we have already 71 | // found. 72 | 73 | let quadrant:Math.IntV2 = (sample_rel.x > 0.5 ? 1 : -1 , sample_rel.y > 0.5 ? 1 : -1), 74 | near:Math.IntV2 = Math.add(bin, ((quadrant.a + 1) >> 1, (quadrant.b + 1) >> 1)), 75 | far:Math.IntV2 = (near.a - quadrant.a , near.b - quadrant.b) 76 | 77 | let nearpoint_disp:Math.DoubleV2 = (abs(sample_rel.x - Double((quadrant.a + 1) >> 1)), 78 | abs(sample_rel.y - Double((quadrant.b + 1) >> 1))) 79 | 80 | var r2:Double = self.distance2(from: sample, generating_point: near), 81 | closest_point:Math.IntV2 = near 82 | 83 | @inline(__always) 84 | func _inspect(generating_point:Math.IntV2, dx:Double = 0, dy:Double = 0) 85 | { 86 | if dx*dx + dy*dy < r2 87 | { 88 | let dr2:Double = self.distance2(from: sample, generating_point: generating_point) 89 | if dr2 < r2 90 | { 91 | r2 = dr2 92 | closest_point = generating_point 93 | } 94 | } 95 | } 96 | 97 | // Cell group I: 98 | // within r^2 = 0.25 99 | // cumulative sample coverage = 65.50% 100 | 101 | // A points 102 | _inspect(generating_point: (near.a, far.b), dy: nearpoint_disp.y - 0.5) 103 | _inspect(generating_point: (far.a, near.b), dx: nearpoint_disp.x - 0.5) 104 | 105 | // far point 106 | _inspect(generating_point: far, dx: nearpoint_disp.x - 0.5, dy: nearpoint_disp.y - 0.5) 107 | 108 | guard r2 > 0.25 109 | else 110 | { 111 | return (closest_point, r2) 112 | } 113 | 114 | // This is the part where shit hits the fan. (`inner` and `outer` are never 115 | // sampled directly, they are used for calculating the coordinates of the 116 | // generating point.) 117 | 118 | // +-------- D ------- E ----- outer 119 | // | | | | | | | 120 | // |----+----|----+----|----+----| 121 | // | | | | | | | 122 | // C ------- A —————— far ------ E 123 | // | | | | | | | 124 | // |----+----|----+----|----+----| 125 | // | | | * | | | | 126 | // B ----- near —————— A ------- D 127 | // | | | | | | | 128 | // |----+----|----+----|----+----| 129 | // | | | | | | | 130 | // inner ----- B ------- C --------+ ← quadrant 131 | // ↓ 132 | 133 | // Cell group II: 134 | // within r^2 = 1.0 135 | // cumulative sample coverage = 99.96% 136 | let inner:Math.IntV2 = Math.add(near, quadrant) 137 | 138 | // B points 139 | _inspect(generating_point: (inner.a, near.b), dx: nearpoint_disp.x + 0.5) 140 | _inspect(generating_point: (near.a, inner.b), dy: nearpoint_disp.y + 0.5) 141 | 142 | // C points 143 | _inspect(generating_point: (inner.a, far.b), dx: nearpoint_disp.x + 0.5, dy: nearpoint_disp.y - 0.5) 144 | _inspect(generating_point: (far.a, inner.b), dx: nearpoint_disp.x - 0.5, dy: nearpoint_disp.y + 0.5) 145 | 146 | guard r2 > 1.0 147 | else 148 | { 149 | return (closest_point, r2) 150 | } 151 | 152 | // Cell group III: 153 | // within r^2 = 2.0 154 | // cumulative sample coverage = 100% 155 | let outer:Math.IntV2 = Math.sub(far, quadrant) 156 | 157 | // D points 158 | _inspect(generating_point: (near.a, outer.b), dy: nearpoint_disp.y - 1.5) 159 | _inspect(generating_point: (outer.a, near.b), dx: nearpoint_disp.x - 1.5) 160 | 161 | // E points 162 | _inspect(generating_point: (far.a, outer.b), dx: nearpoint_disp.x - 0.5, dy: nearpoint_disp.y - 1.5) 163 | _inspect(generating_point: (outer.a, far.b), dx: nearpoint_disp.x - 1.5, dy: nearpoint_disp.y - 0.5) 164 | 165 | return (closest_point, r2) 166 | } 167 | } 168 | 169 | /// A type of two-dimensional cellular noise (sometimes called 170 | /// [Worley noise](https://en.wikipedia.org/wiki/Worley_noise), or Voronoi noise), suitable for 171 | /// texturing two-dimensional planes. 172 | /// 173 | /// ![preview](png/banner_cell2d.png) 174 | /// 175 | /// Unlike many other cell noise implementations, *Noise*’s implementation samples all relevant 176 | /// generating-points, preventing artifacts or discontinuities from ever appearing in the noise. 177 | /// Accordingly, *Noise*’s implementation is heavily optimized to prevent the additional edge 178 | /// cases from impacting the performance of the cell noise. 179 | /// 180 | /// Cell noise has a three-dimensional version, ``CellNoise3D``. 181 | public 182 | struct CellNoise2D:_CellNoise2D, HashedNoise 183 | { 184 | let permutation_table:PermutationTable, 185 | amplitude:Double, 186 | frequency:Double 187 | 188 | init(amplitude:Double, frequency:Double, permutation_table:PermutationTable) 189 | { 190 | self.amplitude = amplitude 191 | self.frequency = frequency 192 | self.permutation_table = permutation_table 193 | } 194 | 195 | /// Creates an instance with the given `amplitude`, `frequency`, and random `seed` values. 196 | /// Creating an instance generates a new pseudo-random permutation table for that instance, 197 | /// and a new instance does not need to be regenerated to sample the same procedural noise 198 | /// field. 199 | /// 200 | /// The given amplitude is adjusted internally to produce output *exactly* within the range 201 | /// of `0 ... amplitude`. However, in practice the cell noise rarely reaches the maximum 202 | /// threshold, as it is often useful to inflate the amplitude to get the desired appearance. 203 | public 204 | init(amplitude:Double, frequency:Double, seed:Int = 0) 205 | { 206 | self.amplitude = amplitude / 2 207 | self.frequency = frequency 208 | self.permutation_table = PermutationTable(seed: seed) 209 | } 210 | 211 | /// Returns the index numbers of the closest feature point to the given coordinate, and the 212 | /// squared distance from the given coordinate to the feature point. These index numbers can 213 | /// be fed to a color hashing function to produce a 214 | /// [Voronoi diagram](https://en.wikipedia.org/wiki/Voronoi_diagram). 215 | public 216 | func closest_point(_ x:Double, _ y:Double) -> (point:(Int, Int), r2:Double) 217 | { 218 | return self._closest_point(x, y) 219 | } 220 | 221 | /// Evaluates the cell noise field at the given coordinates. 222 | public 223 | func evaluate(_ x:Double, _ y:Double) -> Double 224 | { 225 | let (_, r2):((Int, Int), Double) = self.closest_point(x, y) 226 | return self.amplitude * r2 227 | } 228 | 229 | /// Evaluates the cell noise field at the given coordinates. The third coordinate is 230 | /// ignored. 231 | public 232 | func evaluate(_ x:Double, _ y:Double, _:Double) -> Double 233 | { 234 | return self.evaluate(x, y) 235 | } 236 | 237 | /// Evaluates the cell noise field at the given coordinates. The third and fourth 238 | /// coordinates are ignored. 239 | public 240 | func evaluate(_ x:Double, _ y:Double, _:Double, _:Double) -> Double 241 | { 242 | return self.evaluate(x, y) 243 | } 244 | } 245 | 246 | public 247 | struct TilingCellNoise2D:_CellNoise2D, HashedTilingNoise 248 | { 249 | let permutation_table:PermutationTable, 250 | amplitude:Double, 251 | frequency:Double, 252 | wavelengths:Math.IntV2 253 | 254 | init(amplitude:Double, frequency:Double, permutation_table:PermutationTable, wavelengths:Math.IntV2) 255 | { 256 | self.amplitude = amplitude 257 | self.frequency = frequency 258 | self.permutation_table = permutation_table 259 | self.wavelengths = wavelengths 260 | } 261 | 262 | public 263 | init(amplitude:Double, frequency:Double, wavelengths:Int, seed:Int = 0) 264 | { 265 | self.init( amplitude: amplitude, frequency: frequency, 266 | wavelengths_x: wavelengths, 267 | wavelengths_y: wavelengths, 268 | seed: seed) 269 | } 270 | 271 | public 272 | init(amplitude:Double, frequency:Double, wavelengths_x:Int, wavelengths_y:Int, seed:Int = 0) 273 | { 274 | self.amplitude = amplitude / 2 275 | self.frequency = frequency 276 | self.permutation_table = PermutationTable(seed: seed) 277 | self.wavelengths = (wavelengths_x, wavelengths_y) 278 | } 279 | 280 | public 281 | func closest_point(_ x:Double, _ y:Double) -> (point:(Int, Int), r2:Double) 282 | { 283 | return self._closest_point(x, y) 284 | } 285 | 286 | public 287 | func evaluate(_ x:Double, _ y:Double) -> Double 288 | { 289 | let (_, r2):((Int, Int), Double) = self.closest_point(x, y) 290 | return self.amplitude * r2 291 | } 292 | 293 | public 294 | func evaluate(_ x:Double, _ y:Double, _:Double) -> Double 295 | { 296 | return self.evaluate(x, y) 297 | } 298 | 299 | public 300 | func evaluate(_ x:Double, _ y:Double, _:Double, _:Double) -> Double 301 | { 302 | return self.evaluate(x, y) 303 | } 304 | } 305 | 306 | 307 | fileprivate 308 | protocol _CellNoise3D 309 | { 310 | var frequency:Double { get } 311 | var amplitude:Double { get } 312 | 313 | func hash(point:Math.IntV3) -> Int 314 | } 315 | 316 | extension _CellNoise3D 317 | { 318 | @inline(__always) 319 | private 320 | func distance2(from sample_point:Math.DoubleV3, generating_point:Math.IntV3) -> Double 321 | { 322 | let hash:Int = self.hash(point: generating_point) 323 | // hash is within 0 ... 255, take it to 0 ... 0.5 324 | 325 | // Notice that we have 256 possible hashes, and therefore 8 bits of entropy, 326 | // to be divided up between three axes. We can assign 3 bits to the x and 327 | // y axes each (8 levels each), and 2 bits to the z axis (4 levels). To 328 | // compensate for the lack of z resolution, we bump up every other feature 329 | // point by half a level. 330 | 331 | // 0b XXX YYY ZZ 332 | 333 | let axes:Math.DoubleV3 = Math.cast_double(( hash >> 5, 334 | hash >> 2 & 0b0111, 335 | hash << 1 & 0b0111 + ((hash >> 5 ^ hash >> 2) & 1))) 336 | let dp:Math.DoubleV3 = ((axes.x - 7 / 2) * 1.0 / 8, 337 | (axes.y - 7 / 2) * 1.0 / 8, 338 | (axes.z - 7 / 2) * 1.0 / 8) 339 | 340 | let dv:Math.DoubleV3 = Math.sub(Math.add(Math.cast_double(generating_point), dp), sample_point) 341 | return Math.dot(dv, dv) 342 | } 343 | 344 | @inline(__always) 345 | func _closest_point(_ x:Double, _ y:Double, _ z:Double) -> (point:(Int, Int, Int), r2:Double) 346 | { 347 | let sample:Math.DoubleV3 = (x * self.frequency, y * self.frequency, z * self.frequency) 348 | 349 | let (bin, sample_rel):(Math.IntV3, Math.DoubleV3) = Math.fraction(sample) 350 | 351 | // determine kernel 352 | 353 | // Same idea as with the 2D points, except in 3 dimensions 354 | 355 | // near - quadrant.xy ———— near - quadrant.y 356 | // | | | 357 | // |----+----| 358 | // | | * | 359 | // near - quadrant.x ————— near quadrant → 360 | // ↓ 361 | 362 | let quadrant:Math.IntV3 = (sample_rel.x > 0.5 ? 1 : -1, 363 | sample_rel.y > 0.5 ? 1 : -1, 364 | sample_rel.z > 0.5 ? 1 : -1) 365 | let near:Math.IntV3 = Math.add(bin, ((quadrant.a + 1) >> 1, 366 | (quadrant.b + 1) >> 1, 367 | (quadrant.c + 1) >> 1)) 368 | 369 | let nearpoint_disp:Math.DoubleV3 = (abs(sample_rel.x - Double((quadrant.a + 1) >> 1)), 370 | abs(sample_rel.y - Double((quadrant.b + 1) >> 1)), 371 | abs(sample_rel.z - Double((quadrant.c + 1) >> 1))) 372 | 373 | var r2:Double = self.distance2(from: sample, generating_point: near), 374 | closest_point:Math.IntV3 = near 375 | 376 | @inline(__always) 377 | func _inspect_cell(offset:Math.IntV3) 378 | { 379 | // calculate distance from quadrant volume to kernel cell 380 | var cell_distance2:Double 381 | if offset.a != 0 382 | { // move by 0.5 towards zero 383 | let dx:Double = nearpoint_disp.x + Double(offset.a) + (offset.a > 0 ? -0.5 : 0.5) 384 | cell_distance2 = dx*dx 385 | } 386 | else 387 | { 388 | cell_distance2 = 0 389 | } 390 | 391 | if offset.b != 0 392 | { // move by 0.5 towards zero 393 | let dy:Double = nearpoint_disp.y + Double(offset.b) + (offset.b > 0 ? -0.5 : 0.5) 394 | cell_distance2 += dy*dy 395 | } 396 | 397 | if offset.c != 0 398 | { // move by 0.5 towards zero 399 | let dz:Double = nearpoint_disp.z + Double(offset.c) + (offset.c > 0 ? -0.5 : 0.5) 400 | cell_distance2 += dz*dz 401 | } 402 | 403 | guard cell_distance2 < r2 404 | else 405 | { 406 | return 407 | } 408 | 409 | let generating_point:Math.IntV3 = Math.add(near, Math.mult(quadrant, offset)) 410 | let dr2:Double = self.distance2(from: sample, generating_point: generating_point) 411 | if dr2 < r2 412 | { 413 | r2 = dr2 414 | closest_point = generating_point 415 | } 416 | } 417 | 418 | // check each cell group, exiting early if we are guaranteed to have found 419 | // the closest point 420 | 421 | // Cell group I: 422 | // outside r^2 = 0 423 | // cumulative sample coverage = 47.85% 424 | _inspect_cell(offset: (-1, 0, 0)) 425 | _inspect_cell(offset: ( 0, -1, 0)) 426 | _inspect_cell(offset: ( 0, 0, -1)) 427 | 428 | _inspect_cell(offset: ( 0, -1, -1)) 429 | _inspect_cell(offset: (-1, 0, -1)) 430 | _inspect_cell(offset: (-1, -1, 0)) 431 | 432 | _inspect_cell(offset: (-1, -1, -1)) 433 | 434 | // Cell group II: 435 | // outside r^2 = 0.25 436 | // cumulative sample coverage = 88.60% 437 | guard r2 > 0.25 438 | else 439 | { 440 | return (closest_point, r2) 441 | } 442 | for offset in [(1, 0, 0), ( 0, 1, 0), ( 0, 0, 1), 443 | (0, -1, 1), ( 0, 1, -1), ( 1, 0, -1), (-1, 0, 1), (-1, 1, 0), (1, -1, 0), 444 | (1, -1, -1), (-1, 1, -1), (-1, -1, 1)] 445 | { 446 | _inspect_cell(offset: offset) 447 | } 448 | 449 | // Cell group III: 450 | // outside r^2 = 0.5 451 | // cumulative sample coverage = 98.26% 452 | guard r2 > 0.5 453 | else 454 | { 455 | return (closest_point, r2) 456 | } 457 | for offset in [(0, 1, 1), (1, 0, 1), (1, 1, 0), (-1, 1, 1), (1, -1, 1), (1, 1, -1)] 458 | { 459 | _inspect_cell(offset: offset) 460 | } 461 | 462 | // Cell group IV: [(1, 1, 1)] [ occluded ] 463 | // outside r^2 = 0.75 464 | // cumulative sample coverage = 99.94% 465 | 466 | // Cell group V: 467 | // outside r^2 = 1.0 468 | // cumulative sample coverage > 99.99% 469 | guard r2 > 1.0 470 | else 471 | { 472 | return (closest_point, r2) 473 | } 474 | for offset in [(-2, 0, 0), ( 0, -2, 0), ( 0, 0, -2), 475 | ( 0, -2, -1), ( 0, -1, -2), (-2, 0, -1), (-1, 0, -2), (-2, -1, 0), (-1, -2, 0), 476 | (-2, -1, -1), (-1, -2, -1), (-1, -1, -2)] 477 | { 478 | _inspect_cell(offset: offset) 479 | } 480 | 481 | // Cell group VI: 482 | // outside r^2 = 1.25 483 | // cumulative sample coverage > 99.99% 484 | guard r2 > 1.25 485 | else 486 | { 487 | return (closest_point, r2) 488 | } 489 | for offset in [( 0, 1, -2), ( 0, -2, 1), (1, 0, -2), (-2, 0, 1), (1, -2, 0), (-2, 1, 0), 490 | (-2, 1, -1), (-2, -1, 1), (1, -2, -1), (-1, -2, 1), (1, -1, -2), (-1, 1, -2)] 491 | { 492 | _inspect_cell(offset: offset) 493 | } 494 | 495 | // Cell group VII: [(-2, 1, 1), (1, -2, 1), (1, 1, -2)] [ occluded ] 496 | // outside r^2 = 1.5 497 | // cumulative sample coverage > 99.99% 498 | 499 | // Cell group VIII: 500 | // outside = 2.0 501 | // cumulative sample coverage > 99.99% 502 | guard r2 > 2.0 503 | else 504 | { 505 | return (closest_point, r2) 506 | } 507 | for offset in [(0, -2, -2), (-2, 0, -2), (-2, -2, 0), (-1, -2, -2), (-2, -1, -2), (-2, -2, -1)] 508 | { 509 | _inspect_cell(offset: offset) 510 | } 511 | 512 | // Cell group IX: [(1, -2, -2), (-2, -2, 1), (-2, 1, -2)] [ occluded ] 513 | // outside r^2 = 2.25 514 | // cumulative sample coverage > 99.99% 515 | guard r2 > 2.25 516 | else 517 | { 518 | return (closest_point, r2) 519 | } 520 | for offset in [(2, 0, 0), (0, 2, 0), ( 0, 0, 2), 521 | (0, -1, 2), (0, 2, -1), (-1, 0, 2), ( 2, 0, -1), (-1, 2, 0), ( 2, -1, 0), 522 | (2, -1, -1), (-1, -1, 2), (-1, 2, -1)] 523 | { 524 | _inspect_cell(offset: offset) 525 | } 526 | 527 | // Cell group X: 528 | // outside r^2 = 2.5 529 | // cumulative sample coverage > 99.99% 530 | guard r2 > 2.5 531 | else 532 | { 533 | return (closest_point, r2) 534 | } 535 | for offset in [(0, 1, 2), (0, 2, 1), (1, 0, 2), ( 2, 0, 1), (1, 2, 0), ( 2, 1, 0), 536 | (2, 1, -1), (2, -1, 1), (1, 2, -1), (-1, 2, 1), (1, -1, 2), (-1, 1, 2)] 537 | { 538 | _inspect_cell(offset: offset) 539 | } 540 | 541 | // Cell group XI: [(2, 1, 1), (1, 2, 1), (1, 1, 2)] [ occluded ] 542 | // outside r^2 = 2.75 543 | // cumulative sample coverage = 100% 544 | 545 | // stop outside r^2 = 3.0 546 | return (closest_point, r2) 547 | } 548 | } 549 | 550 | /// A type of three-dimensional cellular noise (sometimes called 551 | /// [Worley noise](https://en.wikipedia.org/wiki/Worley_noise), or Voronoi noise), suitable for 552 | /// texturing arbitrary three-dimensional objects. 553 | /// 554 | /// ![preview](png/banner_cell3d.png) 555 | /// 556 | /// Unlike many other cell noise implementations, *Noise*’s implementation samples all relevant 557 | /// generating-points, preventing artifacts or discontinuities from ever appearing in the noise. 558 | /// Accordingly, *Noise*’s implementation is heavily optimized to prevent the additional edge 559 | /// cases from impacting the performance of the cell noise. 560 | /// 561 | /// Three dimensional cell noise is approximately three to four times slower than its 562 | /// [two-dimensional version](doc:CellNoise2D), but has a vastly superior visual appearance, 563 | /// even when sampled in two dimensions. 564 | /// 565 | /// `CellNoise3D` is analogous to 566 | /// [Blender Voronoi noise](https://docs.blender.org/manual/en/dev/render/cycles/nodes/types/textures/voronoi.html), 567 | /// with the *Distance Squared* metric. The *Scale* of Blender Voronoi noise is identical to the 568 | /// frequency of `CellNoise3D`; its range is approximately `0 ... 10/3` in `CellNoise3D` 569 | /// units. 570 | public 571 | struct CellNoise3D:_CellNoise3D, HashedNoise 572 | { 573 | let permutation_table:PermutationTable, 574 | amplitude:Double, 575 | frequency:Double 576 | 577 | init(amplitude:Double, frequency:Double, permutation_table:PermutationTable) 578 | { 579 | self.amplitude = amplitude 580 | self.frequency = frequency 581 | self.permutation_table = permutation_table 582 | } 583 | 584 | /// Creates an instance with the given `amplitude`, `frequency`, and random `seed` values. 585 | /// Creating an instance generates a new pseudo-random permutation table for that instance, 586 | /// and a new instance does not need to be regenerated to sample the same procedural noise 587 | /// field. 588 | /// 589 | /// The given amplitude is adjusted internally to produce output *exactly* within the range 590 | /// of `0 ... amplitude`. However, in practice the cell noise rarely reaches the maximum 591 | /// threshold, as it is often useful to inflate the amplitude to get the desired appearance. 592 | public 593 | init(amplitude:Double, frequency:Double, seed:Int = 0) 594 | { 595 | self.amplitude = amplitude * 1/3 596 | self.frequency = frequency 597 | self.permutation_table = PermutationTable(seed: seed) 598 | } 599 | 600 | /// Returns the index numbers of the closest feature point to the given coordinate, and the 601 | /// squared distance from the given coordinate to the feature point. These index numbers can 602 | /// be fed to a color hashing function to produce a 603 | /// [Voronoi diagram](https://en.wikipedia.org/wiki/Voronoi_diagram). 604 | public 605 | func closest_point(_ x:Double, _ y:Double, _ z:Double) -> (point:(Int, Int, Int), r2:Double) 606 | { 607 | return self._closest_point(x, y, z) 608 | } 609 | 610 | /// Evaluates the cell noise field at the given `x, y` coordinates, supplying `0` for the 611 | /// missing `z` coordinate. 612 | public 613 | func evaluate(_ x:Double, _ y:Double) -> Double 614 | { 615 | return self.evaluate(x, y, 0) 616 | } 617 | 618 | /// Evaluates the cell noise field at the given coordinates. 619 | public 620 | func evaluate(_ x:Double, _ y:Double, _ z:Double) -> Double 621 | { 622 | let (_, r2):((Int, Int, Int), Double) = self.closest_point(x, y, z) 623 | return self.amplitude * r2 624 | } 625 | 626 | /// Evaluates the cell noise field at the given coordinates. The fourth coordinate is 627 | /// ignored. 628 | public 629 | func evaluate(_ x:Double, _ y:Double, _ z:Double, _:Double) -> Double 630 | { 631 | return self.evaluate(x, y, z) 632 | } 633 | } 634 | 635 | public 636 | struct TilingCellNoise3D:_CellNoise3D, HashedTilingNoise 637 | { 638 | let permutation_table:PermutationTable, 639 | amplitude:Double, 640 | frequency:Double, 641 | wavelengths:Math.IntV3 642 | 643 | init(amplitude:Double, frequency:Double, permutation_table:PermutationTable, wavelengths:Math.IntV3) 644 | { 645 | self.amplitude = amplitude 646 | self.frequency = frequency 647 | self.permutation_table = permutation_table 648 | self.wavelengths = wavelengths 649 | } 650 | 651 | public 652 | init(amplitude:Double, frequency:Double, wavelengths:Int, seed:Int = 0) 653 | { 654 | self.init( amplitude: amplitude, frequency: frequency, 655 | wavelengths_x: wavelengths, 656 | wavelengths_y: wavelengths, 657 | wavelengths_z: wavelengths, 658 | seed: seed) 659 | } 660 | 661 | public 662 | init(amplitude:Double, frequency:Double, wavelengths_x:Int, wavelengths_y:Int, wavelengths_z:Int, seed:Int = 0) 663 | { 664 | self.amplitude = 1 / 3 * amplitude 665 | self.frequency = frequency 666 | self.permutation_table = PermutationTable(seed: seed) 667 | self.wavelengths = (wavelengths_x, wavelengths_y, wavelengths_z) 668 | } 669 | 670 | public 671 | func closest_point(_ x:Double, _ y:Double, _ z:Double) -> (point:(Int, Int, Int), r2:Double) 672 | { 673 | return self._closest_point(x, y, z) 674 | } 675 | 676 | public 677 | func evaluate(_ x:Double, _ y:Double) -> Double 678 | { 679 | return self.evaluate(x, y, 0) 680 | } 681 | 682 | public 683 | func evaluate(_ x:Double, _ y:Double, _ z:Double) -> Double 684 | { 685 | let (_, r2):((Int, Int, Int), Double) = self.closest_point(x, y, z) 686 | return self.amplitude * r2 687 | } 688 | 689 | public 690 | func evaluate(_ x:Double, _ y:Double, _ z:Double, _:Double) -> Double 691 | { 692 | return self.evaluate(x, y, z) 693 | } 694 | } 695 | -------------------------------------------------------------------------------- /Sources/Noise/compounds.swift: -------------------------------------------------------------------------------- 1 | /// A generic [fractal brownian motion](https://thebookofshaders.com/13/) noise generator, 2 | /// capable of overlaying multiple instances of procedural ``Noise`` at increasing frequencies. 3 | /// 4 | /// ![preview](png/banner_FBM.png) 5 | public 6 | struct FBM:Noise where Source:Noise 7 | { 8 | private 9 | let generators:[Source] 10 | 11 | // UNDOCUMENTED 12 | public 13 | func amplitude_scaled(by factor:Double) -> FBM 14 | { 15 | return FBM(generators: self.generators.map{ $0.amplitude_scaled(by: factor) }) 16 | } 17 | public 18 | func frequency_scaled(by factor:Double) -> FBM 19 | { 20 | return FBM(generators: self.generators.map{ $0.frequency_scaled(by: factor) }) 21 | } 22 | public 23 | func reseeded() -> FBM 24 | { 25 | return FBM(generators: self.generators.map{ $0.reseeded() }) 26 | } 27 | 28 | private 29 | init(generators:[Source]) 30 | { 31 | self.generators = generators 32 | } 33 | 34 | @available(*, unavailable, message: "init(amplitude:frequency:seed:) defaults to octaves = 1, which does not make sense for FBM modules") 35 | public 36 | init(amplitude:Double, frequency:Double, seed:Int) 37 | { 38 | self.generators = [] 39 | } 40 | 41 | /// Creates an instance with the given number of `octaves` of noise. The given `amplitude` 42 | /// is the amplitude of the first octave of noise, and is multiplied by `persistence` for 43 | /// each successive octave. The given `frequency` is the frequency of the first octave of 44 | /// noise, and is multiplied by the `lacunarity` for each successive octave. The `seed` 45 | /// value is passed through to the first octave of noise, and is incremented for each 46 | /// successive octave. 47 | @available(*, unavailable, message: "use init(_:octaves:persistence:lacunarity:) instead") 48 | public 49 | init(amplitude:Double, frequency:Double, octaves:Int, persistence:Double = 0.75, lacunarity:Double = 2, seed:Int = 0) 50 | { 51 | self.generators = [] 52 | } 53 | 54 | // UNDOCUMENTED, default was changed from 0.75 to 0.5 55 | public 56 | init(_ source:Source, octaves:Int, persistence:Double = 0.5, lacunarity:Double = 2) 57 | { 58 | // calculate maximum range 59 | let range_inverse:Double 60 | if persistence == 0.5 61 | { 62 | range_inverse = Double(1 << (octaves - 1)) / Double(1 << octaves - 1) 63 | } 64 | else 65 | { 66 | var accumulation:Double = 1, 67 | contribution:Double = persistence 68 | for _ in (0 ..< octaves - 1) 69 | { 70 | accumulation += contribution 71 | contribution *= persistence 72 | } 73 | 74 | range_inverse = 1 / accumulation 75 | } 76 | 77 | var generators:[Source] = [source.amplitude_scaled(by: range_inverse)] 78 | generators.reserveCapacity(octaves) 79 | for i in (0 ..< octaves - 1) 80 | { 81 | generators.append(generators[i].amplitude_scaled(by: persistence).frequency_scaled(by: lacunarity).reseeded()) 82 | } 83 | 84 | self.generators = generators 85 | } 86 | 87 | public 88 | func evaluate(_ x:Double, _ y:Double) -> Double 89 | { 90 | var Σ:Double = 0 91 | for generator in self.generators 92 | { 93 | Σ += generator.evaluate(x, y) // a .reduce(:{}) is much slower than a simple loop 94 | } 95 | return Σ 96 | } 97 | 98 | public 99 | func evaluate(_ x:Double, _ y:Double, _ z:Double) -> Double 100 | { 101 | var Σ:Double = 0 102 | for generator in self.generators 103 | { 104 | Σ += generator.evaluate(x, y, z) 105 | } 106 | return Σ 107 | } 108 | 109 | public 110 | func evaluate(_ x:Double, _ y:Double, _ z:Double, _ w:Double) -> Double 111 | { 112 | var Σ:Double = 0 113 | for generator in self.generators 114 | { 115 | Σ += generator.evaluate(x, y, z, w) 116 | } 117 | return Σ 118 | } 119 | } 120 | extension FBM where Source:TilingNoise 121 | { 122 | public 123 | init(tiling source:Source, octaves:Int, persistence:Double = 0.5) 124 | { 125 | // calculate maximum range 126 | let range_inverse:Double 127 | if persistence == 0.5 128 | { 129 | range_inverse = Double(1 << (octaves - 1)) / Double(1 << octaves - 1) 130 | } 131 | else 132 | { 133 | var accumulation:Double = 1, 134 | contribution:Double = persistence 135 | for _ in (0 ..< octaves - 1) 136 | { 137 | accumulation += contribution 138 | contribution *= persistence 139 | } 140 | 141 | range_inverse = 1 / accumulation 142 | } 143 | 144 | var generators:[Source] = [source.amplitude_scaled(by: range_inverse)] 145 | generators.reserveCapacity(octaves) 146 | for i in (0 ..< octaves - 1) 147 | { 148 | generators.append( generators[i].amplitude_scaled(by: persistence) 149 | .frequency_scaled(by: 2) 150 | .transposed(octaves: 1) 151 | .reseeded()) 152 | } 153 | 154 | self.generators = generators 155 | } 156 | } 157 | 158 | // UNDOCUMENTED 159 | public 160 | struct DistortedNoise:Noise where Source:Noise, Displacement:Noise 161 | { 162 | private 163 | let source:Source, 164 | displacement:Displacement 165 | 166 | public 167 | func amplitude_scaled(by factor:Double) -> DistortedNoise 168 | { 169 | return DistortedNoise(displacing: self.source.amplitude_scaled(by: factor), 170 | with: self.displacement) 171 | } 172 | public 173 | func frequency_scaled(by factor:Double) -> DistortedNoise 174 | { 175 | return DistortedNoise(displacing: self.source.frequency_scaled(by: factor), 176 | with: self.displacement.frequency_scaled(by: factor) 177 | .amplitude_scaled(by: factor)) 178 | } 179 | public 180 | func reseeded() -> DistortedNoise 181 | { 182 | return DistortedNoise(displacing: self.source.reseeded(), 183 | with: self.displacement.reseeded()) 184 | } 185 | 186 | public 187 | init(displacing source:Source, with displacement:Displacement) 188 | { 189 | self.source = source 190 | self.displacement = displacement 191 | } 192 | 193 | public 194 | func evaluate(_ x: Double, _ y: Double) -> Double 195 | { 196 | let dx:Double = self.displacement.evaluate(x, y), 197 | dy:Double = self.displacement.evaluate(y, x) 198 | return self.source.evaluate(x + dx, y + dy) 199 | } 200 | 201 | public 202 | func evaluate(_ x: Double, _ y: Double, _ z: Double) -> Double 203 | { 204 | let dx:Double = self.displacement.evaluate(x, y, z), 205 | dy:Double = self.displacement.evaluate(y, z, x), 206 | dz:Double = self.displacement.evaluate(z, x, y) 207 | return self.source.evaluate(x + dx, y + dy, z + dz) 208 | } 209 | 210 | public 211 | func evaluate(_ x: Double, _ y: Double, _ z: Double, _ w:Double) -> Double 212 | { 213 | let dx:Double = self.displacement.evaluate(x, y, z, w), 214 | dy:Double = self.displacement.evaluate(y, z, w, x), 215 | dz:Double = self.displacement.evaluate(z, w, x, y), 216 | dw:Double = self.displacement.evaluate(w, x, y, z) 217 | return self.source.evaluate(x + dx, y + dy, z + dz, w + dw) 218 | } 219 | } 220 | 221 | extension DistortedNoise where Source == Displacement 222 | { 223 | public 224 | init(_ source:Source, strength:Double) 225 | { 226 | self.source = source 227 | self.displacement = source.amplitude_scaled(by: strength) 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /Sources/Noise/disk.swift: -------------------------------------------------------------------------------- 1 | /// A point sampler capable of producing uniform and roughly-evenly spaced pseudo-random point 2 | /// distributions in the plane. Disk sampling is sometimes referred to as 3 | /// [Poisson sampling](https://en.wikipedia.org/wiki/Supersampling#Poisson_disc). 4 | /// 5 | /// ![preview](png/banner_disk2d.png) 6 | /// 7 | /// Disk samples are not a noise field — its generation is inherently sequential, as opposed to 8 | /// most procedural noise fields which are embarrassingly parallel. Thus, disk samples have no 9 | /// concept of *evaluation*; the entire sample set must be generated as a whole. 10 | /// 11 | /// Disk samples have an internal state, which is advanced every time the point generator runs. 12 | /// In many ways, disk samples have more in common with pseudo-random number generators than 13 | /// they do with procedural noise fields. 14 | public 15 | struct DiskSampler2D 16 | { 17 | private 18 | let candidate_ring:[Math.DoubleV2] 19 | 20 | private 21 | var rng:RandomXorshift, 22 | candidate_index:Int = 0 23 | 24 | private 25 | var candidate_offset:Math.DoubleV2 26 | { 27 | return self.candidate_ring[self.candidate_index] 28 | } 29 | 30 | private static 31 | let candidate_table_bitmask:Int = 0b1111111111 // 1023 32 | 33 | /// Creates an instance with the given fixed random `seed`. This process calculates a random 34 | /// table used internally in the sample generation step. The same instance can be reused to 35 | /// generate multiple, different point distributions. 36 | public 37 | init(seed:Int = 0) 38 | { 39 | self.rng = RandomXorshift(seed: seed) 40 | let rand_scale:Double = 4 / Double(self.rng.max) 41 | 42 | var candidates_generated:Int = 0 43 | var candidate_ring:[Math.DoubleV2] = [] 44 | candidate_ring.reserveCapacity(DiskSampler2D.candidate_table_bitmask + 1) 45 | 46 | while candidates_generated <= DiskSampler2D.candidate_table_bitmask 47 | { 48 | let x:Double = Double(self.rng.generate()) * rand_scale - 1, 49 | y:Double = Double(self.rng.generate()) * rand_scale - 1, 50 | r2:Double = x*x + y*y 51 | 52 | guard r2 < 4 && r2 > 1 53 | else 54 | { 55 | continue 56 | } 57 | 58 | candidate_ring.append((x, y)) 59 | candidates_generated += 1 60 | } 61 | 62 | self.candidate_ring = candidate_ring 63 | } 64 | 65 | /// Generates a set of sample points that are spaced no less than `radius` apart over a 66 | /// region sized `width` by `height` . Up to `k` candidate points will be used to generate 67 | /// each sample point; higher values of `k` yield more compact point distributions, but take 68 | /// longer to run. The `seed` point specifies the first point that is added to the 69 | /// distribution, and influences where subsequent sample points are added. This `seed` is 70 | /// orthogonal to the `seed` supplied in the initializer. If `seed` is left `nil`, the seed 71 | /// point is placed at the center of the region. 72 | public mutating 73 | func generate(radius:Double, width:Int, height:Int, k:Int = 32, seed:(Double, Double)? = nil) -> [(Double, Double)] 74 | { 75 | let normalized_width:Double = Double(width ) / radius, 76 | normalized_height:Double = Double(height) / radius, 77 | grid_width:Int = Int((2.squareRoot() * normalized_width ).rounded(.up)), 78 | grid_height:Int = Int((2.squareRoot() * normalized_height).rounded(.up)), 79 | grid_stride:Int = grid_width + 4 80 | var grid = [Math.DoubleV2?](repeating: nil, count: grid_stride * (grid_height + 4)) 81 | 82 | var queue:[Math.DoubleV2] 83 | if let seed:Math.DoubleV2 = seed 84 | { 85 | queue = [(Double(seed.x) / radius, Double(seed.y) / radius)] 86 | } 87 | else 88 | { 89 | queue = [(0.5 * normalized_width, 0.5 * normalized_height)] 90 | } 91 | 92 | _ = DiskSampler2D.attempt_insert(candidate: queue[0], into_grid: &grid, grid_stride: grid_stride) 93 | var points:[(Double, Double)] = [(queue[0].x * radius, queue[0].y * radius)] 94 | outer: while let front:Math.DoubleV2 = queue.last 95 | { 96 | for _ in 0 ..< k 97 | { 98 | let candidate:Math.DoubleV2 = Math.add(front, self.candidate_offset) 99 | self.candidate_index = (self.candidate_index + 1) & DiskSampler2D.candidate_table_bitmask 100 | 101 | guard 0 ..< normalized_width ~= candidate.x && 0 ..< normalized_height ~= candidate.y 102 | else 103 | { 104 | continue 105 | } 106 | 107 | if DiskSampler2D.attempt_insert(candidate: candidate, into_grid: &grid, grid_stride: grid_stride) 108 | { 109 | points.append((candidate.x * radius, candidate.y * radius)) 110 | queue.append(candidate) 111 | queue.swapAt(queue.endIndex - 1, Int(self.rng.generate(less_than: UInt32(queue.endIndex)))) 112 | continue outer 113 | } 114 | } 115 | queue.removeLast() 116 | } 117 | 118 | return points 119 | } 120 | 121 | private static 122 | func attempt_insert(candidate:Math.DoubleV2, into_grid grid:inout [Math.DoubleV2?], grid_stride:Int) -> Bool 123 | { 124 | let i:Int = Int(candidate.y * 2.squareRoot()) + 2, 125 | j:Int = Int(candidate.x * 2.squareRoot()) + 2, 126 | center:Int = i * grid_stride + j 127 | 128 | guard grid[center] == nil 129 | else 130 | { 131 | return false 132 | } 133 | 134 | let base:(Int, Int, Int, Int) = (center - 2*grid_stride, 135 | center - grid_stride, 136 | center + grid_stride, 137 | center + 2*grid_stride) 138 | 139 | let ring:[Math.DoubleV2?] = [ grid[base.0 - 1], grid[base.0], grid[base.0 + 1], 140 | grid[base.1 - 2], grid[base.1 - 1], grid[base.1], grid[base.1 + 1], grid[base.1 + 2], 141 | grid[center - 2], grid[center - 1], grid[center + 1], grid[center + 2], 142 | grid[base.2 - 2], grid[base.2 - 1], grid[base.2], grid[base.2 + 1], grid[base.2 + 2], 143 | grid[base.3 - 1], grid[base.3], grid[base.3 + 1]] 144 | for cell:Math.DoubleV2? in ring 145 | { 146 | guard let occupant:Math.DoubleV2 = cell 147 | else 148 | { 149 | continue 150 | } 151 | 152 | let dv:Math.DoubleV2 = Math.sub(occupant, candidate) 153 | 154 | guard Math.dot(dv, dv) > 1 155 | else 156 | { 157 | return false 158 | } 159 | } 160 | 161 | grid[center] = candidate 162 | return true 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /Sources/Noise/docs.docc/CellNoise2D.md: -------------------------------------------------------------------------------- 1 | # ``CellNoise2D`` 2 | 3 | ## Topics 4 | 5 | ### Evaluating noise 6 | 7 | - ``closest_point(_:_:)`` 8 | - ``evaluate(_:_:)`` 9 | - ``evaluate(_:_:_:)`` 10 | - ``evaluate(_:_:_:_:)`` 11 | -------------------------------------------------------------------------------- /Sources/Noise/docs.docc/CellNoise3D.md: -------------------------------------------------------------------------------- 1 | # ``CellNoise3D`` 2 | 3 | ## Topics 4 | 5 | ### Evaluating noise 6 | 7 | - ``closest_point(_:_:_:)`` 8 | - ``evaluate(_:_:)`` 9 | - ``evaluate(_:_:_:)`` 10 | - ``evaluate(_:_:_:_:)`` 11 | -------------------------------------------------------------------------------- /Sources/Noise/docs.docc/README.md: -------------------------------------------------------------------------------- 1 | # ``/Noise`` 2 | 3 | Generate and combine commonly used procedural noise patterns and distributions. 4 | 5 | ## Topics 6 | 7 | ### Protocols 8 | 9 | - ``Noise`` 10 | 11 | ### Noise generators 12 | 13 | - ``SimplexNoise2D`` 14 | - ``GradientNoise2D`` 15 | - ``GradientNoise3D`` 16 | - ``CellNoise2D`` 17 | - ``CellNoise3D`` 18 | 19 | ### Noise composition 20 | 21 | - ``FBM`` 22 | 23 | ### Pattern generators 24 | 25 | - ``DiskSampler2D`` 26 | 27 | ### Psuedo-random number generators 28 | 29 | - ``PermutationTable`` 30 | - ``RandomXorshift`` 31 | -------------------------------------------------------------------------------- /Sources/Noise/docs.docc/png/banner_FBM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/Sources/Noise/docs.docc/png/banner_FBM.png -------------------------------------------------------------------------------- /Sources/Noise/docs.docc/png/banner_cell2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/Sources/Noise/docs.docc/png/banner_cell2d.png -------------------------------------------------------------------------------- /Sources/Noise/docs.docc/png/banner_cell3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/Sources/Noise/docs.docc/png/banner_cell3d.png -------------------------------------------------------------------------------- /Sources/Noise/docs.docc/png/banner_disk2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/Sources/Noise/docs.docc/png/banner_disk2d.png -------------------------------------------------------------------------------- /Sources/Noise/docs.docc/png/banner_simplex2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/Sources/Noise/docs.docc/png/banner_simplex2d.png -------------------------------------------------------------------------------- /Sources/Noise/docs.docc/png/banner_supersimplex2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/Sources/Noise/docs.docc/png/banner_supersimplex2d.png -------------------------------------------------------------------------------- /Sources/Noise/docs.docc/png/banner_supersimplex3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/Sources/Noise/docs.docc/png/banner_supersimplex3d.png -------------------------------------------------------------------------------- /Sources/Noise/docs.docc/png/banner_voronoi2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/Sources/Noise/docs.docc/png/banner_voronoi2d.png -------------------------------------------------------------------------------- /Sources/Noise/gradient.swift: -------------------------------------------------------------------------------- 1 | fileprivate 2 | protocol _ClassicNoise3D 3 | { 4 | var frequency:Double { get } 5 | var amplitude:Double { get } 6 | 7 | func hash(point:Math.IntV3) -> Int 8 | } 9 | 10 | extension _ClassicNoise3D 11 | { 12 | @inline(__always) 13 | private 14 | func gradient(from generating_point:Math.IntV3, at offset:Math.DoubleV3) -> Double 15 | { 16 | // use vectors to the edge of a cube 17 | let h:Int = self.hash(point: generating_point) & 15, 18 | u:Double = h < 8 ? offset.x : offset.y, 19 | vt:Double = h == 12 || h == 14 ? offset.x : offset.z, 20 | v:Double = h < 4 ? offset.y : vt 21 | return (h & 1 != 0 ? -u : u) + (h & 2 != 0 ? -v : v) 22 | } 23 | 24 | // ugly hack to get around compiler linker bug 25 | @inline(__always) 26 | func _evaluate(_ x:Double, _ y:Double, _ z:Double) -> Double 27 | { 28 | let sample:Math.DoubleV3 = (x * self.frequency, y * self.frequency, z * self.frequency) 29 | 30 | // get integral cube coordinates as well as fractional offsets 31 | let (bin, rel):(Math.IntV3, Math.DoubleV3) = Math.fraction(sample) 32 | 33 | // use smooth interpolation 34 | let U:Math.DoubleV3 = Math.quintic_ease(rel) 35 | 36 | let r:Double = Math.lerp(Math.lerp(Math.lerp(self.gradient(from: bin , at: rel), 37 | self.gradient(from: (bin.a + 1, bin.b , bin.c ), at: (rel.x - 1, rel.y , rel.z)), 38 | factor: U.x), 39 | Math.lerp(self.gradient(from: (bin.a , bin.b + 1, bin.c ), at: (rel.x , rel.y - 1, rel.z)), 40 | self.gradient(from: (bin.a + 1, bin.b + 1, bin.c ), at: (rel.x - 1, rel.y - 1, rel.z)), 41 | factor: U.x), 42 | factor: U.y), 43 | Math.lerp(Math.lerp(self.gradient(from: (bin.a , bin.b , bin.c + 1), at: (rel.x , rel.y , rel.z - 1)), 44 | self.gradient(from: (bin.a + 1, bin.b , bin.c + 1), at: (rel.x - 1, rel.y , rel.z - 1)), 45 | factor: U.x), 46 | Math.lerp(self.gradient(from: (bin.a , bin.b + 1, bin.c + 1), at: (rel.x , rel.y - 1, rel.z - 1)), 47 | self.gradient(from: (bin.a + 1, bin.b + 1, bin.c + 1), at: (rel.x - 1, rel.y - 1, rel.z - 1)), 48 | factor: U.x), 49 | factor: U.y), 50 | factor: U.z) 51 | 52 | return self.amplitude * r 53 | } 54 | } 55 | 56 | // UNDOCUMENTED 57 | public 58 | struct ClassicNoise3D:_ClassicNoise3D, HashedNoise 59 | { 60 | let permutation_table:PermutationTable, 61 | amplitude:Double, 62 | frequency:Double 63 | 64 | init(amplitude:Double, frequency:Double, permutation_table:PermutationTable) 65 | { 66 | self.amplitude = amplitude 67 | self.frequency = frequency 68 | self.permutation_table = permutation_table 69 | } 70 | 71 | public 72 | init(amplitude:Double, frequency:Double, seed:Int = 0) 73 | { 74 | self.amplitude = 0.982 * amplitude 75 | self.frequency = frequency 76 | self.permutation_table = PermutationTable(seed: seed) 77 | } 78 | 79 | public 80 | func evaluate(_ x:Double, _ y:Double) -> Double 81 | { 82 | return self.evaluate(x, y, 0) 83 | } 84 | 85 | public 86 | func evaluate(_ x:Double, _ y:Double, _ z:Double) -> Double 87 | { 88 | return self._evaluate(x, y, z) 89 | } 90 | 91 | public 92 | func evaluate(_ x:Double, _ y:Double, _ z:Double, _:Double) -> Double 93 | { 94 | return self.evaluate(x, y, z) 95 | } 96 | } 97 | 98 | // UNDOCUMENTED 99 | public 100 | struct TilingClassicNoise3D:_ClassicNoise3D, HashedTilingNoise 101 | { 102 | let permutation_table:PermutationTable, 103 | amplitude:Double, 104 | frequency:Double, 105 | wavelengths:Math.IntV3 106 | 107 | init(amplitude:Double, frequency:Double, permutation_table:PermutationTable, wavelengths:Math.IntV3) 108 | { 109 | self.amplitude = amplitude 110 | self.frequency = frequency 111 | self.permutation_table = permutation_table 112 | self.wavelengths = wavelengths 113 | } 114 | 115 | public 116 | init(amplitude:Double, frequency:Double, wavelengths:Int, seed:Int = 0) 117 | { 118 | self.init( amplitude: amplitude, frequency: frequency, 119 | wavelengths_x: wavelengths, 120 | wavelengths_y: wavelengths, 121 | wavelengths_z: wavelengths, 122 | seed: seed) 123 | } 124 | 125 | public 126 | init(amplitude:Double, frequency:Double, wavelengths_x:Int, wavelengths_y:Int, wavelengths_z:Int, seed:Int = 0) 127 | { 128 | self.amplitude = 0.982 * amplitude 129 | self.frequency = frequency 130 | self.permutation_table = PermutationTable(seed: seed) 131 | self.wavelengths = (wavelengths_x, wavelengths_y, wavelengths_z) 132 | } 133 | 134 | public 135 | func evaluate(_ x:Double, _ y:Double) -> Double 136 | { 137 | return self.evaluate(x, y, 0) 138 | } 139 | 140 | public 141 | func evaluate(_ x:Double, _ y:Double, _ z:Double) -> Double 142 | { 143 | return self._evaluate(x, y, z) 144 | } 145 | 146 | public 147 | func evaluate(_ x:Double, _ y:Double, _ z:Double, _:Double) -> Double 148 | { 149 | return self.evaluate(x, y, z) 150 | } 151 | } 152 | 153 | /// A type of two-dimensional gradient noise (sometimes called 154 | /// [Perlin noise](https://en.wikipedia.org/wiki/Perlin_noise)), suitable for texturing 155 | /// two-dimensional planes. Simplex noise was originally an open-source improvement on the 156 | /// classical Perlin gradient noise algorithm, and thus is often referred to as OpenSimplex 157 | /// noise, to distinguish it from a patented variant of simplex noise. 158 | /// 159 | /// ![preview](png/banner_simplex2d.png) 160 | /// 161 | /// Simplex noise is supported in the library mainly because it has historical significance; it 162 | /// has since been superseded by the less popular, but more powerful and more efficient 163 | /// ``GradientNoise2D``. 164 | /// 165 | /// In almost all cases, super-simplex noise should be preferred. 166 | @available(*, deprecated, message: """ 167 | simplex noise nearly identical to and is an inferior implementation of super simplex noise 168 | """) 169 | public 170 | struct SimplexNoise2D:HashedNoise 171 | { 172 | private static 173 | let SQUISH_2D :Double = 0.5 * (1 / 3.squareRoot() - 1), 174 | STRETCH_2D:Double = 0.5 * (3.squareRoot() - 1) 175 | 176 | private static 177 | let gradient_table32:[Math.DoubleV2] = 178 | [ 179 | (1 , 0 ), ( 0 , 1 ), (-1 , 0 ), (0 , -1), 180 | (0.7, 0.7), (-0.7, 0.7), (-0.7, -0.7), (0.7, -0.7), 181 | 182 | (0.7, -0.7), 183 | (1 , 0 ), ( 0 , 1 ), (-1 , 0 ), (0 , -1), 184 | (0.7, 0.7), (-0.7, 0.7), (-0.7, -0.7), 185 | 186 | (-0.7, -0.7), (0.7, -0.7), 187 | (1 , 0 ), ( 0 , 1 ), (-1 , 0 ), (0 , -1), 188 | (0.7, 0.7), (-0.7, 0.7), 189 | 190 | (-0.7, 0.7), (-0.7, -0.7), (0.7, -0.7), 191 | (1 , 0 ), ( 0 , 1 ), (-1 , 0 ), (0 , -1), 192 | (0.7, 0.7) 193 | ] 194 | 195 | let permutation_table:PermutationTable, 196 | amplitude:Double, // not the same amplitude passed into the initializer 197 | frequency:Double 198 | 199 | init(amplitude:Double, frequency:Double, permutation_table:PermutationTable) 200 | { 201 | self.amplitude = amplitude 202 | self.frequency = frequency 203 | self.permutation_table = permutation_table 204 | } 205 | 206 | /// Creates an instance with the given `amplitude`, `frequency`, and random `seed` values. 207 | /// Creating an instance generates a new pseudo-random permutation table for that instance, 208 | /// and a new instance does not need to be regenerated to sample the same procedural noise 209 | /// field. 210 | /// 211 | /// The given amplitude is adjusted internally to produce output approximately within the 212 | /// range of `-amplitude ... amplitude`, however this is not strictly guaranteed. 213 | public 214 | init(amplitude:Double, frequency:Double, seed:Int = 0) 215 | { 216 | self.amplitude = 0.1322 * amplitude 217 | self.frequency = frequency 218 | self.permutation_table = PermutationTable(seed: seed) 219 | } 220 | 221 | func gradient(from point:Math.IntV2, at offset:Math.DoubleV2) -> Double 222 | { 223 | let dr:Double = 2 - Math.dot(offset, offset) 224 | if dr > 0 225 | { 226 | let gradient:Math.DoubleV2 = SimplexNoise2D.gradient_table32[self.permutation_table.hash(point) & 31], 227 | drdr:Double = dr * dr 228 | return drdr * drdr * Math.dot(gradient, offset) 229 | } 230 | else 231 | { 232 | return 0 233 | } 234 | } 235 | 236 | /// Evaluates the simplex noise field at the given coordinates. 237 | public 238 | func evaluate(_ x:Double, _ y:Double) -> Double 239 | { 240 | let sample:Math.DoubleV2 = (x * self.frequency, y * self.frequency) 241 | // transform our coordinate system so that the *simplex* (x, y) forms a 242 | // rectangular grid (u, v) 243 | let squish_offset:Double = (sample.x + sample.y) * SimplexNoise2D.SQUISH_2D, 244 | sample_uv:Math.DoubleV2 = (sample.x + squish_offset, sample.y + squish_offset) 245 | 246 | // get integral (u, v) coordinates of the rhombus and get position inside 247 | // the rhombus relative to (floor(u), floor(v)) 248 | let (bin, sample_uv_rel):(Math.IntV2, Math.DoubleV2) = Math.fraction(sample_uv) 249 | 250 | // (0, 0) ----- (1, 0) 251 | // \ A / \ 252 | // \ / \ ← (x, y) coordinates 253 | // \ / B \ 254 | // (0, 1)-------(1, 1) 255 | 256 | // (1, -1) 257 | // / | 258 | // / D | 259 | // / | 260 | // bin = (0, 0) --- (1, 0) -- (2, 0) 261 | // / | / | / 262 | // / E | A / B | C / ← (u, v) coordinates 263 | // / | / | / 264 | // (-1, 1) -- (0, 1) --- (1, 1) 265 | // | / 266 | // | F / 267 | // | / 268 | // (0, 2) 269 | 270 | // do the same in the original (x, y) coordinate space 271 | 272 | // stretch back to get (x, y) coordinates of rhombus origin 273 | let stretch_offset:Double = Double(bin.a + bin.b) * SimplexNoise2D.STRETCH_2D, 274 | origin:Math.DoubleV2 = (Double(bin.a) + stretch_offset, Double(bin.b) + stretch_offset) 275 | 276 | // get relative position inside the rhombus relative to (xb, xb) 277 | let sample_rel:Math.DoubleV2 = Math.sub(sample, origin) 278 | 279 | var Σ:Double = 0 // the value of the noise function, which we will sum up 280 | 281 | @inline(__always) 282 | func _inspect(point_offset:Math.IntV2, sample_offset:Math.DoubleV2) 283 | { 284 | Σ += gradient(from: Math.add(bin, point_offset), at: Math.sub(sample_rel, sample_offset)) 285 | } 286 | 287 | // contribution from (1, 0) 288 | _inspect(point_offset: (1, 0), sample_offset: (1 + SimplexNoise2D.STRETCH_2D, SimplexNoise2D.STRETCH_2D)) 289 | 290 | // contribution from (0, 1) 291 | _inspect(point_offset: (0, 1), sample_offset: (SimplexNoise2D.STRETCH_2D, 1 + SimplexNoise2D.STRETCH_2D)) 292 | 293 | // decide which triangle we are in 294 | let uv_sum:Double = sample_uv_rel.x + sample_uv_rel.y 295 | if (uv_sum > 1) // we are to the bottom-right of the diagonal line (du = 1 - dv) 296 | { 297 | _inspect(point_offset: (1, 1), sample_offset: (1 + 2*SimplexNoise2D.STRETCH_2D, 1 + 2*SimplexNoise2D.STRETCH_2D)) 298 | 299 | let center_dist:Double = 2 - uv_sum 300 | if center_dist < sample_uv_rel.x || center_dist < sample_uv_rel.y 301 | { 302 | if sample_uv_rel.x > sample_uv_rel.y 303 | { 304 | _inspect(point_offset: (2, 0), sample_offset: (2 + 2*SimplexNoise2D.STRETCH_2D, 2*SimplexNoise2D.STRETCH_2D)) 305 | } 306 | else 307 | { 308 | _inspect(point_offset: (0, 2), sample_offset: (2*SimplexNoise2D.STRETCH_2D, 2 + 2*SimplexNoise2D.STRETCH_2D)) 309 | } 310 | } 311 | else 312 | { 313 | Σ += gradient(from: bin, at: sample_rel) 314 | } 315 | } 316 | else 317 | { 318 | Σ += gradient(from: bin, at: sample_rel) 319 | 320 | let center_dist:Double = 1 - uv_sum 321 | if center_dist > sample_uv_rel.x || center_dist > sample_uv_rel.y 322 | { 323 | if sample_uv_rel.x > sample_uv_rel.y 324 | { 325 | _inspect(point_offset: (1, -1), sample_offset: (-1, 1)) 326 | } 327 | else 328 | { 329 | _inspect(point_offset: (-1, 1), sample_offset: (1, -1)) 330 | } 331 | } 332 | else 333 | { 334 | _inspect(point_offset: (1, 1), sample_offset: (1 + 2*SimplexNoise2D.STRETCH_2D, 1 + 2*SimplexNoise2D.STRETCH_2D)) 335 | } 336 | } 337 | 338 | return self.amplitude * Σ 339 | } 340 | 341 | /// Evaluates the simplex noise field at the given coordinates. The third coordinate is 342 | /// ignored. 343 | public 344 | func evaluate(_ x:Double, _ y:Double, _:Double) -> Double 345 | { 346 | return self.evaluate(x, y) 347 | } 348 | 349 | /// Evaluates the simplex noise field at the given coordinates. The third and fourth 350 | /// coordinates are ignored. 351 | public 352 | func evaluate(_ x:Double, _ y:Double, _:Double, _:Double) -> Double 353 | { 354 | return self.evaluate(x, y) 355 | } 356 | } 357 | 358 | @available(*, unavailable, renamed: "GradientNoise2D") 359 | public 360 | typealias SuperSimplexNoise2D = GradientNoise2D 361 | 362 | /// A type of two-dimensional gradient noise (sometimes called 363 | /// [Perlin noise](https://en.wikipedia.org/wiki/Perlin_noise)), suitable for texturing 364 | /// two-dimensional planes. Super-simplex noise is an improved version of ``SimplexNoise2D`` 365 | /// which runs faster and scales better to higher dimensions. (Simplex noise in turn is an 366 | /// improvement on the classical Perlin gradient noise algorithm.) 367 | /// 368 | /// ![preview](png/banner_supersimplex2d.png) 369 | /// 370 | /// In almost all cases, super-simplex noise should be preferred over its predecessors. 371 | /// Super-simplex noise runs about 25% faster than its simplex predecessor, and produces higher 372 | /// quality gradient noise. Super-simplex noise also comes in a 373 | /// [three-dimensional](doc:GradientNoise3D) version. 374 | public 375 | struct GradientNoise2D:HashedNoise 376 | { 377 | private static 378 | let SQUISH_2D :Double = 0.5 * (1 / 3.squareRoot() - 1), 379 | STRETCH_2D:Double = 0.5 * (3.squareRoot() - 1) 380 | 381 | private static 382 | let points:[(Math.IntV2, Math.DoubleV2)] = 383 | { 384 | var points:[(Math.IntV2, Math.DoubleV2)] = [] 385 | points.reserveCapacity(32) 386 | 387 | @inline(__always) 388 | func _lattice_point(at point:Math.IntV2) -> (Math.IntV2, Math.DoubleV2) 389 | { 390 | let stretch_offset:Double = Double(point.a + point.b) * GradientNoise2D.SQUISH_2D 391 | return (point, (Double(point.a) + stretch_offset, Double(point.b) + stretch_offset)) 392 | } 393 | 394 | for (i1, j1, i2, j2):(Int, Int, Int, Int) in 395 | [ 396 | (-1, 0, 0, -1), (0, 1, 1, 0), (1, 0, 0, -1), (2, 1, 1, 0), 397 | (-1, 0, 0, 1), (0, 1, 1, 2), (1, 0, 0, 1), (2, 1, 1, 2) 398 | ] 399 | { 400 | points.append(_lattice_point(at: ( 0, 0))) 401 | points.append(_lattice_point(at: ( 1, 1))) 402 | points.append(_lattice_point(at: (i1, j1))) 403 | points.append(_lattice_point(at: (i2, j2))) 404 | } 405 | 406 | return points 407 | }() 408 | 409 | private static // each gradient appears four times to mitigate hashing biases 410 | let gradient_table32:[Math.DoubleV2] = 411 | [ 412 | (1 , 0 ), ( 0 , 1 ), (-1 , 0 ), (0 , -1), 413 | (0.7, 0.7), (-0.7, 0.7), (-0.7, -0.7), (0.7, -0.7), 414 | 415 | (0.7, -0.7), 416 | (1 , 0 ), ( 0 , 1 ), (-1 , 0 ), (0, -1), 417 | (0.7, 0.7), (-0.7, 0.7), (-0.7, -0.7), 418 | 419 | (-0.7, -0.7), ( 0.7, -0.7), 420 | ( 1 , 0 ), ( 0 , 1 ), (-1, 0), (0, -1), 421 | ( 0.7, 0.7), (-0.7, 0.7), 422 | 423 | (-0.7, 0.7), (-0.7, -0.7), ( 0.7, -0.7), 424 | ( 1 , 0 ), ( 0 , 1 ), (-1 , 0 ), (0, -1), 425 | ( 0.7, 0.7) 426 | ] 427 | 428 | let permutation_table:PermutationTable, 429 | amplitude:Double, 430 | frequency:Double 431 | 432 | init(amplitude:Double, frequency:Double, permutation_table:PermutationTable) 433 | { 434 | self.amplitude = amplitude 435 | self.frequency = frequency 436 | self.permutation_table = permutation_table 437 | } 438 | 439 | /// Creates an instance with the given `amplitude`, `frequency`, and random `seed` values. 440 | /// Creating an instance generates a new pseudo-random permutation table for that instance, 441 | /// and a new instance does not need to be regenerated to sample the same procedural noise 442 | /// field. 443 | /// 444 | /// The given amplitude is adjusted internally to produce output approximately within the 445 | /// range of `-amplitude ... amplitude`, however this is not strictly guaranteed. 446 | public 447 | init(amplitude:Double, frequency:Double, seed:Int = 0) 448 | { 449 | self.amplitude = 18.5 * amplitude 450 | self.frequency = frequency 451 | self.permutation_table = PermutationTable(seed: seed) 452 | } 453 | 454 | func gradient(from point:Math.IntV2, at offset:Math.DoubleV2) -> Double 455 | { 456 | let dr:Double = 2/3 - Math.dot(offset, offset) 457 | if dr > 0 458 | { 459 | let gradient:Math.DoubleV2 = GradientNoise2D.gradient_table32[self.permutation_table.hash(point) & 31], 460 | drdr:Double = dr * dr 461 | return drdr * drdr * Math.dot(gradient, offset) 462 | } 463 | else 464 | { 465 | return 0 466 | } 467 | } 468 | 469 | /// Evaluates the super-simplex noise field at the given coordinates. 470 | public 471 | func evaluate(_ x:Double, _ y:Double) -> Double 472 | { 473 | let sample:Math.DoubleV2 = (x * self.frequency, y * self.frequency) 474 | // transform our (x, y) coordinate to (u, v) space 475 | let stretch_offset:Double = (sample.x + sample.y) * GradientNoise2D.STRETCH_2D, 476 | sample_uv:Math.DoubleV2 = (sample.x + stretch_offset, sample.y + stretch_offset) 477 | 478 | // (0, 0) ----- (1, 0) 479 | // / \ A / 480 | // / \ / ← (x, y) coordinates 481 | // / B \ / 482 | // (0, 1) ----- (1, 1) 483 | 484 | // (-1, 0) 485 | // | \ 486 | // | \ 487 | // | C \ 488 | // (0, -1) -- (0, 0) -- (1, 0) 489 | // \ D | \ A | \ 490 | // \ | \ | \ ← (u, v) coordinates 491 | // \ | B \ | E \ 492 | // (0, 1) -- (1, 1) -- (2, 1) 493 | // \ F | 494 | // \ | 495 | // \ | 496 | // (1, 2) 497 | 498 | // use the (u, v) coordinates to bin the triangle and get relative offsets 499 | // from the top-left corner of the square (in (u, v) space) 500 | let (bin, sample_uv_rel):(Math.IntV2, Math.DoubleV2) = Math.fraction(sample_uv) 501 | 502 | let a:Int = sample_uv_rel.x + sample_uv_rel.y > 1 ? 1 : 0 503 | let base_vertex_index:Int = a << 2 | 504 | Int((2*sample_uv_rel.x - sample_uv_rel.y - Double(a))*0.5 + 1) << 3 | 505 | Int((2*sample_uv_rel.y - sample_uv_rel.x - Double(a))*0.5 + 1) << 4 506 | /* 507 | This bit of code deserves some explanation. OpenSimplex/SuperSimplex 508 | always samples four vertices. Which four depends on what part of the A–B 509 | square our (x, y) → (u, v) sample coordinate lands in. Think of each 510 | triangular slice of that square as being further subdivided into three 511 | smaller triangles. 512 | 513 | ************************ 514 | ** * a ** 515 | * * * * * * 516 | * * * * * 517 | * * * b * c * 518 | * * e * * * 519 | * d * * * * 520 | * * * * * * 521 | * * * * ** 522 | ** f ** 523 | ************************ 524 | 525 | Obviously we’re running up against the bounds on what can be explained 526 | with an ASCII art comment. Hopefully you get the idea. We have 6 regions, 527 | counter-clockwise in the upper-right triangle from the top, regions a, b, 528 | and c, and clockwise in the lower-left triangle from the left, regions 529 | d, e, and f. 530 | 531 | Region a borders region C, so it gets the extra vertices (-1, 0) and (1, 0) 532 | in addition to (0, 0), and (1, 1), which every region samples. Region c 533 | borders region E, so it gets the extra vertices (1, 0) and (2, 1). Region b 534 | doesn’t border any exterior region, so it just gets the other two vertices 535 | of the square, (0, 1) and (1, 0). Regions d and f are the same as a and c, 536 | except they border D and F, respectively. Region e is essentially the same 537 | as region b. 538 | 539 | This means that we effectively have five different vertex selection outcomes. 540 | That means we need no less than three bits of information, i.e., three tests 541 | to determine where we are. Two such tests could be: 542 | 543 | (0, 0) -------------- (1, 0) 544 | | \ \ | 545 | | \ \ | 546 | | \ \ | 547 | | \ \ | 548 | | \ \ | 549 | | \ \ | 550 | | \ \ | 551 | (1, 0) -------------- (1, 1) 552 | v = 2u v = 2u - 1 553 | 554 | and 555 | 556 | (0, 0) -------------- (1, 0) 557 | | - | 558 | | - | 559 | | - | 560 | | - | 561 | u = 2v - 1 | - | u = 2v 562 | | - | 563 | | - | 564 | (1, 0) -------------- (1, 1) 565 | 566 | Each test has two lines. How do we pick which one? We use a third test: 567 | 568 | (0, 0) -------------- (1, 0) 569 | | / | 570 | | / | 571 | | / | 572 | | / | 573 | | / | 574 | | / u + v = 1 | 575 | | / | 576 | (1, 0) -------------- (1, 1) 577 | 578 | If we’re in the top left, we use the lines without the offsets, otherwise 579 | we use the ones with the -1 offset. That’s where the `- Double(a)` term 580 | comes from. 581 | */ 582 | 583 | // get the relative offset from (0, 0) 584 | let squish_offset:Double = (sample_uv_rel.x + sample_uv_rel.y) * GradientNoise2D.SQUISH_2D, 585 | sample_rel:Math.DoubleV2 = (sample_uv_rel.x + squish_offset, sample_uv_rel.y + squish_offset) 586 | 587 | var Σ:Double = 0 588 | for (point, point_offset) in GradientNoise2D.points[base_vertex_index ..< base_vertex_index + 4] 589 | { 590 | Σ += self.gradient(from: Math.add(bin, point), at: Math.sub(sample_rel, point_offset)) 591 | } 592 | return self.amplitude * Σ 593 | } 594 | 595 | /// Evaluates the super-simplex noise field at the given coordinates. The third coordinate 596 | /// is ignored. 597 | public 598 | func evaluate(_ x:Double, _ y:Double, _:Double) -> Double 599 | { 600 | return self.evaluate(x, y) 601 | } 602 | 603 | /// Evaluates the super-simplex noise field at the given coordinates. The third and fourth 604 | /// coordinates are ignored. 605 | public 606 | func evaluate(_ x:Double, _ y:Double, _:Double, _:Double) -> Double 607 | { 608 | return self.evaluate(x, y) 609 | } 610 | } 611 | 612 | @available(*, unavailable, renamed: "GradientNoise3D") 613 | public 614 | typealias SuperSimplexNoise3D = GradientNoise3D 615 | 616 | /// A type of three-dimensional gradient noise (sometimes called 617 | /// [Perlin noise](https://en.wikipedia.org/wiki/Perlin_noise)), suitable for texturing 618 | /// arbitrary three-dimensional objects. 619 | /// 620 | /// ![preview](png/banner_supersimplex3d.png) 621 | /// 622 | /// Three-dimensional super-simplex noise generally looks somewhat better visually than its 623 | /// [two-dimensional](doc:GradientNoise3D) counterpart, but runs about 20% slower. 624 | /// 625 | /// `GradientNoise3D` is *similar* (but not identical) to 626 | /// [Blender Perlin noise](https://docs.blender.org/manual/en/dev/render/cycles/nodes/types/textures/noise.html). 627 | /// The *Scale* of Blender Perlin noise is approximately equivalent to `5/4` the `frequency` 628 | /// of `GradientNoise3D`. The range of Blender Perlin noise is approximately `0.1875 ... 0.8125` 629 | /// in `GradientNoise3D` units. 630 | public 631 | struct GradientNoise3D:HashedNoise 632 | { 633 | private static 634 | let points:[(Math.IntV3, Math.DoubleV3)] = 635 | { 636 | var points:[(Math.IntV3, Math.DoubleV3)] = [] 637 | points.reserveCapacity(64) 638 | 639 | for n in 0 ..< 16 640 | { 641 | let p1:Math.IntV3 = ( n & 1, n & 1, n & 1), 642 | p2:Math.IntV3 = (1 - n >> 1 & 1, n >> 1 & 1, n >> 1 & 1), 643 | p3:Math.IntV3 = ( n >> 2 & 1, 1 - n >> 2 & 1, n >> 2 & 1), 644 | p4:Math.IntV3 = ( n >> 3 & 1, n >> 3 & 1, 1 - n >> 3 & 1) 645 | 646 | points.append((p1, Math.cast_double(p1))) 647 | points.append((p2, Math.cast_double(p2))) 648 | points.append((p3, Math.cast_double(p3))) 649 | points.append((p4, Math.cast_double(p4))) 650 | } 651 | 652 | return points 653 | }() 654 | 655 | private static 656 | let gradient_table32:[Math.DoubleV3] = 657 | [ 658 | (1, 1, 0), (-1, 1, 0), (1, -1, 0), (-1, -1, 0), 659 | (1, 0, 1), (-1, 0, 1), (1, 0, -1), (-1, 0, -1), 660 | (0, 1, 1), ( 0, -1, 1), (0, 1, -1), ( 0, -1, -1), 661 | (1, 1, 0), (-1, 1, 0), (0, -1, 1), ( 0, -1, -1), 662 | 663 | (0, -1, -1), 664 | (1, 1, 0), (-1, 1, 0), (1, -1, 0), (-1, -1, 0), 665 | (1, 0, 1), (-1, 0, 1), (1, 0, -1), (-1, 0, -1), 666 | (0, 1, 1), ( 0, -1, 1), (0, 1, -1), ( 0, -1, -1), 667 | (1, 1, 0), (-1, 1, 0), (0, -1, 1) 668 | ] 669 | 670 | let permutation_table:PermutationTable, 671 | amplitude:Double, 672 | frequency:Double 673 | 674 | init(amplitude:Double, frequency:Double, permutation_table:PermutationTable) 675 | { 676 | self.amplitude = amplitude 677 | self.frequency = frequency 678 | self.permutation_table = permutation_table 679 | } 680 | 681 | /// Creates an instance with the given `amplitude`, `frequency`, and random `seed` values. 682 | /// Creating an instance generates a new pseudo-random permutation table for that instance, 683 | /// and a new instance does not need to be regenerated to sample the same procedural noise 684 | /// field. 685 | /// 686 | /// The given amplitude is adjusted internally to produce output approximately within the 687 | /// range of `-amplitude ... amplitude`, however this is not strictly guaranteed. 688 | public 689 | init(amplitude:Double, frequency:Double, seed:Int = 0) 690 | { 691 | self.amplitude = 9 * amplitude 692 | self.frequency = frequency 693 | self.permutation_table = PermutationTable(seed: seed) 694 | } 695 | 696 | private 697 | func gradient(from point:Math.IntV3, at offset:Math.DoubleV3) -> Double 698 | { 699 | let dr:Double = 0.75 - Math.dot(offset, offset) 700 | if dr > 0 701 | { 702 | let gradient:Math.DoubleV3 = GradientNoise3D.gradient_table32[self.permutation_table.hash(point) & 31], 703 | drdr:Double = dr * dr 704 | return drdr * drdr * Math.dot(gradient, offset) 705 | } 706 | else 707 | { 708 | return 0 709 | } 710 | } 711 | 712 | /// Evaluates the super-simplex noise field at the given `x, y` coordinates, supplying `0` 713 | /// for the missing `z` coordinate. 714 | public 715 | func evaluate(_ x:Double, _ y:Double) -> Double 716 | { 717 | return self.evaluate(x, y, 0) 718 | } 719 | 720 | /// Evaluates the super-simplex noise field at the given coordinates. 721 | public 722 | func evaluate(_ x:Double, _ y:Double, _ z:Double) -> Double 723 | { 724 | let sample:Math.DoubleV3 = (x * self.frequency, y * self.frequency, z * self.frequency) 725 | 726 | // transform our coordinate system so that out rotated lattice (x, y, z) 727 | // forms an axis-aligned rectangular grid again (u, v, w) 728 | let rot_offset:Double = 2/3 * (sample.x + sample.y + sample.z), 729 | U1:Math.DoubleV3 = (rot_offset - sample.x, rot_offset - sample.y, rot_offset - sample.z), 730 | // do the same for an offset cube lattice 731 | U2:Math.DoubleV3 = (U1.x + 512.5, U1.y + 512.5, U1.z + 512.5) 732 | 733 | // get integral (u, v, w) cube coordinates as well as fractional offsets 734 | let (bin1, sample_rel1):(Math.IntV3, Math.DoubleV3) = Math.fraction(U1), 735 | (bin2, sample_rel2):(Math.IntV3, Math.DoubleV3) = Math.fraction(U2) 736 | 737 | // get nearest points 738 | let base_vertex_index1:Int = 739 | ( sample_rel1.x + sample_rel1.y + sample_rel1.z >= 1.5 ? 4 : 0) | 740 | (-sample_rel1.x + sample_rel1.y + sample_rel1.z >= 0.5 ? 8 : 0) | 741 | ( sample_rel1.x - sample_rel1.y + sample_rel1.z >= 0.5 ? 16 : 0) | 742 | ( sample_rel1.x + sample_rel1.y - sample_rel1.z >= 0.5 ? 32 : 0) 743 | 744 | let base_vertex_index2:Int = 745 | ( sample_rel2.x + sample_rel2.y + sample_rel2.z >= 1.5 ? 4 : 0) | 746 | (-sample_rel2.x + sample_rel2.y + sample_rel2.z >= 0.5 ? 8 : 0) | 747 | ( sample_rel2.x - sample_rel2.y + sample_rel2.z >= 0.5 ? 16 : 0) | 748 | ( sample_rel2.x + sample_rel2.y - sample_rel2.z >= 0.5 ? 32 : 0) 749 | 750 | // sum up the contributions from the two lattices 751 | var Σ:Double = 0 752 | for (point, point_offset) in GradientNoise3D.points[base_vertex_index1 ..< base_vertex_index1 + 4] 753 | { 754 | Σ += self.gradient(from: Math.add(bin1, point), at: Math.sub(sample_rel1, point_offset)) 755 | } 756 | 757 | for (point, point_offset) in GradientNoise3D.points[base_vertex_index2 ..< base_vertex_index2 + 4] 758 | { 759 | Σ += self.gradient(from: Math.add(bin2, point), at: Math.sub(sample_rel2, point_offset)) 760 | } 761 | 762 | return self.amplitude * Σ 763 | } 764 | 765 | /// Evaluates the super-simplex noise field at the given coordinates. The fourth coordinate 766 | /// is ignored. 767 | public 768 | func evaluate(_ x:Double, _ y:Double, _ z:Double, _:Double) -> Double 769 | { 770 | return self.evaluate(x, y, z) 771 | } 772 | } 773 | -------------------------------------------------------------------------------- /Sources/Noise/hash.swift: -------------------------------------------------------------------------------- 1 | // UNDOCUMENTED 2 | protocol HashedNoise:Noise 3 | { 4 | var permutation_table:PermutationTable { get } 5 | var amplitude:Double { get } 6 | var frequency:Double { get } 7 | 8 | init(amplitude:Double, frequency:Double, permutation_table:PermutationTable) 9 | } 10 | 11 | extension HashedNoise 12 | { 13 | public 14 | func amplitude_scaled(by factor:Double) -> Self 15 | { 16 | return Self(amplitude: self.amplitude * factor, frequency: self.frequency, permutation_table: self.permutation_table) 17 | } 18 | public 19 | func frequency_scaled(by factor:Double) -> Self 20 | { 21 | return Self(amplitude: self.amplitude, frequency: self.frequency * factor, permutation_table: self.permutation_table) 22 | } 23 | public 24 | func reseeded() -> Self 25 | { 26 | let new_table:PermutationTable = PermutationTable(reseeding: self.permutation_table) 27 | return Self(amplitude: self.amplitude, frequency: self.frequency, permutation_table: new_table) 28 | } 29 | 30 | func hash(point:Math.IntV2) -> Int 31 | { 32 | return self.permutation_table.hash(point) 33 | } 34 | 35 | func hash(point:Math.IntV3) -> Int 36 | { 37 | return self.permutation_table.hash(point) 38 | } 39 | } 40 | 41 | public 42 | protocol TilingNoise:Noise 43 | { 44 | func transposed(octaves:Int) -> Self 45 | } 46 | 47 | protocol HashedTilingNoise:TilingNoise 48 | { 49 | associatedtype IntV 50 | 51 | var permutation_table:PermutationTable { get } 52 | var amplitude:Double { get } 53 | var frequency:Double { get } 54 | var wavelengths:IntV { get } 55 | 56 | init(amplitude:Double, frequency:Double, permutation_table:PermutationTable, wavelengths:IntV) 57 | 58 | func _transpose_wavelengths(_ wavelengths:IntV, octaves:Int) -> IntV 59 | } 60 | 61 | extension HashedTilingNoise 62 | { 63 | public 64 | func amplitude_scaled(by factor:Double) -> Self 65 | { 66 | return Self(amplitude: self.amplitude * factor, frequency: self.frequency, 67 | permutation_table: self.permutation_table, wavelengths: self.wavelengths) 68 | } 69 | public 70 | func frequency_scaled(by factor:Double) -> Self 71 | { 72 | return Self(amplitude: self.amplitude, frequency: self.frequency * factor, 73 | permutation_table: self.permutation_table, wavelengths: self.wavelengths) 74 | } 75 | public 76 | func reseeded() -> Self 77 | { 78 | let new_table:PermutationTable = PermutationTable(reseeding: self.permutation_table) 79 | return Self(amplitude: self.amplitude, frequency: self.frequency, 80 | permutation_table: new_table, wavelengths: self.wavelengths) 81 | } 82 | 83 | public 84 | func transposed(octaves:Int = 1) -> Self 85 | { 86 | return Self(amplitude: self.amplitude, frequency: self.frequency, 87 | permutation_table: self.permutation_table, 88 | wavelengths: self._transpose_wavelengths(self.wavelengths, octaves: octaves)) 89 | } 90 | } 91 | 92 | extension HashedTilingNoise where IntV == Math.IntV2 93 | { 94 | func _transpose_wavelengths(_ wavelengths:Math.IntV2, octaves:Int) -> Math.IntV2 95 | { 96 | return (wavelengths.a << octaves, wavelengths.b << octaves) 97 | } 98 | 99 | func hash(point:Math.IntV2) -> Int 100 | { 101 | return self.permutation_table.hash(Math.mod(point, self.wavelengths)) 102 | } 103 | } 104 | 105 | extension HashedTilingNoise where IntV == Math.IntV3 106 | { 107 | func _transpose_wavelengths(_ wavelengths:Math.IntV3, octaves:Int) -> Math.IntV3 108 | { 109 | return (wavelengths.a << octaves, wavelengths.b << octaves, wavelengths.c << octaves) 110 | } 111 | 112 | func hash(point:Math.IntV3) -> Int 113 | { 114 | return self.permutation_table.hash(Math.mod(point, self.wavelengths)) 115 | } 116 | } 117 | 118 | /// A cryptographically unsecure 128-bit [Xorshift](https://en.wikipedia.org/wiki/Xorshift) 119 | /// pseudo-random number generator. 120 | public 121 | struct RandomXorshift 122 | { 123 | private 124 | var state128:(UInt32, UInt32, UInt32, UInt32) 125 | 126 | /// The maximum unsigned integer value the random number generator is capable of producing. 127 | public 128 | var max:UInt32 129 | { 130 | return UInt32.max 131 | } 132 | 133 | public 134 | init(seed:Int) 135 | { 136 | self.state128 = (1, 0, UInt32(truncatingIfNeeded: seed >> UInt32.bitWidth), UInt32(truncatingIfNeeded: seed)) 137 | } 138 | 139 | /// Generates a pseudo-random 32 bit unsigned integer, and advances the random number 140 | /// generator state. 141 | public mutating 142 | func generate() -> UInt32 143 | { 144 | var t:UInt32 = self.state128.3 145 | t ^= t &<< 11 146 | t ^= t &>> 8 147 | self.state128.3 = self.state128.2 148 | self.state128.2 = self.state128.1 149 | self.state128.1 = self.state128.0 150 | t ^= self.state128.0 151 | t ^= self.state128.0 &>> 19 152 | self.state128.0 = t 153 | return t 154 | } 155 | 156 | /// Generates a pseudo-random 32 bit unsigned integer less than `maximum`, and advances the 157 | /// random number generator state. This function should be preferred over using the plain 158 | /// ``generate`` method with the modulo operator to avoid modulo biasing. However, if 159 | /// `maximum` is a power of two, a bit mask may be faster. 160 | public mutating 161 | func generate(less_than maximum:UInt32) -> UInt32 162 | { 163 | let upper_bound:UInt32 = self.max - self.max % maximum 164 | var x:UInt32 = 0 165 | repeat 166 | { 167 | x = self.generate() 168 | } while x >= upper_bound 169 | 170 | return x % maximum 171 | } 172 | } 173 | 174 | /// An 8-bit permutation table useful for generating pseudo-random hash values. 175 | /// 176 | /// ![2D voronoi noise](png/banner_voronoi2d.png) 177 | /// 178 | /// Permutation tables can be used, among other things, to hash 179 | /// [cell noise](doc:CellNoise2D/closest_point(_:_:)) to produce a 180 | /// [Voronoi diagram](https://en.wikipedia.org/wiki/Voronoi_diagram). 181 | public 182 | struct PermutationTable 183 | { 184 | private 185 | let permut:[UInt8] // keep these small to minimize cache misses 186 | 187 | /// Creates an instance with the given random `seed` containing the values `0 ... 255` 188 | /// shuffled in a random order. 189 | public 190 | init(seed:Int) 191 | { 192 | self.init(old_permut: [UInt8](0 ... 255), seed: seed) 193 | } 194 | 195 | // UNDOCUMENTED 196 | public 197 | init(reseeding old_table:PermutationTable) 198 | { 199 | self.init(old_permut: old_table.permut, seed: 0) 200 | } 201 | 202 | private 203 | init(old_permut:[UInt8], seed:Int) 204 | { 205 | var permutations:[UInt8] = old_permut, 206 | rng:RandomXorshift = RandomXorshift(seed: seed) 207 | for i in 0 ..< 255 - 1 208 | { 209 | permutations.swapAt(i, Int(rng.generate()) & 255) 210 | } 211 | 212 | self.permut = permutations 213 | } 214 | 215 | func hash(_ n2:Math.IntV2) -> Int 216 | { 217 | return Int(self.permut[Int(self.permut[n2.a & 255]) ^ (n2.b & 255)]) 218 | } 219 | 220 | func hash(_ n3:Math.IntV3) -> Int 221 | { 222 | return Int(self.permut[self.hash((n3.a, n3.b)) ^ (n3.c & 255)]) 223 | } 224 | 225 | /// Hash a single integer value. 226 | public 227 | func hash(_ h1:Int) -> UInt8 228 | { 229 | return self.permut[h1 & 255] 230 | } 231 | /// Hash two integer values. 232 | public 233 | func hash(_ h1:Int, _ h2:Int) -> UInt8 234 | { 235 | return self.permut[Int(self.hash(h1)) ^ (h2 & 255)] 236 | } 237 | /// Hash three integer values. 238 | public 239 | func hash(_ h1:Int, _ h2:Int, _ h3:Int) -> UInt8 240 | { 241 | return self.permut[Int(self.hash(h1, h2)) ^ (h3 & 255)] 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /Sources/Noise/noise.swift: -------------------------------------------------------------------------------- 1 | /// A procedural noise generator. 2 | public 3 | protocol Noise 4 | { 5 | /// Evaluates the noise field at the given coordinate. For three- and higher-dimensional 6 | /// noise fields, the `z` and `w` coordinates, if applicable, are set to zero. 7 | func evaluate(_ x:Double, _ y:Double) -> Double 8 | /// Evaluates the noise field at the given coordinate. For two-dimensional noise fields, the 9 | /// `z` coordinate is ignored. For four-dimensional noise fields, the `w` coordinate is set 10 | /// to zero. 11 | func evaluate(_ x:Double, _ y:Double, _ z:Double) -> Double 12 | /// Evaluates the noise field at the given coordinate. For three-dimensional and lower noise 13 | /// fields, the `z` and `w` coordinates are ignored, if necessary. No existing noise 14 | /// generator in the library currently supports true four-dimensional evaluation. 15 | func evaluate(_ x:Double, _ y:Double, _ z:Double, _ w:Double) -> Double 16 | 17 | func amplitude_scaled(by factor:Double) -> Self 18 | func frequency_scaled(by factor:Double) -> Self 19 | func reseeded() -> Self 20 | 21 | /// Creates an instance with the given `amplitude`, `frequency`, and random `seed` values. 22 | /// Creating an instance generates a new pseudo-random permutation table for that instance, 23 | /// and a new instance does not need to be regenerated to sample the same procedural noise 24 | /// field. 25 | 26 | // It looks like our archaic selves wrote documentation as if this method were a 27 | // requirement, but it only appears on a handful of the concrete ``Noise`` types? 28 | 29 | // init(amplitude:Double, frequency:Double, seed:Int) 30 | } 31 | 32 | public 33 | extension Noise 34 | { 35 | /// Evaluates the noise field over the given area, starting from the origin, and extending 36 | /// over the first quadrant, taking unit steps in both directions. Although the `x` and `y` 37 | /// coordinates are returned, the output vector is guaranteed to be in row-major order. 38 | @available(*, deprecated, message: """ 39 | area sampling is deprecated, iterate over a `Domain2D.Iterator` iterator and sample \ 40 | directly instead. 41 | """) 42 | func sample_area(width:Int, height:Int) -> [(Double, Double, Double)] 43 | { 44 | var samples:[(Double, Double, Double)] = [] 45 | samples.reserveCapacity(width * height) 46 | for i in 0 ..< height 47 | { 48 | for j in 0 ..< width 49 | { 50 | let x:Double = Double(j) + 0.5, 51 | y:Double = Double(i) + 0.5 52 | samples.append((x, y, self.evaluate(x, y))) 53 | } 54 | } 55 | return samples 56 | } 57 | 58 | /// Evaluates the noise field over the given area, starting from the origin, and extending 59 | /// over the first quadrant, storing the values in a row-major array of samples. The samples 60 | /// are clamped, but not scaled, to the range `0 ... 255`. 61 | @available(*, deprecated, message: """ 62 | area sampling is deprecated, iterate over a `Domain2D.Iterator` iterator and sample \ 63 | directly instead. 64 | """) 65 | func sample_area_saturated_to_u8(width:Int, height:Int, offset:Double = 0.5) -> [UInt8] 66 | { 67 | var samples:[UInt8] = [] 68 | samples.reserveCapacity(width * height) 69 | for i in 0 ..< height 70 | { 71 | for j in 0 ..< width 72 | { 73 | let x:Double = Double(j) + 0.5, 74 | y:Double = Double(i) + 0.5 75 | samples.append(UInt8(max(0, min(255, self.evaluate(x, y) + offset)))) 76 | } 77 | } 78 | return samples 79 | } 80 | 81 | /// Evaluates the noise field over the given volume, starting from the origin, and extending 82 | /// over the first octant, taking unit steps in all three directions. Although the `x`, `y`, 83 | /// and `z` coordinates are returned, the output vector is guaranteed to be in 84 | /// `xy` plane-major, and then row-major order. 85 | @available(*, deprecated, message: """ 86 | volume sampling is deprecated, iterate over a `Domain3D.Iterator` iterator and sample \ 87 | directly instead. 88 | """) 89 | func sample_volume(width:Int, height:Int, depth:Int) -> [(Double, Double, Double, Double)] 90 | { 91 | var samples:[(Double, Double, Double, Double)] = [] 92 | samples.reserveCapacity(width * height * depth) 93 | for i in 0 ..< depth 94 | { 95 | for j in 0 ..< height 96 | { 97 | for k in 0 ..< width 98 | { 99 | let x:Double = Double(k) + 0.5, 100 | y:Double = Double(j) + 0.5, 101 | z:Double = Double(i) + 0.5 102 | samples.append((x, y, z, self.evaluate(x, y, z))) 103 | } 104 | } 105 | } 106 | return samples 107 | } 108 | 109 | /// Evaluates the noise field over the given volume, starting from the origin, and extending 110 | /// over the first octant, storing the values in a `xy` plane-major, and then row-major 111 | /// order array of samples. The samples are clamped, but not scaled, to the range 112 | /// `0 ... 255`. 113 | @available(*, deprecated, message: """ 114 | volume sampling is deprecated, iterate over a `Domain3D.Iterator` iterator and sample \ 115 | directly instead. 116 | """) 117 | func sample_volume_saturated_to_u8(width:Int, height:Int, depth:Int, offset:Double = 0.5) -> [UInt8] 118 | { 119 | var samples:[UInt8] = [] 120 | samples.reserveCapacity(width * height * depth) 121 | for i in 0 ..< depth 122 | { 123 | for j in 0 ..< height 124 | { 125 | for k in 0 ..< width 126 | { 127 | let x:Double = Double(k) + 0.5, 128 | y:Double = Double(j) + 0.5, 129 | z:Double = Double(i) + 0.5 130 | samples.append(UInt8(max(0, min(255, self.evaluate(x, y, z) + offset)))) 131 | } 132 | } 133 | } 134 | return samples 135 | } 136 | } 137 | 138 | enum Math 139 | { 140 | typealias IntV2 = (a:Int, b:Int) 141 | typealias IntV3 = (a:Int, b:Int, c:Int) 142 | typealias DoubleV2 = (x:Double, y:Double) 143 | typealias DoubleV3 = (x:Double, y:Double, z:Double) 144 | 145 | @inline(__always) 146 | private static 147 | func fraction(_ x:Double) -> (Int, Double) 148 | { 149 | let integer:Int = x > 0 ? Int(x) : Int(x) - 1 150 | return (integer, x - Double(integer)) 151 | } 152 | 153 | @inline(__always) 154 | static 155 | func fraction(_ v:DoubleV2) -> (IntV2, DoubleV2) 156 | { 157 | let (i1, f1):(Int, Double) = Math.fraction(v.0), 158 | (i2, f2):(Int, Double) = Math.fraction(v.1) 159 | return ((i1, i2), (f1, f2)) 160 | } 161 | @inline(__always) 162 | static 163 | func fraction(_ v:DoubleV3) -> (IntV3, DoubleV3) 164 | { 165 | let (i1, f1):(Int, Double) = Math.fraction(v.0), 166 | (i2, f2):(Int, Double) = Math.fraction(v.1), 167 | (i3, f3):(Int, Double) = Math.fraction(v.2) 168 | return ((i1, i2, i3), (f1, f2, f3)) 169 | } 170 | 171 | @inline(__always) 172 | static 173 | func add(_ v1:IntV2, _ v2:IntV2) -> IntV2 174 | { 175 | return (v1.a + v2.a, v1.b + v2.b) 176 | } 177 | @inline(__always) 178 | static 179 | func add(_ v1:IntV3, _ v2:IntV3) -> IntV3 180 | { 181 | return (v1.a + v2.a, v1.b + v2.b, v1.c + v2.c) 182 | } 183 | 184 | @inline(__always) 185 | static 186 | func add(_ v1:DoubleV2, _ v2:DoubleV2) -> DoubleV2 187 | { 188 | return (v1.x + v2.x, v1.y + v2.y) 189 | } 190 | @inline(__always) 191 | static 192 | func add(_ v1:DoubleV3, _ v2:DoubleV3) -> DoubleV3 193 | { 194 | return (v1.x + v2.x, v1.y + v2.y, v1.z + v2.z) 195 | } 196 | 197 | @inline(__always) 198 | static 199 | func sub(_ v1:IntV2, _ v2:IntV2) -> IntV2 200 | { 201 | return (v1.a - v2.a, v1.b - v2.b) 202 | } 203 | @inline(__always) 204 | static 205 | func sub(_ v1:IntV3, _ v2:IntV3) -> IntV3 206 | { 207 | return (v1.a - v2.a, v1.b - v2.b, v1.c - v2.c) 208 | } 209 | 210 | @inline(__always) 211 | static 212 | func sub(_ v1:DoubleV2, _ v2:DoubleV2) -> DoubleV2 213 | { 214 | return (v1.x - v2.x, v1.y - v2.y) 215 | } 216 | @inline(__always) 217 | static 218 | func sub(_ v1:DoubleV3, _ v2:DoubleV3) -> DoubleV3 219 | { 220 | return (v1.x - v2.x, v1.y - v2.y, v1.z - v2.z) 221 | } 222 | 223 | @inline(__always) 224 | static 225 | func mult(_ v1:IntV2, _ v2:IntV2) -> IntV2 226 | { 227 | return (v1.a * v2.a, v1.b * v2.b) 228 | } 229 | @inline(__always) 230 | static 231 | func mult(_ v1:IntV3, _ v2:IntV3) -> IntV3 232 | { 233 | return (v1.a * v2.a, v1.b * v2.b, v1.c * v2.c) 234 | } 235 | 236 | @inline(__always) 237 | static 238 | func mult(_ v1:DoubleV2, _ v2:DoubleV2) -> DoubleV2 239 | { 240 | return (v1.x * v2.x, v1.y * v2.y) 241 | } 242 | @inline(__always) 243 | static 244 | func mult(_ v1:DoubleV3, _ v2:DoubleV3) -> DoubleV3 245 | { 246 | return (v1.x * v2.x, v1.y * v2.y, v1.z * v2.z) 247 | } 248 | 249 | @inline(__always) 250 | static 251 | func div(_ v1:DoubleV2, _ v2:DoubleV2) -> DoubleV2 252 | { 253 | return (v1.x / v2.x, v1.y / v2.y) 254 | } 255 | @inline(__always) 256 | static 257 | func div(_ v1:DoubleV3, _ v2:DoubleV3) -> DoubleV3 258 | { 259 | return (v1.x / v2.x, v1.y / v2.y, v1.z / v2.z) 260 | } 261 | 262 | @inline(__always) 263 | private static 264 | func mod(_ x:Int, _ n:Int) -> Int 265 | { 266 | let remainder = x % n 267 | return remainder >= 0 ? remainder : remainder + n 268 | } 269 | 270 | @inline(__always) 271 | static 272 | func mod(_ v1:IntV2, _ v2:IntV2) -> IntV2 273 | { 274 | return (Math.mod(v1.a, v2.a), Math.mod(v1.b, v2.b)) 275 | } 276 | @inline(__always) 277 | static 278 | func mod(_ v1:IntV3, _ v2:IntV3) -> IntV3 279 | { 280 | return (Math.mod(v1.a, v2.a), Math.mod(v1.b, v2.b), Math.mod(v1.c, v2.c)) 281 | } 282 | 283 | @inline(__always) 284 | static 285 | func dot(_ v1:DoubleV2, _ v2:DoubleV2) -> Double 286 | { 287 | return v1.x * v2.x + v1.y * v2.y 288 | } 289 | @inline(__always) 290 | static 291 | func dot(_ v1:DoubleV3, _ v2:DoubleV3) -> Double 292 | { 293 | return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z 294 | } 295 | 296 | @inline(__always) 297 | static 298 | func cast_double(_ v:IntV2) -> DoubleV2 299 | { 300 | return (Double(v.a), Double(v.b)) 301 | } 302 | @inline(__always) 303 | static 304 | func cast_double(_ v:IntV3) -> DoubleV3 305 | { 306 | return (Double(v.a), Double(v.b), Double(v.c)) 307 | } 308 | 309 | @inline(__always) 310 | static 311 | func lerp(_ a:Double, _ b:Double, factor:Double) -> Double 312 | { 313 | return (1 - factor) * a + factor * b 314 | } 315 | 316 | @inline(__always) 317 | static 318 | func quintic_ease(_ x:Double) -> Double 319 | { 320 | // 6x^5 - 15x^4 + 10x^3 321 | return x * x * x * (10.addingProduct(x, (-15).addingProduct(x, 6))) 322 | } 323 | 324 | @inline(__always) 325 | static 326 | func quintic_ease(_ v:DoubleV2) -> DoubleV2 327 | { 328 | return (Math.quintic_ease(v.x), Math.quintic_ease(v.y)) 329 | } 330 | @inline(__always) 331 | static 332 | func quintic_ease(_ v:DoubleV3) -> DoubleV3 333 | { 334 | return (Math.quintic_ease(v.x), Math.quintic_ease(v.y), Math.quintic_ease(v.z)) 335 | } 336 | } 337 | 338 | /// UNDOCUMENTED 339 | public 340 | struct Domain2D:Sequence 341 | { 342 | private 343 | let sample_lower_bound:Math.DoubleV2, 344 | sample_upper_bound:Math.DoubleV2, 345 | increment:Math.DoubleV2 346 | 347 | public 348 | struct Iterator:IteratorProtocol 349 | { 350 | private 351 | var sample:Math.DoubleV2 352 | 353 | private 354 | let domain:Domain2D 355 | 356 | init(_ domain:Domain2D) 357 | { 358 | self.sample = Math.add(domain.sample_lower_bound, (-0.5, 0.5)) 359 | self.domain = domain 360 | } 361 | 362 | public mutating 363 | func next() -> (Double, Double)? 364 | { 365 | self.sample.x += 1 366 | if self.sample.x >= self.domain.sample_upper_bound.x 367 | { 368 | self.sample.x = self.domain.sample_lower_bound.x + 0.5 369 | self.sample.y += 1 370 | if self.sample.y >= self.domain.sample_upper_bound.y 371 | { 372 | return nil 373 | } 374 | } 375 | 376 | return Math.mult(self.domain.increment, self.sample) 377 | } 378 | } 379 | 380 | public 381 | init(samples_x:Int, samples_y:Int) 382 | { 383 | self.increment = (1, 1) 384 | self.sample_lower_bound = (0, 0) 385 | self.sample_upper_bound = Math.cast_double((samples_x, samples_y)) 386 | } 387 | 388 | public 389 | init(_ x_range:ClosedRange, _ y_range:ClosedRange, samples_x:Int, samples_y:Int) 390 | { 391 | let sample_count:Math.DoubleV2 = Math.cast_double((samples_x, samples_y)), 392 | range_lower_bound:Math.DoubleV2 = (x_range.lowerBound, y_range.lowerBound), 393 | range_upper_bound:Math.DoubleV2 = (x_range.upperBound, y_range.upperBound), 394 | range_difference:Math.DoubleV2 = Math.sub(range_upper_bound, range_lower_bound) 395 | 396 | self.increment = Math.div(range_difference, sample_count) 397 | self.sample_lower_bound = Math.div(Math.mult(range_lower_bound, sample_count), range_difference) 398 | self.sample_upper_bound = Math.add(self.sample_lower_bound, sample_count) 399 | } 400 | 401 | public 402 | func makeIterator() -> Iterator 403 | { 404 | return Iterator(self) 405 | } 406 | } 407 | 408 | /// UNDOCUMENTED 409 | public 410 | struct Domain3D:Sequence 411 | { 412 | private 413 | let sample_lower_bound:Math.DoubleV3, 414 | sample_upper_bound:Math.DoubleV3, 415 | increment:Math.DoubleV3 416 | 417 | public 418 | struct Iterator:IteratorProtocol 419 | { 420 | private 421 | var sample:Math.DoubleV3 422 | 423 | private 424 | let domain:Domain3D 425 | 426 | init(_ domain:Domain3D) 427 | { 428 | self.sample = Math.add(domain.sample_lower_bound, (-0.5, 0.5, 0.5)) 429 | self.domain = domain 430 | } 431 | 432 | public mutating 433 | func next() -> (Double, Double, Double)? 434 | { 435 | self.sample.x += 1 436 | if self.sample.x >= self.domain.sample_upper_bound.x 437 | { 438 | self.sample.x = self.domain.sample_lower_bound.x + 0.5 439 | self.sample.y += 1 440 | if self.sample.y >= self.domain.sample_upper_bound.y 441 | { 442 | self.sample.y = self.domain.sample_lower_bound.y + 0.5 443 | self.sample.z += 1 444 | if self.sample.z >= self.domain.sample_upper_bound.z 445 | { 446 | return nil 447 | } 448 | } 449 | } 450 | 451 | return Math.mult(self.domain.increment, self.sample) 452 | } 453 | } 454 | 455 | public 456 | init(samples_x:Int, samples_y:Int, samples_z:Int) 457 | { 458 | self.increment = (1, 1, 1) 459 | self.sample_lower_bound = (0, 0, 0) 460 | self.sample_upper_bound = Math.cast_double((samples_x, samples_y, samples_z)) 461 | } 462 | 463 | public 464 | init(_ x_range:ClosedRange, _ y_range:ClosedRange, _ z_range:ClosedRange, 465 | samples_x:Int, samples_y:Int, samples_z:Int) 466 | { 467 | let sample_count:Math.DoubleV3 = Math.cast_double((samples_x, samples_y, samples_z)), 468 | range_lower_bound:Math.DoubleV3 = (x_range.lowerBound, y_range.lowerBound, z_range.lowerBound), 469 | range_upper_bound:Math.DoubleV3 = (x_range.upperBound, y_range.upperBound, z_range.upperBound), 470 | range_difference:Math.DoubleV3 = Math.sub(range_upper_bound, range_lower_bound) 471 | 472 | self.increment = Math.div(range_difference, sample_count) 473 | self.sample_lower_bound = Math.div(Math.mult(range_lower_bound, sample_count), range_difference) 474 | self.sample_upper_bound = Math.add(self.sample_lower_bound, sample_count) 475 | } 476 | 477 | public 478 | func makeIterator() -> Iterator 479 | { 480 | return Iterator(self) 481 | } 482 | } 483 | -------------------------------------------------------------------------------- /Tests/NoiseTests/ConsistentPRNGTests.swift: -------------------------------------------------------------------------------- 1 | import Noise 2 | import XCTest 3 | 4 | final class StubTests_swift: XCTestCase { 5 | 6 | override func setUpWithError() throws { 7 | // This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDownWithError() throws { 11 | // This method is called after the invocation of each test method in the class. 12 | } 13 | 14 | func testRandomXorshiftConsistency() throws { 15 | var rand1 = RandomXorshift(seed: 5) 16 | var rand2 = RandomXorshift(seed: 5) 17 | XCTAssertEqual(rand1.generate(), rand2.generate()) 18 | XCTAssertEqual(rand1.generate(), rand2.generate()) 19 | XCTAssertEqual(rand1.generate(), rand2.generate()) 20 | XCTAssertEqual(rand1.generate(), rand2.generate()) 21 | XCTAssertEqual(rand1.generate(), rand2.generate()) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /examples/banner_FBM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/examples/banner_FBM.png -------------------------------------------------------------------------------- /examples/banner_cell2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/examples/banner_cell2d.png -------------------------------------------------------------------------------- /examples/banner_cell3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/examples/banner_cell3d.png -------------------------------------------------------------------------------- /examples/banner_classic3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/examples/banner_classic3d.png -------------------------------------------------------------------------------- /examples/banner_disk2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/examples/banner_disk2d.png -------------------------------------------------------------------------------- /examples/banner_supersimplex2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/examples/banner_supersimplex2d.png -------------------------------------------------------------------------------- /examples/banner_supersimplex3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/examples/banner_supersimplex3d.png -------------------------------------------------------------------------------- /examples/banner_voronoi2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/examples/banner_voronoi2d.png -------------------------------------------------------------------------------- /examples/calibrate_cell2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/examples/calibrate_cell2d.png -------------------------------------------------------------------------------- /examples/calibrate_cell3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/examples/calibrate_cell3d.png -------------------------------------------------------------------------------- /examples/calibrate_classic-distortion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/examples/calibrate_classic-distortion.png -------------------------------------------------------------------------------- /examples/calibrate_gradient2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/examples/calibrate_gradient2d.png -------------------------------------------------------------------------------- /examples/calibrate_gradient3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tayloraswift/swift-noise/b51bb78237b0119328edc154f72c82bb8ddb5071/examples/calibrate_gradient3d.png --------------------------------------------------------------------------------