diff --git a/src/renderer.rs b/src/renderer.rs index 781f6f7..c4dcf2b 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -118,18 +118,32 @@ impl<'a> Renderer<'a> { let _ = io::stdout().flush(); // Populate job queue - let bucket_n = { - let bucket_count_x = (width / bucket_size) + 1; - let bucket_count_y = (height / bucket_size) + 1; + let hilbert_count = (thread_count as f32).sqrt() as u32; + let (middle_bucket, bucket_n) = { + let bucket_count_x = + (width / bucket_size) + if (width % bucket_size) != 0 { 1 } else { 0 }; + let bucket_count_y = + (height / bucket_size) + if (height % bucket_size) != 0 { 1 } else { 0 }; let larger = cmp::max(bucket_count_x, bucket_count_y); let pow2 = upper_power_of_two(larger); - pow2 * pow2 + let buffer = hilbert_count; // For hilbert spiral. + ( + (bucket_count_x / 2, bucket_count_y / 2), + (pow2 + buffer) * (pow2 + buffer), + ) }; - for hilbert_d in 0..bucket_n { - let (bx, by) = hilbert::decode(hilbert_d); + for hilbert_spiral_i in 0..bucket_n { + let (bx, by) = + crate::space_fill::hilbert_spiral::decode(hilbert_spiral_i, hilbert_count); - let x = bx * bucket_size; - let y = by * bucket_size; + let bx = middle_bucket.0 as i32 + bx; + let by = middle_bucket.1 as i32 + by; + if bx < 0 || by < 0 { + continue; + } + + let x = bx as u32 * bucket_size; + let y = by as u32 * bucket_size; let w = if width >= x { min(bucket_size, width - x) } else { diff --git a/src/space_fill.rs b/src/space_fill.rs index 3663e9f..8e06ea6 100644 --- a/src/space_fill.rs +++ b/src/space_fill.rs @@ -11,13 +11,13 @@ pub mod hilbert { /// y: The y coordinate. Must be no greater than 2^16-1. /// /// Returns the hilbert curve index corresponding to the (x,y) coordinates given. - pub fn encode(x: u32, y: u32) -> u32 { + pub fn encode(x: u32, y: u32, n: u32) -> u32 { assert!(x < N); assert!(y < N); let (mut x, mut y) = (x, y); let mut d = 0; - let mut s = N >> 1; + let mut s = n >> 1; while s > 0 { let rx = if (x & s) > 0 { 1 } else { 0 }; let ry = if (y & s) > 0 { 1 } else { 0 }; @@ -35,11 +35,11 @@ pub mod hilbert { /// d: The hilbert curve index. /// /// Returns the (x, y) coords at the given index. - pub fn decode(d: u32) -> (u32, u32) { + pub fn decode(d: u32, n: u32) -> (u32, u32) { let (mut x, mut y) = (0, 0); let mut s = 1; let mut t = d; - while s < N { + while s < n { let rx = 1 & (t >> 1); let ry = 1 & (t ^ rx); (x, y) = hilbert_rotate(s, rx, ry, x, y); @@ -118,6 +118,76 @@ pub mod morton { } } +/// Yields coordinates in outward spiral, but incorporating a Hilbert +/// curve at the smaller scales. +pub mod hilbert_spiral { + /// Convert from hilbert-spiral index to (x,y). + /// + /// Note: this returns both negative and positive coordinates. + /// It starts at 0,0 and spirals outwards. + /// + /// i: The hilbert-spiral index. + /// hilbert_size: the size of the hulbert blocks on a side. Will be + /// rounded down to the nearest power of two. + /// + /// Returns the (x, y) coords at the given index. + pub fn decode(i: u32, hilbert_size: u32) -> (i32, i32) { + assert!(hilbert_size > 0); + let hilbert_size = 1 << (31 - u32::leading_zeros(hilbert_size)); + + let hilbert_cells = hilbert_size * hilbert_size; + + let hilbert_i = i % hilbert_cells; + let spiral_i = i / hilbert_cells; + + let (mut sx, mut sy, section) = decode_spiral(spiral_i); + sx = (sx * hilbert_size as i32) - (hilbert_size / 2) as i32; + sy = (sy * hilbert_size as i32) - (hilbert_size / 2) as i32; + + let (hx, hy) = { + let (hx, hy) = super::hilbert::decode(hilbert_i, hilbert_size); + let a = hilbert_size - 1; + match section { + 0 => (hx, a - hy), + 1 => (a - hy, hx), + 2 => (a - hx, hy), + 3 => (hy, a - hx), + _ => unreachable!(), + } + }; + + (sx + hx as i32, sy + hy as i32) + } + + pub fn decode_spiral(i: u32) -> (i32, i32, u32) { + if i == 0 { + return (0, 0, 3); + } + + // 0 = first ring outside of center, 1 = second, and so on. + let ring = (((i as f64).sqrt() - 1.0) / 2.0) as u32; + + // The size of the ring along one side. + let size = 1 + ((ring + 1) * 2); + + let n = i - ((size - 2) * (size - 2)); // The zero-indexed cell of the ring. + let arm = n / (size - 1); // The arm of the ring. + let arm_n = n % (size - 1); // The index within the arm of the ring. + + // The two coordinates. They just need to be flipped around depending on the arm. + let radius = ring as i32 + 1; + let d = -(size as i32 / 2) + 1 + arm_n as i32; + + match arm { + 0 => (d, -radius, 0), + 1 => (radius, d, if arm_n == (size - 2) { 2 } else { 1 }), + 2 => (-d, radius, 2), + 3 => (-radius, -d, 3), + _ => unreachable!(), + } + } +} + //------------------------------------------------------------- #[cfg(test)]