diff --git a/fixtures/integer_compressed_floats.usda b/fixtures/integer_compressed_floats.usda new file mode 100644 index 0000000..f36d73a --- /dev/null +++ b/fixtures/integer_compressed_floats.usda @@ -0,0 +1,13 @@ +#usda 1.0 + +# Regression fixture for USDC's `i` (integer-compressed float array) encoding. +# Generated with OpenUSD `usdcat -o integer_compressed_floats.usdc`. +def Scope "IntegerCompressedFloats" +{ + float[] weights = [ + 1.0, 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, 1.0, + 1.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 1.0, + ] +} diff --git a/fixtures/integer_compressed_floats.usdc b/fixtures/integer_compressed_floats.usdc new file mode 100644 index 0000000..adf5e05 Binary files /dev/null and b/fixtures/integer_compressed_floats.usdc differ diff --git a/src/usdc/mod.rs b/src/usdc/mod.rs index 2582c1d..5c04017 100644 --- a/src/usdc/mod.rs +++ b/src/usdc/mod.rs @@ -772,6 +772,24 @@ mod tests { Ok(()) } + #[test] + fn test_read_integer_compressed_float_array() -> Result<()> { + // OpenUSD serialises an all-integral float array with the `i` code: + // its LZ4 payload still needs Usd_IntegerCompression decoding. + let data = read_file("fixtures/integer_compressed_floats.usdc")?; + let weights = data + .get_field(&sdf::path("/IntegerCompressedFloats.weights")?, "default")? + .into_owned() + .try_as_float_vec() + .unwrap(); + + assert_eq!( + weights, + vec![1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0,] + ); + Ok(()) + } + #[test] fn test_read_halfs() -> Result<()> { let data = read_file("fixtures/floats.usdc")?; diff --git a/src/usdc/reader.rs b/src/usdc/reader.rs index 38a608b..42d4e2b 100644 --- a/src/usdc/reader.rs +++ b/src/usdc/reader.rs @@ -662,9 +662,13 @@ impl CrateFile { let code = self.reader.read_pod::()?; match code { - // Compressed integers + // Compressed integers. Pixar's `_ReadCompressedInts` runs the + // LZ4 block through `Usd_IntegerCompression` decoding after + // decompression, so the payload must be integer-decoded + // (`read_encoded_ints`), not reinterpreted as raw `i32`s + // straight out of LZ4. b'i' => { - let ints: Vec = self.read_compressed(count)?; + let ints: Vec = self.read_encoded_ints(count)?; ints.into_iter().map(|i| cast(i).unwrap()).collect() } // Lookup table and indexes @@ -1546,6 +1550,30 @@ mod tests { use super::*; use std::fs; + #[test] + fn integer_compressed_float_fixture_uses_i_encoding() -> Result<()> { + let mut file = CrateFile::open(fs::File::open("fixtures/integer_compressed_floats.usdc")?)?; + let value = file + .fields + .iter() + .find_map(|field| { + let ty = field.value_rep.ty().ok()?; + (file.tokens[field.token_index] == "default" + && ty == Type::Float + && field.value_rep.is_array() + && field.value_rep.is_compressed()) + .then_some(field.value_rep) + }) + .expect("fixture must contain one compressed float default value"); + + file.set_position(value.payload())?; + let (count, compressed) = file.unpack_array_len(value, ArrayKind::Floats)?; + assert_eq!(count, 16); + assert!(compressed); + assert_eq!(file.reader.read_pod::()?, b'i'); + Ok(()) + } + #[test] fn test_read_crate_struct() { let path = "./vendor/usd-wg-assets/full_assets/ElephantWithMonochord/SoC-ElephantWithMonochord.usdc";