├── .gitignore ├── docs ├── error.png ├── classes.png ├── partitions.png └── README.md ├── Cargo.toml ├── examples ├── Cargo.toml └── src │ └── bin │ ├── bench.rs │ ├── measure.rs │ └── plot.rs ├── results └── error.gnuplot ├── pmj ├── Cargo.toml └── src │ └── lib.rs └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /results 3 | Cargo.lock 4 | *.sublime-* 5 | -------------------------------------------------------------------------------- /docs/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjb3d/pmj/HEAD/docs/error.png -------------------------------------------------------------------------------- /docs/classes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjb3d/pmj/HEAD/docs/classes.png -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "pmj", 4 | "examples", 5 | ] 6 | -------------------------------------------------------------------------------- /docs/partitions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjb3d/pmj/HEAD/docs/partitions.png -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pmj-examples" 3 | version = "0.1.0" 4 | authors = ["Simon Brown "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | pmj = { path = "../pmj" } 10 | rand = { version = "0.8.5", features = ["small_rng"] } 11 | -------------------------------------------------------------------------------- /results/error.gnuplot: -------------------------------------------------------------------------------- 1 | set logscale xy 2 | set terminal png 3 | set output 'error.png' 4 | plot 'disc_error.csv' with linespoints title "disc",\ 5 | 'bilinear_error.csv' with linespoints title "bilinear",\ 6 | 0.3*x**-0.75 with lines title "pow(N, -0.75)",\ 7 | 0.3*x**-1.5 with lines title "pow(N, -1.5)" 8 | -------------------------------------------------------------------------------- /pmj/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pmj" 3 | description = "Code to generate pmj02 and pmj02b sample sequences" 4 | repository = "https://github.com/sjb3d/pmj" 5 | version = "0.1.0" 6 | authors = ["Simon Brown "] 7 | edition = "2018" 8 | readme = "../docs/README.md" 9 | keywords = ["progressive", "multi-jittered", "sequence"] 10 | license = "MIT" 11 | 12 | [dependencies] 13 | rand_core = "0.6.3" 14 | 15 | [dev-dependencies] 16 | rand = { version = "0.8.5", features = ["small_rng"] } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Simon Brown 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/src/bin/bench.rs: -------------------------------------------------------------------------------- 1 | use pmj::generate; 2 | use rand::prelude::*; 3 | use rand::rngs::SmallRng; 4 | use std::time::{Duration, Instant}; 5 | 6 | fn generate_bench(count: usize) -> Duration { 7 | let mut rng = SmallRng::seed_from_u64(0); 8 | let start = Instant::now(); 9 | let _samples = generate(count, 0, &mut rng); 10 | start.elapsed() 11 | } 12 | 13 | fn secs_from_duration(d: Duration) -> f64 { 14 | (d.as_secs() as f64) + f64::from(d.subsec_nanos()) * (0.000_000_001) 15 | } 16 | 17 | fn repeat_measure Duration>(f: &F, name: &str) { 18 | const COUNT: usize = 16; 19 | let mut sum = 0.0; 20 | let mut sq_sum = 0.0; 21 | for _ in 0..COUNT { 22 | let d = secs_from_duration(f()); 23 | sum += d; 24 | sq_sum += d * d; 25 | } 26 | let avg = sum / (COUNT as f64); 27 | let var = sum * sum - sq_sum; 28 | println!("{}: avg {} (var: {})", name, avg, var); 29 | } 30 | 31 | fn main() { 32 | repeat_measure(&|| generate_bench(256), "generate(256)"); 33 | repeat_measure(&|| generate_bench(1024), "generate(1024)"); 34 | repeat_measure(&|| generate_bench(4096), "generate(4096)"); 35 | } 36 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Progressive Multi-Jittered Sample Sequences 2 | 3 | A crate to generate pmj02 and pmj02b sample sequences as described in the following papers: 4 | 5 | * [Progressive Multi-Jittered Sample Sequences](https://graphics.pixar.com/library/ProgressiveMultiJitteredSampling/) by Christensen et al. 6 | * [Efficient Generation of Points that Satisfy Two-Dimensional Elementary Intervals](http://jcgt.org/published/0008/01/04/) by Matt Pharr. 7 | 8 | ## Usage 9 | 10 | ```rust 11 | use pmj::{generate, Sample}; 12 | use rand::prelude::*; 13 | use rand::rngs::SmallRng; 14 | 15 | fn main() { 16 | // generate 4096 samples uing SmallRnd from the rand crate 17 | let mut rng = SmallRng::seed_from_u64(0); 18 | let samples = generate(4096, 0, &mut rng); 19 | 20 | // print coordinates 21 | for s in &samples { 22 | println!("sample: {}, {}", s.x(), s.y()); 23 | } 24 | } 25 | ``` 26 | 27 | Performance seems to be comparable to the figures stated in the second paper. Generating 4096 points as above takes around 4.8ms on a 1.7GHz CPU (compared to 3.19ms on a 4GHz CPU stated in the paper). 28 | 29 | ## Examples 30 | 31 | The `measure` example measures the integration convergence rate (averaged over 100 sequences) for the "disc" and "bilinear" functions in the first paper: 32 | 33 | ![error](https://raw.githubusercontent.com/sjb3d/pmj/master/docs/error.png) 34 | 35 | The `plot` example produces svg images to inspect the stratification and classes of the generated samples. Here is the output showing the stratification of a single sequence when looking at the first 4/8/16/32 samples: 36 | 37 | ![partitions](https://raw.githubusercontent.com/sjb3d/pmj/master/docs/partitions.png) 38 | 39 | Here is the output showing the first 1024 samples split into 2 or 4 classes: 40 | 41 | ![classes](https://raw.githubusercontent.com/sjb3d/pmj/master/docs/classes.png) 42 | -------------------------------------------------------------------------------- /examples/src/bin/measure.rs: -------------------------------------------------------------------------------- 1 | use pmj::{generate, Sample}; 2 | use rand::prelude::*; 3 | use std::f32::consts::PI; 4 | use std::fmt; 5 | use std::fs; 6 | use std::io; 7 | use std::io::Write; 8 | use std::path::Path; 9 | use std::result; 10 | 11 | #[derive(Debug)] 12 | enum Error { 13 | Io(io::Error), 14 | Fmt(fmt::Error), 15 | } 16 | 17 | impl From for Error { 18 | fn from(err: io::Error) -> Self { 19 | Error::Io(err) 20 | } 21 | } 22 | impl From for Error { 23 | fn from(err: fmt::Error) -> Self { 24 | Error::Fmt(err) 25 | } 26 | } 27 | 28 | type Result = result::Result; 29 | 30 | fn disc(x: f32, y: f32) -> f32 { 31 | if x * x + y * y < 2.0 / PI { 32 | 1.0 33 | } else { 34 | 0.0 35 | } 36 | } 37 | 38 | fn bilinear(x: f32, y: f32) -> f32 { 39 | x * y 40 | } 41 | 42 | fn accumulate_error( 43 | samples: &[Sample], 44 | errors: &mut [f64], 45 | func: &impl Fn(f32, f32) -> f32, 46 | reference: f64, 47 | ) { 48 | let mut sum = 0.0; 49 | for (i, (sample, error)) in samples.iter().zip(errors.iter_mut()).enumerate() { 50 | sum += f64::from(func(sample.x(), sample.y())); 51 | let count = i + 1; 52 | *error += (sum / (count as f64) - reference).abs(); 53 | } 54 | } 55 | 56 | fn write_error>(path: P, errors: &[f64], seed_count: usize) -> Result<()> { 57 | let file = fs::File::create(path)?; 58 | let mut w = io::BufWriter::new(file); 59 | 60 | for (i, error) in errors.iter().enumerate() { 61 | writeln!(w, "{}, {}", 1 + i, error / (seed_count as f64))?; 62 | } 63 | 64 | Ok(()) 65 | } 66 | 67 | fn main() -> Result<()> { 68 | const SAMPLE_COUNT: usize = 1 << 12; 69 | const BLUE_NOISE_RESAMPLE_COUNT: u32 = 0; 70 | const SEED_COUNT: usize = 100; 71 | 72 | let mut disc_results = vec![0.0; SAMPLE_COUNT]; 73 | let mut bilinear_results = vec![0.0; SAMPLE_COUNT]; 74 | 75 | let disc_reference = 0.5; 76 | let bilinear_reference = 0.25; 77 | 78 | for seed in 0..SEED_COUNT { 79 | let mut rng = SmallRng::seed_from_u64(seed as u64); 80 | let samples = generate(SAMPLE_COUNT, BLUE_NOISE_RESAMPLE_COUNT, &mut rng); 81 | 82 | accumulate_error(&samples, &mut disc_results, &disc, disc_reference); 83 | accumulate_error( 84 | &samples, 85 | &mut bilinear_results, 86 | &bilinear, 87 | bilinear_reference, 88 | ); 89 | } 90 | 91 | write_error("results/disc_error.csv", &disc_results, SEED_COUNT)?; 92 | write_error("results/bilinear_error.csv", &bilinear_results, SEED_COUNT)?; 93 | Ok(()) 94 | } 95 | -------------------------------------------------------------------------------- /examples/src/bin/plot.rs: -------------------------------------------------------------------------------- 1 | use pmj::{generate, PairClass, QuadClass, Sample}; 2 | use rand::prelude::*; 3 | use rand::rngs::SmallRng; 4 | use std::fmt; 5 | use std::fs; 6 | use std::io; 7 | use std::io::Write; 8 | use std::result; 9 | 10 | #[derive(Debug)] 11 | enum Error { 12 | Io(io::Error), 13 | Fmt(fmt::Error), 14 | } 15 | 16 | impl From for Error { 17 | fn from(err: io::Error) -> Self { 18 | Error::Io(err) 19 | } 20 | } 21 | impl From for Error { 22 | fn from(err: fmt::Error) -> Self { 23 | Error::Fmt(err) 24 | } 25 | } 26 | 27 | type Result = result::Result; 28 | 29 | fn write_samples_svg( 30 | w: &mut impl io::Write, 31 | samples: &[Sample], 32 | sample_col: impl Fn(Sample) -> &'static str, 33 | (x, y): (f32, f32), 34 | grid_size: f32, 35 | (grid_x, grid_y): (u32, u32), 36 | ) -> Result<()> { 37 | const SAMPLE_RADIUS: f32 = 2.0; 38 | writeln!( 39 | w, 40 | r#""#, 41 | x = x, 42 | y = y, 43 | size = grid_size 44 | )?; 45 | for i in 1..grid_x { 46 | writeln!( 47 | w, 48 | r#""#, 49 | x1 = x, 50 | x2 = x + grid_size, 51 | y = y + grid_size * (i as f32) / (grid_x as f32) 52 | )?; 53 | } 54 | for i in 1..grid_y { 55 | writeln!( 56 | w, 57 | r#""#, 58 | y1 = y, 59 | y2 = y + grid_size, 60 | x = x + grid_size * (i as f32) / (grid_y as f32) 61 | )?; 62 | } 63 | for &sample in samples.iter() { 64 | writeln!( 65 | w, 66 | r#""#, 67 | cx = x + sample.x() * grid_size, 68 | cy = y + sample.y() * grid_size, 69 | r = SAMPLE_RADIUS, 70 | col = sample_col(sample), 71 | )?; 72 | } 73 | Ok(()) 74 | } 75 | 76 | fn write_partitions_svg() -> Result<()> { 77 | const MAX_LOG2_SAMPLE_COUNT: u32 = 5; 78 | const BLUE_NOISE_RESAMPLE_COUNT: u32 = 4; 79 | const SEED: u64 = 0; 80 | let mut rng = SmallRng::seed_from_u64(SEED); 81 | let samples = generate( 82 | 1 << MAX_LOG2_SAMPLE_COUNT, 83 | BLUE_NOISE_RESAMPLE_COUNT, 84 | &mut rng, 85 | ); 86 | 87 | const GRID_SIZE: f32 = 128.0; 88 | const ORIGIN: f32 = 0.5; 89 | const MARGIN: f32 = 10.0; 90 | let file = fs::File::create("results/partitions.svg")?; 91 | let mut w = io::BufWriter::new(file); 92 | writeln!( 93 | w, 94 | r#""#, 95 | width = (MAX_LOG2_SAMPLE_COUNT + 1) as f32 * (GRID_SIZE + MARGIN) + MARGIN, 96 | height = (MAX_LOG2_SAMPLE_COUNT - 1) as f32 * (GRID_SIZE + MARGIN) + MARGIN 97 | )?; 98 | let mut y = ORIGIN + MARGIN; 99 | for log2_sample_count in 2..=MAX_LOG2_SAMPLE_COUNT { 100 | let sample_count = 1 << log2_sample_count; 101 | let mut x = ORIGIN 102 | + MARGIN 103 | + 0.5 * ((MAX_LOG2_SAMPLE_COUNT - log2_sample_count) as f32) * (GRID_SIZE + MARGIN); 104 | for log2_grid_x in 0..=log2_sample_count { 105 | let log2_grid_y = log2_sample_count - log2_grid_x; 106 | write_samples_svg( 107 | &mut w, 108 | &samples[0..sample_count], 109 | |_| "blue", 110 | (x, y), 111 | GRID_SIZE, 112 | (1 << log2_grid_x, 1 << log2_grid_y), 113 | )?; 114 | x += GRID_SIZE + MARGIN; 115 | } 116 | y += GRID_SIZE + MARGIN; 117 | } 118 | writeln!(w, "")?; 119 | Ok(()) 120 | } 121 | 122 | fn write_classes_svg() -> Result<()> { 123 | const SAMPLE_COUNT: usize = 1 << 10; 124 | const BLUE_NOISE_RESAMPLE_COUNT: u32 = 4; 125 | const SEED: u64 = 0; 126 | 127 | const GRID_SIZE: f32 = 384.0; 128 | const ORIGIN: f32 = 0.5; 129 | const MARGIN: f32 = 10.0; 130 | let file = fs::File::create("results/classes.svg")?; 131 | let mut w = io::BufWriter::new(file); 132 | writeln!( 133 | w, 134 | r#""#, 135 | width = 2.0 * GRID_SIZE + 3.0 * MARGIN, 136 | height = GRID_SIZE + 2.0 * MARGIN, 137 | )?; 138 | 139 | let mut x = ORIGIN + MARGIN; 140 | let y = ORIGIN + MARGIN; 141 | 142 | let mut rng = SmallRng::seed_from_u64(SEED); 143 | let samples = generate(SAMPLE_COUNT, BLUE_NOISE_RESAMPLE_COUNT, &mut rng); 144 | 145 | write_samples_svg( 146 | &mut w, 147 | &samples, 148 | |sample| match sample.pair_class() { 149 | PairClass::A => "blue", 150 | PairClass::B => "red", 151 | }, 152 | (x, y), 153 | GRID_SIZE, 154 | (1, 1), 155 | )?; 156 | x += GRID_SIZE + MARGIN; 157 | 158 | write_samples_svg( 159 | &mut w, 160 | &samples, 161 | |sample| match sample.quad_class() { 162 | QuadClass::A => "blue", 163 | QuadClass::B => "red", 164 | QuadClass::C => "green", 165 | QuadClass::D => "orange", 166 | }, 167 | (x, y), 168 | GRID_SIZE, 169 | (1, 1), 170 | )?; 171 | 172 | writeln!(w, "")?; 173 | Ok(()) 174 | } 175 | 176 | fn main() -> Result<()> { 177 | write_partitions_svg()?; 178 | write_classes_svg()?; 179 | Ok(()) 180 | } 181 | -------------------------------------------------------------------------------- /pmj/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | 3 | //! # Overview 4 | //! 5 | //! A crate to generate pmj02 and pmj02b sample sequences as described in the following papers: 6 | //! * [Progressive Multi-Jittered Sample Sequences](https://graphics.pixar.com/library/ProgressiveMultiJitteredSampling/) by Christensen et al. 7 | //! * [Efficient Generation of Points that Satisfy Two-Dimensional Elementary Intervals](http://jcgt.org/published/0008/01/04/) by Matt Pharr. 8 | 9 | use rand_core::RngCore; 10 | use std::fmt; 11 | use std::num::NonZeroU64; 12 | 13 | /// Divides a set of samples into 2 classes. 14 | #[derive(Debug, Copy, Clone)] 15 | pub enum PairClass { 16 | /// Class 1 of 2. 17 | A, 18 | /// Class 2 of 2. 19 | B, 20 | } 21 | 22 | /// Divides a set of samples into 4 classes. 23 | #[derive(Debug, Copy, Clone)] 24 | pub enum QuadClass { 25 | /// Class 1 of 4. 26 | A, 27 | /// Class 2 of 4. 28 | B, 29 | /// Class 3 of 4. 30 | C, 31 | /// Class 4 of 4. 32 | D, 33 | } 34 | 35 | /// Represents a 2D sample coordinate and classes. 36 | #[derive(Copy, Clone, Eq, PartialEq, Hash)] 37 | pub struct Sample(NonZeroU64); 38 | 39 | impl Sample { 40 | const COORD_BIT_COUNT: u32 = 23; 41 | const COORD_MASK: u32 = 0x007f_ffff; 42 | const CLASS_MASK: u32 = 0x0000_0003; 43 | const X_SHIFT: u32 = 1; 44 | const Y_SHIFT: u32 = Self::X_SHIFT + Self::COORD_BIT_COUNT; 45 | const CLASS_SHIFT: u32 = Self::Y_SHIFT + Self::COORD_BIT_COUNT; 46 | 47 | fn new(x_bits: u32, y_bits: u32, class_bits: u32) -> Self { 48 | debug_assert_eq!(x_bits & Self::COORD_MASK, x_bits); 49 | debug_assert_eq!(y_bits & Self::COORD_MASK, y_bits); 50 | debug_assert_eq!(class_bits & Self::CLASS_MASK, class_bits); 51 | let all_bits = 1 52 | | (u64::from(x_bits) << Self::X_SHIFT) 53 | | (u64::from(y_bits) << Self::Y_SHIFT) 54 | | (u64::from(class_bits) << Self::CLASS_SHIFT); 55 | unsafe { Self(NonZeroU64::new_unchecked(all_bits)) } 56 | } 57 | 58 | /// Returns up to 23 bits of data for the x coordinate of this sample. 59 | /// 60 | /// Returns the high bits when less than 23 bits of data is requested. 61 | /// To return all available bits as a floating-point number, call [Self::x()] instead. 62 | #[inline] 63 | pub fn x_bits(self, bit_count: u32) -> u32 { 64 | debug_assert!(bit_count <= Self::COORD_BIT_COUNT); 65 | let x_bits = ((self.0.get() >> Self::X_SHIFT) as u32) & Self::COORD_MASK; 66 | x_bits >> (Self::COORD_BIT_COUNT - bit_count) 67 | } 68 | 69 | /// Returns up to 23 bits of data for the y coordinate of this sample. 70 | /// 71 | /// Returns the high bits when less than 23 bits of data is requested. 72 | /// To return all available bits as a floating-point number, call [Self::y()] instead. 73 | #[inline] 74 | pub fn y_bits(self, bit_count: u32) -> u32 { 75 | debug_assert!(bit_count <= Self::COORD_BIT_COUNT); 76 | let y_bits = ((self.0.get() >> Self::Y_SHIFT) as u32) & Self::COORD_MASK; 77 | y_bits >> (Self::COORD_BIT_COUNT - bit_count) 78 | } 79 | 80 | #[inline] 81 | fn class_bits(self) -> u32 { 82 | ((self.0.get() >> Self::CLASS_SHIFT) as u32) & Self::CLASS_MASK 83 | } 84 | 85 | #[inline] 86 | fn grid_index(self, x_bit_count: u32, y_bit_count: u32) -> usize { 87 | ((self.y_bits(y_bit_count) << x_bit_count) | self.x_bits(x_bit_count)) as usize 88 | } 89 | 90 | /// Returns the x coordinate of this sample in [0, 1). 91 | /// 92 | /// # Example 93 | /// 94 | /// ``` 95 | /// # use rand::prelude::*; 96 | /// # use rand::rngs::SmallRng; 97 | /// # use pmj::*; 98 | /// let mut rng = SmallRng::seed_from_u64(0); 99 | /// let samples = generate(1, 0, &mut rng); 100 | /// let x = samples[0].x(); 101 | /// assert!(0.0 <= x && x < 1.0); 102 | /// ``` 103 | #[inline] 104 | pub fn x(self) -> f32 { 105 | f32::from_bits(0x3f80_0000 | self.x_bits(Self::COORD_BIT_COUNT)) - 1f32 106 | } 107 | 108 | /// Returns the y coordinate of this sample in [0, 1). 109 | /// 110 | /// # Example 111 | /// 112 | /// ``` 113 | /// # use rand::prelude::*; 114 | /// # use rand::rngs::SmallRng; 115 | /// # use pmj::*; 116 | /// let mut rng = SmallRng::seed_from_u64(0); 117 | /// let samples = generate(1, 0, &mut rng); 118 | /// let y = samples[0].y(); 119 | /// assert!(0.0 <= y && y < 1.0); 120 | /// ``` 121 | #[inline] 122 | pub fn y(self) -> f32 { 123 | f32::from_bits(0x3f80_0000 | self.y_bits(Self::COORD_BIT_COUNT)) - 1f32 124 | } 125 | 126 | /// Returns which class this sample belongs to if samples are divided into 2 classes. 127 | #[inline] 128 | pub fn pair_class(self) -> PairClass { 129 | if (self.class_bits() & 0x2) == 0 { 130 | PairClass::A 131 | } else { 132 | PairClass::B 133 | } 134 | } 135 | 136 | /// Returns which class this sample belongs to if samples are divided into 4 classes. 137 | #[inline] 138 | pub fn quad_class(self) -> QuadClass { 139 | match self.class_bits() { 140 | 0 => QuadClass::A, 141 | 1 => QuadClass::B, 142 | 2 => QuadClass::C, 143 | _ => QuadClass::D, 144 | } 145 | } 146 | } 147 | 148 | impl fmt::Debug for Sample { 149 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 150 | fmt.debug_struct("Sample") 151 | .field("x", &self.x()) 152 | .field("y", &self.y()) 153 | .field("pair_class", &self.pair_class()) 154 | .field("quad_class", &self.quad_class()) 155 | .finish() 156 | } 157 | } 158 | 159 | impl fmt::Display for Sample { 160 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 161 | write!(fmt, "({}, {})", self.x(), self.y()) 162 | } 163 | } 164 | 165 | #[inline] 166 | fn generate_sample_bits( 167 | high_bit_count: u32, 168 | high_bits: u32, 169 | rng: &mut R, 170 | ) -> u32 { 171 | let low_bit_count = Sample::COORD_BIT_COUNT - high_bit_count; 172 | let rng_shift = 32 - low_bit_count; 173 | (high_bits << low_bit_count) | (rng.next_u32() >> rng_shift) 174 | } 175 | 176 | #[inline] 177 | fn generate_index(len: usize, rng: &mut R) -> usize { 178 | let u = rng.next_u32(); 179 | let prod = u64::from(u) * (len as u64); 180 | (prod >> 32) as usize 181 | } 182 | 183 | #[derive(Default)] 184 | struct SampleCoordSet { 185 | valid_x: Vec, 186 | valid_y: Vec, 187 | bit_count: u32, 188 | } 189 | 190 | impl SampleCoordSet { 191 | fn clear(&mut self, bit_count: u32) { 192 | self.valid_x.clear(); 193 | self.valid_y.clear(); 194 | self.bit_count = bit_count; 195 | } 196 | 197 | fn sample(&self, rng: &mut R, class_bits: u32) -> Sample { 198 | let x_index = generate_index(self.valid_x.len(), rng); 199 | let y_index = generate_index(self.valid_y.len(), rng); 200 | let valid_x = self.valid_x.get(x_index).cloned().expect("no valid x"); 201 | let valid_y = self.valid_y.get(y_index).cloned().expect("no valid y"); 202 | let x_bits = generate_sample_bits(self.bit_count, valid_x, rng); 203 | let y_bits = generate_sample_bits(self.bit_count, valid_y, rng); 204 | Sample::new(x_bits, y_bits, class_bits) 205 | } 206 | } 207 | 208 | struct BitArray { 209 | blocks: Box<[u32]>, 210 | count: usize, 211 | } 212 | 213 | impl BitArray { 214 | fn new(count: usize) -> Self { 215 | let block_count = ((count + 31) / 32).max(1); 216 | Self { 217 | blocks: vec![0; block_count].into_boxed_slice(), 218 | count, 219 | } 220 | } 221 | 222 | fn set(&mut self, index: usize) { 223 | assert!(index < self.count); 224 | self.blocks[index / 32] |= 1u32 << (index % 32); 225 | } 226 | 227 | fn is_set(&self, index: usize) -> bool { 228 | assert!(index < self.count); 229 | (self.blocks[index / 32] & (1u32 << (index % 32))) != 0 230 | } 231 | 232 | fn is_all_set(&self) -> bool { 233 | self.blocks.iter().enumerate().all(|(block_index, block)| { 234 | let end_index = 32 * (block_index + 1); 235 | let skip_count = end_index.max(self.count) - self.count; 236 | let mask = 0xffff_ffff >> skip_count; 237 | (block & mask) == mask 238 | }) 239 | } 240 | } 241 | 242 | struct StratificationAccel { 243 | levels: Vec, 244 | bit_count: u32, 245 | } 246 | 247 | impl StratificationAccel { 248 | fn new(bit_count: u32, samples: &[Sample]) -> Self { 249 | let mut levels = Vec::new(); 250 | for x_bit_count in 0..=bit_count { 251 | let y_bit_count = bit_count - x_bit_count; 252 | let mut level = BitArray::new(1 << bit_count); 253 | for sample in samples { 254 | let index = sample.grid_index(x_bit_count, y_bit_count); 255 | debug_assert!(!level.is_set(index)); 256 | level.set(index); 257 | } 258 | levels.push(level); 259 | } 260 | Self { levels, bit_count } 261 | } 262 | 263 | fn filter_x(&self, x_bits: u32, y_bits: u32, low_bit_count: u32) -> bool { 264 | (0..low_bit_count).all(|y_bit_count| { 265 | let x_bit_count = self.bit_count - y_bit_count; 266 | let x_mask = (1 << x_bit_count) - 1; 267 | let index = (y_bits & !x_mask) | (x_bits >> y_bit_count); 268 | !self.levels[x_bit_count as usize].is_set(index as usize) 269 | }) 270 | } 271 | 272 | fn filter_y(&self, x_bits: u32, y_bits: u32, low_bit_count: u32) -> bool { 273 | (0..low_bit_count).all(|x_bit_count| { 274 | let y_bit_count = self.bit_count - x_bit_count; 275 | let x_mask = (1 << x_bit_count) - 1; 276 | let index = (y_bits & !x_mask) | (x_bits >> y_bit_count); 277 | !self.levels[x_bit_count as usize].is_set(index as usize) 278 | }) 279 | } 280 | 281 | fn get_valid_coords( 282 | &self, 283 | high_bit_count: u32, 284 | high_x: u32, 285 | high_y: u32, 286 | coord_set: &mut SampleCoordSet, 287 | ) { 288 | let low_bit_count = self.bit_count - high_bit_count; 289 | let partial_x = high_x << low_bit_count; 290 | let partial_y = high_y << low_bit_count; 291 | let low_count = 1 << low_bit_count; 292 | 293 | coord_set.clear(self.bit_count); 294 | 295 | coord_set.valid_x.extend((0..low_count).filter_map(|low_x| { 296 | // build coordinates 297 | let x_bits = partial_x | low_x; 298 | let y_bits = partial_y; 299 | 300 | // brute force check this location is valid 301 | if self.filter_x(x_bits, y_bits, low_bit_count) { 302 | Some(x_bits) 303 | } else { 304 | None 305 | } 306 | })); 307 | 308 | coord_set.valid_y.extend((0..low_count).filter_map(|low_y| { 309 | // build coordinates 310 | let x_bits = partial_x; 311 | let y_bits = partial_y | low_y; 312 | 313 | // brute force check this location is valid 314 | if self.filter_y(x_bits, y_bits, low_bit_count) { 315 | Some(y_bits) 316 | } else { 317 | None 318 | } 319 | })); 320 | } 321 | 322 | fn set(&mut self, sample: Sample) { 323 | for (x_bit_count, level) in self.levels.iter_mut().enumerate() { 324 | let x_bit_count = x_bit_count as u32; 325 | let y_bit_count = self.bit_count - x_bit_count; 326 | let index = sample.grid_index(x_bit_count, y_bit_count); 327 | debug_assert!(!level.is_set(index)); 328 | level.set(index); 329 | } 330 | } 331 | 332 | fn is_all_set(&self) -> bool { 333 | self.levels.iter().all(|level| level.is_all_set()) 334 | } 335 | } 336 | 337 | struct BlueNoiseAccel { 338 | grid: Vec>, 339 | bit_count: u32, 340 | } 341 | 342 | impl BlueNoiseAccel { 343 | fn new(bit_count: u32, samples: &[Sample]) -> Self { 344 | let grid_size = 1 << (2 * bit_count); 345 | let mut grid = vec![None; grid_size]; 346 | for sample in samples { 347 | let index = sample.grid_index(bit_count, bit_count); 348 | let elem = unsafe { grid.get_unchecked_mut(index) }; 349 | debug_assert!(elem.is_none()); 350 | *elem = Some(*sample); 351 | } 352 | Self { grid, bit_count } 353 | } 354 | 355 | fn get_min_distance_sq(&self, sample: Sample) -> f32 { 356 | let grid_size = 1 << (2 * self.bit_count); 357 | let grid_stride = 1 << self.bit_count; 358 | let centre_index = sample.grid_index(self.bit_count, self.bit_count); 359 | 360 | // check all neighbouring grid cells 361 | let ref_x = sample.x(); 362 | let ref_y = sample.y(); 363 | [ 364 | grid_size - grid_stride - 1, 365 | grid_size - grid_stride, 366 | grid_size - grid_stride + 1, 367 | grid_size - 1, 368 | // skip centre sample, we know it is empty 369 | grid_size + 1, 370 | grid_size + grid_stride - 1, 371 | grid_size + grid_stride, 372 | grid_size + grid_stride + 1, 373 | ] 374 | .iter() 375 | .filter_map(|offset| { 376 | let index = (centre_index + offset) & (grid_size - 1); 377 | unsafe { self.grid.get_unchecked(index) }.map(|s| { 378 | let xd = (s.x() - ref_x).abs(); 379 | let yd = (s.y() - ref_y).abs(); 380 | let xd = xd.min(1f32 - xd); 381 | let yd = yd.min(1f32 - yd); 382 | xd * xd + yd * yd 383 | }) 384 | }) 385 | .fold(2f32, |m, x| m.min(x)) 386 | } 387 | 388 | fn set(&mut self, sample: Sample) { 389 | let index = sample.grid_index(self.bit_count, self.bit_count); 390 | let elem = unsafe { self.grid.get_unchecked_mut(index) }; 391 | debug_assert!(elem.is_none()); 392 | *elem = Some(sample); 393 | } 394 | } 395 | 396 | fn pick_sample( 397 | strat_result: &SampleCoordSet, 398 | bn_accel: Option<&BlueNoiseAccel>, 399 | blue_noise_retry_count: u32, 400 | rng: &mut R, 401 | class_bits: u32, 402 | ) -> Sample { 403 | let mut sample = strat_result.sample(rng, class_bits); 404 | if let Some(bn_accel) = bn_accel { 405 | let mut dist_sq = bn_accel.get_min_distance_sq(sample); 406 | for _ in 0..blue_noise_retry_count { 407 | let other_sample = strat_result.sample(rng, class_bits); 408 | let other_dist_sq = bn_accel.get_min_distance_sq(other_sample); 409 | if dist_sq < other_dist_sq { 410 | sample = other_sample; 411 | dist_sq = other_dist_sq; 412 | } 413 | } 414 | } 415 | sample 416 | } 417 | 418 | /// Generates samples of a pmj02 sequence. 419 | /// 420 | /// If `blue_noise_retry_count` is non-zero, then this many additional candidates 421 | /// are considered as each sample is generated, and the candidate that is the greatest 422 | /// distance from all previous samples is selected. 423 | /// 424 | /// The random number generator can be any type that implements the RngCore trait 425 | /// from the rand_core crate. 426 | /// 427 | /// # Example 428 | /// 429 | /// ``` 430 | /// # use rand::prelude::*; 431 | /// # use rand::rngs::SmallRng; 432 | /// # use pmj::*; 433 | /// let mut rng = SmallRng::seed_from_u64(0); 434 | /// let samples = generate(1024, 0, &mut rng); 435 | /// ``` 436 | pub fn generate( 437 | sample_count: usize, 438 | blue_noise_retry_count: u32, 439 | rng: &mut R, 440 | ) -> Vec { 441 | // first sample is anywhere 442 | let mut samples = Vec::with_capacity(sample_count); 443 | { 444 | let x_bits = generate_sample_bits(0, 0, rng); 445 | let y_bits = generate_sample_bits(0, 0, rng); 446 | samples.push(Sample::new(x_bits, y_bits, 0)); 447 | } 448 | 449 | // sample to next power of 2, with stratification 450 | loop { 451 | if samples.len() >= sample_count { 452 | break; 453 | } 454 | 455 | let quadrant_count = samples.len(); 456 | let current_bit_count = (quadrant_count as u32).trailing_zeros(); 457 | let shuffle_mask = (quadrant_count as u32) - 1; 458 | let mut strat_result = SampleCoordSet::default(); 459 | 460 | let high_bit_count = current_bit_count / 2 + 1; 461 | let mut bn_accel = if blue_noise_retry_count > 0 { 462 | Some(BlueNoiseAccel::new(high_bit_count, &samples)) 463 | } else { 464 | None 465 | }; 466 | 467 | // Generate a set of samples in the diagonally opposite quadrants to existing samples 468 | let mut strat_accel = StratificationAccel::new(current_bit_count + 1, &samples); 469 | let old_shuffle = (rng.next_u32() & shuffle_mask) as usize; 470 | for old_sample_index in 0..quadrant_count { 471 | let old_sample = samples[old_sample_index ^ old_shuffle]; 472 | strat_accel.get_valid_coords( 473 | high_bit_count, 474 | old_sample.x_bits(high_bit_count) ^ 1, 475 | old_sample.y_bits(high_bit_count) ^ 1, 476 | &mut strat_result, 477 | ); 478 | let sample = pick_sample( 479 | &strat_result, 480 | bn_accel.as_ref(), 481 | blue_noise_retry_count, 482 | rng, 483 | old_sample.class_bits() ^ 0x1, 484 | ); 485 | samples.push(sample); 486 | strat_accel.set(sample); 487 | if let Some(bn_accel) = bn_accel.as_mut() { 488 | bn_accel.set(sample); 489 | } 490 | } 491 | debug_assert!(strat_accel.is_all_set()); 492 | 493 | if samples.len() >= sample_count { 494 | break; 495 | } 496 | 497 | // Now pick one of the two remaining quadrants to existing samples 498 | /* 499 | Currently we pick a quadrant by either flipping all the initial 500 | quadrants in x or flipping them all in y, as either of these 501 | choices seems to result in a 02 sequence for this sub-sequence, 502 | which gets good convergence in the test integrals. 503 | 504 | It is possible to choose each quadrant randomly, but this has worse 505 | convergence on the test integrals. So far it does not seem possible to 506 | pick randomly and keep this sub-sequence a 02 sequence, this greedy 507 | algorithm seems to get stuck where there are no valid samples for a quadrant. 508 | */ 509 | let old_shuffle = (rng.next_u32() & shuffle_mask) as usize; 510 | let flip_choice = rng.next_u32() & 1; 511 | let mut strat_accel = StratificationAccel::new(current_bit_count + 2, &samples); 512 | let mut sub_strat_check = StratificationAccel::new(current_bit_count, &[]); 513 | for old_sample_index in 0..quadrant_count { 514 | let old_sample = samples[old_sample_index ^ old_shuffle]; 515 | strat_accel.get_valid_coords( 516 | high_bit_count, 517 | old_sample.x_bits(high_bit_count) ^ flip_choice, 518 | old_sample.y_bits(high_bit_count) ^ flip_choice ^ 1, 519 | &mut strat_result, 520 | ); 521 | let sample = pick_sample( 522 | &strat_result, 523 | bn_accel.as_ref(), 524 | blue_noise_retry_count, 525 | rng, 526 | old_sample.class_bits() ^ 0x2, 527 | ); 528 | samples.push(sample); 529 | strat_accel.set(sample); 530 | sub_strat_check.set(sample); 531 | if let Some(bn_accel) = bn_accel.as_mut() { 532 | bn_accel.set(sample); 533 | } 534 | } 535 | debug_assert!(sub_strat_check.is_all_set()); 536 | drop(sub_strat_check); 537 | 538 | // Finally pick the remaining quadrant (diagonally opposite the previous set) 539 | let old_shuffle = (rng.next_u32() & shuffle_mask) as usize; 540 | for old_sample_index in 0..quadrant_count { 541 | let old_sample = samples[(quadrant_count << 1) | (old_sample_index ^ old_shuffle)]; 542 | strat_accel.get_valid_coords( 543 | high_bit_count, 544 | old_sample.x_bits(high_bit_count) ^ 1, 545 | old_sample.y_bits(high_bit_count) ^ 1, 546 | &mut strat_result, 547 | ); 548 | let sample = pick_sample( 549 | &strat_result, 550 | bn_accel.as_ref(), 551 | blue_noise_retry_count, 552 | rng, 553 | old_sample.class_bits() ^ 0x1, 554 | ); 555 | samples.push(sample); 556 | strat_accel.set(sample); 557 | if let Some(bn_accel) = bn_accel.as_mut() { 558 | bn_accel.set(sample); 559 | } 560 | } 561 | debug_assert!(strat_accel.is_all_set()); 562 | } 563 | 564 | samples.truncate(sample_count); 565 | samples 566 | } 567 | 568 | #[cfg(test)] 569 | mod tests { 570 | use super::*; 571 | 572 | #[test] 573 | fn fmt_debug() { 574 | let s = Sample::new(0x40_0000, 0x40_0000, 0x2); 575 | assert_eq!( 576 | format!("{:?}", s), 577 | "Sample { x: 0.5, y: 0.5, pair_class: B, quad_class: C }" 578 | ); 579 | } 580 | 581 | #[test] 582 | fn fmt_display() { 583 | let s = Sample::new(0x40_0000, 0x40_0000, 0x2); 584 | assert_eq!(format!("{}", s), "(0.5, 0.5)"); 585 | } 586 | } 587 | --------------------------------------------------------------------------------