steel_utils/codec/
lp_vec3.rs1use std::io::{Cursor, Result, Write};
2
3use glam::DVec3;
4
5use crate::{
6 codec::VarInt,
7 serial::{ReadFrom, WriteTo},
8};
9
10const DATA_BITS_MASK: u64 = 32_767;
11const MAX_QUANTIZED_VALUE: f64 = 32_766.0;
12const CONTINUATION_FLAG: u8 = 4;
13const ABS_MAX_VALUE: f64 = 1.717_986_918_3E10;
14const ABS_MIN_VALUE: f64 = 3.051_944_088_384_301E-5;
15
16#[derive(Debug, Clone, Copy, PartialEq)]
18pub struct LpVec3(pub DVec3);
19
20impl ReadFrom for LpVec3 {
21 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
22 let lowest = u8::read(data)?;
23 if lowest == 0 {
24 return Ok(Self(DVec3::ZERO));
25 }
26
27 let middle = u8::read(data)?;
28 let highest = u32::read(data)?;
29 let buffer = (u64::from(highest) << 16) | (u64::from(middle) << 8) | u64::from(lowest);
30 let mut scale = u64::from(lowest & 3);
31 if has_continuation_bit(lowest) {
32 scale |= u64::from(VarInt::read(data)?.0 as u32) << 2;
33 }
34 let scale = scale as f64;
35
36 Ok(Self(DVec3::new(
37 unpack(buffer >> 3) * scale,
38 unpack(buffer >> 18) * scale,
39 unpack(buffer >> 33) * scale,
40 )))
41 }
42}
43
44impl WriteTo for LpVec3 {
45 fn write(&self, writer: &mut impl Write) -> Result<()> {
46 let x = sanitize(self.0.x);
47 let y = sanitize(self.0.y);
48 let z = sanitize(self.0.z);
49 let chessboard_length = x.abs().max(y.abs().max(z.abs()));
50
51 if chessboard_length < ABS_MIN_VALUE {
52 return writer.write_all(&[0]);
53 }
54
55 let scale = chessboard_length.ceil() as i64;
56 let is_partial = (scale & 3) != scale;
57 let markers = if is_partial {
58 (scale & 3) | i64::from(CONTINUATION_FLAG)
59 } else {
60 scale
61 };
62 let buffer = markers
63 | (pack(x / scale as f64) << 3)
64 | (pack(y / scale as f64) << 18)
65 | (pack(z / scale as f64) << 33);
66
67 writer.write_all(&[buffer as u8])?;
68 writer.write_all(&[(buffer >> 8) as u8])?;
69 writer.write_all(&((buffer >> 16) as u32).to_be_bytes())?;
70 if is_partial {
71 VarInt((scale >> 2) as i32).write(writer)?;
72 }
73 Ok(())
74 }
75}
76
77const fn has_continuation_bit(value: u8) -> bool {
78 (value & CONTINUATION_FLAG) == CONTINUATION_FLAG
79}
80
81fn sanitize(value: f64) -> f64 {
82 if value.is_nan() {
83 0.0
84 } else {
85 value.clamp(-ABS_MAX_VALUE, ABS_MAX_VALUE)
86 }
87}
88
89fn pack(value: f64) -> i64 {
90 ((value * 0.5 + 0.5) * MAX_QUANTIZED_VALUE).round() as i64
91}
92
93fn unpack(value: u64) -> f64 {
94 (value & DATA_BITS_MASK).min(MAX_QUANTIZED_VALUE as u64) as f64 * 2.0 / MAX_QUANTIZED_VALUE
95 - 1.0
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 fn round_trip(value: DVec3) -> Result<DVec3> {
103 let mut bytes = Vec::new();
104 LpVec3(value).write(&mut bytes)?;
105 Ok(LpVec3::read(&mut Cursor::new(&bytes))?.0)
106 }
107
108 #[test]
109 fn zero_vector_is_single_zero_byte() -> Result<()> {
110 let mut bytes = Vec::new();
111
112 LpVec3(DVec3::ZERO).write(&mut bytes)?;
113
114 assert_eq!(bytes, vec![0]);
115 assert_eq!(LpVec3::read(&mut Cursor::new(&bytes))?.0, DVec3::ZERO);
116 Ok(())
117 }
118
119 #[test]
120 fn packed_vector_matches_vanilla_quantization() -> Result<()> {
121 let decoded = round_trip(DVec3::new(0.25, 0.5, 0.75))?;
122
123 assert!((decoded.x - 0.25).abs() < 0.0001);
124 assert!((decoded.y - 0.5).abs() < 0.0001);
125 assert!((decoded.z - 0.75).abs() < 0.0001);
126 Ok(())
127 }
128
129 #[test]
130 fn continuation_scale_round_trips_large_vector() -> Result<()> {
131 let decoded = round_trip(DVec3::new(5.0, 0.0, -3.0))?;
132
133 assert!((decoded.x - 5.0).abs() < 0.001);
134 assert!(decoded.y.abs() < 0.001);
135 assert!((decoded.z + 3.0).abs() < 0.001);
136 Ok(())
137 }
138}