├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── _config.yml ├── build-ghpages.sh ├── build-wasm.sh ├── doc ├── Pre-filtering Environment Maps.lyx └── demo.jpg ├── examples ├── dist │ ├── index.html │ ├── reset.css │ └── style.css ├── images │ └── nissibeach2 │ │ ├── negx.jpg │ │ ├── negy.jpg │ │ ├── negz.jpg │ │ ├── posx.jpg │ │ ├── posy.jpg │ │ ├── posz.jpg │ │ └── readme.txt ├── ts │ ├── imagewell.tsx │ ├── main.tsx │ ├── model.ts │ ├── port.tsx │ └── viewport.tsx ├── tsconfig.json ├── webpack.common.js ├── webpack.config.js └── webpack.prod.js ├── package-lock.json ├── package.json ├── rust ├── Cargo.toml ├── benches │ └── blur.rs ├── examples │ ├── blurcubemap.rs │ ├── images │ │ ├── negx.jpg │ │ ├── negy.jpg │ │ ├── negz.jpg │ │ ├── posx.jpg │ │ ├── posy.jpg │ │ ├── posz.jpg │ │ └── readme.txt │ └── impulses │ │ ├── negx.png │ │ ├── negy.png │ │ ├── negz.png │ │ ├── posx.png │ │ ├── posy.png │ │ └── posz.png └── src │ ├── accessor.rs │ ├── cubemap.rs │ ├── lib.rs │ ├── ltasgblur.rs │ └── rand │ ├── .gitignore │ ├── .travis.yml │ ├── Cargo.toml │ ├── LICENSE-APACHE │ ├── LICENSE-MIT │ ├── README.md │ └── src │ ├── chacha.rs │ ├── distributions │ ├── exponential.rs │ ├── gamma.rs │ ├── mod.rs │ ├── normal.rs │ ├── range.rs │ └── ziggurat_tables.rs │ ├── isaac.rs │ ├── lib.rs │ ├── os.rs │ ├── rand_impls.rs │ ├── read.rs │ └── reseeding.rs ├── tools └── btoa.js ├── ts ├── image.ts ├── index.ts ├── ltasgblur.ts ├── types.ts ├── utils.ts ├── wasm-blob.d.ts └── wasm.ts ├── tsconfig.json └── wasmbinding ├── Cargo.toml └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /target 3 | /dist 4 | /examples/dist/*.bundle.js 5 | /examples/dist/*.jpg 6 | /examples/dist/*.map 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "rust", 4 | "wasmbinding", 5 | ] 6 | 7 | [profile.release] 8 | debug = true 9 | lto = true 10 | panic = "abort" 11 | 12 | [profile.bench] 13 | lto = true 14 | debug = true 15 | 16 | [patch.crates-io] 17 | "rand" = { path = "rust/src/rand" } 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 yvt 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Hyper3D EnvMapGen 2 | ================= 3 | 4 | Pre-filtered mipmapped radiance environment map generator that runs on WebAssembly. The core functionality is implemented by Rust and is available as a standalone crate. 5 | 6 | ![](doc/demo.jpg) 7 | 8 | [See the example.](https://hyper3d.github.io/hyper3d-envmapgen/examples/index.html) 9 | 10 | ## Features 11 | 12 | - LTASG (linear-time approximate spherical Gaussian) filtering that can be used to approximate the Blinn-Phong NDF for small roughness values. 13 | - The algorithm is implemented to run entirely on the CPU. This allows asynchronous processing that does not interfere with the main thread's operation, which is hard to achieve with a WebGL-based implementation. 14 | 15 | ### Possible TODOs 16 | 17 | - More filtering algorithms and techniques 18 | - GGX + importance sampling 19 | - GGX + [Fast Filtering of Reflection Probes] 20 | - Linear filtering in LTASG 21 | - The currently used nearest neighbor filtering produces a poor result unless the number of passes is set to at least two or three. 22 | - Investigate the behavior with large σ values 23 | 24 | [Fast Filtering of Reflection Probes]: https://dl.acm.org/citation.cfm?id=3071786 25 | 26 | ## Browser Support 27 | 28 | `hyper3d-envmapgen` requires [a web browser supporting WebAssembly](https://caniuse.com/#feat=wasm). 29 | 30 | | IE | Edge | Firefox | Chrome | Safari | iOS Safari | 31 | | :-: | :--: | :-----: | :----: | :----: | :--------: | 32 | | No | ≥ 16 | ≥ 53 | ≥ 57 | ≥ 11 | ≥ 11 | 33 | 34 | ## Usage: JavaScript 35 | 36 | With npm do: 37 | 38 | npm install --save hyper3d-envmapgen 39 | 40 | > Other installation methods are not supported at this moment. Use webpack or Browserify. They are really awesome. 41 | 42 | The following TypeScript code shows the basic usage of `hyper3d-envmapgen`: 43 | 44 | > Just remove the type annotations to convert it to JavaScript. 45 | 46 | ```ts 47 | import { LtasgBlur, ImageFormat } from 'hyper3d-envmapgen'; 48 | 49 | (async () => { 50 | 51 | // See `ts/ltasgblur.ts` for all options 52 | const ltasg: LtasgBlur = await LtasgBlur.create({ 53 | // The size of the base mipmap 54 | imageSize: 128, 55 | 56 | // The σ value (blur size) of each mipmap level. Must be reasonably 57 | // smaller than 1. 58 | mipLevelSigmas: Array.from( 59 | new Array(8), 60 | (_, i) => 0.5 ** (6 - Math.min(4, i)) 61 | ), 62 | 63 | // The number of passes — have an impact on the filtering quality. 64 | // You usually specify one of 1, 2, and 3. 65 | minNumPasses: 2, 66 | }); 67 | 68 | // Construct the mipmap pyramid 69 | const mipmapLevels = ltasg.process( 70 | [ 71 | images.positiveX, images.negativeX, 72 | images.positiveY, images.negativeY, 73 | images.positiveZ, images.negativeZ, 74 | ], 75 | ImageFormat.Srgbx8, 76 | ImageFormat.Srgbx8, 77 | ) as Uint8Array[][]; 78 | 79 | for (let level = 0; level <= Math.log2(ltasg.size); ++level) { 80 | for (let i = 0; i < 6; ++i) { 81 | gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + k, level, 82 | gl.SRGB_ALPHA_EXT, ltasg.size >> level, ltasg.size >> level, 83 | 0, gl.SRGB_ALPHA_EXT, gl.UNSIGNED_BYTE, 84 | level < mipmapLevels.length ? mipmapLevels[level][i] : null); 85 | } 86 | } 87 | })(); 88 | ``` 89 | 90 | And then in your fragment shader... 91 | 92 | ```glsl 93 | float phong_power = 1024.0; 94 | float lod = 6.0 - log2(phong_power) * 0.5 - 1.0; 95 | vec3 image = textureCubeLodEXT(u_Texture, v_Reflect, lod); 96 | ``` 97 | 98 | ## Usage: Rust 99 | 100 | This repository provides a crate named `hyperenvmapgen`, which can be found in the directory `rust`. 101 | 102 | This crate is never meant to be stablized. Therefore, it is strongly recommended to specify the revision hash as shown below: 103 | 104 | ```toml 105 | [dependencies.hyperenvmap] 106 | git = "https://github.com/Hyper3D/hyper3d-envmapgen" 107 | rev = "INSERT REVISION HASH HERE" 108 | ``` 109 | 110 | See `rust/examples/blurcubemap.rs` for the usage. 111 | 112 | ## Building 113 | 114 | ```shell 115 | # Install the Rust toolchain for WebAssembly compilation 116 | rustup target add wasm32-unknown-unknown --toolchain nightly 117 | cargo install --git https://github.com/alexcrichton/wasm-gc 118 | 119 | # Install necessary packages 120 | npm install 121 | 122 | # Build the library 123 | npm run build 124 | 125 | # Build and open the demo 126 | npm run start:examples 127 | ``` 128 | 129 | ## License 130 | 131 | hyper3d-envmapgen, Copyright © 2017 yvt 132 | 133 | The source code of this library is licensed under [the MIT License]. 134 | 135 | [the MIT License]: https://opensource.org/licenses/MIT 136 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal 2 | -------------------------------------------------------------------------------- /build-ghpages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "$(dirname "$0")" 4 | 5 | git checkout master || exit $? 6 | git checkout -B gh-pages || exit $? 7 | 8 | npm run build || exit $? 9 | npm run build:examples || exit $? 10 | 11 | # Build the directory structure for the example 12 | cp examples/dist/* examples/ 13 | git add -f examples/*.html 14 | git add -f examples/*.js 15 | git add -f examples/*.css 16 | git add -f examples/*.jpg 17 | git add -f examples/*.map 18 | 19 | git commit -m "Update GitHub Pages" 20 | 21 | git push -u origin gh-pages --force 22 | -------------------------------------------------------------------------------- /build-wasm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -x 4 | cd "`dirname "$0"`" 5 | 6 | pushd wasmbinding 7 | cargo build --target=wasm32-unknown-unknown --release || exit 1 8 | popd 9 | 10 | wasm-gc target/wasm32-unknown-unknown/release/hyperenvmap_wasm.wasm target/hyperenvmap_wasm.wasm 11 | node tools/btoa.js target/hyperenvmap_wasm.wasm > dist/wasm-blob.js 12 | -------------------------------------------------------------------------------- /doc/Pre-filtering Environment Maps.lyx: -------------------------------------------------------------------------------- 1 | #LyX 2.2 created this file. For more info see http://www.lyx.org/ 2 | \lyxformat 508 3 | \begin_document 4 | \begin_header 5 | \save_transient_properties true 6 | \origin unavailable 7 | \textclass article 8 | \use_default_options true 9 | \begin_modules 10 | logicalmkup 11 | \end_modules 12 | \maintain_unincluded_children false 13 | \language english 14 | \language_package default 15 | \inputencoding auto 16 | \fontencoding global 17 | \font_roman "default" "default" 18 | \font_sans "default" "default" 19 | \font_typewriter "default" "default" 20 | \font_math "auto" "auto" 21 | \font_default_family default 22 | \use_non_tex_fonts false 23 | \font_sc false 24 | \font_osf false 25 | \font_sf_scale 100 100 26 | \font_tt_scale 100 100 27 | \graphics default 28 | \default_output_format default 29 | \output_sync 0 30 | \bibtex_command default 31 | \index_command default 32 | \paperfontsize default 33 | \spacing single 34 | \use_hyperref true 35 | \pdf_title "Storing 3D Data Inside a 2D Texture" 36 | \pdf_author "yvt " 37 | \pdf_bookmarks true 38 | \pdf_bookmarksnumbered false 39 | \pdf_bookmarksopen false 40 | \pdf_bookmarksopenlevel 1 41 | \pdf_breaklinks false 42 | \pdf_pdfborder true 43 | \pdf_colorlinks false 44 | \pdf_backref false 45 | \pdf_pdfusetitle true 46 | \papersize default 47 | \use_geometry false 48 | \use_package amsmath 1 49 | \use_package amssymb 1 50 | \use_package cancel 1 51 | \use_package esint 1 52 | \use_package mathdots 1 53 | \use_package mathtools 1 54 | \use_package mhchem 1 55 | \use_package stackrel 1 56 | \use_package stmaryrd 1 57 | \use_package undertilde 1 58 | \cite_engine basic 59 | \cite_engine_type default 60 | \biblio_style plain 61 | \use_bibtopic false 62 | \use_indices false 63 | \paperorientation portrait 64 | \suppress_date false 65 | \justification true 66 | \use_refstyle 1 67 | \index Index 68 | \shortcut idx 69 | \color #008000 70 | \end_index 71 | \secnumdepth 3 72 | \tocdepth 3 73 | \paragraph_separation indent 74 | \paragraph_indentation default 75 | \quotes_language english 76 | \papercolumns 1 77 | \papersides 1 78 | \paperpagestyle default 79 | \tracking_changes false 80 | \output_changes false 81 | \html_math_output 0 82 | \html_css_as_file 0 83 | \html_be_strict false 84 | \end_header 85 | 86 | \begin_body 87 | 88 | \begin_layout Title 89 | Pre-filtering Environment Maps 90 | \end_layout 91 | 92 | \begin_layout Author 93 | yvt < 94 | \begin_inset Flex URL 95 | status open 96 | 97 | \begin_layout Plain Layout 98 | 99 | i@yvt.jp 100 | \end_layout 101 | 102 | \end_inset 103 | 104 | > 105 | \end_layout 106 | 107 | \begin_layout Date 108 | November 29, 2017 109 | \end_layout 110 | 111 | \begin_layout Standard 112 | Blinn-Phong approximates a Gaussian distribution as the specular exponent 113 | increases [Lyon1993]. 114 | [Olano2010] has shown the following relation in terms of the angle 115 | \begin_inset Formula $\theta$ 116 | \end_inset 117 | 118 | between 119 | \begin_inset Formula $n$ 120 | \end_inset 121 | 122 | and 123 | \begin_inset Formula $h$ 124 | \end_inset 125 | 126 | : 127 | \begin_inset Formula 128 | \[ 129 | \cos^{s}(\theta)\approx\exp\left(-\frac{s}{2}\tan^{2}\theta\right) 130 | \] 131 | 132 | \end_inset 133 | 134 | 135 | \end_layout 136 | 137 | \begin_layout Standard 138 | This makes the spherical Gaussian blur an excellent and appropriate choice 139 | for generating environmental maps. 140 | By extending the Gaussian blur's separable property, it is possible to 141 | implement it in 142 | \begin_inset Formula $O(K)$ 143 | \end_inset 144 | 145 | for reasonably small 146 | \begin_inset Formula $\sigma$ 147 | \end_inset 148 | 149 | values. 150 | \end_layout 151 | 152 | \begin_layout Section 153 | Related Work 154 | \end_layout 155 | 156 | \begin_layout Standard 157 | AMD's CubeMapGen has been a popular choice to generate pre-filtered environment 158 | maps. 159 | However, it is designed for a offline generation and is too slow for a 160 | real-time use. 161 | \end_layout 162 | 163 | \begin_layout Standard 164 | In three.js, 165 | \begin_inset Flex Code 166 | status open 167 | 168 | \begin_layout Plain Layout 169 | PMREMGenerator 170 | \end_layout 171 | 172 | \end_inset 173 | 174 | is responsible for pre-filtering environment maps. 175 | It is implemented as a fragment shader that performs Monte Carlo sampling, 176 | and when the sample count is set as low as 32 it is capable of running 177 | at 60fps on Intel HD Graphics 4000 178 | \begin_inset Foot 179 | status open 180 | 181 | \begin_layout Plain Layout 182 | \begin_inset Flex URL 183 | status open 184 | 185 | \begin_layout Plain Layout 186 | 187 | https://github.com/mrdoob/three.js/pull/7902 188 | \end_layout 189 | 190 | \end_inset 191 | 192 | 193 | \end_layout 194 | 195 | \end_inset 196 | 197 | . 198 | [Colbert2007] describes a practical implementation of GPU-based importance 199 | sampling for environment map pre-filtering. 200 | \end_layout 201 | 202 | \begin_layout Section 203 | Mapping Gloss Values to Mip Levels 204 | \end_layout 205 | 206 | \begin_layout Standard 207 | I wanted to have a constant kernel radius of 208 | \begin_inset Formula $K$ 209 | \end_inset 210 | 211 | (= 8) pixels for every mip level. 212 | 213 | \begin_inset Formula $\sigma$ 214 | \end_inset 215 | 216 | should be a somewhat smaller value than 217 | \begin_inset Formula $K$ 218 | \end_inset 219 | 220 | in order to fit the significant part of the Gaussian distribution within 221 | the kernel radius. 222 | I chose 223 | \begin_inset Formula $\sigma=K/r$ 224 | \end_inset 225 | 226 | where 227 | \begin_inset Formula $r=4$ 228 | \end_inset 229 | 230 | . 231 | \end_layout 232 | 233 | \begin_layout Standard 234 | Under this condition and given that the image size of the base mip level 235 | is 236 | \begin_inset Formula $N$ 237 | \end_inset 238 | 239 | pixels, the relationship between the specular exponent 240 | \begin_inset Formula $s$ 241 | \end_inset 242 | 243 | and the mip level 244 | \begin_inset Formula $n$ 245 | \end_inset 246 | 247 | is found as following: 248 | \begin_inset Formula 249 | \begin{align*} 250 | \sigma=1/\sqrt{s} & =\frac{K}{r}\cdot\frac{1}{2^{-n}N}\\ 251 | s & =\left(\frac{2^{-n}Nr}{K}\right)^{2}\\ 252 | & =0.25(2^{-n}N)^{2}\\ 253 | n & =\frac{1}{2}\log_{2}4sN^{2} 254 | \end{align*} 255 | 256 | \end_inset 257 | 258 | 259 | \end_layout 260 | 261 | \begin_layout Section 262 | Separate Filtering 263 | \end_layout 264 | 265 | \begin_layout Standard 266 | The basic idea of the separate Gaussian filter is decomposing a 267 | \begin_inset Formula $n$ 268 | \end_inset 269 | 270 | -dimensional Gaussian filter into 271 | \begin_inset Formula $n$ 272 | \end_inset 273 | 274 | cascaded one-dimensional Gaussian filters as shown in the following example 275 | where 276 | \begin_inset Formula $n=2$ 277 | \end_inset 278 | 279 | : 280 | \begin_inset Formula 281 | \begin{align*} 282 | G(x,y) & =\frac{1}{2\pi\sigma^{2}}\exp\left(-\frac{x^{2}+y^{2}}{2\sigma^{2}}\right)\\ 283 | G_{x}(x,y) & =\begin{cases} 284 | \frac{1}{2\pi\sigma^{2}}\exp\left(-\frac{x^{2}}{2\sigma^{2}}\right) & y=0\\ 285 | 0 & y\ne0 286 | \end{cases}\\ 287 | G_{y}(x,y) & =\text{ditto.}\\ 288 | G & =G_{x}\circ G_{y} 289 | \end{align*} 290 | 291 | \end_inset 292 | 293 | The decomposition allows a 294 | \begin_inset Formula $n$ 295 | \end_inset 296 | 297 | -dimensional Gaussian filter to be implemented with the time complexity 298 | 299 | \begin_inset Formula $O(K)$ 300 | \end_inset 301 | 302 | instead of 303 | \begin_inset Formula $O(K^{n})$ 304 | \end_inset 305 | 306 | . 307 | \end_layout 308 | 309 | \begin_layout Standard 310 | At cost of accuracy, this idea can be extended for a wider variety of filters 311 | that locally resemble a Gaussian filter, examples of which include a spatially 312 | varying anisotropic Gaussian filter [Zheng2011]. 313 | \end_layout 314 | 315 | \begin_layout Standard 316 | To apply this technique, one has to find the functions 317 | \begin_inset Formula $A_{1}(\vec{x}),\ldots,A_{k}(\vec{x})$ 318 | \end_inset 319 | 320 | each of which define the axis direction and the standard deviation of the 321 | corresponding one-dimensional Gaussian filter. 322 | Note that 323 | \begin_inset Formula $\vec{x}$ 324 | \end_inset 325 | 326 | represents a point in a 327 | \begin_inset Formula $n$ 328 | \end_inset 329 | 330 | -manifold 331 | \begin_inset Formula $\Gamma$ 332 | \end_inset 333 | 334 | embedded in a Euclidean space, and 335 | \begin_inset Formula $A_{i}(\vec{x})$ 336 | \end_inset 337 | 338 | must be a tangent vector of 339 | \begin_inset Formula $\Gamma$ 340 | \end_inset 341 | 342 | at 343 | \begin_inset Formula $\vec{x}$ 344 | \end_inset 345 | 346 | . 347 | The axis functions must fulfill the following condition in order for the 348 | resulting filter to locally resemble a 349 | \begin_inset Formula $n$ 350 | \end_inset 351 | 352 | -dimensional Gaussian filter: 353 | \begin_inset Formula 354 | \[ 355 | \mathrm{rank}(A_{1}(\vec{x})\ \cdots\ A_{k}(\vec{x}))\ge n 356 | \] 357 | 358 | \end_inset 359 | 360 | In addition, from a practical perspective, 361 | \begin_inset Formula $A_{1}(\vec{x}),\ldots,A_{k}(\vec{x})$ 362 | \end_inset 363 | 364 | must be as smooth as possible because abrupt changes in them lead to visual 365 | artifacts. 366 | \end_layout 367 | 368 | \begin_layout Standard 369 | For a spherical Gaussian blur ( 370 | \begin_inset Formula $\Gamma=S^{2}$ 371 | \end_inset 372 | 373 | , 374 | \begin_inset Formula $n=2$ 375 | \end_inset 376 | 377 | ), there exists no 378 | \begin_inset Formula $A_{1}(\vec{x}),A_{2}(\vec{x})$ 379 | \end_inset 380 | 381 | that satisfies this condition on every 382 | \begin_inset Formula $\vec{x}\in\Gamma$ 383 | \end_inset 384 | 385 | , which is obvious from the 386 | \begin_inset Quotes eld 387 | \end_inset 388 | 389 | hairy ball theorem 390 | \begin_inset Quotes erd 391 | \end_inset 392 | 393 | stating that there exists no nonvanishing continuous tangent vector field 394 | on even-dimensional 395 | \begin_inset Formula $n$ 396 | \end_inset 397 | 398 | -spheres. 399 | Therefore, at least 3 axis functions are required to realize a spherical 400 | Gaussian blur using this technique. 401 | 402 | \end_layout 403 | 404 | \begin_layout Standard 405 | I propose the following axis functions ( 406 | \begin_inset Formula $\left\{ \vec{a_{1}},\vec{a_{2}},\vec{a_{3}}\right\} $ 407 | \end_inset 408 | 409 | is an orthonormal basis of 410 | \begin_inset Formula $\mathbb{R}^{3}$ 411 | \end_inset 412 | 413 | ): 414 | \begin_inset Formula 415 | \begin{align*} 416 | A_{1}(\vec{x}) & =\sigma(\vec{a_{1}}-\vec{x}(\vec{x}\cdot\vec{a_{1}}))\\ 417 | A_{2}(\vec{x}) & =\sigma(\vec{a_{2}}-\vec{x}(\vec{x}\cdot\vec{a_{2}}))\\ 418 | A_{3}(\vec{x}) & =\sigma(\vec{a_{3}}-\vec{x}(\vec{x}\cdot\vec{a_{3}})) 419 | \end{align*} 420 | 421 | \end_inset 422 | 423 | Each of them represents a tangent vector along the latitude, assuming the 424 | points 425 | \begin_inset Formula $\pm\vec{a_{i}}$ 426 | \end_inset 427 | 428 | are the north and south poles of the sphere. 429 | If 430 | \begin_inset Formula $\left\{ \vec{a_{1}},\vec{a_{2}},\vec{a_{3}}\right\} $ 431 | \end_inset 432 | 433 | is substituted with the standard basis, they can be written more neatly 434 | as: 435 | \begin_inset Formula 436 | \begin{align*} 437 | A_{1}(\vec{x}) & =\sigma(\vec{e_{x}}-x_{x}\vec{x})\\ 438 | A_{2}(\vec{x}) & =\sigma(\vec{e_{y}}-x_{y}\vec{x})\\ 439 | A_{3}(\vec{x}) & =\sigma(\vec{e_{z}}-x_{z}\vec{x})\mathbf{} 440 | \end{align*} 441 | 442 | \end_inset 443 | 444 | 445 | \end_layout 446 | 447 | \begin_layout Subsection 448 | Derivation 449 | \end_layout 450 | 451 | \begin_layout Standard 452 | todo (unambiguously derived from the tangential condition + the uniform 453 | blur condition + the latitudinal condition) 454 | \end_layout 455 | 456 | \begin_layout Subsection 457 | Implementation on Cube Maps 458 | \end_layout 459 | 460 | \begin_layout Standard 461 | For each one-dimensional filter ( 462 | \begin_inset Formula $i\in\{1,2,3\}$ 463 | \end_inset 464 | 465 | ) and the cube face, there are two cases to handle: 466 | \end_layout 467 | 468 | \begin_layout Itemize 469 | \begin_inset Formula $\pm\vec{a_{i}}$ 470 | \end_inset 471 | 472 | is inside the face — In this case, the filter is implemented as a radial 473 | blur oriented toward the pole 474 | \begin_inset Formula $\pm\vec{a_{i}}$ 475 | \end_inset 476 | 477 | . 478 | \end_layout 479 | 480 | \begin_layout Itemize 481 | \begin_inset Formula $\pm\vec{a_{i}}$ 482 | \end_inset 483 | 484 | is outside the face — In this case, the filter is implemented as a directional 485 | blur along the U or V direction. 486 | \end_layout 487 | 488 | \begin_layout Standard 489 | We will only consider the positive Z cube face in the following discussion. 490 | \end_layout 491 | 492 | \begin_layout Standard 493 | Given a texture coordinate 494 | \begin_inset Formula $(u,v)$ 495 | \end_inset 496 | 497 | , the corresponding point 498 | \begin_inset Formula $\vec{x}\in S^{2}$ 499 | \end_inset 500 | 501 | is found as: 502 | \begin_inset Formula 503 | \[ 504 | \vec{x}=\frac{1}{\sqrt{1+u^{2}+v^{2}}}\begin{pmatrix}u\\ 505 | v\\ 506 | 1 507 | \end{pmatrix} 508 | \] 509 | 510 | \end_inset 511 | 512 | In the first case where 513 | \begin_inset Formula $\pm\vec{a_{i}}$ 514 | \end_inset 515 | 516 | is inside the face (hence 517 | \begin_inset Formula $\vec{a_{i}}=\vec{e_{z}}$ 518 | \end_inset 519 | 520 | ) 521 | \begin_inset Formula 522 | \[ 523 | A_{i}(\vec{x})=\sigma\begin{pmatrix}-\frac{u}{\sqrt{1+u^{2}+v^{2}}}\\ 524 | -\frac{v}{\sqrt{1+u^{2}+v^{2}}}\\ 525 | 1-\frac{1}{1+u^{2}+v^{2}} 526 | \end{pmatrix} 527 | \] 528 | 529 | \end_inset 530 | 531 | By projecting it on the plane 532 | \begin_inset Formula $z=1$ 533 | \end_inset 534 | 535 | we obtain: 536 | \end_layout 537 | 538 | \begin_layout Standard 539 | \begin_inset Formula 540 | \[ 541 | \left.\frac{d}{dt}\frac{\vec{x}+A_{i}(\vec{x})\cdot t}{\vec{e_{z}}\cdot\left(\vec{x}+A_{i}(\vec{x})\cdot t\right)}\right|_{t=0}=\begin{pmatrix}-u\sigma\sqrt{1+u^{2}+v^{2}}\\ 542 | -v\sigma\sqrt{1+u^{2}+v^{2}}\\ 543 | 0 544 | \end{pmatrix} 545 | \] 546 | 547 | \end_inset 548 | 549 | 550 | \end_layout 551 | 552 | \begin_layout Standard 553 | In the second case where 554 | \begin_inset Formula $\pm\vec{a_{i}}$ 555 | \end_inset 556 | 557 | is inside the face, assuming 558 | \begin_inset Formula $\vec{a_{i}}=\vec{e_{x}}$ 559 | \end_inset 560 | 561 | 562 | \begin_inset Formula 563 | \[ 564 | A_{i}(\vec{x})=\sigma\begin{pmatrix}1-\frac{u^{2}}{1+u^{2}+v^{2}}\\ 565 | -\frac{uv}{1+u^{2}+v^{2}}\\ 566 | -\frac{u}{1+u^{2}+v^{2}} 567 | \end{pmatrix} 568 | \] 569 | 570 | \end_inset 571 | 572 | 573 | \end_layout 574 | 575 | \begin_layout Standard 576 | By projecting it on the plane 577 | \begin_inset Formula $z=1$ 578 | \end_inset 579 | 580 | we obtain: 581 | \end_layout 582 | 583 | \begin_layout Standard 584 | \begin_inset Formula 585 | \[ 586 | \left.\frac{d}{dt}\frac{\vec{x}+A_{i}(\vec{x})\cdot t}{\vec{e_{z}}\cdot\left(\vec{x}+A_{i}(\vec{x})\cdot t\right)}\right|_{t=0}=\begin{pmatrix}\sigma\sqrt{1+u^{2}+v^{2}}\\ 587 | 0\\ 588 | 0 589 | \end{pmatrix} 590 | \] 591 | 592 | \end_inset 593 | 594 | 595 | \end_layout 596 | 597 | \begin_layout Section* 598 | References 599 | \end_layout 600 | 601 | \begin_layout Standard 602 | [Lyon1993] Lyron, R. 603 | 1993. 604 | Phong shading reformulation for hardware rendering. 605 | Tech. 606 | Rep. 607 | 43, Apple. 608 | \end_layout 609 | 610 | \begin_layout Standard 611 | [Olano2010] Olano, M., & Baker, D. 612 | (2010, February). 613 | LEAN mapping. 614 | In Proceedings of the 2010 ACM SIGGRAPH symposium on Interactive 3D Graphics 615 | and Games (pp. 616 | 181-188). 617 | ACM. 618 | \end_layout 619 | 620 | \begin_layout Standard 621 | [Zheng2011] Zheng, Z., & Saito, S. 622 | (2011, August). 623 | Screen space anisotropic blurred soft shadows. 624 | In SIGGRAPH Posters (p. 625 | 75). 626 | \end_layout 627 | 628 | \begin_layout Standard 629 | [Colbert2007] Colbert, M., & Krivanek, J. 630 | (2007). 631 | GPU-based importance sampling. 632 | GPU Gems, 3, 459-476. 633 | \end_layout 634 | 635 | \end_body 636 | \end_document 637 | -------------------------------------------------------------------------------- /doc/demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hyper3D/hyper3d-envmapgen/ca8ad7e2f6b91d3577cfdd195ccdd88b9f56db6c/doc/demo.jpg -------------------------------------------------------------------------------- /examples/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | hyper3d-envmapgen Example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/dist/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } -------------------------------------------------------------------------------- /examples/dist/style.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | background: #333; 3 | font-family: sans-serif; 4 | font-size: 13px; 5 | color: #eee; 6 | -webkit-font-smoothing: antialiased; 7 | overflow: hidden; 8 | line-height: 1.4; 9 | } 10 | a { 11 | color: white; 12 | background: rgba(255, 255, 255, 0.1); 13 | border-radius: 2px; 14 | text-decoration: none; 15 | padding: 0 2px; 16 | } 17 | a:hover { 18 | background: rgba(255, 255, 255, 0.2); 19 | } 20 | div.viewport { 21 | position: absolute; 22 | left: 0; top: 0; 23 | width: 100%; height: 100%; 24 | } 25 | div.controls { 26 | position: absolute; 27 | right: 20px; top: 20px; 28 | width: 240px; max-height: 100%; 29 | background: rgba(64, 64, 64, 0.6); 30 | border: 1px solid rgba(56, 56, 56, 0.3); 31 | border-radius: 4px; 32 | box-sizing: border-box; 33 | overflow-y: auto; 34 | padding: 10px; 35 | box-shadow: 0px 1.5px 3px rgba(0, 0, 0, 0.2); 36 | -webkit-backdrop-filter: blur(20px); 37 | } 38 | h1 { 39 | margin: 0.2em 0; 40 | font-size: 14px; 41 | font-weight: bold; 42 | text-transform: uppercase; 43 | color: #ddd; 44 | } 45 | h2 { 46 | margin: 0.7em 0; 47 | font-size: 12px; 48 | font-weight: bold; 49 | text-transform: uppercase; 50 | color: #ccc; 51 | } 52 | 53 | /* 54 | * 55 | */ 56 | div.imagewell { 57 | position: relative; 58 | border: 2px solid rgba(255, 255,255, 0.1); 59 | overflow: hidden; 60 | border-radius: 4px; 61 | cursor: pointer; 62 | } 63 | div.imagewell:hover { 64 | border-color: rgba(255, 255,255, 0.2); 65 | } 66 | div.imagewell.accept { 67 | border-style: dashed; 68 | border-color: #00ff00; 69 | } 70 | div.imagewell:after { 71 | content: ""; 72 | position: absolute; 73 | left: 0; top: 0; width: 100%; height: 100%; 74 | box-shadow: inset 1px 2px 2px 0px rgba(0, 0, 0, 0.5), inset 0px 0px 0px 1px rgba(0, 0, 0, 0.5); 75 | border-radius: 2px; 76 | } 77 | div.imagewell img { 78 | max-width: 100%; max-height: 100%; 79 | width: auto; height: auto; 80 | position: absolute; 81 | left: 50%; top: 50%; 82 | transform: translate(-50%, -50%); 83 | } 84 | 85 | /* 86 | * Controls — Images 87 | */ 88 | ul.images { 89 | position: relative; 90 | height: 165px; 91 | margin-top: -25px; 92 | } 93 | ul.images > li { 94 | position: absolute; 95 | width: 50px; height: 50px; 96 | } 97 | ul.images > li:nth-of-type(1) { left: 110px; top: 55px; } 98 | ul.images > li:nth-of-type(2) { left: 0px; top: 55px; } 99 | ul.images > li:nth-of-type(3) { left: 55px; top: 0px; } 100 | ul.images > li:nth-of-type(4) { left: 55px; top: 110px; } 101 | ul.images > li:nth-of-type(5) { left: 55px; top: 55px; } 102 | ul.images > li:nth-of-type(6) { left: 165px; top: 55px; } 103 | ul.images > li > div.imagewell { 104 | width: 100%; height: 100%; 105 | } 106 | ul.images > li > span { 107 | position: absolute; 108 | font-size: 11px; 109 | background: #aaa; 110 | border-radius: 2px; 111 | left: 4px; top: 4px; 112 | padding: 2px; 113 | box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3); 114 | font-weight: bold; 115 | text-transform: uppercase; 116 | color: #222; 117 | z-index: 1; 118 | pointer-events: none; 119 | } -------------------------------------------------------------------------------- /examples/images/nissibeach2/negx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hyper3D/hyper3d-envmapgen/ca8ad7e2f6b91d3577cfdd195ccdd88b9f56db6c/examples/images/nissibeach2/negx.jpg -------------------------------------------------------------------------------- /examples/images/nissibeach2/negy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hyper3D/hyper3d-envmapgen/ca8ad7e2f6b91d3577cfdd195ccdd88b9f56db6c/examples/images/nissibeach2/negy.jpg -------------------------------------------------------------------------------- /examples/images/nissibeach2/negz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hyper3D/hyper3d-envmapgen/ca8ad7e2f6b91d3577cfdd195ccdd88b9f56db6c/examples/images/nissibeach2/negz.jpg -------------------------------------------------------------------------------- /examples/images/nissibeach2/posx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hyper3D/hyper3d-envmapgen/ca8ad7e2f6b91d3577cfdd195ccdd88b9f56db6c/examples/images/nissibeach2/posx.jpg -------------------------------------------------------------------------------- /examples/images/nissibeach2/posy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hyper3D/hyper3d-envmapgen/ca8ad7e2f6b91d3577cfdd195ccdd88b9f56db6c/examples/images/nissibeach2/posy.jpg -------------------------------------------------------------------------------- /examples/images/nissibeach2/posz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hyper3D/hyper3d-envmapgen/ca8ad7e2f6b91d3577cfdd195ccdd88b9f56db6c/examples/images/nissibeach2/posz.jpg -------------------------------------------------------------------------------- /examples/images/nissibeach2/readme.txt: -------------------------------------------------------------------------------- 1 | Author 2 | ====== 3 | 4 | This is the work of Emil Persson, aka Humus. 5 | http://www.humus.name 6 | humus@comhem.se 7 | 8 | 9 | 10 | Legal stuff 11 | =========== 12 | 13 | This work is free and may be used by anyone for any purpose 14 | and may be distributed freely to anyone using any distribution 15 | media or distribution method as long as this file is included. 16 | Distribution without this file is allowed if it's distributed 17 | with free non-commercial software; however, fair credit of the 18 | original author is expected. 19 | Any commercial distribution of this software requires the written 20 | approval of Emil Persson. 21 | -------------------------------------------------------------------------------- /examples/ts/imagewell.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 yvt 3 | * 4 | * This file is a part of hyper3d-envmapgen. Please read the license text that 5 | * comes with the source code for use conditions. 6 | */ 7 | import * as React from 'react'; 8 | import bind from 'bind-decorator'; 9 | import * as Dropzone from 'react-dropzone'; 10 | const loadImage: (path: string) => Promise = require('image-promise'); 11 | 12 | import { Port } from './port'; 13 | 14 | const DropzoneComponent: typeof Dropzone = (Dropzone as any).default; 15 | 16 | export interface ImageWellProps { 17 | image: HTMLImageElement | null; 18 | onChange: (image: HTMLImageElement) => void; 19 | 20 | className?: string; 21 | style?: React.CSSProperties; 22 | } 23 | 24 | interface State {} 25 | 26 | export class ImageWell extends React.PureComponent { 27 | @bind 28 | private async handleDrop(acceptedFiles: Dropzone.ImageFile[], rejectedFiles: Dropzone.ImageFile[]): Promise { 29 | const image = acceptedFiles[0]; 30 | if (!image) { 31 | return; 32 | } 33 | 34 | const url = URL.createObjectURL(image); 35 | try { 36 | const image = await loadImage(url); 37 | this.props.onChange(image); 38 | } finally { 39 | URL.revokeObjectURL(url); 40 | } 41 | } 42 | 43 | render() { 44 | const {props} = this; 45 | return 51 | { props.image && } 52 | ; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/ts/main.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 yvt 3 | * 4 | * This file is a part of hyper3d-envmapgen. Please read the license text that 5 | * comes with the source code for use conditions. 6 | */ 7 | import * as React from "react"; 8 | import * as ReactDOM from "react-dom"; 9 | import bind from 'bind-decorator'; 10 | const loadImage: (path: string) => Promise = require('image-promise'); 11 | 12 | import { Viewport, ViewportPersistent } from './viewport'; 13 | import { ViewerState, DEFAULT_VIEWER_STATE, SceneState, DEFAULT_SCENE_STATE } from './model'; 14 | import { ImageWell } from './imagewell'; 15 | 16 | const envImages: string[] = [ 17 | require('file-loader!../images/nissibeach2/posx.jpg'), 18 | require('file-loader!../images/nissibeach2/negx.jpg'), 19 | require('file-loader!../images/nissibeach2/posy.jpg'), 20 | require('file-loader!../images/nissibeach2/negy.jpg'), 21 | require('file-loader!../images/nissibeach2/posz.jpg'), 22 | require('file-loader!../images/nissibeach2/negz.jpg'), 23 | ]; 24 | 25 | interface State { 26 | viewportPersistent: ViewportPersistent; 27 | viewerState: ViewerState; 28 | sceneState: SceneState; 29 | } 30 | 31 | class App extends React.Component<{}, State> { 32 | constructor(props: {}) { 33 | super(props); 34 | this.state = { 35 | viewportPersistent: new ViewportPersistent(), 36 | viewerState: DEFAULT_VIEWER_STATE, 37 | sceneState: DEFAULT_SCENE_STATE, 38 | }; 39 | 40 | // Load the initial image 41 | for (let i = 0; i < 6; ++i) { 42 | (i => { 43 | loadImage(envImages[i]).then(image => { 44 | this.setState(state => { 45 | const faceImages = state.viewerState.faceImages.slice(0); 46 | if (faceImages[i]) { 47 | return state; 48 | } 49 | faceImages[i] = image; 50 | return { 51 | ... state, 52 | viewerState: { 53 | ... state.viewerState, 54 | faceImages, 55 | }, 56 | }; 57 | }); 58 | }); 59 | })(i); 60 | } 61 | } 62 | 63 | private handleImageChange(i: number, image: HTMLImageElement) { 64 | this.setState(state => { 65 | const faceImages = state.viewerState.faceImages.slice(0); 66 | faceImages[i] = image; 67 | return { 68 | ... state, 69 | viewerState: { 70 | ... state.viewerState, 71 | faceImages, 72 | }, 73 | }; 74 | }); 75 | } 76 | 77 | @bind private handleImageChange0(image: HTMLImageElement) { this.handleImageChange(0, image); } 78 | @bind private handleImageChange1(image: HTMLImageElement) { this.handleImageChange(1, image); } 79 | @bind private handleImageChange2(image: HTMLImageElement) { this.handleImageChange(2, image); } 80 | @bind private handleImageChange3(image: HTMLImageElement) { this.handleImageChange(3, image); } 81 | @bind private handleImageChange4(image: HTMLImageElement) { this.handleImageChange(4, image); } 82 | @bind private handleImageChange5(image: HTMLImageElement) { this.handleImageChange(5, image); } 83 | 84 | @bind 85 | private handleChangeCubeMapSize(e: React.ChangeEvent) { 86 | const value = parseInt(e.target.value, 10); 87 | this.setState(state => ({ 88 | ... state, 89 | viewerState: { 90 | ... state.viewerState, 91 | cubeMapSize: value, 92 | }, 93 | })); 94 | } 95 | 96 | @bind 97 | private handleChangeMinNumPasses(e: React.ChangeEvent) { 98 | const value = parseInt(e.target.value, 10); 99 | this.setState(state => ({ 100 | ... state, 101 | viewerState: { 102 | ... state.viewerState, 103 | minNumPasses: value, 104 | }, 105 | })); 106 | } 107 | 108 | @bind 109 | private handleChangeKernelResolution(e: React.ChangeEvent) { 110 | const value = parseFloat(e.target.value); 111 | this.setState(state => ({ 112 | ... state, 113 | viewerState: { 114 | ... state.viewerState, 115 | kernelResolution: value, 116 | }, 117 | })); 118 | } 119 | 120 | @bind 121 | private handleChangeGeometry(e: React.ChangeEvent) { 122 | const value = e.target.value; 123 | this.setState(state => ({ 124 | ... state, 125 | sceneState: { 126 | ... state.sceneState, 127 | geometry: value as any, 128 | }, 129 | })); 130 | } 131 | 132 | render() { 133 | const {state} = this; 134 | 135 | return
136 | 140 |
141 |

hyper3d-envmapgen demo

142 |

Images

143 |
    144 |
  • 145 | +X 146 | 147 |
  • 148 |
  • 149 | -X 150 | 151 |
  • 152 |
  • 153 | +Y 154 | 155 |
  • 156 |
  • 157 | -Y 158 | 159 |
  • 160 |
  • 161 | +Z 162 | 163 |
  • 164 |
  • 165 | -Z 166 | 167 |
  • 168 |
169 |

Quality

170 |

171 | Size: 179 |  # Pass:
186 | Density: 191 |

192 |

Render

193 |

194 | 200 |

201 |

Copyright

202 |

203 | The default cube map image is a work by Emil Persson, aka Humus. 204 |

205 |

206 | hyper3d-envmapgen © 2017 yvt 207 |

208 |
209 |
; 210 | } 211 | } 212 | 213 | ReactDOM.render( 214 | , 215 | document.getElementById('app-root'), 216 | ); 217 | -------------------------------------------------------------------------------- /examples/ts/model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 yvt 3 | * 4 | * This file is a part of hyper3d-envmapgen. Please read the license text that 5 | * comes with the source code for use conditions. 6 | */ 7 | export interface ViewerState 8 | { 9 | readonly faceImages: ReadonlyArray; 10 | readonly cubeMapSize: number; 11 | readonly minNumPasses: number; 12 | readonly kernelResolution: number; 13 | } 14 | 15 | export interface SceneState 16 | { 17 | readonly geometry: 'sphere' | 'teapot'; 18 | } 19 | 20 | export const DEFAULT_VIEWER_STATE: ViewerState = { 21 | faceImages: [null, null, null, null, null, null], 22 | cubeMapSize: 128, 23 | minNumPasses: 2, 24 | kernelResolution: 2, 25 | }; 26 | 27 | export const DEFAULT_SCENE_STATE: SceneState = { 28 | geometry: 'sphere', 29 | }; 30 | -------------------------------------------------------------------------------- /examples/ts/port.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 yvt 3 | * 4 | * This file is a part of hyper3d-envmapgen. Please read the license text that 5 | * comes with the source code for use conditions. 6 | */ 7 | import * as React from 'react'; 8 | 9 | export interface PortProps { 10 | element: HTMLElement; 11 | 12 | className?: string; 13 | style?: React.CSSProperties; 14 | } 15 | 16 | /** 17 | * Displays a given element in a `
` wrapper. Useful for stateful elements. 18 | */ 19 | export class Port extends React.PureComponent { 20 | private wrapper: null | HTMLDivElement = null; 21 | 22 | componentDidMount(): void { 23 | if (!this.wrapper) { 24 | throw new Error(); 25 | } 26 | this.wrapper.appendChild(this.props.element); 27 | } 28 | 29 | componentWillUnmount(): void { 30 | if (!this.wrapper) { 31 | throw new Error(); 32 | } 33 | this.wrapper.removeChild(this.props.element); 34 | } 35 | 36 | componentDidUpdate(prevProps: PortProps, prevState: {}): void { 37 | if (this.props.element === prevProps.element) { 38 | return; 39 | } 40 | if (!this.wrapper) { 41 | throw new Error(); 42 | } 43 | this.wrapper.removeChild(prevProps.element); 44 | this.wrapper.appendChild(this.props.element); 45 | } 46 | 47 | render() { 48 | return
{this.wrapper = e}} />; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/ts/viewport.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 yvt 3 | * 4 | * This file is a part of hyper3d-envmapgen. Please read the license text that 5 | * comes with the source code for use conditions. 6 | */ 7 | import * as React from 'react'; 8 | import bind from 'bind-decorator'; 9 | import * as THREE from 'three'; 10 | import * as emg from '../../dist/index'; 11 | 12 | import { Port } from './port'; 13 | import { ViewerState, SceneState } from './model'; 14 | 15 | // Load three.js sample components 16 | const three = (window as any).THREE = THREE; 17 | require('three/examples/js/controls/OrbitControls'); 18 | require('three/examples/js/geometries/TeapotBufferGeometry'); 19 | 20 | // envmapgen 21 | const MIN_SIGMA = 2 ** -6; 22 | const emgCore = emg.CoreInstance.create(); 23 | 24 | const ACES = ` 25 | mediump vec3 acesToneMapping(mediump vec3 x) 26 | { 27 | return clamp((x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14), 0.0, 1.0); 28 | } 29 | `; 30 | 31 | const SKYBOX_VERTEX = ` 32 | varying vec3 v_WSPosition; 33 | 34 | void main() { 35 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 36 | v_WSPosition = position; 37 | } 38 | `; 39 | 40 | const SKYBOX_FRAGMENT = ` 41 | uniform samplerCube u_EnvironmentTexture; 42 | varying vec3 v_WSPosition; 43 | 44 | ${ACES} 45 | 46 | void main() { 47 | vec3 image = textureCube(u_EnvironmentTexture, v_WSPosition).xyz; 48 | gl_FragColor.xyz = image * image; 49 | 50 | // Tone mapping 51 | gl_FragColor.xyz = acesToneMapping(gl_FragColor.xyz); 52 | 53 | // Gamma correct 54 | gl_FragColor.xyz = sqrt(gl_FragColor.xyz); 55 | 56 | gl_FragColor.w = 1.0; 57 | } 58 | `; 59 | 60 | const OBJECT_VERTEX = ` 61 | varying vec3 v_WSPosition; 62 | varying vec3 v_WSReflect; 63 | varying vec3 v_WSNormal; 64 | 65 | void main() { 66 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 67 | v_WSPosition = position; 68 | 69 | v_WSNormal = normal; 70 | 71 | vec3 ws_position_rel = normalize(v_WSPosition.xyz - cameraPosition); 72 | v_WSReflect = reflect(ws_position_rel, v_WSNormal); 73 | } 74 | `; 75 | 76 | const OBJECT_FRAGMENT = ` 77 | #define PI 3.141592654 78 | uniform samplerCube u_EnvironmentTexture; 79 | varying vec3 v_WSPosition; 80 | varying vec3 v_WSReflect; 81 | varying vec3 v_WSNormal; 82 | 83 | ${ACES} 84 | 85 | void main() { 86 | vec3 ws_light_dir = normalize(vec3(0.3, 1.0, 0.3)); 87 | 88 | // Blinn-Phong power 89 | float t = fract(dot(v_WSPosition, vec3(1.0, 1.0, 0.0)) * 0.01); 90 | t = abs(t - 0.5); 91 | float power = t < 0.1 ? 256.0 : 92 | t < 0.4 ? 64.0 : 93 | 16.0; 94 | 95 | gl_FragColor.xyz = vec3(0.0); 96 | 97 | // Puctual light 98 | vec3 ws_normal = normalize(v_WSNormal); 99 | float dot_nl = dot(ws_normal, ws_light_dir); 100 | if (dot_nl > 0.0) { 101 | float sp_brdf = (power + 2.0) / (2.0 * PI) * 102 | pow(max(dot(ws_light_dir, normalize(v_WSReflect)), 0.0), power) * dot_nl; 103 | gl_FragColor.xyz = vec3(sp_brdf); 104 | } 105 | 106 | // IBL 107 | float spec_sigma = 1.0 / sqrt(power); 108 | float image_lod = log2(spec_sigma / ${MIN_SIGMA}) - 1.0; // FIXME: What is the source of this bias? 109 | vec3 image = textureCubeLodEXT(u_EnvironmentTexture, v_WSReflect, image_lod).xyz; 110 | gl_FragColor.xyz += image * image; 111 | 112 | // Tone mapping 113 | gl_FragColor.xyz = acesToneMapping(gl_FragColor.xyz); 114 | 115 | // Gamma correct 116 | gl_FragColor.xyz = sqrt(gl_FragColor.xyz); 117 | 118 | gl_FragColor.w = 1.0; 119 | } 120 | `; 121 | 122 | export class ViewportPersistent { 123 | readonly renderer = new THREE.WebGLRenderer(); 124 | readonly scene = new THREE.Scene(); 125 | readonly camera = new THREE.PerspectiveCamera(40, 1, 10, 100000); 126 | readonly material: THREE.ShaderMaterial; 127 | readonly skyboxMaterial: THREE.ShaderMaterial; 128 | 129 | readonly sphereObject: THREE.Mesh; 130 | readonly teapotObject: THREE.Mesh; 131 | 132 | sceneState: SceneState | null = null; 133 | 134 | constructor() 135 | { 136 | const sphereGeometry = new THREE.SphereBufferGeometry(100, 128, 64); 137 | const teapotGeometry = new (three as any).TeapotBufferGeometry(70, 16); 138 | const material = this.material = new THREE.ShaderMaterial({ 139 | uniforms: { 140 | u_EnvironmentTexture: { 141 | type: 't', value: 0, 142 | }, 143 | }, 144 | vertexShader: OBJECT_VERTEX, 145 | fragmentShader: OBJECT_FRAGMENT, 146 | }); 147 | material.extensions.shaderTextureLOD = true; 148 | 149 | const skyboxMaterial = this.skyboxMaterial = new THREE.ShaderMaterial({ 150 | vertexShader: SKYBOX_VERTEX, 151 | fragmentShader: SKYBOX_FRAGMENT, 152 | uniforms: { 153 | u_EnvironmentTexture: { 154 | type: 't', value: 0, 155 | }, 156 | }, 157 | depthWrite: false, 158 | side: THREE.BackSide, 159 | }); 160 | 161 | const skybox = new THREE.Mesh(new THREE.BoxBufferGeometry(40000, 40000, 40000), skyboxMaterial); 162 | this.scene.add(skybox); 163 | 164 | const sphereObject = this.sphereObject = new THREE.Mesh(sphereGeometry, material); 165 | this.scene.add(sphereObject); 166 | 167 | const teapotObject = this.teapotObject = new THREE.Mesh(teapotGeometry, material); 168 | this.scene.add(teapotObject); 169 | 170 | this.camera.position.set(0, 0, 400); 171 | // webpack does not know the existence of `THREE.OrbitControls` 172 | const controls = new three.OrbitControls(this.camera, this.canvas); 173 | controls.minDistance = 200; 174 | controls.maxDistance = 1000; 175 | 176 | this.renderer.autoClear = false; 177 | 178 | this.update(); 179 | } 180 | 181 | get canvas(): HTMLCanvasElement { return this.renderer.domElement; } 182 | 183 | @bind 184 | private update(): void 185 | { 186 | requestAnimationFrame(this.update); 187 | 188 | if (!this.canvas.parentElement || !this.sceneState) { 189 | return; 190 | } 191 | 192 | this.sphereObject.visible = this.sceneState.geometry === 'sphere'; 193 | this.teapotObject.visible = this.sceneState.geometry === 'teapot'; 194 | 195 | const bounds = this.canvas.parentElement!.getBoundingClientRect(); 196 | const newWidth = Math.max(1, bounds.width) | 0; 197 | const newHeight = Math.max(1, bounds.height) | 0; 198 | this.renderer.setSize(newWidth, newHeight); 199 | this.renderer.setPixelRatio(window.devicePixelRatio); 200 | 201 | this.camera.aspect = newWidth / newHeight; 202 | this.camera.updateProjectionMatrix(); 203 | 204 | this.renderer.render(this.scene, this.camera); 205 | } 206 | 207 | setTexture(texture: THREE.Texture): void 208 | { 209 | const prev = this.skyboxMaterial.uniforms['u_EnvironmentTexture'].value; 210 | if (prev) { 211 | prev.dispose(); 212 | } 213 | 214 | this.skyboxMaterial.uniforms['u_EnvironmentTexture'].value = texture; 215 | this.material.uniforms['u_EnvironmentTexture'].value = texture; 216 | // this.material.uniforms[] 217 | } 218 | } 219 | 220 | export interface ViewportProps { 221 | persistent: ViewportPersistent; 222 | viewerState: ViewerState; 223 | sceneState: SceneState; 224 | } 225 | 226 | interface State { 227 | } 228 | 229 | export class Viewport extends React.Component { 230 | private currentViewerState: ViewerState | null = null; 231 | 232 | private async update(): Promise { 233 | const {viewerState, sceneState} = this.props; 234 | 235 | this.props.persistent.sceneState = sceneState; 236 | 237 | if (viewerState == this.currentViewerState) { 238 | return; 239 | } 240 | this.currentViewerState = viewerState; 241 | 242 | // Are all image slots filled? 243 | for (const image of viewerState.faceImages) { 244 | if (!image) { 245 | return; 246 | } 247 | } 248 | 249 | // Create a PMREM cube map 250 | // Note: This synchronous constructor is only available when `core` is 251 | // supplied. 252 | const options: emg.LtasgOptions = { 253 | core: await emgCore, 254 | imageSize: viewerState.cubeMapSize, 255 | mipLevelSigmas: Array.from(new Array(9), (_, i) => MIN_SIGMA * 2 ** Math.min(i, 4)), 256 | minNumPasses: viewerState.minNumPasses, 257 | kernelResolution: viewerState.kernelResolution, 258 | }; 259 | console.log(options); 260 | 261 | console.time('LTASG plan'); 262 | const ltasg = new emg.LtasgBlur(options); 263 | console.timeEnd('LTASG plan'); 264 | 265 | console.time('LTASG process'); 266 | const output = ltasg.process(viewerState.faceImages as any, emg.ImageFormat.Srgbx8, emg.ImageFormat.Srgbx8); 267 | console.timeEnd('LTASG process'); 268 | 269 | // I absolutely have no idea what is the canonical way to pass a PMREM 270 | // data to three.js 271 | // Also, I'd like to use sRGB encoding, but there does not seem to be an 272 | // easy way to get it working. 273 | const baseSize = ltasg.size; 274 | const threeCubeImages = Array.from(new Array(6), (_, face) => ({ 275 | isDataTexture: true, 276 | image: { 277 | width: baseSize, 278 | height: baseSize, 279 | data: output[0][face], 280 | mipmaps: output.map((image, level) => { 281 | return { 282 | width: baseSize >> level, 283 | height: baseSize >> level, 284 | data: image[face] as Uint8Array, 285 | }; 286 | }), 287 | }, 288 | })); 289 | const texture = new THREE.DataTexture(null!, baseSize, baseSize); 290 | (texture as any).image = threeCubeImages; 291 | (texture as any).isCubeTexture = true; 292 | (texture as any).isCompressedTexture = true; // Force manual mipmap upload 293 | texture.magFilter = THREE.LinearFilter; 294 | texture.minFilter = THREE.LinearMipMapLinearFilter; 295 | texture.version = 1; 296 | 297 | this.props.persistent.setTexture(texture); 298 | } 299 | 300 | componentDidUpdate(prevProps: ViewportProps, prevState: State): void { 301 | this.update(); 302 | } 303 | 304 | render() { 305 | return ; 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "noImplicitAny": true, 5 | "sourceMap": true, 6 | "strict": true, 7 | "strictNullChecks": true, 8 | "module": "es6", 9 | "target": "es5", 10 | "jsx": "react", 11 | "moduleResolution": "Node", 12 | "lib": [ 13 | "ES5", 14 | "DOM", 15 | "ES2015.Collection", 16 | "ES2015.Promise", 17 | "ES2015.Core", 18 | ], 19 | "allowJs": true, 20 | "experimentalDecorators": true, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: './ts/main.tsx', 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.tsx?$/, 10 | use: 'ts-loader', 11 | exclude: /node_modules/ 12 | }, 13 | ] 14 | }, 15 | resolve: { 16 | extensions: [ '.tsx', '.ts', '.js' ] 17 | }, 18 | resolveLoader: { 19 | alias: { 20 | 'pieglsl-loader': path.resolve(__dirname, 'tools/pieglsl-loader.coffee'), 21 | }, 22 | }, 23 | output: { 24 | filename: '[name].bundle.js', 25 | path: path.resolve(__dirname, 'dist'), 26 | }, 27 | plugins: [ 28 | new webpack.DefinePlugin({ 29 | 'process.env': { 30 | }, 31 | }), 32 | ], 33 | }; 34 | -------------------------------------------------------------------------------- /examples/webpack.config.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | 4 | module.exports = merge(common, { 5 | devtool: 'inline-source-map', 6 | devServer: { 7 | contentBase: './dist', 8 | inline: false, 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /examples/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const merge = require('webpack-merge'); 3 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 4 | const common = require('./webpack.common.js'); 5 | 6 | module.exports = merge(common, { 7 | devtool: 'source-map', 8 | plugins: [ 9 | new UglifyJSPlugin({ 10 | sourceMap: true, 11 | }), 12 | new webpack.DefinePlugin({ 13 | 'process.env': { 14 | 'NODE_ENV': JSON.stringify('production') 15 | }, 16 | }), 17 | ], 18 | }); 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyper3d-envmapgen", 3 | "version": "0.1.1", 4 | "description": "Pre-filtered mipmapped radiance environment map generator that runs on WebAssembly.", 5 | "main": "dist/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/Hyper3D/hyper3d-envmapgen.git" 9 | }, 10 | "dependencies": { 11 | "base64-js": "^1.2.1" 12 | }, 13 | "devDependencies": { 14 | "@types/node": "^8.5.1", 15 | "@types/react": "^16.0.31", 16 | "@types/react-dom": "^16.0.3", 17 | "@types/react-dropzone": "^4.1.0", 18 | "@types/three": "^0.84.35", 19 | "@types/webassembly-js-api": "0.0.1", 20 | "bind-decorator": "^1.0.11", 21 | "file-loader": "^1.1.6", 22 | "image-promise": "^5.0.1", 23 | "react": "^16.2.0", 24 | "react-dom": "^16.2.0", 25 | "react-dropzone": "^4.2.3", 26 | "three": "^0.88.0", 27 | "ts-loader": "^3.2.0", 28 | "typescript": "^2.6.2", 29 | "uglifyjs-webpack-plugin": "^1.1.4", 30 | "webpack": "^3.10.0", 31 | "webpack-dev-server": "^2.9.7", 32 | "webpack-merge": "^4.1.1" 33 | }, 34 | "scripts": { 35 | "build": "./build-wasm.sh && tsc", 36 | "build:examples": "cd examples && webpack --config webpack.prod.js", 37 | "start:examples": "cd examples && webpack-dev-server --open", 38 | "test": "echo \"Error: no test specified\" && exit 1", 39 | "prepare": "npm run build" 40 | }, 41 | "author": "yvt ", 42 | "license": "MIT", 43 | "files": [ 44 | "README.md", 45 | "LICENSE", 46 | "dist" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hyperenvmap" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | 6 | [dependencies] 7 | cgmath = "0.15.0" 8 | lazy_static = "1.0.0" 9 | 10 | [dev-dependencies] 11 | clap = "2.26.0" 12 | image = "0.17.0" 13 | -------------------------------------------------------------------------------- /rust/benches/blur.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 yvt 3 | * 4 | * This file is a part of hyper3d-envmapgen. Please read the license text that 5 | * comes with the source code for use conditions. 6 | */ 7 | #![feature(test)] 8 | extern crate test; 9 | extern crate hyperenvmap; 10 | use hyperenvmap::ltasgblur; 11 | 12 | fn run_single(b: &mut test::Bencher, size: usize, pass: usize) { 13 | let kernel = ltasgblur::gaussian_kernel(8, 4.0); 14 | let count = size * size; 15 | let src = vec![vec![0f32; count]; 6]; 16 | let mut dst = vec![vec![0f32; count]; 6]; 17 | b.iter(move || { 18 | ltasgblur::ltasg_single( 19 | dst.iter_mut() 20 | .map(Vec::as_mut_slice) 21 | .collect::>() 22 | .as_mut_slice(), 23 | src.iter().map(Vec::as_slice).collect::>().as_slice(), 24 | size, 25 | &kernel, 26 | 0.5, 27 | pass, 28 | ltasgblur::StandardCubeMapTrait, 29 | ); 30 | }) 31 | } 32 | 33 | #[bench] 34 | fn blur1_16(b: &mut test::Bencher) { 35 | run_single(b, 16, 0) 36 | } 37 | 38 | #[bench] 39 | fn blur1_32(b: &mut test::Bencher) { 40 | run_single(b, 32, 0) 41 | } 42 | 43 | #[bench] 44 | fn blur1_64(b: &mut test::Bencher) { 45 | run_single(b, 64, 0) 46 | } 47 | 48 | #[bench] 49 | fn blur1_128(b: &mut test::Bencher) { 50 | run_single(b, 128, 0) 51 | } 52 | 53 | #[bench] 54 | fn blur2_16(b: &mut test::Bencher) { 55 | run_single(b, 16, 1) 56 | } 57 | 58 | #[bench] 59 | fn blur2_32(b: &mut test::Bencher) { 60 | run_single(b, 32, 1) 61 | } 62 | 63 | #[bench] 64 | fn blur2_64(b: &mut test::Bencher) { 65 | run_single(b, 64, 1) 66 | } 67 | 68 | #[bench] 69 | fn blur2_128(b: &mut test::Bencher) { 70 | run_single(b, 128, 1) 71 | } 72 | 73 | #[bench] 74 | fn blur3_16(b: &mut test::Bencher) { 75 | run_single(b, 16, 2) 76 | } 77 | 78 | #[bench] 79 | fn blur3_32(b: &mut test::Bencher) { 80 | run_single(b, 32, 2) 81 | } 82 | 83 | #[bench] 84 | fn blur3_64(b: &mut test::Bencher) { 85 | run_single(b, 64, 2) 86 | } 87 | 88 | #[bench] 89 | fn blur3_128(b: &mut test::Bencher) { 90 | run_single(b, 128, 2) 91 | } 92 | 93 | #[bench] 94 | fn blur_mip_pyramid(b: &mut test::Bencher) { 95 | // Based on the parameters from ARcane's `context.rs` 96 | const LOG_SIZE: usize = 6; 97 | const SIZE: usize = 1 << LOG_SIZE; 98 | 99 | static BLUR_KERNEL_SIGMA: f32 = 4.0; 100 | static BLUR_KERNEL_RATIO: f32 = 2.0; 101 | 102 | let blur_table: Vec<(f32, usize)> = { 103 | let mut last_variance = 0.0; 104 | (0..5u8) 105 | .map(|i| { 106 | let size = SIZE >> i; 107 | let sigma = (i as f32 - 5.0).exp2(); 108 | 109 | // The amount of ltasgblur applied on this stage 110 | let res_sigma = (sigma * sigma - last_variance).sqrt(); 111 | last_variance = sigma * sigma; 112 | 113 | // Upper bound of ltasgblur amount that can be applied by a single run of 114 | // `ltasg_single(..., {0, 1, 2}, ...)` 115 | let sigma_limit = 0.5 / BLUR_KERNEL_RATIO; 116 | let num_passes = (res_sigma * res_sigma / (sigma_limit * sigma_limit)).ceil(); 117 | 118 | let level_sigma = (res_sigma * res_sigma / num_passes).sqrt() * size as f32 / 119 | BLUR_KERNEL_SIGMA; 120 | 121 | (level_sigma, num_passes as usize) 122 | }) 123 | .collect() 124 | }; 125 | 126 | let kernel = ltasgblur::gaussian_kernel( 127 | (BLUR_KERNEL_SIGMA * BLUR_KERNEL_RATIO) as usize, 128 | BLUR_KERNEL_SIGMA, 129 | ); 130 | 131 | let count = SIZE * SIZE; 132 | let src = vec![vec![0f32; count]; 6]; 133 | let mut dst = vec![vec![0f32; count]; 6]; 134 | b.iter(move || for (i, &(kernel_scale, num_passes)) in 135 | blur_table.iter().enumerate() 136 | { 137 | for _ in 0..num_passes { 138 | for pass in 0..3 { 139 | ltasgblur::ltasg_single( 140 | dst.iter_mut() 141 | .map(Vec::as_mut_slice) 142 | .collect::>() 143 | .as_mut_slice(), 144 | src.iter().map(Vec::as_slice).collect::>().as_slice(), 145 | SIZE >> i, 146 | &kernel, 147 | kernel_scale, 148 | pass, 149 | ltasgblur::StandardCubeMapTrait, 150 | ); 151 | } 152 | } 153 | }) 154 | } 155 | -------------------------------------------------------------------------------- /rust/examples/blurcubemap.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 yvt 3 | * 4 | * This file is a part of hyper3d-envmapgen. Please read the license text that 5 | * comes with the source code for use conditions. 6 | */ 7 | extern crate clap; 8 | extern crate image; 9 | extern crate hyperenvmap; 10 | extern crate cgmath; 11 | 12 | use std::mem::swap; 13 | use std::path::{Path, PathBuf}; 14 | use std::ffi::{OsStr, OsString}; 15 | 16 | use cgmath::Vector4; 17 | 18 | use hyperenvmap::cubemap::CubeFace; 19 | use hyperenvmap::ltasgblur; 20 | 21 | #[derive(Debug, Copy, Clone)] 22 | struct Image { 23 | pub data: T, 24 | pub width: usize, 25 | pub height: usize, 26 | } 27 | 28 | struct CubeMapPathSet { 29 | base: PathBuf, 30 | ext: OsString, 31 | } 32 | 33 | impl CubeMapPathSet { 34 | pub fn from_one(name: &Path) -> Option { 35 | let stem = name.file_stem(); 36 | if ["posx", "negx", "posy", "negy", "posz", "negz"] 37 | .iter() 38 | .all(|s| stem != Some(OsStr::new(s))) 39 | { 40 | return None; 41 | } 42 | 43 | Some(Self { 44 | base: name.parent().unwrap().to_owned(), 45 | ext: match name.extension() { 46 | Some(ext) => { 47 | let mut s = OsStr::new(".").to_owned(); 48 | s.push(ext); 49 | s 50 | } 51 | None => OsString::new(), 52 | }, 53 | }) 54 | } 55 | 56 | pub fn path(&self, face: CubeFace) -> PathBuf { 57 | let mut name = OsStr::new(match face { 58 | CubeFace::PositiveX => "posx", 59 | CubeFace::NegativeX => "negx", 60 | CubeFace::PositiveY => "posy", 61 | CubeFace::NegativeY => "negy", 62 | CubeFace::PositiveZ => "posz", 63 | CubeFace::NegativeZ => "negz", 64 | }).to_owned(); 65 | name.push(&self.ext); 66 | self.base.join(name) 67 | } 68 | 69 | pub fn paths(&self) -> [PathBuf; 6] { 70 | [ 71 | self.path(CubeFace::PositiveX), 72 | self.path(CubeFace::NegativeX), 73 | self.path(CubeFace::PositiveY), 74 | self.path(CubeFace::NegativeY), 75 | self.path(CubeFace::PositiveZ), 76 | self.path(CubeFace::NegativeZ), 77 | ] 78 | } 79 | } 80 | 81 | fn main() { 82 | use clap::{App, Arg}; 83 | let matches = App::new("blurcubemap") 84 | .author("yvt ") 85 | .arg( 86 | Arg::with_name("input") 87 | .required(true) 88 | .index(1) 89 | .value_name("INDIR") 90 | .help( 91 | "Specifies the path to a cube map. \ 92 | A cube map is composed of six image files named posx.EXT, \ 93 | negx.EXT (EXT can be anything), and so forth, and one of \ 94 | such files must be specified as the parameter.", 95 | ), 96 | ) 97 | .arg( 98 | Arg::with_name("output") 99 | .required(true) 100 | .index(2) 101 | .value_name("OUTDIR") 102 | .help( 103 | "Specifies the path to save the generated cube map. \ 104 | A cube map is composed of six image files named posx.EXT, \ 105 | negx.EXT (EXT can be anything), and so forth, and one of \ 106 | such files must be specified as the parameter.", 107 | ), 108 | ) 109 | .arg( 110 | Arg::with_name("sigma") 111 | .short("s") 112 | .long("sigma") 113 | .value_name("SIGMA") 114 | .help("Specifies the standard deviation of the blur kernel.") 115 | .takes_value(true) 116 | .default_value("0.01"), 117 | ) 118 | .arg( 119 | Arg::with_name("normalize") 120 | .short("n") 121 | .long("normalize") 122 | .help("Scale the output values to range [0, 1]"), 123 | ) 124 | .get_matches(); 125 | 126 | let input_files = CubeMapPathSet::from_one(Path::new(matches.value_of_os("input").unwrap())) 127 | .ok_or("Invalid input path — Try --help") 128 | .unwrap(); 129 | 130 | let output_files = CubeMapPathSet::from_one(Path::new(matches.value_of_os("output").unwrap())) 131 | .ok_or("Invalid output path — Try --help") 132 | .unwrap(); 133 | 134 | use std::str::FromStr; 135 | let sigma = f32::from_str(matches.value_of("sigma").unwrap()).unwrap(); 136 | 137 | let mut images: Vec<_> = input_files 138 | .paths() 139 | .iter() 140 | .map(|path| { 141 | println!("Loading {}", path.display()); 142 | let img = image::open(&path).unwrap().to_rgba(); 143 | 144 | // Convert to RGBAF32 145 | ( 146 | Image { 147 | data: img.pixels() 148 | .map(|rgba| { 149 | let rgba = Vector4::new( 150 | rgba.data[0], 151 | rgba.data[1], 152 | rgba.data[2], 153 | rgba.data[3], 154 | ).cast::(); 155 | 156 | // Linearize 157 | Vector4::new( 158 | rgba.x * rgba.x * rgba.w, 159 | rgba.y * rgba.y * rgba.w, 160 | rgba.z * rgba.z * rgba.w, 161 | rgba.w, 162 | ) 163 | }) 164 | .collect::>(), 165 | width: img.width() as usize, 166 | height: img.height() as usize, 167 | }, 168 | path.clone(), 169 | ) 170 | }) 171 | .collect(); 172 | 173 | let size = images[0].0.width; 174 | 175 | // Validate the image size 176 | for &(ref image, ref name) in images.iter() { 177 | if image.width != size || image.height != size { 178 | panic!( 179 | "Image size of '{}' is invalid — all images must be square and have the same size", 180 | name.display() 181 | ); 182 | } 183 | } 184 | 185 | // Strip path info 186 | let mut images: Vec<_> = images.drain(..).map(|(img, _)| img).collect(); 187 | 188 | // Design the filter. 189 | // Find the smallest `num_passes` such that 190 | // - `sigma1 * sigma1 * num_passes = sigma * sigma` 191 | // - `sigma1 < 1 / sqrt(3) / kernel_ratio` 192 | println!("Image size = {}", size); 193 | 194 | let kernel_ratio = 2.0f32; 195 | let kernel_upsample = 3.0f32; 196 | let sigma1_limit = 1.0 / 2.0 / kernel_ratio; 197 | let num_passes = (sigma * sigma / (sigma1_limit * sigma1_limit)).ceil() as usize; 198 | let sigma1 = (sigma * sigma / num_passes as f32).sqrt(); 199 | let sigma1_pxs = sigma1 * size as f32; 200 | let kernel_radius = (sigma1_pxs * kernel_ratio * kernel_upsample).ceil() as usize; 201 | println!("(Input) σ = {}", sigma); 202 | println!("σ₁ = {} = {}px / {}px", sigma1, sigma1_pxs, size); 203 | println!("# of Passes = {}", num_passes); 204 | println!( 205 | "Kernel Radius = {} ≈ 2σ₁ * kernel_upsample", 206 | kernel_radius 207 | ); 208 | 209 | let kernel = ltasgblur::gaussian_kernel(kernel_radius, sigma1_pxs); 210 | 211 | // Apply the filter 212 | let mut images2 = images.clone(); 213 | for i in 0..num_passes { 214 | println!("Pass {}/{}", i + 1, num_passes); 215 | for k in 0..3 { 216 | println!(" Phase {}... ", k + 1); 217 | 218 | { 219 | let mut out_faces: Vec<_> = images2.iter_mut().map(|i| &mut i.data[..]).collect(); 220 | let in_faces: Vec<_> = images.iter().map(|i| &i.data[..]).collect(); 221 | ltasgblur::ltasg_single( 222 | &mut out_faces, 223 | &in_faces, 224 | size, 225 | &kernel, 226 | 1.0 / kernel_upsample, 227 | k, 228 | ltasgblur::StandardCubeMapTrait, 229 | ); 230 | } 231 | 232 | swap(&mut images, &mut images2); 233 | } 234 | } 235 | 236 | if matches.is_present("normalize") { 237 | let max_value = images 238 | .iter() 239 | .map(|image| { 240 | image 241 | .data 242 | .iter() 243 | .map(|pixel| { 244 | [ 245 | pixel[0] / pixel[3], 246 | pixel[1] / pixel[3], 247 | pixel[2] / pixel[3], 248 | ].iter() 249 | .fold(0.0f32, |x, y| x.max(*y)) 250 | }) 251 | .fold(0.0f32, |x, y| x.max(y)) 252 | }) 253 | .fold(0.0f32, |x, y| x.max(y)); 254 | println!("Maximum value = {}", max_value); 255 | for image in images.iter_mut() { 256 | for x in image.data.iter_mut() { 257 | x[0] *= (255.0 * 255.0) / max_value; 258 | x[1] *= (255.0 * 255.0) / max_value; 259 | x[2] *= (255.0 * 255.0) / max_value; 260 | } 261 | } 262 | } 263 | 264 | // Output the processed images 265 | let mut img = image::RgbaImage::new(size as u32, size as u32); 266 | for (image, path) in images.iter().zip(output_files.paths().iter()) { 267 | for (y, x) in img.pixels_mut().zip(image.data.iter()) { 268 | let rgba = *x; 269 | 270 | // De-linearize, convert to straight alpha, and round 271 | let rgba = Vector4::new( 272 | ((rgba.x / rgba.w).sqrt()).round().min(255.0), 273 | ((rgba.y / rgba.w).sqrt()).round().min(255.0), 274 | ((rgba.z / rgba.w).sqrt()).round().min(255.0), 275 | (rgba.w).round().min(255.0), 276 | ).cast::(); 277 | 278 | y.data[0] = rgba.x; 279 | y.data[1] = rgba.y; 280 | y.data[2] = rgba.z; 281 | y.data[3] = rgba.w; 282 | } 283 | println!("Saving {}", path.display()); 284 | img.save(path).unwrap(); 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /rust/examples/images/negx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hyper3D/hyper3d-envmapgen/ca8ad7e2f6b91d3577cfdd195ccdd88b9f56db6c/rust/examples/images/negx.jpg -------------------------------------------------------------------------------- /rust/examples/images/negy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hyper3D/hyper3d-envmapgen/ca8ad7e2f6b91d3577cfdd195ccdd88b9f56db6c/rust/examples/images/negy.jpg -------------------------------------------------------------------------------- /rust/examples/images/negz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hyper3D/hyper3d-envmapgen/ca8ad7e2f6b91d3577cfdd195ccdd88b9f56db6c/rust/examples/images/negz.jpg -------------------------------------------------------------------------------- /rust/examples/images/posx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hyper3D/hyper3d-envmapgen/ca8ad7e2f6b91d3577cfdd195ccdd88b9f56db6c/rust/examples/images/posx.jpg -------------------------------------------------------------------------------- /rust/examples/images/posy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hyper3D/hyper3d-envmapgen/ca8ad7e2f6b91d3577cfdd195ccdd88b9f56db6c/rust/examples/images/posy.jpg -------------------------------------------------------------------------------- /rust/examples/images/posz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hyper3D/hyper3d-envmapgen/ca8ad7e2f6b91d3577cfdd195ccdd88b9f56db6c/rust/examples/images/posz.jpg -------------------------------------------------------------------------------- /rust/examples/images/readme.txt: -------------------------------------------------------------------------------- 1 | Author 2 | ====== 3 | 4 | This is the work of Emil Persson, aka Humus. 5 | http://www.humus.name 6 | humus@comhem.se 7 | 8 | 9 | 10 | Legal stuff 11 | =========== 12 | 13 | This work is free and may be used by anyone for any purpose 14 | and may be distributed freely to anyone using any distribution 15 | media or distribution method as long as this file is included. 16 | Distribution without this file is allowed if it's distributed 17 | with free non-commercial software; however, fair credit of the 18 | original author is expected. 19 | Any commercial distribution of this software requires the written 20 | approval of Emil Persson. 21 | -------------------------------------------------------------------------------- /rust/examples/impulses/negx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hyper3D/hyper3d-envmapgen/ca8ad7e2f6b91d3577cfdd195ccdd88b9f56db6c/rust/examples/impulses/negx.png -------------------------------------------------------------------------------- /rust/examples/impulses/negy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hyper3D/hyper3d-envmapgen/ca8ad7e2f6b91d3577cfdd195ccdd88b9f56db6c/rust/examples/impulses/negy.png -------------------------------------------------------------------------------- /rust/examples/impulses/negz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hyper3D/hyper3d-envmapgen/ca8ad7e2f6b91d3577cfdd195ccdd88b9f56db6c/rust/examples/impulses/negz.png -------------------------------------------------------------------------------- /rust/examples/impulses/posx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hyper3D/hyper3d-envmapgen/ca8ad7e2f6b91d3577cfdd195ccdd88b9f56db6c/rust/examples/impulses/posx.png -------------------------------------------------------------------------------- /rust/examples/impulses/posy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hyper3D/hyper3d-envmapgen/ca8ad7e2f6b91d3577cfdd195ccdd88b9f56db6c/rust/examples/impulses/posy.png -------------------------------------------------------------------------------- /rust/examples/impulses/posz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hyper3D/hyper3d-envmapgen/ca8ad7e2f6b91d3577cfdd195ccdd88b9f56db6c/rust/examples/impulses/posz.png -------------------------------------------------------------------------------- /rust/src/accessor.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 yvt 3 | * 4 | * This file is a part of hyper3d-envmapgen. Please read the license text that 5 | * comes with the source code for use conditions. 6 | */ 7 | //! Defines the `SliceAccessor` type that can be used to bypass bounds checking 8 | //! on the release builds. 9 | 10 | use std::{ops, convert}; 11 | 12 | #[cfg(not(debug_assertions))] 13 | use std::slice; 14 | 15 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] 16 | pub struct SliceAccessor { 17 | slice: T, 18 | } 19 | 20 | impl SliceAccessor { 21 | pub unsafe fn new(x: T) -> Self { 22 | SliceAccessor { slice: x } 23 | } 24 | } 25 | 26 | impl<'a, T> convert::Into> for SliceAccessor<&'a mut [T]> { 27 | fn into(self) -> SliceAccessor<&'a [T]> { 28 | unsafe { SliceAccessor::new(self.slice) } 29 | } 30 | } 31 | 32 | impl ops::Deref for SliceAccessor { 33 | type Target = T; 34 | fn deref(&self) -> &Self::Target { 35 | &self.slice 36 | } 37 | } 38 | 39 | impl ops::DerefMut for SliceAccessor { 40 | fn deref_mut(&mut self) -> &mut Self::Target { 41 | &mut self.slice 42 | } 43 | } 44 | 45 | #[cfg(not(debug_assertions))] 46 | impl<'a, T, I> ops::Index for SliceAccessor<&'a [T]> 47 | where 48 | I: slice::SliceIndex<[T]>, 49 | { 50 | type Output = I::Output; 51 | fn index(&self, index: I) -> &Self::Output { 52 | unsafe { self.slice.get_unchecked(index) } 53 | } 54 | } 55 | 56 | #[cfg(not(debug_assertions))] 57 | impl<'a, T, I> ops::Index for SliceAccessor<&'a Vec> 58 | where 59 | I: slice::SliceIndex<[T]>, 60 | { 61 | type Output = I::Output; 62 | fn index(&self, index: I) -> &Self::Output { 63 | unsafe { self.slice.get_unchecked(index) } 64 | } 65 | } 66 | 67 | #[cfg(not(debug_assertions))] 68 | impl<'a, T, I> ops::Index for SliceAccessor<&'a mut [T]> 69 | where 70 | I: slice::SliceIndex<[T]>, 71 | { 72 | type Output = I::Output; 73 | fn index(&self, index: I) -> &Self::Output { 74 | unsafe { self.slice.get_unchecked(index) } 75 | } 76 | } 77 | 78 | #[cfg(not(debug_assertions))] 79 | impl<'a, T, I> ops::IndexMut for SliceAccessor<&'a mut [T]> 80 | where 81 | I: slice::SliceIndex<[T]>, 82 | { 83 | fn index_mut(&mut self, index: I) -> &mut Self::Output { 84 | unsafe { self.slice.get_unchecked_mut(index) } 85 | } 86 | } 87 | 88 | #[cfg(not(debug_assertions))] 89 | impl<'a, T, I> ops::Index for SliceAccessor<&'a mut Vec> 90 | where 91 | I: slice::SliceIndex<[T]>, 92 | { 93 | type Output = I::Output; 94 | fn index(&self, index: I) -> &Self::Output { 95 | unsafe { self.slice.get_unchecked(index) } 96 | } 97 | } 98 | 99 | #[cfg(not(debug_assertions))] 100 | impl<'a, T, I> ops::IndexMut for SliceAccessor<&'a mut Vec> 101 | where 102 | I: slice::SliceIndex<[T]>, 103 | { 104 | fn index_mut(&mut self, index: I) -> &mut Self::Output { 105 | unsafe { self.slice.get_unchecked_mut(index) } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /rust/src/cubemap.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 yvt 3 | * 4 | * This file is a part of hyper3d-envmapgen. Please read the license text that 5 | * comes with the source code for use conditions. 6 | */ 7 | //! Provides cube map face definitions. 8 | use std::ops; 9 | use cgmath::{Vector3, Matrix4}; 10 | use cgmath::num_traits::NumCast; 11 | use cgmath::prelude::*; 12 | 13 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 14 | #[repr(u8)] 15 | pub enum CubeFace { 16 | PositiveX = 0, 17 | NegativeX = 1, 18 | PositiveY = 2, 19 | NegativeY = 3, 20 | PositiveZ = 4, 21 | NegativeZ = 5, 22 | } 23 | 24 | pub static CUBE_FACES: [CubeFace; 6] = [ 25 | CubeFace::PositiveX, 26 | CubeFace::NegativeX, 27 | CubeFace::PositiveY, 28 | CubeFace::NegativeY, 29 | CubeFace::PositiveZ, 30 | CubeFace::NegativeZ, 31 | ]; 32 | 33 | impl CubeFace { 34 | pub unsafe fn from_ordinal_unchecked(i: usize) -> CubeFace { 35 | use std::mem::transmute; 36 | transmute(i as u8) 37 | } 38 | 39 | pub fn from_ordinal(i: usize) -> Option { 40 | if i < 6 { 41 | Some(unsafe { Self::from_ordinal_unchecked(i) }) 42 | } else { 43 | None 44 | } 45 | } 46 | 47 | pub fn as_ordinal(&self) -> usize { 48 | (*self) as usize 49 | } 50 | 51 | pub fn u_face(&self) -> CubeFace { 52 | match self { 53 | &CubeFace::PositiveX => CubeFace::NegativeZ, 54 | &CubeFace::NegativeX => CubeFace::PositiveZ, 55 | &CubeFace::PositiveY => CubeFace::PositiveX, 56 | &CubeFace::NegativeY => CubeFace::PositiveX, 57 | &CubeFace::PositiveZ => CubeFace::PositiveX, 58 | &CubeFace::NegativeZ => CubeFace::NegativeX, 59 | } 60 | } 61 | 62 | pub fn v_face(&self) -> CubeFace { 63 | match self { 64 | &CubeFace::PositiveX => CubeFace::NegativeY, 65 | &CubeFace::NegativeX => CubeFace::NegativeY, 66 | &CubeFace::PositiveY => CubeFace::PositiveZ, 67 | &CubeFace::NegativeY => CubeFace::NegativeZ, 68 | &CubeFace::PositiveZ => CubeFace::NegativeY, 69 | &CubeFace::NegativeZ => CubeFace::NegativeY, 70 | } 71 | } 72 | 73 | pub fn u_vec(&self) -> Vector3 { 74 | self.u_face().normal() 75 | } 76 | 77 | pub fn v_vec(&self) -> Vector3 { 78 | self.v_face().normal() 79 | } 80 | 81 | pub fn normal(&self) -> Vector3 { 82 | match self { 83 | &CubeFace::PositiveX => Vector3::new(1, 0, 0), 84 | &CubeFace::NegativeX => Vector3::new(-1, 0, 0), 85 | &CubeFace::PositiveY => Vector3::new(0, 1, 0), 86 | &CubeFace::NegativeY => Vector3::new(0, -1, 0), 87 | &CubeFace::PositiveZ => Vector3::new(0, 0, 1), 88 | &CubeFace::NegativeZ => Vector3::new(0, 0, -1), 89 | }.cast() 90 | } 91 | 92 | pub fn info(&self) -> &'static CubeFaceInfo { 93 | &CUBE_FACE_INFOS[*self as usize] 94 | } 95 | 96 | pub fn abs(&self) -> Self { 97 | unsafe { 98 | Self::from_ordinal_unchecked(self.as_ordinal() & !1) 99 | } 100 | } 101 | } 102 | 103 | impl ops::Neg for CubeFace { 104 | type Output = Self; 105 | fn neg(self) -> Self { 106 | unsafe { 107 | Self::from_ordinal_unchecked(self.as_ordinal() ^ 1) 108 | } 109 | } 110 | } 111 | 112 | pub struct CubeFaceInfo { 113 | pub view_proj_mat: Matrix4, 114 | pub inv_view_proj_mat: Matrix4, 115 | } 116 | 117 | lazy_static! { 118 | pub static ref CUBE_FACE_INFOS: Vec = CUBE_FACES.iter() 119 | .map(|face| { 120 | let u = face.u_vec(); 121 | let v = face.v_vec(); 122 | let n = face.normal(); 123 | let view_proj_mat = Matrix4::new( 124 | u[0], v[0], 0.0, n[0], 125 | u[1], v[1], 0.0, n[1], 126 | u[2], v[2], 0.0, n[2], 127 | 0.0, 0.0, 1.0, 0.0, 128 | ); 129 | let inv_view_proj_mat = view_proj_mat.invert().unwrap(); 130 | CubeFaceInfo { 131 | view_proj_mat, 132 | inv_view_proj_mat, 133 | } 134 | }) 135 | .collect(); 136 | } 137 | -------------------------------------------------------------------------------- /rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 yvt 3 | * 4 | * This file is a part of hyper3d-envmapgen. Please read the license text that 5 | * comes with the source code for use conditions. 6 | */ 7 | #![cfg_attr(not(debug_assertions), feature(slice_get_slice))] 8 | extern crate cgmath; 9 | #[macro_use] 10 | extern crate lazy_static; 11 | 12 | mod accessor; 13 | pub mod ltasgblur; 14 | pub mod cubemap; 15 | -------------------------------------------------------------------------------- /rust/src/rand/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /rust/src/rand/.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | before_script: 4 | - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH 5 | 6 | matrix: 7 | include: 8 | - rust: 1.15.0 9 | - rust: stable 10 | - rust: stable 11 | os: osx 12 | - rust: beta 13 | - rust: nightly 14 | script: 15 | - cargo test 16 | - cargo test --features nightly 17 | - cargo test --manifest-path rand-derive/Cargo.toml 18 | - cargo doc --no-deps --features nightly 19 | script: 20 | - cargo test 21 | - cargo test --manifest-path rand-derive/Cargo.toml 22 | after_success: 23 | - travis-cargo --only nightly doc-upload 24 | env: 25 | global: 26 | secure: "BdDntVHSompN+Qxz5Rz45VI4ZqhD72r6aPl166FADlnkIwS6N6FLWdqs51O7G5CpoMXEDvyYrjmRMZe/GYLIG9cmqmn/wUrWPO+PauGiIuG/D2dmfuUNvSTRcIe7UQLXrfP3yyfZPgqsH6pSnNEVopquQKy3KjzqepgriOJtbyY=" 27 | 28 | notifications: 29 | email: 30 | on_success: never 31 | -------------------------------------------------------------------------------- /rust/src/rand/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rand" 3 | version = "0.3.18" 4 | authors = ["The Rust Project Developers"] 5 | license = "MIT/Apache-2.0" 6 | readme = "README.md" 7 | repository = "https://github.com/rust-lang-nursery/rand" 8 | documentation = "https://docs.rs/rand" 9 | homepage = "https://github.com/rust-lang-nursery/rand" 10 | description = """ 11 | Random number generators and other randomness functionality. 12 | """ 13 | keywords = ["random", "rng"] 14 | categories = ["algorithms"] 15 | 16 | [features] 17 | i128_support = [] 18 | nightly = ["i128_support"] 19 | 20 | [dependencies] 21 | libc = "0.2" 22 | 23 | [dev-dependencies] 24 | log = "0.3.0" 25 | 26 | [workspace] 27 | members = ["rand-derive"] 28 | 29 | [target.'cfg(target_os = "fuchsia")'.dependencies] 30 | fuchsia-zircon = "0.3" 31 | -------------------------------------------------------------------------------- /rust/src/rand/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /rust/src/rand/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 The Rust Project Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /rust/src/rand/README.md: -------------------------------------------------------------------------------- 1 | # `rand` 2 | 3 | A stripped copy of https://github.com/rust-lang-nursery/rand at the commit [`821acdf`] 4 | with an OS facade for the wasm32 target. 5 | 6 | [`821acdf`]: https://github.com/rust-lang-nursery/rand/commit/821acdf6152c619d2e803c068b4ec9646ea3351d 7 | 8 | rand 9 | ==== 10 | 11 | A Rust library for random number generators and other randomness functionality. 12 | 13 | [![Build Status](https://travis-ci.org/rust-lang-nursery/rand.svg?branch=master)](https://travis-ci.org/rust-lang-nursery/rand) 14 | [![Build status](https://ci.appveyor.com/api/projects/status/rm5c9o33k3jhchbw?svg=true)](https://ci.appveyor.com/project/alexcrichton/rand) 15 | 16 | [Documentation](https://docs.rs/rand) 17 | 18 | ## Usage 19 | 20 | Add this to your `Cargo.toml`: 21 | 22 | ```toml 23 | [dependencies] 24 | rand = "0.3" 25 | ``` 26 | 27 | and this to your crate root: 28 | 29 | ```rust 30 | extern crate rand; 31 | ``` 32 | 33 | ## Examples 34 | 35 | There is built-in support for a random number generator (RNG) associated with each thread stored in thread-local storage. This RNG can be accessed via thread_rng, or used implicitly via random. This RNG is normally randomly seeded from an operating-system source of randomness, e.g. /dev/urandom on Unix systems, and will automatically reseed itself from this source after generating 32 KiB of random data. 36 | 37 | ```rust 38 | let tuple = rand::random::<(f64, char)>(); 39 | println!("{:?}", tuple) 40 | ``` 41 | 42 | ```rust 43 | use rand::Rng; 44 | 45 | let mut rng = rand::thread_rng(); 46 | if rng.gen() { // random bool 47 | println!("i32: {}, u32: {}", rng.gen::(), rng.gen::()) 48 | } 49 | ``` 50 | 51 | It is also possible to use other RNG types, which have a similar interface. The following uses the "ChaCha" algorithm instead of the default. 52 | 53 | ```rust 54 | use rand::{Rng, ChaChaRng}; 55 | 56 | let mut rng = rand::ChaChaRng::new_unseeded(); 57 | println!("i32: {}, u32: {}", rng.gen::(), rng.gen::()) 58 | ``` 59 | 60 | # `derive(Rand)` 61 | 62 | You can derive the `Rand` trait for your custom type via the `#[derive(Rand)]` 63 | directive. To use this first add this to your Cargo.toml: 64 | 65 | ```toml 66 | rand = "0.3" 67 | rand_derive = "0.3" 68 | ``` 69 | 70 | Next in your crate: 71 | 72 | ```rust 73 | extern crate rand; 74 | #[macro_use] 75 | extern crate rand_derive; 76 | 77 | #[derive(Rand, Debug)] 78 | struct MyStruct { 79 | a: i32, 80 | b: u32, 81 | } 82 | 83 | fn main() { 84 | println!("{:?}", rand::random::()); 85 | } 86 | ``` 87 | 88 | 89 | # License 90 | 91 | `rand` is primarily distributed under the terms of both the MIT 92 | license and the Apache License (Version 2.0). 93 | 94 | See LICENSE-APACHE, and LICENSE-MIT for details. 95 | -------------------------------------------------------------------------------- /rust/src/rand/src/chacha.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! The ChaCha random number generator. 12 | 13 | use std::num::Wrapping as w; 14 | use {Rng, SeedableRng, Rand, w32}; 15 | 16 | const KEY_WORDS : usize = 8; // 8 words for the 256-bit key 17 | const STATE_WORDS : usize = 16; 18 | const CHACHA_ROUNDS: u32 = 20; // Cryptographically secure from 8 upwards as of this writing 19 | 20 | /// A random number generator that uses the ChaCha20 algorithm [1]. 21 | /// 22 | /// The ChaCha algorithm is widely accepted as suitable for 23 | /// cryptographic purposes, but this implementation has not been 24 | /// verified as such. Prefer a generator like `OsRng` that defers to 25 | /// the operating system for cases that need high security. 26 | /// 27 | /// [1]: D. J. Bernstein, [*ChaCha, a variant of 28 | /// Salsa20*](http://cr.yp.to/chacha.html) 29 | #[derive(Copy, Clone, Debug)] 30 | pub struct ChaChaRng { 31 | buffer: [w32; STATE_WORDS], // Internal buffer of output 32 | state: [w32; STATE_WORDS], // Initial state 33 | index: usize, // Index into state 34 | } 35 | 36 | static EMPTY: ChaChaRng = ChaChaRng { 37 | buffer: [w(0); STATE_WORDS], 38 | state: [w(0); STATE_WORDS], 39 | index: STATE_WORDS 40 | }; 41 | 42 | 43 | macro_rules! quarter_round{ 44 | ($a: expr, $b: expr, $c: expr, $d: expr) => {{ 45 | $a = $a + $b; $d = $d ^ $a; $d = w($d.0.rotate_left(16)); 46 | $c = $c + $d; $b = $b ^ $c; $b = w($b.0.rotate_left(12)); 47 | $a = $a + $b; $d = $d ^ $a; $d = w($d.0.rotate_left( 8)); 48 | $c = $c + $d; $b = $b ^ $c; $b = w($b.0.rotate_left( 7)); 49 | }} 50 | } 51 | 52 | macro_rules! double_round{ 53 | ($x: expr) => {{ 54 | // Column round 55 | quarter_round!($x[ 0], $x[ 4], $x[ 8], $x[12]); 56 | quarter_round!($x[ 1], $x[ 5], $x[ 9], $x[13]); 57 | quarter_round!($x[ 2], $x[ 6], $x[10], $x[14]); 58 | quarter_round!($x[ 3], $x[ 7], $x[11], $x[15]); 59 | // Diagonal round 60 | quarter_round!($x[ 0], $x[ 5], $x[10], $x[15]); 61 | quarter_round!($x[ 1], $x[ 6], $x[11], $x[12]); 62 | quarter_round!($x[ 2], $x[ 7], $x[ 8], $x[13]); 63 | quarter_round!($x[ 3], $x[ 4], $x[ 9], $x[14]); 64 | }} 65 | } 66 | 67 | #[inline] 68 | fn core(output: &mut [w32; STATE_WORDS], input: &[w32; STATE_WORDS]) { 69 | *output = *input; 70 | 71 | for _ in 0..CHACHA_ROUNDS / 2 { 72 | double_round!(output); 73 | } 74 | 75 | for i in 0..STATE_WORDS { 76 | output[i] = output[i] + input[i]; 77 | } 78 | } 79 | 80 | impl ChaChaRng { 81 | 82 | /// Create an ChaCha random number generator using the default 83 | /// fixed key of 8 zero words. 84 | /// 85 | /// # Examples 86 | /// 87 | /// ```rust 88 | /// use rand::{Rng, ChaChaRng}; 89 | /// 90 | /// let mut ra = ChaChaRng::new_unseeded(); 91 | /// println!("{:?}", ra.next_u32()); 92 | /// println!("{:?}", ra.next_u32()); 93 | /// ``` 94 | /// 95 | /// Since this equivalent to a RNG with a fixed seed, repeated executions 96 | /// of an unseeded RNG will produce the same result. This code sample will 97 | /// consistently produce: 98 | /// 99 | /// - 2917185654 100 | /// - 2419978656 101 | pub fn new_unseeded() -> ChaChaRng { 102 | let mut rng = EMPTY; 103 | rng.init(&[0; KEY_WORDS]); 104 | rng 105 | } 106 | 107 | /// Sets the internal 128-bit ChaCha counter to 108 | /// a user-provided value. This permits jumping 109 | /// arbitrarily ahead (or backwards) in the pseudorandom stream. 110 | /// 111 | /// Since the nonce words are used to extend the counter to 128 bits, 112 | /// users wishing to obtain the conventional ChaCha pseudorandom stream 113 | /// associated with a particular nonce can call this function with 114 | /// arguments `0, desired_nonce`. 115 | /// 116 | /// # Examples 117 | /// 118 | /// ```rust 119 | /// use rand::{Rng, ChaChaRng}; 120 | /// 121 | /// let mut ra = ChaChaRng::new_unseeded(); 122 | /// ra.set_counter(0u64, 1234567890u64); 123 | /// println!("{:?}", ra.next_u32()); 124 | /// println!("{:?}", ra.next_u32()); 125 | /// ``` 126 | pub fn set_counter(&mut self, counter_low: u64, counter_high: u64) { 127 | self.state[12] = w((counter_low >> 0) as u32); 128 | self.state[13] = w((counter_low >> 32) as u32); 129 | self.state[14] = w((counter_high >> 0) as u32); 130 | self.state[15] = w((counter_high >> 32) as u32); 131 | self.index = STATE_WORDS; // force recomputation 132 | } 133 | 134 | /// Initializes `self.state` with the appropriate key and constants 135 | /// 136 | /// We deviate slightly from the ChaCha specification regarding 137 | /// the nonce, which is used to extend the counter to 128 bits. 138 | /// This is provably as strong as the original cipher, though, 139 | /// since any distinguishing attack on our variant also works 140 | /// against ChaCha with a chosen-nonce. See the XSalsa20 [1] 141 | /// security proof for a more involved example of this. 142 | /// 143 | /// The modified word layout is: 144 | /// ```text 145 | /// constant constant constant constant 146 | /// key key key key 147 | /// key key key key 148 | /// counter counter counter counter 149 | /// ``` 150 | /// [1]: Daniel J. Bernstein. [*Extending the Salsa20 151 | /// nonce.*](http://cr.yp.to/papers.html#xsalsa) 152 | fn init(&mut self, key: &[u32; KEY_WORDS]) { 153 | self.state[0] = w(0x61707865); 154 | self.state[1] = w(0x3320646E); 155 | self.state[2] = w(0x79622D32); 156 | self.state[3] = w(0x6B206574); 157 | 158 | for i in 0..KEY_WORDS { 159 | self.state[4+i] = w(key[i]); 160 | } 161 | 162 | self.state[12] = w(0); 163 | self.state[13] = w(0); 164 | self.state[14] = w(0); 165 | self.state[15] = w(0); 166 | 167 | self.index = STATE_WORDS; 168 | } 169 | 170 | /// Refill the internal output buffer (`self.buffer`) 171 | fn update(&mut self) { 172 | core(&mut self.buffer, &self.state); 173 | self.index = 0; 174 | // update 128-bit counter 175 | self.state[12] = self.state[12] + w(1); 176 | if self.state[12] != w(0) { return }; 177 | self.state[13] = self.state[13] + w(1); 178 | if self.state[13] != w(0) { return }; 179 | self.state[14] = self.state[14] + w(1); 180 | if self.state[14] != w(0) { return }; 181 | self.state[15] = self.state[15] + w(1); 182 | } 183 | } 184 | 185 | impl Rng for ChaChaRng { 186 | #[inline] 187 | fn next_u32(&mut self) -> u32 { 188 | if self.index == STATE_WORDS { 189 | self.update(); 190 | } 191 | 192 | let value = self.buffer[self.index % STATE_WORDS]; 193 | self.index += 1; 194 | value.0 195 | } 196 | } 197 | 198 | impl<'a> SeedableRng<&'a [u32]> for ChaChaRng { 199 | 200 | fn reseed(&mut self, seed: &'a [u32]) { 201 | // reset state 202 | self.init(&[0u32; KEY_WORDS]); 203 | // set key in place 204 | let key = &mut self.state[4 .. 4+KEY_WORDS]; 205 | for (k, s) in key.iter_mut().zip(seed.iter()) { 206 | *k = w(*s); 207 | } 208 | } 209 | 210 | /// Create a ChaCha generator from a seed, 211 | /// obtained from a variable-length u32 array. 212 | /// Only up to 8 words are used; if less than 8 213 | /// words are used, the remaining are set to zero. 214 | fn from_seed(seed: &'a [u32]) -> ChaChaRng { 215 | let mut rng = EMPTY; 216 | rng.reseed(seed); 217 | rng 218 | } 219 | } 220 | 221 | impl Rand for ChaChaRng { 222 | fn rand(other: &mut R) -> ChaChaRng { 223 | let mut key : [u32; KEY_WORDS] = [0; KEY_WORDS]; 224 | for word in key.iter_mut() { 225 | *word = other.gen(); 226 | } 227 | SeedableRng::from_seed(&key[..]) 228 | } 229 | } 230 | 231 | 232 | #[cfg(test)] 233 | mod test { 234 | use {Rng, SeedableRng}; 235 | use super::ChaChaRng; 236 | 237 | #[test] 238 | fn test_rng_rand_seeded() { 239 | let s = ::test::rng().gen_iter::().take(8).collect::>(); 240 | let mut ra: ChaChaRng = SeedableRng::from_seed(&s[..]); 241 | let mut rb: ChaChaRng = SeedableRng::from_seed(&s[..]); 242 | assert!(::test::iter_eq(ra.gen_ascii_chars().take(100), 243 | rb.gen_ascii_chars().take(100))); 244 | } 245 | 246 | #[test] 247 | fn test_rng_seeded() { 248 | let seed : &[_] = &[0,1,2,3,4,5,6,7]; 249 | let mut ra: ChaChaRng = SeedableRng::from_seed(seed); 250 | let mut rb: ChaChaRng = SeedableRng::from_seed(seed); 251 | assert!(::test::iter_eq(ra.gen_ascii_chars().take(100), 252 | rb.gen_ascii_chars().take(100))); 253 | } 254 | 255 | #[test] 256 | fn test_rng_reseed() { 257 | let s = ::test::rng().gen_iter::().take(8).collect::>(); 258 | let mut r: ChaChaRng = SeedableRng::from_seed(&s[..]); 259 | let string1: String = r.gen_ascii_chars().take(100).collect(); 260 | 261 | r.reseed(&s); 262 | 263 | let string2: String = r.gen_ascii_chars().take(100).collect(); 264 | assert_eq!(string1, string2); 265 | } 266 | 267 | #[test] 268 | fn test_rng_true_values() { 269 | // Test vectors 1 and 2 from 270 | // http://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04 271 | let seed : &[_] = &[0u32; 8]; 272 | let mut ra: ChaChaRng = SeedableRng::from_seed(seed); 273 | 274 | let v = (0..16).map(|_| ra.next_u32()).collect::>(); 275 | assert_eq!(v, 276 | vec!(0xade0b876, 0x903df1a0, 0xe56a5d40, 0x28bd8653, 277 | 0xb819d2bd, 0x1aed8da0, 0xccef36a8, 0xc70d778b, 278 | 0x7c5941da, 0x8d485751, 0x3fe02477, 0x374ad8b8, 279 | 0xf4b8436a, 0x1ca11815, 0x69b687c3, 0x8665eeb2)); 280 | 281 | let v = (0..16).map(|_| ra.next_u32()).collect::>(); 282 | assert_eq!(v, 283 | vec!(0xbee7079f, 0x7a385155, 0x7c97ba98, 0x0d082d73, 284 | 0xa0290fcb, 0x6965e348, 0x3e53c612, 0xed7aee32, 285 | 0x7621b729, 0x434ee69c, 0xb03371d5, 0xd539d874, 286 | 0x281fed31, 0x45fb0a51, 0x1f0ae1ac, 0x6f4d794b)); 287 | 288 | 289 | let seed : &[_] = &[0,1,2,3,4,5,6,7]; 290 | let mut ra: ChaChaRng = SeedableRng::from_seed(seed); 291 | 292 | // Store the 17*i-th 32-bit word, 293 | // i.e., the i-th word of the i-th 16-word block 294 | let mut v : Vec = Vec::new(); 295 | for _ in 0..16 { 296 | v.push(ra.next_u32()); 297 | for _ in 0..16 { 298 | ra.next_u32(); 299 | } 300 | } 301 | 302 | assert_eq!(v, 303 | vec!(0xf225c81a, 0x6ab1be57, 0x04d42951, 0x70858036, 304 | 0x49884684, 0x64efec72, 0x4be2d186, 0x3615b384, 305 | 0x11cfa18e, 0xd3c50049, 0x75c775f6, 0x434c6530, 306 | 0x2c5bad8f, 0x898881dc, 0x5f1c86d9, 0xc1f8e7f4)); 307 | } 308 | 309 | #[test] 310 | fn test_rng_clone() { 311 | let seed : &[_] = &[0u32; 8]; 312 | let mut rng: ChaChaRng = SeedableRng::from_seed(seed); 313 | let mut clone = rng.clone(); 314 | for _ in 0..16 { 315 | assert_eq!(rng.next_u64(), clone.next_u64()); 316 | } 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /rust/src/rand/src/distributions/exponential.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! The exponential distribution. 12 | 13 | use {Rng, Rand}; 14 | use distributions::{ziggurat, ziggurat_tables, Sample, IndependentSample}; 15 | 16 | /// A wrapper around an `f64` to generate Exp(1) random numbers. 17 | /// 18 | /// See `Exp` for the general exponential distribution. 19 | /// 20 | /// Implemented via the ZIGNOR variant[1] of the Ziggurat method. The 21 | /// exact description in the paper was adjusted to use tables for the 22 | /// exponential distribution rather than normal. 23 | /// 24 | /// [1]: Jurgen A. Doornik (2005). [*An Improved Ziggurat Method to 25 | /// Generate Normal Random 26 | /// Samples*](http://www.doornik.com/research/ziggurat.pdf). Nuffield 27 | /// College, Oxford 28 | /// 29 | /// # Example 30 | /// 31 | /// ```rust 32 | /// use rand::distributions::exponential::Exp1; 33 | /// 34 | /// let Exp1(x) = rand::random(); 35 | /// println!("{}", x); 36 | /// ``` 37 | #[derive(Clone, Copy, Debug)] 38 | pub struct Exp1(pub f64); 39 | 40 | // This could be done via `-rng.gen::().ln()` but that is slower. 41 | impl Rand for Exp1 { 42 | #[inline] 43 | fn rand(rng: &mut R) -> Exp1 { 44 | #[inline] 45 | fn pdf(x: f64) -> f64 { 46 | (-x).exp() 47 | } 48 | #[inline] 49 | fn zero_case(rng: &mut R, _u: f64) -> f64 { 50 | ziggurat_tables::ZIG_EXP_R - rng.gen::().ln() 51 | } 52 | 53 | Exp1(ziggurat(rng, false, 54 | &ziggurat_tables::ZIG_EXP_X, 55 | &ziggurat_tables::ZIG_EXP_F, 56 | pdf, zero_case)) 57 | } 58 | } 59 | 60 | /// The exponential distribution `Exp(lambda)`. 61 | /// 62 | /// This distribution has density function: `f(x) = lambda * 63 | /// exp(-lambda * x)` for `x > 0`. 64 | /// 65 | /// # Example 66 | /// 67 | /// ```rust 68 | /// use rand::distributions::{Exp, IndependentSample}; 69 | /// 70 | /// let exp = Exp::new(2.0); 71 | /// let v = exp.ind_sample(&mut rand::thread_rng()); 72 | /// println!("{} is from a Exp(2) distribution", v); 73 | /// ``` 74 | #[derive(Clone, Copy, Debug)] 75 | pub struct Exp { 76 | /// `lambda` stored as `1/lambda`, since this is what we scale by. 77 | lambda_inverse: f64 78 | } 79 | 80 | impl Exp { 81 | /// Construct a new `Exp` with the given shape parameter 82 | /// `lambda`. Panics if `lambda <= 0`. 83 | #[inline] 84 | pub fn new(lambda: f64) -> Exp { 85 | assert!(lambda > 0.0, "Exp::new called with `lambda` <= 0"); 86 | Exp { lambda_inverse: 1.0 / lambda } 87 | } 88 | } 89 | 90 | impl Sample for Exp { 91 | fn sample(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } 92 | } 93 | impl IndependentSample for Exp { 94 | fn ind_sample(&self, rng: &mut R) -> f64 { 95 | let Exp1(n) = rng.gen::(); 96 | n * self.lambda_inverse 97 | } 98 | } 99 | 100 | #[cfg(test)] 101 | mod test { 102 | use distributions::{Sample, IndependentSample}; 103 | use super::Exp; 104 | 105 | #[test] 106 | fn test_exp() { 107 | let mut exp = Exp::new(10.0); 108 | let mut rng = ::test::rng(); 109 | for _ in 0..1000 { 110 | assert!(exp.sample(&mut rng) >= 0.0); 111 | assert!(exp.ind_sample(&mut rng) >= 0.0); 112 | } 113 | } 114 | #[test] 115 | #[should_panic] 116 | fn test_exp_invalid_lambda_zero() { 117 | Exp::new(0.0); 118 | } 119 | #[test] 120 | #[should_panic] 121 | fn test_exp_invalid_lambda_neg() { 122 | Exp::new(-10.0); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /rust/src/rand/src/distributions/gamma.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | // 11 | // ignore-lexer-test FIXME #15679 12 | 13 | //! The Gamma and derived distributions. 14 | 15 | use self::GammaRepr::*; 16 | use self::ChiSquaredRepr::*; 17 | 18 | use {Rng, Open01}; 19 | use super::normal::StandardNormal; 20 | use super::{IndependentSample, Sample, Exp}; 21 | 22 | /// The Gamma distribution `Gamma(shape, scale)` distribution. 23 | /// 24 | /// The density function of this distribution is 25 | /// 26 | /// ```text 27 | /// f(x) = x^(k - 1) * exp(-x / θ) / (Γ(k) * θ^k) 28 | /// ``` 29 | /// 30 | /// where `Γ` is the Gamma function, `k` is the shape and `θ` is the 31 | /// scale and both `k` and `θ` are strictly positive. 32 | /// 33 | /// The algorithm used is that described by Marsaglia & Tsang 2000[1], 34 | /// falling back to directly sampling from an Exponential for `shape 35 | /// == 1`, and using the boosting technique described in [1] for 36 | /// `shape < 1`. 37 | /// 38 | /// # Example 39 | /// 40 | /// ```rust 41 | /// use rand::distributions::{IndependentSample, Gamma}; 42 | /// 43 | /// let gamma = Gamma::new(2.0, 5.0); 44 | /// let v = gamma.ind_sample(&mut rand::thread_rng()); 45 | /// println!("{} is from a Gamma(2, 5) distribution", v); 46 | /// ``` 47 | /// 48 | /// [1]: George Marsaglia and Wai Wan Tsang. 2000. "A Simple Method 49 | /// for Generating Gamma Variables" *ACM Trans. Math. Softw.* 26, 3 50 | /// (September 2000), 51 | /// 363-372. DOI:[10.1145/358407.358414](http://doi.acm.org/10.1145/358407.358414) 52 | #[derive(Clone, Copy, Debug)] 53 | pub struct Gamma { 54 | repr: GammaRepr, 55 | } 56 | 57 | #[derive(Clone, Copy, Debug)] 58 | enum GammaRepr { 59 | Large(GammaLargeShape), 60 | One(Exp), 61 | Small(GammaSmallShape) 62 | } 63 | 64 | // These two helpers could be made public, but saving the 65 | // match-on-Gamma-enum branch from using them directly (e.g. if one 66 | // knows that the shape is always > 1) doesn't appear to be much 67 | // faster. 68 | 69 | /// Gamma distribution where the shape parameter is less than 1. 70 | /// 71 | /// Note, samples from this require a compulsory floating-point `pow` 72 | /// call, which makes it significantly slower than sampling from a 73 | /// gamma distribution where the shape parameter is greater than or 74 | /// equal to 1. 75 | /// 76 | /// See `Gamma` for sampling from a Gamma distribution with general 77 | /// shape parameters. 78 | #[derive(Clone, Copy, Debug)] 79 | struct GammaSmallShape { 80 | inv_shape: f64, 81 | large_shape: GammaLargeShape 82 | } 83 | 84 | /// Gamma distribution where the shape parameter is larger than 1. 85 | /// 86 | /// See `Gamma` for sampling from a Gamma distribution with general 87 | /// shape parameters. 88 | #[derive(Clone, Copy, Debug)] 89 | struct GammaLargeShape { 90 | scale: f64, 91 | c: f64, 92 | d: f64 93 | } 94 | 95 | impl Gamma { 96 | /// Construct an object representing the `Gamma(shape, scale)` 97 | /// distribution. 98 | /// 99 | /// Panics if `shape <= 0` or `scale <= 0`. 100 | #[inline] 101 | pub fn new(shape: f64, scale: f64) -> Gamma { 102 | assert!(shape > 0.0, "Gamma::new called with shape <= 0"); 103 | assert!(scale > 0.0, "Gamma::new called with scale <= 0"); 104 | 105 | let repr = if shape == 1.0 { 106 | One(Exp::new(1.0 / scale)) 107 | } else if shape < 1.0 { 108 | Small(GammaSmallShape::new_raw(shape, scale)) 109 | } else { 110 | Large(GammaLargeShape::new_raw(shape, scale)) 111 | }; 112 | Gamma { repr: repr } 113 | } 114 | } 115 | 116 | impl GammaSmallShape { 117 | fn new_raw(shape: f64, scale: f64) -> GammaSmallShape { 118 | GammaSmallShape { 119 | inv_shape: 1. / shape, 120 | large_shape: GammaLargeShape::new_raw(shape + 1.0, scale) 121 | } 122 | } 123 | } 124 | 125 | impl GammaLargeShape { 126 | fn new_raw(shape: f64, scale: f64) -> GammaLargeShape { 127 | let d = shape - 1. / 3.; 128 | GammaLargeShape { 129 | scale: scale, 130 | c: 1. / (9. * d).sqrt(), 131 | d: d 132 | } 133 | } 134 | } 135 | 136 | impl Sample for Gamma { 137 | fn sample(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } 138 | } 139 | impl Sample for GammaSmallShape { 140 | fn sample(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } 141 | } 142 | impl Sample for GammaLargeShape { 143 | fn sample(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } 144 | } 145 | 146 | impl IndependentSample for Gamma { 147 | fn ind_sample(&self, rng: &mut R) -> f64 { 148 | match self.repr { 149 | Small(ref g) => g.ind_sample(rng), 150 | One(ref g) => g.ind_sample(rng), 151 | Large(ref g) => g.ind_sample(rng), 152 | } 153 | } 154 | } 155 | impl IndependentSample for GammaSmallShape { 156 | fn ind_sample(&self, rng: &mut R) -> f64 { 157 | let Open01(u) = rng.gen::>(); 158 | 159 | self.large_shape.ind_sample(rng) * u.powf(self.inv_shape) 160 | } 161 | } 162 | impl IndependentSample for GammaLargeShape { 163 | fn ind_sample(&self, rng: &mut R) -> f64 { 164 | loop { 165 | let StandardNormal(x) = rng.gen::(); 166 | let v_cbrt = 1.0 + self.c * x; 167 | if v_cbrt <= 0.0 { // a^3 <= 0 iff a <= 0 168 | continue 169 | } 170 | 171 | let v = v_cbrt * v_cbrt * v_cbrt; 172 | let Open01(u) = rng.gen::>(); 173 | 174 | let x_sqr = x * x; 175 | if u < 1.0 - 0.0331 * x_sqr * x_sqr || 176 | u.ln() < 0.5 * x_sqr + self.d * (1.0 - v + v.ln()) { 177 | return self.d * v * self.scale 178 | } 179 | } 180 | } 181 | } 182 | 183 | /// The chi-squared distribution `χ²(k)`, where `k` is the degrees of 184 | /// freedom. 185 | /// 186 | /// For `k > 0` integral, this distribution is the sum of the squares 187 | /// of `k` independent standard normal random variables. For other 188 | /// `k`, this uses the equivalent characterisation 189 | /// `χ²(k) = Gamma(k/2, 2)`. 190 | /// 191 | /// # Example 192 | /// 193 | /// ```rust 194 | /// use rand::distributions::{ChiSquared, IndependentSample}; 195 | /// 196 | /// let chi = ChiSquared::new(11.0); 197 | /// let v = chi.ind_sample(&mut rand::thread_rng()); 198 | /// println!("{} is from a χ²(11) distribution", v) 199 | /// ``` 200 | #[derive(Clone, Copy, Debug)] 201 | pub struct ChiSquared { 202 | repr: ChiSquaredRepr, 203 | } 204 | 205 | #[derive(Clone, Copy, Debug)] 206 | enum ChiSquaredRepr { 207 | // k == 1, Gamma(alpha, ..) is particularly slow for alpha < 1, 208 | // e.g. when alpha = 1/2 as it would be for this case, so special- 209 | // casing and using the definition of N(0,1)^2 is faster. 210 | DoFExactlyOne, 211 | DoFAnythingElse(Gamma), 212 | } 213 | 214 | impl ChiSquared { 215 | /// Create a new chi-squared distribution with degrees-of-freedom 216 | /// `k`. Panics if `k < 0`. 217 | pub fn new(k: f64) -> ChiSquared { 218 | let repr = if k == 1.0 { 219 | DoFExactlyOne 220 | } else { 221 | assert!(k > 0.0, "ChiSquared::new called with `k` < 0"); 222 | DoFAnythingElse(Gamma::new(0.5 * k, 2.0)) 223 | }; 224 | ChiSquared { repr: repr } 225 | } 226 | } 227 | impl Sample for ChiSquared { 228 | fn sample(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } 229 | } 230 | impl IndependentSample for ChiSquared { 231 | fn ind_sample(&self, rng: &mut R) -> f64 { 232 | match self.repr { 233 | DoFExactlyOne => { 234 | // k == 1 => N(0,1)^2 235 | let StandardNormal(norm) = rng.gen::(); 236 | norm * norm 237 | } 238 | DoFAnythingElse(ref g) => g.ind_sample(rng) 239 | } 240 | } 241 | } 242 | 243 | /// The Fisher F distribution `F(m, n)`. 244 | /// 245 | /// This distribution is equivalent to the ratio of two normalised 246 | /// chi-squared distributions, that is, `F(m,n) = (χ²(m)/m) / 247 | /// (χ²(n)/n)`. 248 | /// 249 | /// # Example 250 | /// 251 | /// ```rust 252 | /// use rand::distributions::{FisherF, IndependentSample}; 253 | /// 254 | /// let f = FisherF::new(2.0, 32.0); 255 | /// let v = f.ind_sample(&mut rand::thread_rng()); 256 | /// println!("{} is from an F(2, 32) distribution", v) 257 | /// ``` 258 | #[derive(Clone, Copy, Debug)] 259 | pub struct FisherF { 260 | numer: ChiSquared, 261 | denom: ChiSquared, 262 | // denom_dof / numer_dof so that this can just be a straight 263 | // multiplication, rather than a division. 264 | dof_ratio: f64, 265 | } 266 | 267 | impl FisherF { 268 | /// Create a new `FisherF` distribution, with the given 269 | /// parameter. Panics if either `m` or `n` are not positive. 270 | pub fn new(m: f64, n: f64) -> FisherF { 271 | assert!(m > 0.0, "FisherF::new called with `m < 0`"); 272 | assert!(n > 0.0, "FisherF::new called with `n < 0`"); 273 | 274 | FisherF { 275 | numer: ChiSquared::new(m), 276 | denom: ChiSquared::new(n), 277 | dof_ratio: n / m 278 | } 279 | } 280 | } 281 | impl Sample for FisherF { 282 | fn sample(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } 283 | } 284 | impl IndependentSample for FisherF { 285 | fn ind_sample(&self, rng: &mut R) -> f64 { 286 | self.numer.ind_sample(rng) / self.denom.ind_sample(rng) * self.dof_ratio 287 | } 288 | } 289 | 290 | /// The Student t distribution, `t(nu)`, where `nu` is the degrees of 291 | /// freedom. 292 | /// 293 | /// # Example 294 | /// 295 | /// ```rust 296 | /// use rand::distributions::{StudentT, IndependentSample}; 297 | /// 298 | /// let t = StudentT::new(11.0); 299 | /// let v = t.ind_sample(&mut rand::thread_rng()); 300 | /// println!("{} is from a t(11) distribution", v) 301 | /// ``` 302 | #[derive(Clone, Copy, Debug)] 303 | pub struct StudentT { 304 | chi: ChiSquared, 305 | dof: f64 306 | } 307 | 308 | impl StudentT { 309 | /// Create a new Student t distribution with `n` degrees of 310 | /// freedom. Panics if `n <= 0`. 311 | pub fn new(n: f64) -> StudentT { 312 | assert!(n > 0.0, "StudentT::new called with `n <= 0`"); 313 | StudentT { 314 | chi: ChiSquared::new(n), 315 | dof: n 316 | } 317 | } 318 | } 319 | impl Sample for StudentT { 320 | fn sample(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } 321 | } 322 | impl IndependentSample for StudentT { 323 | fn ind_sample(&self, rng: &mut R) -> f64 { 324 | let StandardNormal(norm) = rng.gen::(); 325 | norm * (self.dof / self.chi.ind_sample(rng)).sqrt() 326 | } 327 | } 328 | 329 | #[cfg(test)] 330 | mod test { 331 | use distributions::{Sample, IndependentSample}; 332 | use super::{ChiSquared, StudentT, FisherF}; 333 | 334 | #[test] 335 | fn test_chi_squared_one() { 336 | let mut chi = ChiSquared::new(1.0); 337 | let mut rng = ::test::rng(); 338 | for _ in 0..1000 { 339 | chi.sample(&mut rng); 340 | chi.ind_sample(&mut rng); 341 | } 342 | } 343 | #[test] 344 | fn test_chi_squared_small() { 345 | let mut chi = ChiSquared::new(0.5); 346 | let mut rng = ::test::rng(); 347 | for _ in 0..1000 { 348 | chi.sample(&mut rng); 349 | chi.ind_sample(&mut rng); 350 | } 351 | } 352 | #[test] 353 | fn test_chi_squared_large() { 354 | let mut chi = ChiSquared::new(30.0); 355 | let mut rng = ::test::rng(); 356 | for _ in 0..1000 { 357 | chi.sample(&mut rng); 358 | chi.ind_sample(&mut rng); 359 | } 360 | } 361 | #[test] 362 | #[should_panic] 363 | fn test_chi_squared_invalid_dof() { 364 | ChiSquared::new(-1.0); 365 | } 366 | 367 | #[test] 368 | fn test_f() { 369 | let mut f = FisherF::new(2.0, 32.0); 370 | let mut rng = ::test::rng(); 371 | for _ in 0..1000 { 372 | f.sample(&mut rng); 373 | f.ind_sample(&mut rng); 374 | } 375 | } 376 | 377 | #[test] 378 | fn test_t() { 379 | let mut t = StudentT::new(11.0); 380 | let mut rng = ::test::rng(); 381 | for _ in 0..1000 { 382 | t.sample(&mut rng); 383 | t.ind_sample(&mut rng); 384 | } 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /rust/src/rand/src/distributions/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Sampling from random distributions. 12 | //! 13 | //! This is a generalization of `Rand` to allow parameters to control the 14 | //! exact properties of the generated values, e.g. the mean and standard 15 | //! deviation of a normal distribution. The `Sample` trait is the most 16 | //! general, and allows for generating values that change some state 17 | //! internally. The `IndependentSample` trait is for generating values 18 | //! that do not need to record state. 19 | 20 | use std::marker; 21 | 22 | use {Rng, Rand}; 23 | 24 | pub use self::range::Range; 25 | pub use self::gamma::{Gamma, ChiSquared, FisherF, StudentT}; 26 | pub use self::normal::{Normal, LogNormal}; 27 | pub use self::exponential::Exp; 28 | 29 | pub mod range; 30 | pub mod gamma; 31 | pub mod normal; 32 | pub mod exponential; 33 | 34 | /// Types that can be used to create a random instance of `Support`. 35 | pub trait Sample { 36 | /// Generate a random value of `Support`, using `rng` as the 37 | /// source of randomness. 38 | fn sample(&mut self, rng: &mut R) -> Support; 39 | } 40 | 41 | /// `Sample`s that do not require keeping track of state. 42 | /// 43 | /// Since no state is recorded, each sample is (statistically) 44 | /// independent of all others, assuming the `Rng` used has this 45 | /// property. 46 | // FIXME maybe having this separate is overkill (the only reason is to 47 | // take &self rather than &mut self)? or maybe this should be the 48 | // trait called `Sample` and the other should be `DependentSample`. 49 | pub trait IndependentSample: Sample { 50 | /// Generate a random value. 51 | fn ind_sample(&self, &mut R) -> Support; 52 | } 53 | 54 | /// A wrapper for generating types that implement `Rand` via the 55 | /// `Sample` & `IndependentSample` traits. 56 | #[derive(Debug)] 57 | pub struct RandSample { 58 | _marker: marker::PhantomData Sup>, 59 | } 60 | 61 | impl Copy for RandSample {} 62 | impl Clone for RandSample { 63 | fn clone(&self) -> Self { *self } 64 | } 65 | 66 | impl Sample for RandSample { 67 | fn sample(&mut self, rng: &mut R) -> Sup { self.ind_sample(rng) } 68 | } 69 | 70 | impl IndependentSample for RandSample { 71 | fn ind_sample(&self, rng: &mut R) -> Sup { 72 | rng.gen() 73 | } 74 | } 75 | 76 | impl RandSample { 77 | pub fn new() -> RandSample { 78 | RandSample { _marker: marker::PhantomData } 79 | } 80 | } 81 | 82 | /// A value with a particular weight for use with `WeightedChoice`. 83 | #[derive(Copy, Clone, Debug)] 84 | pub struct Weighted { 85 | /// The numerical weight of this item 86 | pub weight: u32, 87 | /// The actual item which is being weighted 88 | pub item: T, 89 | } 90 | 91 | /// A distribution that selects from a finite collection of weighted items. 92 | /// 93 | /// Each item has an associated weight that influences how likely it 94 | /// is to be chosen: higher weight is more likely. 95 | /// 96 | /// The `Clone` restriction is a limitation of the `Sample` and 97 | /// `IndependentSample` traits. Note that `&T` is (cheaply) `Clone` for 98 | /// all `T`, as is `u32`, so one can store references or indices into 99 | /// another vector. 100 | /// 101 | /// # Example 102 | /// 103 | /// ```rust 104 | /// use rand::distributions::{Weighted, WeightedChoice, IndependentSample}; 105 | /// 106 | /// let mut items = vec!(Weighted { weight: 2, item: 'a' }, 107 | /// Weighted { weight: 4, item: 'b' }, 108 | /// Weighted { weight: 1, item: 'c' }); 109 | /// let wc = WeightedChoice::new(&mut items); 110 | /// let mut rng = rand::thread_rng(); 111 | /// for _ in 0..16 { 112 | /// // on average prints 'a' 4 times, 'b' 8 and 'c' twice. 113 | /// println!("{}", wc.ind_sample(&mut rng)); 114 | /// } 115 | /// ``` 116 | #[derive(Debug)] 117 | pub struct WeightedChoice<'a, T:'a> { 118 | items: &'a mut [Weighted], 119 | weight_range: Range 120 | } 121 | 122 | impl<'a, T: Clone> WeightedChoice<'a, T> { 123 | /// Create a new `WeightedChoice`. 124 | /// 125 | /// Panics if: 126 | /// 127 | /// - `v` is empty 128 | /// - the total weight is 0 129 | /// - the total weight is larger than a `u32` can contain. 130 | pub fn new(items: &'a mut [Weighted]) -> WeightedChoice<'a, T> { 131 | // strictly speaking, this is subsumed by the total weight == 0 case 132 | assert!(!items.is_empty(), "WeightedChoice::new called with no items"); 133 | 134 | let mut running_total: u32 = 0; 135 | 136 | // we convert the list from individual weights to cumulative 137 | // weights so we can binary search. This *could* drop elements 138 | // with weight == 0 as an optimisation. 139 | for item in items.iter_mut() { 140 | running_total = match running_total.checked_add(item.weight) { 141 | Some(n) => n, 142 | None => panic!("WeightedChoice::new called with a total weight \ 143 | larger than a u32 can contain") 144 | }; 145 | 146 | item.weight = running_total; 147 | } 148 | assert!(running_total != 0, "WeightedChoice::new called with a total weight of 0"); 149 | 150 | WeightedChoice { 151 | items: items, 152 | // we're likely to be generating numbers in this range 153 | // relatively often, so might as well cache it 154 | weight_range: Range::new(0, running_total) 155 | } 156 | } 157 | } 158 | 159 | impl<'a, T: Clone> Sample for WeightedChoice<'a, T> { 160 | fn sample(&mut self, rng: &mut R) -> T { self.ind_sample(rng) } 161 | } 162 | 163 | impl<'a, T: Clone> IndependentSample for WeightedChoice<'a, T> { 164 | fn ind_sample(&self, rng: &mut R) -> T { 165 | // we want to find the first element that has cumulative 166 | // weight > sample_weight, which we do by binary since the 167 | // cumulative weights of self.items are sorted. 168 | 169 | // choose a weight in [0, total_weight) 170 | let sample_weight = self.weight_range.ind_sample(rng); 171 | 172 | // short circuit when it's the first item 173 | if sample_weight < self.items[0].weight { 174 | return self.items[0].item.clone(); 175 | } 176 | 177 | let mut idx = 0; 178 | let mut modifier = self.items.len(); 179 | 180 | // now we know that every possibility has an element to the 181 | // left, so we can just search for the last element that has 182 | // cumulative weight <= sample_weight, then the next one will 183 | // be "it". (Note that this greatest element will never be the 184 | // last element of the vector, since sample_weight is chosen 185 | // in [0, total_weight) and the cumulative weight of the last 186 | // one is exactly the total weight.) 187 | while modifier > 1 { 188 | let i = idx + modifier / 2; 189 | if self.items[i].weight <= sample_weight { 190 | // we're small, so look to the right, but allow this 191 | // exact element still. 192 | idx = i; 193 | // we need the `/ 2` to round up otherwise we'll drop 194 | // the trailing elements when `modifier` is odd. 195 | modifier += 1; 196 | } else { 197 | // otherwise we're too big, so go left. (i.e. do 198 | // nothing) 199 | } 200 | modifier /= 2; 201 | } 202 | return self.items[idx + 1].item.clone(); 203 | } 204 | } 205 | 206 | mod ziggurat_tables; 207 | 208 | /// Sample a random number using the Ziggurat method (specifically the 209 | /// ZIGNOR variant from Doornik 2005). Most of the arguments are 210 | /// directly from the paper: 211 | /// 212 | /// * `rng`: source of randomness 213 | /// * `symmetric`: whether this is a symmetric distribution, or one-sided with P(x < 0) = 0. 214 | /// * `X`: the $x_i$ abscissae. 215 | /// * `F`: precomputed values of the PDF at the $x_i$, (i.e. $f(x_i)$) 216 | /// * `F_DIFF`: precomputed values of $f(x_i) - f(x_{i+1})$ 217 | /// * `pdf`: the probability density function 218 | /// * `zero_case`: manual sampling from the tail when we chose the 219 | /// bottom box (i.e. i == 0) 220 | 221 | // the perf improvement (25-50%) is definitely worth the extra code 222 | // size from force-inlining. 223 | #[inline(always)] 224 | fn ziggurat( 225 | rng: &mut R, 226 | symmetric: bool, 227 | x_tab: ziggurat_tables::ZigTable, 228 | f_tab: ziggurat_tables::ZigTable, 229 | mut pdf: P, 230 | mut zero_case: Z) 231 | -> f64 where P: FnMut(f64) -> f64, Z: FnMut(&mut R, f64) -> f64 { 232 | const SCALE: f64 = (1u64 << 53) as f64; 233 | loop { 234 | // reimplement the f64 generation as an optimisation suggested 235 | // by the Doornik paper: we have a lot of precision-space 236 | // (i.e. there are 11 bits of the 64 of a u64 to use after 237 | // creating a f64), so we might as well reuse some to save 238 | // generating a whole extra random number. (Seems to be 15% 239 | // faster.) 240 | // 241 | // This unfortunately misses out on the benefits of direct 242 | // floating point generation if an RNG like dSMFT is 243 | // used. (That is, such RNGs create floats directly, highly 244 | // efficiently and overload next_f32/f64, so by not calling it 245 | // this may be slower than it would be otherwise.) 246 | // FIXME: investigate/optimise for the above. 247 | let bits: u64 = rng.gen(); 248 | let i = (bits & 0xff) as usize; 249 | let f = (bits >> 11) as f64 / SCALE; 250 | 251 | // u is either U(-1, 1) or U(0, 1) depending on if this is a 252 | // symmetric distribution or not. 253 | let u = if symmetric {2.0 * f - 1.0} else {f}; 254 | let x = u * x_tab[i]; 255 | 256 | let test_x = if symmetric { x.abs() } else {x}; 257 | 258 | // algebraically equivalent to |u| < x_tab[i+1]/x_tab[i] (or u < x_tab[i+1]/x_tab[i]) 259 | if test_x < x_tab[i + 1] { 260 | return x; 261 | } 262 | if i == 0 { 263 | return zero_case(rng, u); 264 | } 265 | // algebraically equivalent to f1 + DRanU()*(f0 - f1) < 1 266 | if f_tab[i + 1] + (f_tab[i] - f_tab[i + 1]) * rng.gen::() < pdf(x) { 267 | return x; 268 | } 269 | } 270 | } 271 | 272 | #[cfg(test)] 273 | mod tests { 274 | 275 | use {Rng, Rand}; 276 | use super::{RandSample, WeightedChoice, Weighted, Sample, IndependentSample}; 277 | 278 | #[derive(PartialEq, Debug)] 279 | struct ConstRand(usize); 280 | impl Rand for ConstRand { 281 | fn rand(_: &mut R) -> ConstRand { 282 | ConstRand(0) 283 | } 284 | } 285 | 286 | // 0, 1, 2, 3, ... 287 | struct CountingRng { i: u32 } 288 | impl Rng for CountingRng { 289 | fn next_u32(&mut self) -> u32 { 290 | self.i += 1; 291 | self.i - 1 292 | } 293 | fn next_u64(&mut self) -> u64 { 294 | self.next_u32() as u64 295 | } 296 | } 297 | 298 | #[test] 299 | fn test_rand_sample() { 300 | let mut rand_sample = RandSample::::new(); 301 | 302 | assert_eq!(rand_sample.sample(&mut ::test::rng()), ConstRand(0)); 303 | assert_eq!(rand_sample.ind_sample(&mut ::test::rng()), ConstRand(0)); 304 | } 305 | #[test] 306 | fn test_weighted_choice() { 307 | // this makes assumptions about the internal implementation of 308 | // WeightedChoice, specifically: it doesn't reorder the items, 309 | // it doesn't do weird things to the RNG (so 0 maps to 0, 1 to 310 | // 1, internally; modulo a modulo operation). 311 | 312 | macro_rules! t { 313 | ($items:expr, $expected:expr) => {{ 314 | let mut items = $items; 315 | let wc = WeightedChoice::new(&mut items); 316 | let expected = $expected; 317 | 318 | let mut rng = CountingRng { i: 0 }; 319 | 320 | for &val in expected.iter() { 321 | assert_eq!(wc.ind_sample(&mut rng), val) 322 | } 323 | }} 324 | } 325 | 326 | t!(vec!(Weighted { weight: 1, item: 10}), [10]); 327 | 328 | // skip some 329 | t!(vec!(Weighted { weight: 0, item: 20}, 330 | Weighted { weight: 2, item: 21}, 331 | Weighted { weight: 0, item: 22}, 332 | Weighted { weight: 1, item: 23}), 333 | [21,21, 23]); 334 | 335 | // different weights 336 | t!(vec!(Weighted { weight: 4, item: 30}, 337 | Weighted { weight: 3, item: 31}), 338 | [30,30,30,30, 31,31,31]); 339 | 340 | // check that we're binary searching 341 | // correctly with some vectors of odd 342 | // length. 343 | t!(vec!(Weighted { weight: 1, item: 40}, 344 | Weighted { weight: 1, item: 41}, 345 | Weighted { weight: 1, item: 42}, 346 | Weighted { weight: 1, item: 43}, 347 | Weighted { weight: 1, item: 44}), 348 | [40, 41, 42, 43, 44]); 349 | t!(vec!(Weighted { weight: 1, item: 50}, 350 | Weighted { weight: 1, item: 51}, 351 | Weighted { weight: 1, item: 52}, 352 | Weighted { weight: 1, item: 53}, 353 | Weighted { weight: 1, item: 54}, 354 | Weighted { weight: 1, item: 55}, 355 | Weighted { weight: 1, item: 56}), 356 | [50, 51, 52, 53, 54, 55, 56]); 357 | } 358 | 359 | #[test] 360 | fn test_weighted_clone_initialization() { 361 | let initial : Weighted = Weighted {weight: 1, item: 1}; 362 | let clone = initial.clone(); 363 | assert_eq!(initial.weight, clone.weight); 364 | assert_eq!(initial.item, clone.item); 365 | } 366 | 367 | #[test] #[should_panic] 368 | fn test_weighted_clone_change_weight() { 369 | let initial : Weighted = Weighted {weight: 1, item: 1}; 370 | let mut clone = initial.clone(); 371 | clone.weight = 5; 372 | assert_eq!(initial.weight, clone.weight); 373 | } 374 | 375 | #[test] #[should_panic] 376 | fn test_weighted_clone_change_item() { 377 | let initial : Weighted = Weighted {weight: 1, item: 1}; 378 | let mut clone = initial.clone(); 379 | clone.item = 5; 380 | assert_eq!(initial.item, clone.item); 381 | 382 | } 383 | 384 | #[test] #[should_panic] 385 | fn test_weighted_choice_no_items() { 386 | WeightedChoice::::new(&mut []); 387 | } 388 | #[test] #[should_panic] 389 | fn test_weighted_choice_zero_weight() { 390 | WeightedChoice::new(&mut [Weighted { weight: 0, item: 0}, 391 | Weighted { weight: 0, item: 1}]); 392 | } 393 | #[test] #[should_panic] 394 | fn test_weighted_choice_weight_overflows() { 395 | let x = ::std::u32::MAX / 2; // x + x + 2 is the overflow 396 | WeightedChoice::new(&mut [Weighted { weight: x, item: 0 }, 397 | Weighted { weight: 1, item: 1 }, 398 | Weighted { weight: x, item: 2 }, 399 | Weighted { weight: 1, item: 3 }]); 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /rust/src/rand/src/distributions/normal.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! The normal and derived distributions. 12 | 13 | use {Rng, Rand, Open01}; 14 | use distributions::{ziggurat, ziggurat_tables, Sample, IndependentSample}; 15 | 16 | /// A wrapper around an `f64` to generate N(0, 1) random numbers 17 | /// (a.k.a. a standard normal, or Gaussian). 18 | /// 19 | /// See `Normal` for the general normal distribution. 20 | /// 21 | /// Implemented via the ZIGNOR variant[1] of the Ziggurat method. 22 | /// 23 | /// [1]: Jurgen A. Doornik (2005). [*An Improved Ziggurat Method to 24 | /// Generate Normal Random 25 | /// Samples*](http://www.doornik.com/research/ziggurat.pdf). Nuffield 26 | /// College, Oxford 27 | /// 28 | /// # Example 29 | /// 30 | /// ```rust 31 | /// use rand::distributions::normal::StandardNormal; 32 | /// 33 | /// let StandardNormal(x) = rand::random(); 34 | /// println!("{}", x); 35 | /// ``` 36 | #[derive(Clone, Copy, Debug)] 37 | pub struct StandardNormal(pub f64); 38 | 39 | impl Rand for StandardNormal { 40 | fn rand(rng: &mut R) -> StandardNormal { 41 | #[inline] 42 | fn pdf(x: f64) -> f64 { 43 | (-x*x/2.0).exp() 44 | } 45 | #[inline] 46 | fn zero_case(rng: &mut R, u: f64) -> f64 { 47 | // compute a random number in the tail by hand 48 | 49 | // strange initial conditions, because the loop is not 50 | // do-while, so the condition should be true on the first 51 | // run, they get overwritten anyway (0 < 1, so these are 52 | // good). 53 | let mut x = 1.0f64; 54 | let mut y = 0.0f64; 55 | 56 | while -2.0 * y < x * x { 57 | let Open01(x_) = rng.gen::>(); 58 | let Open01(y_) = rng.gen::>(); 59 | 60 | x = x_.ln() / ziggurat_tables::ZIG_NORM_R; 61 | y = y_.ln(); 62 | } 63 | 64 | if u < 0.0 { x - ziggurat_tables::ZIG_NORM_R } else { ziggurat_tables::ZIG_NORM_R - x } 65 | } 66 | 67 | StandardNormal(ziggurat( 68 | rng, 69 | true, // this is symmetric 70 | &ziggurat_tables::ZIG_NORM_X, 71 | &ziggurat_tables::ZIG_NORM_F, 72 | pdf, zero_case)) 73 | } 74 | } 75 | 76 | /// The normal distribution `N(mean, std_dev**2)`. 77 | /// 78 | /// This uses the ZIGNOR variant of the Ziggurat method, see 79 | /// `StandardNormal` for more details. 80 | /// 81 | /// # Example 82 | /// 83 | /// ```rust 84 | /// use rand::distributions::{Normal, IndependentSample}; 85 | /// 86 | /// // mean 2, standard deviation 3 87 | /// let normal = Normal::new(2.0, 3.0); 88 | /// let v = normal.ind_sample(&mut rand::thread_rng()); 89 | /// println!("{} is from a N(2, 9) distribution", v) 90 | /// ``` 91 | #[derive(Clone, Copy, Debug)] 92 | pub struct Normal { 93 | mean: f64, 94 | std_dev: f64, 95 | } 96 | 97 | impl Normal { 98 | /// Construct a new `Normal` distribution with the given mean and 99 | /// standard deviation. 100 | /// 101 | /// # Panics 102 | /// 103 | /// Panics if `std_dev < 0`. 104 | #[inline] 105 | pub fn new(mean: f64, std_dev: f64) -> Normal { 106 | assert!(std_dev >= 0.0, "Normal::new called with `std_dev` < 0"); 107 | Normal { 108 | mean: mean, 109 | std_dev: std_dev 110 | } 111 | } 112 | } 113 | impl Sample for Normal { 114 | fn sample(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } 115 | } 116 | impl IndependentSample for Normal { 117 | fn ind_sample(&self, rng: &mut R) -> f64 { 118 | let StandardNormal(n) = rng.gen::(); 119 | self.mean + self.std_dev * n 120 | } 121 | } 122 | 123 | 124 | /// The log-normal distribution `ln N(mean, std_dev**2)`. 125 | /// 126 | /// If `X` is log-normal distributed, then `ln(X)` is `N(mean, 127 | /// std_dev**2)` distributed. 128 | /// 129 | /// # Example 130 | /// 131 | /// ```rust 132 | /// use rand::distributions::{LogNormal, IndependentSample}; 133 | /// 134 | /// // mean 2, standard deviation 3 135 | /// let log_normal = LogNormal::new(2.0, 3.0); 136 | /// let v = log_normal.ind_sample(&mut rand::thread_rng()); 137 | /// println!("{} is from an ln N(2, 9) distribution", v) 138 | /// ``` 139 | #[derive(Clone, Copy, Debug)] 140 | pub struct LogNormal { 141 | norm: Normal 142 | } 143 | 144 | impl LogNormal { 145 | /// Construct a new `LogNormal` distribution with the given mean 146 | /// and standard deviation. 147 | /// 148 | /// # Panics 149 | /// 150 | /// Panics if `std_dev < 0`. 151 | #[inline] 152 | pub fn new(mean: f64, std_dev: f64) -> LogNormal { 153 | assert!(std_dev >= 0.0, "LogNormal::new called with `std_dev` < 0"); 154 | LogNormal { norm: Normal::new(mean, std_dev) } 155 | } 156 | } 157 | impl Sample for LogNormal { 158 | fn sample(&mut self, rng: &mut R) -> f64 { self.ind_sample(rng) } 159 | } 160 | impl IndependentSample for LogNormal { 161 | fn ind_sample(&self, rng: &mut R) -> f64 { 162 | self.norm.ind_sample(rng).exp() 163 | } 164 | } 165 | 166 | #[cfg(test)] 167 | mod tests { 168 | use distributions::{Sample, IndependentSample}; 169 | use super::{Normal, LogNormal}; 170 | 171 | #[test] 172 | fn test_normal() { 173 | let mut norm = Normal::new(10.0, 10.0); 174 | let mut rng = ::test::rng(); 175 | for _ in 0..1000 { 176 | norm.sample(&mut rng); 177 | norm.ind_sample(&mut rng); 178 | } 179 | } 180 | #[test] 181 | #[should_panic] 182 | fn test_normal_invalid_sd() { 183 | Normal::new(10.0, -1.0); 184 | } 185 | 186 | 187 | #[test] 188 | fn test_log_normal() { 189 | let mut lnorm = LogNormal::new(10.0, 10.0); 190 | let mut rng = ::test::rng(); 191 | for _ in 0..1000 { 192 | lnorm.sample(&mut rng); 193 | lnorm.ind_sample(&mut rng); 194 | } 195 | } 196 | #[test] 197 | #[should_panic] 198 | fn test_log_normal_invalid_sd() { 199 | LogNormal::new(10.0, -1.0); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /rust/src/rand/src/distributions/range.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Generating numbers between two others. 12 | 13 | // this is surprisingly complicated to be both generic & correct 14 | 15 | use std::num::Wrapping as w; 16 | 17 | use Rng; 18 | use distributions::{Sample, IndependentSample}; 19 | 20 | /// Sample values uniformly between two bounds. 21 | /// 22 | /// This gives a uniform distribution (assuming the RNG used to sample 23 | /// it is itself uniform & the `SampleRange` implementation for the 24 | /// given type is correct), even for edge cases like `low = 0u8`, 25 | /// `high = 170u8`, for which a naive modulo operation would return 26 | /// numbers less than 85 with double the probability to those greater 27 | /// than 85. 28 | /// 29 | /// Types should attempt to sample in `[low, high)`, i.e., not 30 | /// including `high`, but this may be very difficult. All the 31 | /// primitive integer types satisfy this property, and the float types 32 | /// normally satisfy it, but rounding may mean `high` can occur. 33 | /// 34 | /// # Example 35 | /// 36 | /// ```rust 37 | /// use rand::distributions::{IndependentSample, Range}; 38 | /// 39 | /// fn main() { 40 | /// let between = Range::new(10, 10000); 41 | /// let mut rng = rand::thread_rng(); 42 | /// let mut sum = 0; 43 | /// for _ in 0..1000 { 44 | /// sum += between.ind_sample(&mut rng); 45 | /// } 46 | /// println!("{}", sum); 47 | /// } 48 | /// ``` 49 | #[derive(Clone, Copy, Debug)] 50 | pub struct Range { 51 | low: X, 52 | range: X, 53 | accept_zone: X 54 | } 55 | 56 | impl Range { 57 | /// Create a new `Range` instance that samples uniformly from 58 | /// `[low, high)`. Panics if `low >= high`. 59 | pub fn new(low: X, high: X) -> Range { 60 | assert!(low < high, "Range::new called with `low >= high`"); 61 | SampleRange::construct_range(low, high) 62 | } 63 | } 64 | 65 | impl Sample for Range { 66 | #[inline] 67 | fn sample(&mut self, rng: &mut R) -> Sup { self.ind_sample(rng) } 68 | } 69 | impl IndependentSample for Range { 70 | fn ind_sample(&self, rng: &mut R) -> Sup { 71 | SampleRange::sample_range(self, rng) 72 | } 73 | } 74 | 75 | /// The helper trait for types that have a sensible way to sample 76 | /// uniformly between two values. This should not be used directly, 77 | /// and is only to facilitate `Range`. 78 | pub trait SampleRange : Sized { 79 | /// Construct the `Range` object that `sample_range` 80 | /// requires. This should not ever be called directly, only via 81 | /// `Range::new`, which will check that `low < high`, so this 82 | /// function doesn't have to repeat the check. 83 | fn construct_range(low: Self, high: Self) -> Range; 84 | 85 | /// Sample a value from the given `Range` with the given `Rng` as 86 | /// a source of randomness. 87 | fn sample_range(r: &Range, rng: &mut R) -> Self; 88 | } 89 | 90 | macro_rules! integer_impl { 91 | ($ty:ty, $unsigned:ident) => { 92 | impl SampleRange for $ty { 93 | // we play free and fast with unsigned vs signed here 94 | // (when $ty is signed), but that's fine, since the 95 | // contract of this macro is for $ty and $unsigned to be 96 | // "bit-equal", so casting between them is a no-op & a 97 | // bijection. 98 | 99 | #[inline] 100 | fn construct_range(low: $ty, high: $ty) -> Range<$ty> { 101 | let range = (w(high as $unsigned) - w(low as $unsigned)).0; 102 | let unsigned_max: $unsigned = ::std::$unsigned::MAX; 103 | 104 | // this is the largest number that fits into $unsigned 105 | // that `range` divides evenly, so, if we've sampled 106 | // `n` uniformly from this region, then `n % range` is 107 | // uniform in [0, range) 108 | let zone = unsigned_max - unsigned_max % range; 109 | 110 | Range { 111 | low: low, 112 | range: range as $ty, 113 | accept_zone: zone as $ty 114 | } 115 | } 116 | 117 | #[inline] 118 | fn sample_range(r: &Range<$ty>, rng: &mut R) -> $ty { 119 | loop { 120 | // rejection sample 121 | let v = rng.gen::<$unsigned>(); 122 | // until we find something that fits into the 123 | // region which r.range evenly divides (this will 124 | // be uniformly distributed) 125 | if v < r.accept_zone as $unsigned { 126 | // and return it, with some adjustments 127 | return (w(r.low) + w((v % r.range as $unsigned) as $ty)).0; 128 | } 129 | } 130 | } 131 | } 132 | } 133 | } 134 | 135 | integer_impl! { i8, u8 } 136 | integer_impl! { i16, u16 } 137 | integer_impl! { i32, u32 } 138 | integer_impl! { i64, u64 } 139 | integer_impl! { isize, usize } 140 | integer_impl! { u8, u8 } 141 | integer_impl! { u16, u16 } 142 | integer_impl! { u32, u32 } 143 | integer_impl! { u64, u64 } 144 | integer_impl! { usize, usize } 145 | 146 | macro_rules! float_impl { 147 | ($ty:ty) => { 148 | impl SampleRange for $ty { 149 | fn construct_range(low: $ty, high: $ty) -> Range<$ty> { 150 | Range { 151 | low: low, 152 | range: high - low, 153 | accept_zone: 0.0 // unused 154 | } 155 | } 156 | fn sample_range(r: &Range<$ty>, rng: &mut R) -> $ty { 157 | r.low + r.range * rng.gen::<$ty>() 158 | } 159 | } 160 | } 161 | } 162 | 163 | float_impl! { f32 } 164 | float_impl! { f64 } 165 | 166 | #[cfg(test)] 167 | mod tests { 168 | use distributions::{Sample, IndependentSample}; 169 | use super::Range as Range; 170 | 171 | #[should_panic] 172 | #[test] 173 | fn test_range_bad_limits_equal() { 174 | Range::new(10, 10); 175 | } 176 | #[should_panic] 177 | #[test] 178 | fn test_range_bad_limits_flipped() { 179 | Range::new(10, 5); 180 | } 181 | 182 | #[test] 183 | fn test_integers() { 184 | let mut rng = ::test::rng(); 185 | macro_rules! t { 186 | ($($ty:ident),*) => {{ 187 | $( 188 | let v: &[($ty, $ty)] = &[(0, 10), 189 | (10, 127), 190 | (::std::$ty::MIN, ::std::$ty::MAX)]; 191 | for &(low, high) in v.iter() { 192 | let mut sampler: Range<$ty> = Range::new(low, high); 193 | for _ in 0..1000 { 194 | let v = sampler.sample(&mut rng); 195 | assert!(low <= v && v < high); 196 | let v = sampler.ind_sample(&mut rng); 197 | assert!(low <= v && v < high); 198 | } 199 | } 200 | )* 201 | }} 202 | } 203 | t!(i8, i16, i32, i64, isize, 204 | u8, u16, u32, u64, usize) 205 | } 206 | 207 | #[test] 208 | fn test_floats() { 209 | let mut rng = ::test::rng(); 210 | macro_rules! t { 211 | ($($ty:ty),*) => {{ 212 | $( 213 | let v: &[($ty, $ty)] = &[(0.0, 100.0), 214 | (-1e35, -1e25), 215 | (1e-35, 1e-25), 216 | (-1e35, 1e35)]; 217 | for &(low, high) in v.iter() { 218 | let mut sampler: Range<$ty> = Range::new(low, high); 219 | for _ in 0..1000 { 220 | let v = sampler.sample(&mut rng); 221 | assert!(low <= v && v < high); 222 | let v = sampler.ind_sample(&mut rng); 223 | assert!(low <= v && v < high); 224 | } 225 | } 226 | )* 227 | }} 228 | } 229 | 230 | t!(f32, f64) 231 | } 232 | 233 | } 234 | -------------------------------------------------------------------------------- /rust/src/rand/src/rand_impls.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! The implementations of `Rand` for the built-in types. 12 | 13 | use std::char; 14 | use std::mem; 15 | 16 | use {Rand,Rng}; 17 | 18 | impl Rand for isize { 19 | #[inline] 20 | fn rand(rng: &mut R) -> isize { 21 | if mem::size_of::() == 4 { 22 | rng.gen::() as isize 23 | } else { 24 | rng.gen::() as isize 25 | } 26 | } 27 | } 28 | 29 | impl Rand for i8 { 30 | #[inline] 31 | fn rand(rng: &mut R) -> i8 { 32 | rng.next_u32() as i8 33 | } 34 | } 35 | 36 | impl Rand for i16 { 37 | #[inline] 38 | fn rand(rng: &mut R) -> i16 { 39 | rng.next_u32() as i16 40 | } 41 | } 42 | 43 | impl Rand for i32 { 44 | #[inline] 45 | fn rand(rng: &mut R) -> i32 { 46 | rng.next_u32() as i32 47 | } 48 | } 49 | 50 | impl Rand for i64 { 51 | #[inline] 52 | fn rand(rng: &mut R) -> i64 { 53 | rng.next_u64() as i64 54 | } 55 | } 56 | 57 | #[cfg(feature = "i128_support")] 58 | impl Rand for i128 { 59 | #[inline] 60 | fn rand(rng: &mut R) -> i128 { 61 | rng.gen::() as i128 62 | } 63 | } 64 | 65 | impl Rand for usize { 66 | #[inline] 67 | fn rand(rng: &mut R) -> usize { 68 | if mem::size_of::() == 4 { 69 | rng.gen::() as usize 70 | } else { 71 | rng.gen::() as usize 72 | } 73 | } 74 | } 75 | 76 | impl Rand for u8 { 77 | #[inline] 78 | fn rand(rng: &mut R) -> u8 { 79 | rng.next_u32() as u8 80 | } 81 | } 82 | 83 | impl Rand for u16 { 84 | #[inline] 85 | fn rand(rng: &mut R) -> u16 { 86 | rng.next_u32() as u16 87 | } 88 | } 89 | 90 | impl Rand for u32 { 91 | #[inline] 92 | fn rand(rng: &mut R) -> u32 { 93 | rng.next_u32() 94 | } 95 | } 96 | 97 | impl Rand for u64 { 98 | #[inline] 99 | fn rand(rng: &mut R) -> u64 { 100 | rng.next_u64() 101 | } 102 | } 103 | 104 | #[cfg(feature = "i128_support")] 105 | impl Rand for u128 { 106 | #[inline] 107 | fn rand(rng: &mut R) -> u128 { 108 | ((rng.next_u64() as u128) << 64) | (rng.next_u64() as u128) 109 | } 110 | } 111 | 112 | 113 | macro_rules! float_impls { 114 | ($mod_name:ident, $ty:ty, $mantissa_bits:expr, $method_name:ident) => { 115 | mod $mod_name { 116 | use {Rand, Rng, Open01, Closed01}; 117 | 118 | const SCALE: $ty = (1u64 << $mantissa_bits) as $ty; 119 | 120 | impl Rand for $ty { 121 | /// Generate a floating point number in the half-open 122 | /// interval `[0,1)`. 123 | /// 124 | /// See `Closed01` for the closed interval `[0,1]`, 125 | /// and `Open01` for the open interval `(0,1)`. 126 | #[inline] 127 | fn rand(rng: &mut R) -> $ty { 128 | rng.$method_name() 129 | } 130 | } 131 | impl Rand for Open01<$ty> { 132 | #[inline] 133 | fn rand(rng: &mut R) -> Open01<$ty> { 134 | // add a small amount (specifically 2 bits below 135 | // the precision of f64/f32 at 1.0), so that small 136 | // numbers are larger than 0, but large numbers 137 | // aren't pushed to/above 1. 138 | Open01(rng.$method_name() + 0.25 / SCALE) 139 | } 140 | } 141 | impl Rand for Closed01<$ty> { 142 | #[inline] 143 | fn rand(rng: &mut R) -> Closed01<$ty> { 144 | // rescale so that 1.0 - epsilon becomes 1.0 145 | // precisely. 146 | Closed01(rng.$method_name() * SCALE / (SCALE - 1.0)) 147 | } 148 | } 149 | } 150 | } 151 | } 152 | float_impls! { f64_rand_impls, f64, 53, next_f64 } 153 | float_impls! { f32_rand_impls, f32, 24, next_f32 } 154 | 155 | impl Rand for char { 156 | #[inline] 157 | fn rand(rng: &mut R) -> char { 158 | // a char is 21 bits 159 | const CHAR_MASK: u32 = 0x001f_ffff; 160 | loop { 161 | // Rejection sampling. About 0.2% of numbers with at most 162 | // 21-bits are invalid codepoints (surrogates), so this 163 | // will succeed first go almost every time. 164 | match char::from_u32(rng.next_u32() & CHAR_MASK) { 165 | Some(c) => return c, 166 | None => {} 167 | } 168 | } 169 | } 170 | } 171 | 172 | impl Rand for bool { 173 | #[inline] 174 | fn rand(rng: &mut R) -> bool { 175 | rng.gen::() & 1 == 1 176 | } 177 | } 178 | 179 | macro_rules! tuple_impl { 180 | // use variables to indicate the arity of the tuple 181 | ($($tyvar:ident),* ) => { 182 | // the trailing commas are for the 1 tuple 183 | impl< 184 | $( $tyvar : Rand ),* 185 | > Rand for ( $( $tyvar ),* , ) { 186 | 187 | #[inline] 188 | fn rand(_rng: &mut R) -> ( $( $tyvar ),* , ) { 189 | ( 190 | // use the $tyvar's to get the appropriate number of 191 | // repeats (they're not actually needed) 192 | $( 193 | _rng.gen::<$tyvar>() 194 | ),* 195 | , 196 | ) 197 | } 198 | } 199 | } 200 | } 201 | 202 | impl Rand for () { 203 | #[inline] 204 | fn rand(_: &mut R) -> () { () } 205 | } 206 | tuple_impl!{A} 207 | tuple_impl!{A, B} 208 | tuple_impl!{A, B, C} 209 | tuple_impl!{A, B, C, D} 210 | tuple_impl!{A, B, C, D, E} 211 | tuple_impl!{A, B, C, D, E, F} 212 | tuple_impl!{A, B, C, D, E, F, G} 213 | tuple_impl!{A, B, C, D, E, F, G, H} 214 | tuple_impl!{A, B, C, D, E, F, G, H, I} 215 | tuple_impl!{A, B, C, D, E, F, G, H, I, J} 216 | tuple_impl!{A, B, C, D, E, F, G, H, I, J, K} 217 | tuple_impl!{A, B, C, D, E, F, G, H, I, J, K, L} 218 | 219 | macro_rules! array_impl { 220 | {$n:expr, $t:ident, $($ts:ident,)*} => { 221 | array_impl!{($n - 1), $($ts,)*} 222 | 223 | impl Rand for [T; $n] where T: Rand { 224 | #[inline] 225 | fn rand(_rng: &mut R) -> [T; $n] { 226 | [_rng.gen::<$t>(), $(_rng.gen::<$ts>()),*] 227 | } 228 | } 229 | }; 230 | {$n:expr,} => { 231 | impl Rand for [T; $n] { 232 | fn rand(_rng: &mut R) -> [T; $n] { [] } 233 | } 234 | }; 235 | } 236 | 237 | array_impl!{32, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,} 238 | 239 | impl Rand for Option { 240 | #[inline] 241 | fn rand(rng: &mut R) -> Option { 242 | if rng.gen() { 243 | Some(rng.gen()) 244 | } else { 245 | None 246 | } 247 | } 248 | } 249 | 250 | #[cfg(test)] 251 | mod tests { 252 | use {Rng, thread_rng, Open01, Closed01}; 253 | 254 | struct ConstantRng(u64); 255 | impl Rng for ConstantRng { 256 | fn next_u32(&mut self) -> u32 { 257 | let ConstantRng(v) = *self; 258 | v as u32 259 | } 260 | fn next_u64(&mut self) -> u64 { 261 | let ConstantRng(v) = *self; 262 | v 263 | } 264 | } 265 | 266 | #[test] 267 | fn floating_point_edge_cases() { 268 | // the test for exact equality is correct here. 269 | assert!(ConstantRng(0xffff_ffff).gen::() != 1.0); 270 | assert!(ConstantRng(0xffff_ffff_ffff_ffff).gen::() != 1.0); 271 | } 272 | 273 | #[test] 274 | fn rand_open() { 275 | // this is unlikely to catch an incorrect implementation that 276 | // generates exactly 0 or 1, but it keeps it sane. 277 | let mut rng = thread_rng(); 278 | for _ in 0..1_000 { 279 | // strict inequalities 280 | let Open01(f) = rng.gen::>(); 281 | assert!(0.0 < f && f < 1.0); 282 | 283 | let Open01(f) = rng.gen::>(); 284 | assert!(0.0 < f && f < 1.0); 285 | } 286 | } 287 | 288 | #[test] 289 | fn rand_closed() { 290 | let mut rng = thread_rng(); 291 | for _ in 0..1_000 { 292 | // strict inequalities 293 | let Closed01(f) = rng.gen::>(); 294 | assert!(0.0 <= f && f <= 1.0); 295 | 296 | let Closed01(f) = rng.gen::>(); 297 | assert!(0.0 <= f && f <= 1.0); 298 | } 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /rust/src/rand/src/read.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! A wrapper around any Read to treat it as an RNG. 12 | 13 | use std::io::{self, Read}; 14 | use std::mem; 15 | use Rng; 16 | 17 | /// An RNG that reads random bytes straight from a `Read`. This will 18 | /// work best with an infinite reader, but this is not required. 19 | /// 20 | /// # Panics 21 | /// 22 | /// It will panic if it there is insufficient data to fulfill a request. 23 | /// 24 | /// # Example 25 | /// 26 | /// ```rust 27 | /// use rand::{read, Rng}; 28 | /// 29 | /// let data = vec![1, 2, 3, 4, 5, 6, 7, 8]; 30 | /// let mut rng = read::ReadRng::new(&data[..]); 31 | /// println!("{:x}", rng.gen::()); 32 | /// ``` 33 | #[derive(Debug)] 34 | pub struct ReadRng { 35 | reader: R 36 | } 37 | 38 | impl ReadRng { 39 | /// Create a new `ReadRng` from a `Read`. 40 | pub fn new(r: R) -> ReadRng { 41 | ReadRng { 42 | reader: r 43 | } 44 | } 45 | } 46 | 47 | impl Rng for ReadRng { 48 | fn next_u32(&mut self) -> u32 { 49 | // This is designed for speed: reading a LE integer on a LE 50 | // platform just involves blitting the bytes into the memory 51 | // of the u32, similarly for BE on BE; avoiding byteswapping. 52 | let mut buf = [0; 4]; 53 | fill(&mut self.reader, &mut buf).unwrap(); 54 | unsafe { *(buf.as_ptr() as *const u32) } 55 | } 56 | fn next_u64(&mut self) -> u64 { 57 | // see above for explanation. 58 | let mut buf = [0; 8]; 59 | fill(&mut self.reader, &mut buf).unwrap(); 60 | unsafe { *(buf.as_ptr() as *const u64) } 61 | } 62 | fn fill_bytes(&mut self, v: &mut [u8]) { 63 | if v.len() == 0 { return } 64 | fill(&mut self.reader, v).unwrap(); 65 | } 66 | } 67 | 68 | fn fill(r: &mut Read, mut buf: &mut [u8]) -> io::Result<()> { 69 | while buf.len() > 0 { 70 | match try!(r.read(buf)) { 71 | 0 => return Err(io::Error::new(io::ErrorKind::Other, 72 | "end of file reached")), 73 | n => buf = &mut mem::replace(&mut buf, &mut [])[n..], 74 | } 75 | } 76 | Ok(()) 77 | } 78 | 79 | #[cfg(test)] 80 | mod test { 81 | use super::ReadRng; 82 | use Rng; 83 | 84 | #[test] 85 | fn test_reader_rng_u64() { 86 | // transmute from the target to avoid endianness concerns. 87 | let v = vec![0u8, 0, 0, 0, 0, 0, 0, 1, 88 | 0 , 0, 0, 0, 0, 0, 0, 2, 89 | 0, 0, 0, 0, 0, 0, 0, 3]; 90 | let mut rng = ReadRng::new(&v[..]); 91 | 92 | assert_eq!(rng.next_u64(), 1_u64.to_be()); 93 | assert_eq!(rng.next_u64(), 2_u64.to_be()); 94 | assert_eq!(rng.next_u64(), 3_u64.to_be()); 95 | } 96 | #[test] 97 | fn test_reader_rng_u32() { 98 | let v = vec![0u8, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3]; 99 | let mut rng = ReadRng::new(&v[..]); 100 | 101 | assert_eq!(rng.next_u32(), 1_u32.to_be()); 102 | assert_eq!(rng.next_u32(), 2_u32.to_be()); 103 | assert_eq!(rng.next_u32(), 3_u32.to_be()); 104 | } 105 | #[test] 106 | fn test_reader_rng_fill_bytes() { 107 | let v = [1u8, 2, 3, 4, 5, 6, 7, 8]; 108 | let mut w = [0u8; 8]; 109 | 110 | let mut rng = ReadRng::new(&v[..]); 111 | rng.fill_bytes(&mut w); 112 | 113 | assert!(v == w); 114 | } 115 | 116 | #[test] 117 | #[should_panic] 118 | fn test_reader_rng_insufficient_bytes() { 119 | let mut rng = ReadRng::new(&[][..]); 120 | let mut v = [0u8; 3]; 121 | rng.fill_bytes(&mut v); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /rust/src/rand/src/reseeding.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! A wrapper around another RNG that reseeds it after it 12 | //! generates a certain number of random bytes. 13 | 14 | use std::default::Default; 15 | 16 | use {Rng, SeedableRng}; 17 | 18 | /// How many bytes of entropy the underling RNG is allowed to generate 19 | /// before it is reseeded 20 | const DEFAULT_GENERATION_THRESHOLD: u64 = 32 * 1024; 21 | 22 | /// A wrapper around any RNG which reseeds the underlying RNG after it 23 | /// has generated a certain number of random bytes. 24 | #[derive(Debug)] 25 | pub struct ReseedingRng { 26 | rng: R, 27 | generation_threshold: u64, 28 | bytes_generated: u64, 29 | /// Controls the behaviour when reseeding the RNG. 30 | pub reseeder: Rsdr, 31 | } 32 | 33 | impl> ReseedingRng { 34 | /// Create a new `ReseedingRng` with the given parameters. 35 | /// 36 | /// # Arguments 37 | /// 38 | /// * `rng`: the random number generator to use. 39 | /// * `generation_threshold`: the number of bytes of entropy at which to reseed the RNG. 40 | /// * `reseeder`: the reseeding object to use. 41 | pub fn new(rng: R, generation_threshold: u64, reseeder: Rsdr) -> ReseedingRng { 42 | ReseedingRng { 43 | rng: rng, 44 | generation_threshold: generation_threshold, 45 | bytes_generated: 0, 46 | reseeder: reseeder 47 | } 48 | } 49 | 50 | /// Reseed the internal RNG if the number of bytes that have been 51 | /// generated exceed the threshold. 52 | pub fn reseed_if_necessary(&mut self) { 53 | if self.bytes_generated >= self.generation_threshold { 54 | self.reseeder.reseed(&mut self.rng); 55 | self.bytes_generated = 0; 56 | } 57 | } 58 | } 59 | 60 | 61 | impl> Rng for ReseedingRng { 62 | fn next_u32(&mut self) -> u32 { 63 | self.reseed_if_necessary(); 64 | self.bytes_generated += 4; 65 | self.rng.next_u32() 66 | } 67 | 68 | fn next_u64(&mut self) -> u64 { 69 | self.reseed_if_necessary(); 70 | self.bytes_generated += 8; 71 | self.rng.next_u64() 72 | } 73 | 74 | fn fill_bytes(&mut self, dest: &mut [u8]) { 75 | self.reseed_if_necessary(); 76 | self.bytes_generated += dest.len() as u64; 77 | self.rng.fill_bytes(dest) 78 | } 79 | } 80 | 81 | impl, Rsdr: Reseeder + Default> 82 | SeedableRng<(Rsdr, S)> for ReseedingRng { 83 | fn reseed(&mut self, (rsdr, seed): (Rsdr, S)) { 84 | self.rng.reseed(seed); 85 | self.reseeder = rsdr; 86 | self.bytes_generated = 0; 87 | } 88 | 89 | /// Create a new `ReseedingRng` from the given reseeder and 90 | /// seed. This uses a default value for `generation_threshold`. 91 | fn from_seed((rsdr, seed): (Rsdr, S)) -> ReseedingRng { 92 | ReseedingRng { 93 | rng: SeedableRng::from_seed(seed), 94 | generation_threshold: DEFAULT_GENERATION_THRESHOLD, 95 | bytes_generated: 0, 96 | reseeder: rsdr 97 | } 98 | } 99 | } 100 | 101 | /// Something that can be used to reseed an RNG via `ReseedingRng`. 102 | /// 103 | /// # Example 104 | /// 105 | /// ```rust 106 | /// use rand::{Rng, SeedableRng, StdRng}; 107 | /// use rand::reseeding::{Reseeder, ReseedingRng}; 108 | /// 109 | /// struct TickTockReseeder { tick: bool } 110 | /// impl Reseeder for TickTockReseeder { 111 | /// fn reseed(&mut self, rng: &mut StdRng) { 112 | /// let val = if self.tick {0} else {1}; 113 | /// rng.reseed(&[val]); 114 | /// self.tick = !self.tick; 115 | /// } 116 | /// } 117 | /// fn main() { 118 | /// let rsdr = TickTockReseeder { tick: true }; 119 | /// 120 | /// let inner = StdRng::new().unwrap(); 121 | /// let mut rng = ReseedingRng::new(inner, 10, rsdr); 122 | /// 123 | /// // this will repeat, because it gets reseeded very regularly. 124 | /// let s: String = rng.gen_ascii_chars().take(100).collect(); 125 | /// println!("{}", s); 126 | /// } 127 | /// 128 | /// ``` 129 | pub trait Reseeder { 130 | /// Reseed the given RNG. 131 | fn reseed(&mut self, rng: &mut R); 132 | } 133 | 134 | /// Reseed an RNG using a `Default` instance. This reseeds by 135 | /// replacing the RNG with the result of a `Default::default` call. 136 | #[derive(Clone, Copy, Debug)] 137 | pub struct ReseedWithDefault; 138 | 139 | impl Reseeder for ReseedWithDefault { 140 | fn reseed(&mut self, rng: &mut R) { 141 | *rng = Default::default(); 142 | } 143 | } 144 | impl Default for ReseedWithDefault { 145 | fn default() -> ReseedWithDefault { ReseedWithDefault } 146 | } 147 | 148 | #[cfg(test)] 149 | mod test { 150 | use std::default::Default; 151 | use std::iter::repeat; 152 | use super::{ReseedingRng, ReseedWithDefault}; 153 | use {SeedableRng, Rng}; 154 | 155 | struct Counter { 156 | i: u32 157 | } 158 | 159 | impl Rng for Counter { 160 | fn next_u32(&mut self) -> u32 { 161 | self.i += 1; 162 | // very random 163 | self.i - 1 164 | } 165 | } 166 | impl Default for Counter { 167 | fn default() -> Counter { 168 | Counter { i: 0 } 169 | } 170 | } 171 | impl SeedableRng for Counter { 172 | fn reseed(&mut self, seed: u32) { 173 | self.i = seed; 174 | } 175 | fn from_seed(seed: u32) -> Counter { 176 | Counter { i: seed } 177 | } 178 | } 179 | type MyRng = ReseedingRng; 180 | 181 | #[test] 182 | fn test_reseeding() { 183 | let mut rs = ReseedingRng::new(Counter {i:0}, 400, ReseedWithDefault); 184 | 185 | let mut i = 0; 186 | for _ in 0..1000 { 187 | assert_eq!(rs.next_u32(), i % 100); 188 | i += 1; 189 | } 190 | } 191 | 192 | #[test] 193 | fn test_rng_seeded() { 194 | let mut ra: MyRng = SeedableRng::from_seed((ReseedWithDefault, 2)); 195 | let mut rb: MyRng = SeedableRng::from_seed((ReseedWithDefault, 2)); 196 | assert!(::test::iter_eq(ra.gen_ascii_chars().take(100), 197 | rb.gen_ascii_chars().take(100))); 198 | } 199 | 200 | #[test] 201 | fn test_rng_reseed() { 202 | let mut r: MyRng = SeedableRng::from_seed((ReseedWithDefault, 3)); 203 | let string1: String = r.gen_ascii_chars().take(100).collect(); 204 | 205 | r.reseed((ReseedWithDefault, 3)); 206 | 207 | let string2: String = r.gen_ascii_chars().take(100).collect(); 208 | assert_eq!(string1, string2); 209 | } 210 | 211 | const FILL_BYTES_V_LEN: usize = 13579; 212 | #[test] 213 | fn test_rng_fill_bytes() { 214 | let mut v = repeat(0u8).take(FILL_BYTES_V_LEN).collect::>(); 215 | ::test::rng().fill_bytes(&mut v); 216 | 217 | // Sanity test: if we've gotten here, `fill_bytes` has not infinitely 218 | // recursed. 219 | assert_eq!(v.len(), FILL_BYTES_V_LEN); 220 | 221 | // To test that `fill_bytes` actually did something, check that the 222 | // average of `v` is not 0. 223 | let mut sum = 0.0; 224 | for &x in v.iter() { 225 | sum += x as f64; 226 | } 227 | assert!(sum / v.len() as f64 != 0.0); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /tools/btoa.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 yvt 3 | * 4 | * This file is a part of hyper3d-envmapgen. Please read the license text that 5 | * comes with the source code for use conditions. 6 | */ 7 | const fs = require('fs'); 8 | const data = fs.readFileSync(process.argv[2]); 9 | const base64 = data.toString('base64'); 10 | process.stdout.write('const base64js = require("base64-js");\n'); 11 | process.stdout.write('module.exports = base64js.toByteArray("'); 12 | process.stdout.write(base64); 13 | process.stdout.write('");\n'); 14 | -------------------------------------------------------------------------------- /ts/image.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 yvt 3 | * 4 | * This file is a part of hyper3d-envmapgen. Please read the license text that 5 | * comes with the source code for use conditions. 6 | */ 7 | import { table } from './utils'; 8 | 9 | const $fr = Math.fround || ((x: number) => x); 10 | 11 | export enum ImageFormat { 12 | Srgbx8, 13 | Srgba8StraightAlpha, 14 | RgbaF32PremulAlpha, 15 | } 16 | 17 | export type ImageLike = HTMLImageElement | HTMLCanvasElement | HTMLVideoElement; 18 | 19 | export function coerceImageToSrgba8StraightAlpha( 20 | image: Uint8Array | ImageLike, 21 | width?: number, 22 | height?: number, 23 | ): Uint8Array { 24 | if (image instanceof Uint8Array) { 25 | return image; 26 | } 27 | 28 | if (!width || !height) { 29 | if (image instanceof HTMLImageElement) { 30 | width = image.naturalWidth; 31 | height = image.naturalHeight; 32 | } else if (image instanceof HTMLCanvasElement) { 33 | width = image.width; 34 | height = image.height; 35 | } else if (image instanceof HTMLVideoElement) { 36 | width = image.videoWidth; 37 | height = image.videoHeight; 38 | } 39 | } 40 | if (!width || !height) { 41 | throw new Error("Cannot guess the size of the image"); 42 | } 43 | 44 | const canvas = document.createElement('canvas'); 45 | canvas.width = width; 46 | canvas.height = height; 47 | const context = canvas.getContext('2d')!; 48 | context.drawImage(image, 0, 0, width, height); 49 | return context.getImageData(0, 0, width, height).data; 50 | } 51 | 52 | export function coerceToRgbaF32PremulAlphaFrom( 53 | image: Uint8Array | Float32Array | ImageLike, 54 | format: ImageFormat, 55 | width?: number, 56 | height?: number, 57 | ): Float32Array { 58 | switch (format) { 59 | case ImageFormat.RgbaF32PremulAlpha: 60 | if (image instanceof Float32Array) { 61 | return image; 62 | } else { 63 | throw new Error("Invalid type for RgbaF32PremulAlpha"); 64 | } 65 | case ImageFormat.Srgba8StraightAlpha: { 66 | if (image instanceof Float32Array) { 67 | throw new Error("Invalid type for RgbaF32PremulAlpha"); 68 | } 69 | if (!(image instanceof Uint8Array)) { 70 | image = coerceImageToSrgba8StraightAlpha(image, width, height); 71 | } 72 | const u8 = image; 73 | const f32 = new Float32Array(u8.length); 74 | const table = SRGB_DECODE_TABLE; 75 | for (let i = 0; i < u8.length; i += 4) { 76 | const a = u8[i + 3] * (1 / 255) + 1e-20; 77 | f32[i] = table[u8[i]] * a; 78 | f32[i + 1] = table[u8[i + 1]] * a; 79 | f32[i + 2] = table[u8[i + 2]] * a; 80 | f32[i + 3] = a; 81 | } 82 | return f32; 83 | } 84 | case ImageFormat.Srgbx8: { 85 | if (image instanceof Float32Array) { 86 | throw new Error("Invalid type for RgbaF32PremulAlpha"); 87 | } 88 | if (!(image instanceof Uint8Array)) { 89 | image = coerceImageToSrgba8StraightAlpha(image, width, height); 90 | } 91 | const u8 = image; 92 | const f32 = new Float32Array(u8.length); 93 | const table = SRGB_DECODE_TABLE; 94 | for (let i = 0; i < u8.length; i += 4) { 95 | f32[i] = table[u8[i]]; 96 | f32[i + 1] = table[u8[i + 1]]; 97 | f32[i + 2] = table[u8[i + 2]]; 98 | f32[i + 3] = 1; 99 | } 100 | return f32; 101 | } 102 | default: 103 | throw new Error(`Invalid ImageFormat: ${format}`); 104 | } 105 | } 106 | 107 | export function coerceRgbaF32PremulAlphaTo( 108 | image: Float32Array, 109 | format: ImageFormat, 110 | ): Uint8Array | Float32Array { 111 | switch (format) { 112 | case ImageFormat.RgbaF32PremulAlpha: 113 | return image; 114 | case ImageFormat.Srgba8StraightAlpha: 115 | case ImageFormat.Srgbx8: { 116 | const u8 = new Uint8Array(image.length); 117 | const table = SRGB_ENCODE_TABLE; 118 | for (let i = 0; i < u8.length; i += 4) { 119 | const a = image[i + 3]; 120 | const scale = $fr(65000 / a); 121 | u8[i] = table[$fr(image[i] * scale) & 0xffff]; 122 | u8[i + 1] = table[$fr(image[i + 1] * scale) & 0xffff]; 123 | u8[i + 2] = table[$fr(image[i + 2] * scale) & 0xffff]; 124 | u8[i + 3] = $fr(a * 255) + 0.5; 125 | } 126 | return u8; 127 | } 128 | default: 129 | throw new Error(`Invalid ImageFormat: ${format}`); 130 | } 131 | } 132 | 133 | export const SRGB_DECODE_TABLE = new Float32Array(table(256, i => { 134 | i /= 255; 135 | return (i < 0.04045 ? i / 12.92 : ((i + 0.055) / 1.055) ** 2.4); 136 | })); 137 | 138 | export const SRGB_ENCODE_TABLE = new Uint8Array(table(65536, i => { 139 | i = Math.min(i / 65000, 1); 140 | return (i < 0.0031308 ? 12.92 * i : 1.055 * (i ** (1 / 2.4)) - 0.055) * 255 + 0.5 | 0; 141 | })); 142 | 143 | export function resampleRgbF32( 144 | inImage: Float32Array, 145 | inWidth: number, 146 | inHeight: number, 147 | outWidth: number, 148 | outHeight: number, 149 | ): Float32Array 150 | { 151 | const outImage = new Float32Array(outWidth * outHeight * 4); 152 | 153 | if (inWidth < outWidth || inHeight < outHeight) { 154 | throw new Error("Does not support magnification yet"); 155 | } 156 | 157 | let outIndex = 0; 158 | 159 | if (inWidth === outWidth * 2 && inHeight === outHeight * 2) { 160 | for (let y = 0; y < outHeight; ++y) { 161 | let inIndex1 = (y * 2 * inWidth) << 2; 162 | let inIndex2 = inIndex1 + (inWidth << 2); 163 | for (let x = 0; x < outWidth; ++x) { 164 | let r1 = inImage[inIndex1], g1 = inImage[inIndex1 + 1], 165 | b1 = inImage[inIndex1 + 2], a1 = inImage[inIndex1 + 3]; 166 | let r2 = inImage[inIndex1 + 4], g2 = inImage[inIndex1 + 4 + 1], 167 | b2 = inImage[inIndex1 + 4 + 2], a2 = inImage[inIndex1 + 4 + 3]; 168 | let r3 = inImage[inIndex2], g3 = inImage[inIndex2 + 1], 169 | b3 = inImage[inIndex2 + 2], a3 = inImage[inIndex2 + 3]; 170 | let r4 = inImage[inIndex2 + 4], g4 = inImage[inIndex2 + 4 + 1], 171 | b4 = inImage[inIndex2 + 4 + 2], a4 = inImage[inIndex2 + 4 + 3]; 172 | 173 | outImage[outIndex] = $fr($fr(r1 + r2) + $fr(r3 + r4)) * 0.25; 174 | outImage[outIndex + 1] = $fr($fr(g1 + g2) + $fr(g3 + g4)) * 0.25; 175 | outImage[outIndex + 2] = $fr($fr(b1 + b2) + $fr(b3 + b4)) * 0.25; 176 | outImage[outIndex + 3] = $fr($fr(a1 + a2) + $fr(a3 + a4)) * 0.25; 177 | 178 | outIndex += 4; inIndex1 += 8; inIndex2 += 8; 179 | } 180 | } 181 | } else { 182 | let inY = $fr(0.5 * inHeight / outHeight - 0.5); 183 | const inDY = $fr(inHeight / outHeight); 184 | let inX = $fr(0.5 * inWidth / outWidth - 0.5); 185 | const inDX = $fr(inWidth / outWidth); 186 | for (let y = 0; y < outHeight; ++y) { 187 | const inBase1 = Math.floor(inY) * inWidth << 2; 188 | const inBase2 = Math.ceil(inY) * inWidth << 2; 189 | const facY2 = $fr(inY - Math.floor(inY)); 190 | const facY1 = $fr(1 - facY2); 191 | let inX2 = inX; 192 | for (let x = 0; x < outWidth; ++x) { 193 | // Linear interpolation 194 | const x1 = Math.floor(inX2) << 2; 195 | const x2 = Math.ceil(inX2) << 2; 196 | const facX2 = $fr(inX2 - Math.floor(inX2)); 197 | const facX1 = $fr(1 - facX2); 198 | 199 | let r1 = inImage[inBase1 + x1], g1 = inImage[inBase1 + x1 + 1], 200 | b1 = inImage[inBase1 + x1 + 2], a1 = inImage[inBase1 + x1 + 3]; 201 | let r2 = inImage[inBase1 + x2], g2 = inImage[inBase1 + x2 + 1], 202 | b2 = inImage[inBase1 + x2 + 2], a2 = inImage[inBase1 + x2 + 3]; 203 | let r3 = inImage[inBase2 + x1], g3 = inImage[inBase2 + x1 + 1], 204 | b3 = inImage[inBase2 + x1 + 2], a3 = inImage[inBase2 + x1 + 3]; 205 | let r4 = inImage[inBase2 + x2], g4 = inImage[inBase2 + x2 + 1], 206 | b4 = inImage[inBase2 + x2 + 2], a4 = inImage[inBase2 + x2 + 3]; 207 | 208 | r1 = $fr($fr(r1 * facY1) + $fr(r3 * facY2)); 209 | g1 = $fr($fr(g1 * facY1) + $fr(g3 * facY2)); 210 | b1 = $fr($fr(b1 * facY1) + $fr(b3 * facY2)); 211 | a1 = $fr($fr(a1 * facY1) + $fr(a3 * facY2)); 212 | 213 | r2 = $fr($fr(r2 * facY1) + $fr(r4 * facY2)); 214 | g2 = $fr($fr(g2 * facY1) + $fr(g4 * facY2)); 215 | b2 = $fr($fr(b2 * facY1) + $fr(b4 * facY2)); 216 | a2 = $fr($fr(a2 * facY1) + $fr(a4 * facY2)); 217 | 218 | r1 = $fr($fr(r1 * facX1) + $fr(r2 * facX2)); 219 | g1 = $fr($fr(g1 * facX1) + $fr(g2 * facX2)); 220 | b1 = $fr($fr(b1 * facX1) + $fr(b2 * facX2)); 221 | a1 = $fr($fr(a1 * facX1) + $fr(a2 * facX2)); 222 | 223 | outImage[outIndex] = r1; 224 | outImage[outIndex + 1] = g1; 225 | outImage[outIndex + 2] = b1; 226 | outImage[outIndex + 3] = a1; 227 | 228 | outIndex += 4; 229 | inX2 += inDX; 230 | inX2 = $fr(inX2); 231 | } 232 | inY += inDY; 233 | inY = $fr(inY); 234 | } 235 | } 236 | 237 | return outImage; 238 | } 239 | -------------------------------------------------------------------------------- /ts/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 yvt 3 | * 4 | * This file is a part of hyper3d-envmapgen. Please read the license text that 5 | * comes with the source code for use conditions. 6 | */ 7 | export { CoreOptions, CoreInstance, getGlobalCoreModule, getCoreWasmBlob } from './wasm'; 8 | export { ImageFormat } from './image'; 9 | export { LtasgOptions, LtasgBlur } from './ltasgblur'; 10 | -------------------------------------------------------------------------------- /ts/ltasgblur.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 yvt 3 | * 4 | * This file is a part of hyper3d-envmapgen. Please read the license text that 5 | * comes with the source code for use conditions. 6 | */ 7 | import { CoreInstance, CoreOptions } from './wasm'; 8 | import { 9 | ImageLike, ImageFormat, coerceToRgbaF32PremulAlphaFrom, coerceRgbaF32PremulAlphaTo, 10 | resampleRgbF32, 11 | } from './image'; 12 | import { table } from './utils'; 13 | 14 | export interface LtasgOptions 15 | { 16 | core?: Readonly; 17 | 18 | /** The input image size. */ 19 | imageSize: number; 20 | 21 | /** 22 | * The σ (standard deviation) value for each generated mip level. 23 | * 24 | * Large values (> 0.2) significantly increase the processing time in the 25 | * order of `O(σ²)` since the assumption that LTASG makes is no longer valid 26 | * under large σ values and it has to realize a large blur by a repeated 27 | * application of small-sized blurs. 28 | */ 29 | mipLevelSigmas: ArrayLike; 30 | 31 | /** 32 | * The minimum number of passes. Specify `1` for best performance, and 33 | * `2` or `3` for best quality. A larger value usually hurts both of 34 | * performance and quality. 35 | * 36 | * Defaults to `2`. 37 | */ 38 | minNumPasses?: number; 39 | 40 | /** 41 | * (Advanced parameter) The resolution of the kernel. Defaults to `2`. 42 | */ 43 | kernelResolution?: number; 44 | 45 | /** 46 | * (Advanced parameter) Specifies the size of the Gaussian kernel by the 47 | * ratio to the σ value. Defaults to `3`. 48 | */ 49 | kernelWidth?: number; 50 | } 51 | 52 | function generateGaussianKernel(radius: number, sigma: number): Float32Array { 53 | const v = new Float32Array(radius * 2 + 1); 54 | 55 | let sum = 0; 56 | for (let i = 0; i <= radius * 2; ++i) { 57 | sum += (v[i] = Math.exp(-0.5 * ((i - radius) / sigma) ** 2)); 58 | } 59 | 60 | // normalize 61 | const scale = 1 / sum; 62 | for (let i = 0; i < v.length; ++i) { 63 | v[i] *= scale; 64 | } 65 | 66 | return v; 67 | } 68 | 69 | /** 70 | * Provides a linear-time approximate spherical Gaussian blur algorithm. 71 | */ 72 | export class LtasgBlur { 73 | readonly core: CoreInstance; 74 | 75 | readonly size: number; 76 | private readonly plan: { 77 | kernel: Float32Array, 78 | kernelScale: number; 79 | numPasses: number; 80 | }[] = []; 81 | 82 | /** 83 | * Asynchronous constructor of `LtasgBlur`. 84 | */ 85 | static async create(options: Readonly): Promise { 86 | const core = await CoreInstance.create(options.core); 87 | return new LtasgBlur({ ... options, core }); 88 | } 89 | 90 | /** 91 | * Synchronous constructor of `LtasgBlur`. `options.core`, 92 | * `options.core.module` and `options.core.instance` must not be `null` or 93 | * `undefined` if this constructor is called from the main thread. 94 | */ 95 | constructor(options: Readonly) { 96 | this.core = new CoreInstance(options.core); 97 | this.size = options.imageSize | 0; 98 | const minNumPasses = (options.minNumPasses || 2) | 0; 99 | const kernelWidth = options.kernelWidth || 3; 100 | const kernelResolution = options.kernelResolution || 2; 101 | 102 | let lastVariance = 0; 103 | 104 | const levels = options.mipLevelSigmas; 105 | for (let i = 0; i < levels.length; ++i) { 106 | const size = (this.size + (1 << i) - 1) >> i; 107 | 108 | const desiredSigma = levels[i]; 109 | const desiredVar = desiredSigma * desiredSigma; 110 | const residueVar = desiredVar - lastVariance; 111 | if (residueVar < 0) { 112 | throw new Error("mipLevelSigmas must be a monotonically increasing sequence"); 113 | } 114 | 115 | lastVariance = desiredVar; 116 | 117 | // Upper bound of blur amount that can be applied by a single run of 118 | // `ltasg_single(..., {0, 1, 2}, ...)` 119 | const sigmaLimit = 0.5 / kernelWidth; 120 | const numPasses = Math.max(Math.ceil(residueVar / (sigmaLimit * sigmaLimit)), minNumPasses); 121 | const levelSigma = Math.sqrt(residueVar / numPasses) * size; 122 | 123 | const kernelRadius = Math.floor(levelSigma * kernelResolution * kernelWidth); 124 | const kernelScale = 1 / kernelResolution; 125 | 126 | const kernel = generateGaussianKernel( 127 | kernelRadius, levelSigma * kernelResolution, 128 | ); 129 | 130 | this.plan.push({ 131 | kernel, 132 | kernelScale, 133 | numPasses, 134 | }); 135 | } 136 | } 137 | 138 | /** 139 | * Apply the blur on the cube map image. 140 | * 141 | * @return The processed mipmapped cube map image. 142 | */ 143 | process( 144 | input: ArrayLike, 145 | inFormat: ImageFormat, 146 | outFormat: ImageFormat, 147 | ): Float32Array[][] | Uint8Array[][] { 148 | if (input.length < 6) { 149 | throw new Error("input.length must be ≥ 6"); 150 | } 151 | 152 | const {core, plan, size} = this; 153 | 154 | // Coerce to `RgbaF32PremulAlpha` 155 | const baseLevel = table(6, i => { 156 | const img = input[i]; 157 | let f32 = coerceToRgbaF32PremulAlphaFrom(img, inFormat, size, size); 158 | 159 | // Make it an unique object 160 | if (f32 === img) { 161 | f32 = new Float32Array(f32); 162 | } 163 | 164 | if (f32.length < size * size * 4) { 165 | throw new Error("One of the input images is too small"); 166 | } 167 | 168 | return f32; 169 | }); 170 | 171 | // Generate levels 172 | const levels: (Float32Array | Uint8Array)[][] = []; 173 | let currentLevel = baseLevel; 174 | let currentSize = size; 175 | 176 | for (let i = 0; i < plan.length; ++i) { 177 | let newSize = currentSize; 178 | if (i > 0) { 179 | newSize = (newSize + 1) >> 1; 180 | // Resample from the previous mip level 181 | currentLevel = currentLevel.map(image => resampleRgbF32( 182 | image, currentSize, currentSize, newSize, newSize, 183 | )); 184 | } 185 | currentSize = newSize; 186 | 187 | const {kernel, kernelScale, numPasses} = plan[i]; 188 | core.ltasg( 189 | currentLevel, 190 | currentLevel, 191 | currentSize, 192 | ImageFormat.RgbaF32PremulAlpha, 193 | kernel, 194 | kernelScale, 195 | numPasses, 196 | ); 197 | 198 | if (outFormat != ImageFormat.RgbaF32PremulAlpha) { 199 | levels.push(currentLevel.map(image => coerceRgbaF32PremulAlphaTo(image, outFormat))); 200 | } else { 201 | levels.push(currentLevel); 202 | } 203 | } 204 | 205 | return levels as any; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /ts/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 yvt 3 | * 4 | * This file is a part of hyper3d-envmapgen. Please read the license text that 5 | * comes with the source code for use conditions. 6 | */ -------------------------------------------------------------------------------- /ts/utils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 yvt 3 | * 4 | * This file is a part of hyper3d-envmapgen. Please read the license text that 5 | * comes with the source code for use conditions. 6 | */ 7 | 8 | export function table(length: number, cb: (i: number) => T): T[] { 9 | const result: T[] = []; 10 | for (let i = 0; i < length; ++i) { 11 | result.push(cb(i)); 12 | } 13 | return result; 14 | } 15 | -------------------------------------------------------------------------------- /ts/wasm-blob.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 yvt 3 | * 4 | * This file is a part of hyper3d-envmapgen. Please read the license text that 5 | * comes with the source code for use conditions. 6 | */ 7 | declare const blob: Uint8Array; 8 | export default blob; 9 | -------------------------------------------------------------------------------- /ts/wasm.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 yvt 3 | * 4 | * This file is a part of hyper3d-envmapgen. Please read the license text that 5 | * comes with the source code for use conditions. 6 | */ 7 | import { ImageFormat } from './image'; 8 | 9 | export type Ptr = number; 10 | 11 | /** 12 | * Options relevant to the creation of the core WebAssembly module. 13 | */ 14 | export interface CoreOptions { 15 | /** 16 | * An optional pre-compiled or cached WebAssembly module, created from 17 | * `hyperenvmap_wasm.wasm`. 18 | * 19 | * If omitted, a global module will be created automatically by calling 20 | * `getGlobalCoreModule` or `getGlobalCoreModulePromise`. 21 | */ 22 | module?: WebAssembly.Module; 23 | 24 | /** 25 | * An optional pre-created or cached WebAssembly instance. 26 | */ 27 | instance?: WebAssembly.Instance; 28 | } 29 | 30 | /** The API exported by `hyperenvmap_wasm.wasm`. */ 31 | export interface CoreExports { 32 | memory: WebAssembly.Memory; 33 | 34 | emg_init(): void; 35 | 36 | emg_malloc(size: number): Ptr; 37 | emg_free(ptr: Ptr): void; 38 | 39 | emg_ltasg_single( 40 | out_faces: Ptr, 41 | in_faces: Ptr, 42 | size: number, 43 | kernel: Ptr, 44 | kernel_size: number, 45 | kernel_scale: number, 46 | phase: number, 47 | ): void; 48 | } 49 | 50 | let globalModule: WebAssembly.Module | null = null; 51 | let globalModulePromise: Promise | null = null; 52 | 53 | /** 54 | * Retrieves the built-in core WebAssembly binary module. Can be used to 55 | * customize the initialization behavior. 56 | */ 57 | export function getCoreWasmBlob(): Uint8Array { 58 | return require('./wasm-blob'); 59 | } 60 | 61 | /** 62 | * Retrieves the compiled global WebAssembly module. Triggers a synchronous 63 | * compilation on first use. 64 | */ 65 | export function getGlobalCoreModule(): WebAssembly.Module { 66 | if (!globalModule) { 67 | globalModule = new WebAssembly.Module(getCoreWasmBlob()); 68 | } 69 | return globalModule; 70 | } 71 | 72 | /** 73 | * Retrieves the compiled global WebAssembly module. 74 | */ 75 | export function getGlobalCoreModulePromise(): Promise { 76 | if (!globalModulePromise) { 77 | globalModulePromise = WebAssembly.compile(getCoreWasmBlob()); 78 | } 79 | return globalModulePromise; 80 | } 81 | 82 | /** 83 | * Provides a JavaScript interface to the core services. 84 | */ 85 | export class CoreInstance implements Readonly { 86 | readonly module: WebAssembly.Module; 87 | readonly instance: WebAssembly.Instance; 88 | 89 | /** 90 | * Asynchronous constructor of `CoreInstance`. 91 | */ 92 | static async create(options: Readonly = {}): Promise { 93 | const module = options.module || await getGlobalCoreModulePromise(); 94 | const instance = options.instance || await WebAssembly.instantiate(module); 95 | return new CoreInstance({ ... options, module, instance }); 96 | } 97 | 98 | /** 99 | * Synchronous constructor of `CoreInstance`. `options.module` and 100 | * `options.instance` must not be `null` or `undefined` if this 101 | * constructor is called from the main thread. 102 | */ 103 | constructor(options: Readonly = {}) { 104 | this.module = options.module || getGlobalCoreModule(); 105 | this.instance = options.instance || new WebAssembly.Instance(this.module); 106 | 107 | const emg: CoreExports = this.instance.exports; 108 | emg.emg_init(); 109 | } 110 | 111 | /** 112 | * Applies a linear-time approximate spherical Gaussian blur on a given 113 | * cube map image. 114 | * 115 | * `C` is the number of elements per pixel and fixed at `4` in the current 116 | * version. 117 | * 118 | * @param inFaces The input cube map image. Must have at least 6 elements 119 | * and each one must be at least `(size ** 2) * C` long. 120 | * @param outFaces The output cube map image. Must have at least 6 elements 121 | * and each one must be at least `(size ** 2) * C` long. 122 | * You can specfify the same value as `inFaces` for in-place 123 | * operation. 124 | * @param size The length of each side of the cube map, in pixels. 125 | * @param format The image format. Must be `RgbaF32PremulAlpha`. 126 | * @param kernel The convolution kernel. Must be odd-sized. 127 | * @param kernelScale The spatial scale of the kernel. See `ltasgblur.rs` for 128 | * more info. 129 | * @param numPasses Specifies how many times a Gaussian blur is applied on 130 | * the image. It is more efficient to use this parameter 131 | * than calling this function for multiple times. 132 | */ 133 | ltasg( 134 | inFaces: ArrayLike, 135 | outFaces: ArrayLike, 136 | size: number, 137 | format: ImageFormat, 138 | kernel: Float32Array, 139 | kernelScale: number, 140 | numPasses: number, 141 | ): void { 142 | // Fail-fast 143 | if (inFaces.length < 6) { 144 | throw new Error("inFaces.length ≥ 6"); 145 | } 146 | if (outFaces.length < 6) { 147 | throw new Error("outFaces.length must be ≥ 6"); 148 | } 149 | 150 | size |= 0; // coerce to integer 151 | numPasses |= 0; // coerce to integer 152 | 153 | const pixels = size * size; 154 | const channels = 4; 155 | const elements = pixels * channels; 156 | 157 | for (let i = 0; i < 6; ++i) { 158 | if (inFaces[i].length < elements) { 159 | throw new Error("∀i∈ℕ.(0 ≤ i < 6 ⇒ inFaces[i].length ≥ elements)"); 160 | } 161 | if (outFaces[i].length < elements) { 162 | throw new Error("∀i∈ℕ.(0 ≤ i < 6 ⇒ outFaces[i].length ≥ elements)"); 163 | } 164 | } 165 | 166 | if (format != ImageFormat.RgbaF32PremulAlpha) { 167 | throw new Error("format must be RgbaF32PremulAlpha"); 168 | } 169 | 170 | if ((kernel.length & 1) === 0) { 171 | throw new Error("kernel must be odd-sized"); 172 | } 173 | if (!(kernelScale > 0)) { 174 | throw new Error("kernelScale must be a positive number"); 175 | } 176 | const kernelRadius = kernel.length >> 1; 177 | if (!(size > kernelRadius * kernelScale * 1.8 /* ≈ √3 */)) { 178 | throw new Error("kernel is too large compared to the image size"); 179 | } 180 | 181 | if (size > 32768) { 182 | throw new Error("The image is too large"); 183 | } 184 | if (numPasses < 0) { 185 | throw new Error("numPasses must be a positive number"); 186 | } 187 | 188 | // Allocate buffers 189 | // - `elements * sizeof::() * 6 * 2` bytes for input/output passing 190 | // and temporary (ping-pong buffer) 191 | // - `kernel.length * sizeof::()` for kernel 192 | const emg: CoreExports = this.instance.exports; 193 | const bufferLen = elements * 48 + kernel.length * 4; 194 | const pBuffer = emg.emg_malloc(bufferLen); 195 | 196 | { 197 | // Increase the memory allocation if needed 198 | const extra = ((pBuffer + bufferLen + 0xffff) >> 16) - emg.memory.buffer.byteLength; 199 | if (extra > 0) { 200 | emg.memory.grow(extra); 201 | } 202 | } 203 | 204 | let pImages1 = pBuffer; 205 | let pImages2 = pBuffer + elements * 4 * 6; 206 | const pKernel = pImages2 + elements * 4 * 6; 207 | 208 | // Upload the inputs 209 | for (let i = 0; i < 6; ++i) { 210 | new Float32Array(emg.memory.buffer, pImages1 + i * (elements * 4)) 211 | .set(inFaces[i]); 212 | } 213 | new Float32Array(emg.memory.buffer, pKernel).set(kernel); 214 | 215 | // Let's get this show on the road 🍎 216 | for (let i = 0; i < numPasses; ++i) { 217 | for (let k = 0; k < 3; ++k) { 218 | // `pImages1` → `pImages2` 219 | emg.emg_ltasg_single(pImages2, pImages1, size, pKernel, kernel.length, kernelScale, k); 220 | 221 | // Swap buffers 222 | let t = pImages1; 223 | pImages1 = pImages2; 224 | pImages2 = t; 225 | } 226 | } 227 | 228 | // Retrieve the outputs 229 | for (let i = 0; i < 6; ++i) { 230 | outFaces[i].set(new Float32Array(emg.memory.buffer, pImages1 + i * (elements * 4), elements)); 231 | } 232 | emg.emg_free(pBuffer); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "declaration": true, 5 | "noImplicitAny": true, 6 | "sourceMap": true, 7 | "strict": true, 8 | "strictNullChecks": true, 9 | "module": "CommonJS", 10 | "target": "es5", 11 | "jsx": "react", 12 | "moduleResolution": "Node", 13 | "lib": [ 14 | "ES5", 15 | "DOM", 16 | "ES2015.Collection", 17 | "ES2015.Promise", 18 | "ES2015.Core", 19 | ], 20 | "experimentalDecorators": true, 21 | }, 22 | "include": [ 23 | "ts/**/*" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /wasmbinding/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hyperenvmap_wasm" 3 | version = "0.1.0" 4 | authors = ["yvt "] 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | cgmath = "0.15.0" 11 | smallvec = "0.5.0" 12 | hyperenvmap = { path = "../rust" } 13 | -------------------------------------------------------------------------------- /wasmbinding/src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 yvt 3 | * 4 | * This file is a part of hyper3d-envmapgen. Please read the license text that 5 | * comes with the source code for use conditions. 6 | */ 7 | #![feature(allocator_api)] 8 | extern crate cgmath; 9 | extern crate hyperenvmap; 10 | extern crate smallvec; 11 | 12 | use std::heap::{Heap, Alloc, Layout}; 13 | use std::{ptr, mem}; 14 | use std::slice::{from_raw_parts, from_raw_parts_mut}; 15 | 16 | use smallvec::SmallVec; 17 | use cgmath::Vector4; 18 | 19 | use hyperenvmap::ltasgblur; 20 | 21 | #[no_mangle] 22 | pub unsafe fn emg_init() { 23 | // Force the initialization of the lazily initialized value. 24 | // (This must be done before `emg_malloc` in order to prevent memory 25 | // fragmentation.) 26 | &*hyperenvmap::cubemap::CUBE_FACE_INFOS; 27 | } 28 | 29 | #[no_mangle] 30 | pub unsafe fn emg_malloc(size: usize) -> *mut u8 { 31 | let layout = Layout::from_size_align(size + mem::size_of::(), 4).unwrap(); 32 | let p = Heap.alloc(layout.clone()).unwrap(); 33 | ptr::write(p as *mut Layout, layout); 34 | for i in 0..size / 4 { 35 | ptr::write( 36 | p.offset(mem::size_of::() as isize + (i * 4) as isize) as *mut u32, 37 | 0xdeadbeef, 38 | ); 39 | } 40 | p.offset(mem::size_of::() as isize) 41 | } 42 | 43 | #[no_mangle] 44 | pub unsafe fn emg_free(p: *mut u8) { 45 | let p = p.offset(-(mem::size_of::() as isize)); 46 | let layout = ptr::read(p as *mut _); 47 | Heap.dealloc(p, layout); 48 | } 49 | 50 | #[no_mangle] 51 | pub unsafe fn emg_ltasg_single( 52 | mut out_faces: *mut Vector4, 53 | mut in_faces: *const Vector4, 54 | size: usize, 55 | kernel: *const f32, 56 | kernel_size: usize, 57 | kernel_scale: f32, 58 | phase: usize, 59 | ) { 60 | ltasgblur::ltasg_single( 61 | (0..6) 62 | .map(|_| { 63 | let slice = from_raw_parts_mut(out_faces, size * size); 64 | out_faces = out_faces.offset((size * size) as isize); 65 | slice 66 | }) 67 | .collect::>() 68 | .as_mut_slice(), 69 | (0..6) 70 | .map(|_| { 71 | let slice = from_raw_parts(in_faces, size * size); 72 | in_faces = in_faces.offset((size * size) as isize); 73 | slice 74 | }) 75 | .collect::>() 76 | .as_slice(), 77 | size, 78 | from_raw_parts(kernel, kernel_size), 79 | kernel_scale, 80 | phase, 81 | ltasgblur::StandardCubeMapTrait, 82 | ); 83 | } 84 | --------------------------------------------------------------------------------