1use binrw::{
2 BinRead, BinResult, Endian,
3 io::{Read, Seek, Write},
4};
5use glam::{EulerRot, Quat, Vec3};
6use std::string::FromUtf8Error;
7
8use crate::{PositionKey, RotationKey};
9
10pub(crate) const KEYFRAME_MOTION_VERSION: u16 = 1;
11pub(crate) const KEYFRAME_MOTION_SUBVERSION: u16 = 0;
12const KEYFRAME_MOTION_OLD_VERSION: u16 = 0;
13const KEYFRAME_MOTION_OLD_SUBVERSION: u16 = 1;
14pub(crate) const MAX_PELVIS_OFFSET: f32 = 5.0;
15
16const OOU16MAX: f32 = 1.0f32 / u16::MAX as f32;
17
18fn clamp(value: f32, lower: f32, upper: f32) -> f32 {
19 value.min(upper).max(lower)
20}
21
22pub(crate) fn f32_to_u16(value: f32, lower: f32, upper: f32) -> u16 {
23 let mut val = clamp(value, lower, upper);
24 val -= lower;
25 val /= upper - lower;
26 (val * u16::MAX as f32).floor() as u16
27}
28
29pub(crate) fn u16_to_f32(value: u16, lower: f32, upper: f32) -> f32 {
30 let mut val = value as f32 * OOU16MAX;
31 let delta = upper - lower;
32 val *= delta;
33 val += lower;
34
35 let max_error = delta * OOU16MAX;
36 if val.abs() < max_error {
37 val = 0.0;
38 }
39
40 val
41}
42
43pub fn read_null_terminated_string<R: Read + Seek>(
44 r: &mut R,
45 _: Endian,
46 _: (),
47) -> BinResult<String> {
48 let mut buf = Vec::new();
49 let mut byte = [0u8; 1];
50 loop {
51 r.read_exact(&mut byte)?;
52 if byte[0] == 0 {
53 break;
54 }
55 buf.push(byte[0]);
56 }
57 String::from_utf8(buf).map_err(|e: FromUtf8Error| binrw::Error::AssertFail {
58 pos: 0,
59 message: format!("Invalid UTF-8 in null-terminated string: {e}"),
60 })
61}
62
63pub fn write_null_terminated_string<W: Write + Seek>(
64 data: &String,
65 w: &mut W,
66 _: Endian,
67 _: (),
68) -> BinResult<()> {
69 w.write_all(data.as_bytes())?;
70 w.write_all(&[0])?;
71 Ok(())
72}
73
74pub fn read_fixed_length_string<R: Read + Seek>(
75 r: &mut R,
76 _: Endian,
77 args: (usize,),
78) -> BinResult<String> {
79 let length = args.0;
80 let mut buf = vec![0u8; length];
81 r.read_exact(&mut buf)?;
82 if let Some(pos) = buf.iter().position(|&b| b == 0) {
83 buf.truncate(pos);
84 }
85 Ok(String::from_utf8(buf).unwrap_or_default())
86}
87
88pub fn write_fixed_length_string<W: Write + Seek>(
89 data: &String,
90 w: &mut W,
91 _: Endian,
92 args: (usize,),
93) -> BinResult<()> {
94 let length = args.0;
95 let mut buf = data.as_bytes().to_vec();
96 buf.resize(length, 0);
97 w.write_all(&buf)?;
98 Ok(())
99}
100
101pub fn read_rot_quat<R: Read + Seek>(reader: &mut R, e: Endian, _: ()) -> BinResult<Quat> {
102 use binrw::BinRead;
103 let x: f32 = u16_to_f32(u16::read_options(reader, e, ())?, -1.0, 1.0);
104 let y: f32 = u16_to_f32(u16::read_options(reader, e, ())?, -1.0, 1.0);
105 let z: f32 = u16_to_f32(u16::read_options(reader, e, ())?, -1.0, 1.0);
106 let sum = x * x + y * y + z * z;
107 let w = if sum <= 1.0 { (1.0 - sum).sqrt() } else { 0.0 };
108 let mut q = Quat::from_xyzw(x, y, z, w);
109 if q.length_squared() > 0.0 {
110 q = q.normalize();
111 }
112 if q.w < 0.0 {
113 q = Quat::from_xyzw(-q.x, -q.y, -q.z, -q.w);
114 }
115 Ok(q)
116}
117
118pub fn write_rot_quat<W: Write + Seek>(
119 value: &Quat,
120 writer: &mut W,
121 e: Endian,
122 _: (),
123) -> BinResult<()> {
124 use binrw::BinWrite;
125 let mut q = if value.length_squared() > 0.0 {
126 value.normalize()
127 } else {
128 *value
129 };
130 if q.w < 0.0 {
132 q = Quat::from_xyzw(-q.x, -q.y, -q.z, -q.w);
133 }
134 f32_to_u16(q.x, -1.0, 1.0).write_options(writer, e, ())?;
135 f32_to_u16(q.y, -1.0, 1.0).write_options(writer, e, ())?;
136 f32_to_u16(q.z, -1.0, 1.0).write_options(writer, e, ())
137}
138
139pub fn read_pos_vec3<R: Read + Seek>(reader: &mut R, e: Endian, _: ()) -> BinResult<Vec3> {
140 use binrw::BinRead;
141 let x = u16_to_f32(
142 u16::read_options(reader, e, ())?,
143 -MAX_PELVIS_OFFSET,
144 MAX_PELVIS_OFFSET,
145 );
146 let y = u16_to_f32(
147 u16::read_options(reader, e, ())?,
148 -MAX_PELVIS_OFFSET,
149 MAX_PELVIS_OFFSET,
150 );
151 let z = u16_to_f32(
152 u16::read_options(reader, e, ())?,
153 -MAX_PELVIS_OFFSET,
154 MAX_PELVIS_OFFSET,
155 );
156 Ok(Vec3::new(x, y, z))
157}
158
159pub fn write_pos_vec3<W: Write + Seek>(
160 value: &Vec3,
161 writer: &mut W,
162 e: Endian,
163 _: (),
164) -> BinResult<()> {
165 use binrw::BinWrite;
166 f32_to_u16(value.x, -MAX_PELVIS_OFFSET, MAX_PELVIS_OFFSET).write_options(writer, e, ())?;
167 f32_to_u16(value.y, -MAX_PELVIS_OFFSET, MAX_PELVIS_OFFSET).write_options(writer, e, ())?;
168 f32_to_u16(value.z, -MAX_PELVIS_OFFSET, MAX_PELVIS_OFFSET).write_options(writer, e, ())
169}
170
171pub fn quantize_rotation(q: Quat) -> (u16, u16, u16) {
177 let mut qn = if q.length_squared() > 0.0 {
178 q.normalize()
179 } else {
180 q
181 };
182 if qn.w < 0.0 {
183 qn = Quat::from_xyzw(-qn.x, -qn.y, -qn.z, -qn.w);
184 }
185 (
186 f32_to_u16(qn.x, -1.0, 1.0),
187 f32_to_u16(qn.y, -1.0, 1.0),
188 f32_to_u16(qn.z, -1.0, 1.0),
189 )
190}
191
192pub fn quantize_position(v: Vec3) -> (u16, u16, u16) {
193 (
194 f32_to_u16(v.x, -MAX_PELVIS_OFFSET, MAX_PELVIS_OFFSET),
195 f32_to_u16(v.y, -MAX_PELVIS_OFFSET, MAX_PELVIS_OFFSET),
196 f32_to_u16(v.z, -MAX_PELVIS_OFFSET, MAX_PELVIS_OFFSET),
197 )
198}
199
200#[derive(Clone, Copy, Debug, PartialEq, Eq)]
201pub enum AnimVersion {
202 Old,
203 Current,
204}
205
206pub(crate) fn classify_anim_version(version: u16, sub_version: u16) -> BinResult<AnimVersion> {
207 if version == KEYFRAME_MOTION_OLD_VERSION && sub_version == KEYFRAME_MOTION_OLD_SUBVERSION {
208 Ok(AnimVersion::Old)
209 } else if version == KEYFRAME_MOTION_VERSION && sub_version == KEYFRAME_MOTION_SUBVERSION {
210 Ok(AnimVersion::Current)
211 } else {
212 Err(binrw::Error::AssertFail {
213 pos: 0,
214 message: format!("Unsupported animation version {version}.{sub_version}"),
215 })
216 }
217}
218
219#[derive(Clone, Copy, Debug)]
220pub struct AnimReadContext {
221 pub version: AnimVersion,
222 pub duration: f32,
223}
224
225fn canonicalize_quat(mut quat: Quat) -> Quat {
226 if quat.length_squared() > 0.0 {
227 quat = quat.normalize();
228 }
229 if quat.w < 0.0 {
230 quat = Quat::from_xyzw(-quat.x, -quat.y, -quat.z, -quat.w);
231 }
232 quat
233}
234
235fn quat_from_degrees_zyx(x_deg: f32, y_deg: f32, z_deg: f32) -> Quat {
236 canonicalize_quat(Quat::from_euler(
237 EulerRot::ZYX,
238 z_deg.to_radians(),
239 y_deg.to_radians(),
240 x_deg.to_radians(),
241 ))
242}
243
244fn quantize_time(time: f32, duration: f32) -> u16 {
245 if duration <= 0.0 {
246 return 0;
247 }
248 let clamped = time.clamp(0.0, duration);
249 f32_to_u16(clamped, 0.0, duration)
250}
251
252pub(crate) fn read_rotation_keys<R: Read + Seek>(
253 reader: &mut R,
254 endian: Endian,
255 count: i32,
256 ctx: AnimReadContext,
257) -> BinResult<Vec<RotationKey>> {
258 if count < 0 {
259 return Err(binrw::Error::AssertFail {
260 pos: 0,
261 message: "num_rot_keys must be non-negative".into(),
262 });
263 }
264 let mut keys = Vec::with_capacity(count as usize);
265 for _ in 0..count {
266 let time = if ctx.version == AnimVersion::Old {
267 let time_seconds: f32 = f32::read_options(reader, endian, ())?;
268 quantize_time(time_seconds, ctx.duration)
269 } else {
270 u16::read_options(reader, endian, ())?
271 };
272
273 let rot = if ctx.version == AnimVersion::Old {
274 let angles: [f32; 3] = <[f32; 3]>::read_options(reader, endian, ())?;
275 quat_from_degrees_zyx(angles[0], angles[1], angles[2])
276 } else {
277 read_rot_quat(reader, endian, ())?
278 };
279
280 keys.push(RotationKey { time, rot });
281 }
282 Ok(keys)
283}
284
285pub(crate) fn read_position_keys<R: Read + Seek>(
286 reader: &mut R,
287 endian: Endian,
288 count: i32,
289 ctx: AnimReadContext,
290) -> BinResult<Vec<PositionKey>> {
291 if count < 0 {
292 return Err(binrw::Error::AssertFail {
293 pos: 0,
294 message: "num_pos_keys must be non-negative".into(),
295 });
296 }
297 let mut keys = Vec::with_capacity(count as usize);
298 for _ in 0..count {
299 let time = if ctx.version == AnimVersion::Old {
300 let time_seconds: f32 = f32::read_options(reader, endian, ())?;
301 quantize_time(time_seconds, ctx.duration)
302 } else {
303 u16::read_options(reader, endian, ())?
304 };
305
306 let pos = if ctx.version == AnimVersion::Old {
307 let mut components: [f32; 3] = <[f32; 3]>::read_options(reader, endian, ())?;
308 for value in &mut components {
309 *value = value.clamp(-MAX_PELVIS_OFFSET, MAX_PELVIS_OFFSET);
310 }
311 Vec3::from_array(components)
312 } else {
313 read_pos_vec3(reader, endian, ())?
314 };
315
316 keys.push(PositionKey { time, pos });
317 }
318 Ok(keys)
319}