From 8e15dba29d37d3891f0620f94ebb9185dfa22fd9 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Wed, 28 Nov 2018 23:41:12 -0800 Subject: [PATCH] Implementation of the Oct32 encoding of unit vectors. The code still needs testing, but initial toying around suggests that it's working correctly. --- Cargo.lock | 5 ++ Cargo.toml | 4 ++ sub_crates/oct32norm/Cargo.toml | 9 +++ sub_crates/oct32norm/src/lib.rs | 102 ++++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+) create mode 100644 sub_crates/oct32norm/Cargo.toml create mode 100644 sub_crates/oct32norm/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f8d8894..ac82861 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,6 +111,10 @@ dependencies = [ "libc 0.2.38 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "oct32norm" +version = "0.1.0" + [[package]] name = "openexr" version = "0.6.0" @@ -158,6 +162,7 @@ dependencies = [ "mem_arena 0.1.0", "nom 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "oct32norm 0.1.0", "openexr 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "png_encode_mini 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 9e74485..3b655ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "sub_crates/halton", "sub_crates/math3d", "sub_crates/mem_arena", + "sub_crates/oct32norm", "sub_crates/sobol", "sub_crates/spectra_xyz", "sub_crates/trifloat" @@ -53,6 +54,9 @@ path = "sub_crates/math3d" [dependencies.mem_arena] path = "sub_crates/mem_arena" +[dependencies.oct32norm] +path = "sub_crates/oct32norm" + [dependencies.sobol] path = "sub_crates/sobol" diff --git a/sub_crates/oct32norm/Cargo.toml b/sub_crates/oct32norm/Cargo.toml new file mode 100644 index 0000000..9cd04aa --- /dev/null +++ b/sub_crates/oct32norm/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "oct32norm" +version = "0.1.0" +authors = ["Nathan Vegdahl "] +license = "MIT" + +[lib] +name = "oct32norm" +path = "src/lib.rs" diff --git a/sub_crates/oct32norm/src/lib.rs b/sub_crates/oct32norm/src/lib.rs new file mode 100644 index 0000000..81c0309 --- /dev/null +++ b/sub_crates/oct32norm/src/lib.rs @@ -0,0 +1,102 @@ +//! Encoding/decoding for a 32-bit representation of unit 3d vectors. +//! +//! Follows the Oct32 encoding specified in the paper "A Survey +//! of Efficient Representations for Independent Unit Vectors" by +//! Cigolle et al. + +/// Encodes a vector of three floats to the oct32 format. +/// +/// The input vector does not need to be normalized--only the direction +/// matters to the encoding process, not the length. +#[inline] +pub fn encode(vec: (f32, f32, f32)) -> u32 { + let inv_l1_norm = 1.0f32 / (vec.0.abs() + vec.1.abs() + vec.2.abs()); + + let (u, v) = if vec.2 < 0.0 { + ( + to_snorm_16((1.0 - (vec.1 * inv_l1_norm).abs()) * sign(vec.0)) as u32, + to_snorm_16((1.0 - (vec.0 * inv_l1_norm).abs()) * sign(vec.1)) as u32, + ) + } else { + ( + to_snorm_16(vec.0 * inv_l1_norm) as u32, + to_snorm_16(vec.1 * inv_l1_norm) as u32, + ) + }; + + (u << 16) | v +} + +/// Decodes from an oct32 to a vector of three floats. +/// +/// The returned vector will not generally be normalized. Code that +/// needs a normalized vector should normalize the returned vector. +#[inline] +pub fn decode(n: u32) -> (f32, f32, f32) { + let mut vec0 = from_snorm_16((n >> 16) as u16); + let mut vec1 = from_snorm_16(n as u16); + let vec2 = 1.0 - (vec0.abs() + vec1.abs()); + + if vec2 < 0.0 { + let old_x = vec0; + vec0 = (1.0 - vec1.abs()) * sign(old_x); + vec1 = (1.0 - old_x.abs()) * sign(vec1); + } + + (vec0, vec1, vec2) +} + +#[inline(always)] +fn to_snorm_16(n: f32) -> u16 { + (n.max(-1.0).min(1.0) * ((1u32 << (16 - 1)) - 1) as f32).round() as i16 as u16 +} + +#[inline(always)] +fn from_snorm_16(n: u16) -> f32 { + (n as i16 as f32) + * (1.0f32 / ((1u32 << (16 - 1)) - 1) as f32) + .max(-1.0) + .min(1.0) +} + +#[inline(always)] +fn sign(n: f32) -> f32 { + if n < 0.0 { + -1.0 + } else { + 1.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn axis_directions() { + let px = (1.0, 0.0, 0.0); + let px_oct = encode(px); + + let nx = (-1.0, 0.0, 0.0); + let nx_oct = encode(nx); + + let py = (0.0, 1.0, 0.0); + let py_oct = encode(py); + + let ny = (0.0, -1.0, 0.0); + let ny_oct = encode(ny); + + let pz = (0.0, 0.0, 1.0); + let pz_oct = encode(pz); + + let nz = (0.0, 0.0, -1.0); + let nz_oct = encode(nz); + + assert_eq!(px, decode(px_oct)); + assert_eq!(nx, decode(nx_oct)); + assert_eq!(py, decode(py_oct)); + assert_eq!(ny, decode(ny_oct)); + assert_eq!(pz, decode(pz_oct)); + assert_eq!(nz, decode(nz_oct)); + } +}