1use binrw::io::{Read, Seek, Write};
2use binrw::{BinRead, BinResult, BinWrite, Endian, binrw};
3use glam::{EulerRot, Quat, Vec3};
4use llsd_rs::Llsd;
5use std::collections::HashSet;
6use thiserror::Error;
7
8pub mod io;
9pub mod skeleton;
10
11use crate::io::{
12 AnimReadContext, AnimVersion, KEYFRAME_MOTION_SUBVERSION, KEYFRAME_MOTION_VERSION,
13 classify_anim_version, read_fixed_length_string, read_null_terminated_string, read_pos_vec3,
14 read_position_keys, read_rot_quat, read_rotation_keys, write_fixed_length_string,
15 write_null_terminated_string, write_pos_vec3, write_rot_quat,
16};
17
18pub use AnimError as Error;
19pub use skeleton::{SkeletonBone, SkeletonDefinition};
20pub type Result<T> = std::result::Result<T, AnimError>;
21
22#[derive(Debug, Error)]
23pub enum AnimError {
24 #[error("I/O error: {0}")]
25 Io(#[from] std::io::Error),
26 #[error("Binary parsing error: {0}")]
27 BinRw(#[from] binrw::Error),
28 #[error("Invalid structure: {0}")]
29 InvalidStructure(String),
30 #[error("LLSD parse error: {0}")]
31 Llsd(String),
32}
33
34#[binrw]
35#[brw(little)]
36#[derive(Clone, Debug, PartialEq)]
37pub struct AnimationHeader {
38 pub version: u16,
39 pub sub_version: u16,
40 pub base_priority: i32,
41 pub duration: f32,
42 #[br(parse_with = read_null_terminated_string)]
43 #[bw(write_with = write_null_terminated_string)]
44 pub emote_name: String,
45 pub loop_in_point: f32,
46 pub loop_out_point: f32,
47 pub looped: i32,
48 pub ease_in_duration: f32,
49 pub ease_out_duration: f32,
50 pub hand_pose: u32,
51}
52
53impl Default for AnimationHeader {
54 fn default() -> Self {
55 Self {
56 version: 1,
57 sub_version: 0,
58 base_priority: 6,
59 duration: 0.017,
60 emote_name: String::new(),
61 loop_in_point: 0.0,
62 loop_out_point: 0.017,
63 looped: 1,
64 ease_in_duration: 1.0,
65 ease_out_duration: 1.0,
66 hand_pose: 0,
67 }
68 }
69}
70
71#[binrw]
72#[brw(little)]
73#[derive(Clone, Debug, Default, PartialEq)]
74pub struct RotationKey {
75 pub time: u16,
76 #[br(parse_with = read_rot_quat)]
77 #[bw(write_with = write_rot_quat)]
78 pub rot: Quat,
79}
80
81impl From<Quat> for RotationKey {
82 fn from(rot: Quat) -> Self {
83 Self { time: 0, rot }
84 }
85}
86
87#[binrw]
88#[brw(little)]
89#[derive(Clone, Debug, Default, PartialEq)]
90pub struct PositionKey {
91 pub time: u16,
92 #[br(parse_with = read_pos_vec3)]
93 #[bw(write_with = write_pos_vec3)]
94 pub pos: Vec3,
95}
96
97impl From<Vec3> for PositionKey {
98 fn from(pos: Vec3) -> Self {
99 Self { time: 0, pos }
100 }
101}
102
103#[derive(Clone, Debug, Default, PartialEq)]
104pub struct JointData {
105 pub name: String,
106 pub priority: i32,
107 pub rotation_keys: Vec<RotationKey>,
108 pub position_keys: Vec<PositionKey>,
109}
110
111impl BinRead for JointData {
112 type Args<'a> = AnimReadContext;
113
114 fn read_options<R: Read + Seek>(
115 reader: &mut R,
116 endian: Endian,
117 ctx: Self::Args<'_>,
118 ) -> BinResult<Self> {
119 let name = read_null_terminated_string(reader, endian, ())?;
120 let priority = i32::read_options(reader, endian, ())?;
121
122 let num_rot_keys = i32::read_options(reader, endian, ())?;
123 let rotation_keys = read_rotation_keys(reader, endian, num_rot_keys, ctx)?;
124
125 let num_pos_keys = i32::read_options(reader, endian, ())?;
126 let position_keys = read_position_keys(reader, endian, num_pos_keys, ctx)?;
127
128 Ok(Self {
129 name,
130 priority,
131 rotation_keys,
132 position_keys,
133 })
134 }
135}
136
137impl BinWrite for JointData {
138 type Args<'a> = ();
139
140 fn write_options<W: Write + Seek>(
141 &self,
142 writer: &mut W,
143 endian: Endian,
144 _args: Self::Args<'_>,
145 ) -> BinResult<()> {
146 write_null_terminated_string(&self.name, writer, endian, ())?;
147 self.priority.write_options(writer, endian, ())?;
148 (self.rotation_keys.len() as i32).write_options(writer, endian, ())?;
149 for key in &self.rotation_keys {
150 key.write_options(writer, endian, ())?;
151 }
152
153 (self.position_keys.len() as i32).write_options(writer, endian, ())?;
154 for key in &self.position_keys {
155 key.write_options(writer, endian, ())?;
156 }
157 Ok(())
158 }
159}
160
161#[binrw]
162#[brw(little)]
163#[derive(Clone, Debug, Default, PartialEq)]
164pub struct Constraint {
165 pub chain_length: u8,
166 pub constraint_type: u8,
167
168 #[br(parse_with = read_fixed_length_string, args(16usize))]
169 #[bw(write_with = write_fixed_length_string, args(16usize))]
170 pub source_volume: String,
171
172 pub source_offset: [f32; 3],
173
174 #[br(parse_with = read_fixed_length_string, args(16usize))]
175 #[bw(write_with = write_fixed_length_string, args(16usize))]
176 pub target_volume: String,
177
178 pub target_offset: [f32; 3],
179 pub target_dir: [f32; 3],
180 pub ease_in_start: f32,
181 pub ease_in_stop: f32,
182 pub ease_out_start: f32,
183 pub ease_out_stop: f32,
184}
185
186#[derive(Clone, Debug, Default, PartialEq)]
187pub struct Animation {
188 pub header: AnimationHeader,
189 pub joints: Vec<JointData>,
190 pub constraints: Vec<Constraint>,
191}
192
193impl BinRead for Animation {
194 type Args<'a> = ();
195
196 fn read_options<R: Read + Seek>(
197 reader: &mut R,
198 endian: Endian,
199 _args: Self::Args<'_>,
200 ) -> BinResult<Self> {
201 let mut header = AnimationHeader::read_options(reader, endian, ())?;
202 let version = classify_anim_version(header.version, header.sub_version)?;
203 if version == AnimVersion::Old {
204 header.version = KEYFRAME_MOTION_VERSION;
205 header.sub_version = KEYFRAME_MOTION_SUBVERSION;
206 }
207 let ctx = AnimReadContext {
208 version,
209 duration: header.duration,
210 };
211
212 let num_joints = u32::read_options(reader, endian, ())?;
213 let mut joints = Vec::with_capacity(num_joints as usize);
214 for _ in 0..num_joints {
215 joints.push(JointData::read_options(reader, endian, ctx)?);
216 }
217
218 let num_constraints = i32::read_options(reader, endian, ())?;
219 if num_constraints < 0 {
220 return Err(binrw::Error::AssertFail {
221 pos: 0,
222 message: "num_constraints must be non-negative".into(),
223 });
224 }
225 let mut constraints = Vec::with_capacity(num_constraints as usize);
226 for _ in 0..num_constraints {
227 constraints.push(Constraint::read_options(reader, endian, ())?);
228 }
229
230 Ok(Self {
231 header,
232 joints,
233 constraints,
234 })
235 }
236}
237
238impl BinWrite for Animation {
239 type Args<'a> = ();
240
241 fn write_options<W: Write + Seek>(
242 &self,
243 writer: &mut W,
244 endian: Endian,
245 _args: Self::Args<'_>,
246 ) -> BinResult<()> {
247 let header = AnimationHeader {
248 version: KEYFRAME_MOTION_VERSION,
249 sub_version: KEYFRAME_MOTION_SUBVERSION,
250 ..self.header.clone()
251 };
252 header.write_options(writer, endian, ())?;
253 (self.joints.len() as u32).write_options(writer, endian, ())?;
254 for joint in &self.joints {
255 joint.write_options(writer, endian, ())?;
256 }
257
258 (self.constraints.len() as i32).write_options(writer, endian, ())?;
259 for constraint in &self.constraints {
260 constraint.write_options(writer, endian, ())?;
261 }
262 Ok(())
263 }
264}
265
266#[derive(Copy, Clone, Debug, Eq, PartialEq)]
268pub enum DuplicateKeyStrategy {
269 KeepFirst,
271 KeepLast,
273 Average,
275}
276
277fn group_average_rot(keys: &[RotationKey]) -> Vec<RotationKey> {
278 if keys.is_empty() {
279 return Vec::new();
280 }
281 let mut out = Vec::new();
282 let mut i = 0usize;
283 while i < keys.len() {
284 let t = keys[i].time;
285 let mut acc = glam::Quat::IDENTITY;
286 let mut count = 0f32;
287 let mut j = i;
288 while j < keys.len() && keys[j].time == t {
289 acc = if count == 0.0 {
290 keys[j].rot
291 } else {
292 acc.slerp(keys[j].rot, 1.0 / (count + 1.0))
293 };
294 count += 1.0;
295 j += 1;
296 }
297 out.push(RotationKey {
298 time: t,
299 rot: acc.normalize(),
300 });
301 i = j;
302 }
303 out
304}
305
306fn group_average_pos(keys: &[PositionKey]) -> Vec<PositionKey> {
307 if keys.is_empty() {
308 return Vec::new();
309 }
310 let mut out = Vec::new();
311 let mut i = 0usize;
312 while i < keys.len() {
313 let t = keys[i].time;
314 let mut acc = glam::Vec3::ZERO;
315 let mut count = 0.0f32;
316 let mut j = i;
317 while j < keys.len() && keys[j].time == t {
318 acc += keys[j].pos;
319 count += 1.0;
320 j += 1;
321 }
322 out.push(PositionKey {
323 time: t,
324 pos: acc / count,
325 });
326 i = j;
327 }
328 out
329}
330
331impl Animation {
332 pub fn new() -> Self {
333 Self::default()
334 }
335
336 pub fn set_priority(&mut self, priority: i32) -> &mut Self {
337 self.header.base_priority = priority;
338 for joint in &mut self.joints {
339 joint.priority = priority;
340 }
341 self
342 }
343
344 pub fn set_duration(&mut self, duration: f32) -> &mut Self {
345 let duration = duration.max(0.0);
346 self.header.duration = duration;
347 self.header.loop_in_point = 0.0;
348 self.header.loop_out_point = duration;
349 self
350 }
351
352 pub fn set_joint_priority(&mut self, priority: i32) -> &mut Self {
353 for joint in &mut self.joints {
354 joint.priority = priority;
355 }
356 self
357 }
358
359 pub fn drop_empty_joints(&mut self) -> &mut Self {
360 self.joints
361 .retain(|joint| !joint.position_keys.is_empty() || !joint.rotation_keys.is_empty());
362 self
363 }
364
365 pub fn drop_position_keys(&mut self) -> &mut Self {
366 for joint in &mut self.joints {
367 joint.position_keys.clear();
368 }
369 self
370 }
371
372 pub fn drop_position(&mut self, joints: impl Fn(&JointData) -> bool) -> &mut Self {
373 for joint in &mut self.joints {
374 if joints(joint) {
375 joint.position_keys.clear();
376 }
377 }
378 self
379 }
380
381 pub fn drop_zero_position_keys(&mut self, epsilon: f32) -> &mut Self {
382 let epsilon_sq = epsilon.max(0.0) * epsilon.max(0.0);
383 for joint in &mut self.joints {
384 joint
385 .position_keys
386 .retain(|key| key.pos.length_squared() > epsilon_sq);
387 }
388 self
389 }
390
391 pub fn drop_rotation_keys(&mut self) -> &mut Self {
392 for joint in &mut self.joints {
393 joint.rotation_keys.clear();
394 }
395 self
396 }
397
398 pub fn drop_rotation(&mut self, joints: impl Fn(&JointData) -> bool) -> &mut Self {
399 for joint in &mut self.joints {
400 if joints(joint) {
401 joint.rotation_keys.clear();
402 }
403 }
404 self
405 }
406
407 pub fn cleanup_keys(&mut self) -> &mut Self {
408 for joint in &mut self.joints {
409 let mut seen_times = HashSet::new();
410 joint.rotation_keys.reverse();
411 joint
412 .rotation_keys
413 .retain(|key| seen_times.insert(key.time));
414 joint.rotation_keys.sort_by_key(|key| key.time);
415 seen_times.clear();
416 joint.position_keys.reverse();
417 joint
418 .position_keys
419 .retain(|key| seen_times.insert(key.time));
420 joint.position_keys.sort_by_key(|key| key.time);
421 }
422 self
423 }
424
425 pub fn cleanup_keys_with(&mut self, strategy: DuplicateKeyStrategy) -> &mut Self {
427 for joint in &mut self.joints {
428 match strategy {
429 DuplicateKeyStrategy::KeepFirst => {
430 let mut seen = HashSet::new();
431 joint.rotation_keys.retain(|k| seen.insert(k.time));
432 seen.clear();
433 joint.position_keys.retain(|k| seen.insert(k.time));
434 }
435 DuplicateKeyStrategy::KeepLast => {
436 let mut seen = HashSet::new();
438 joint.rotation_keys.reverse();
439 joint.rotation_keys.retain(|k| seen.insert(k.time));
440 joint.rotation_keys.reverse();
441 seen.clear();
442 joint.position_keys.reverse();
443 joint.position_keys.retain(|k| seen.insert(k.time));
444 joint.position_keys.reverse();
445 }
446 DuplicateKeyStrategy::Average => {
447 joint.rotation_keys.sort_by_key(|k| k.time);
449 joint.position_keys.sort_by_key(|k| k.time);
450 joint.rotation_keys = group_average_rot(&joint.rotation_keys);
451 joint.position_keys = group_average_pos(&joint.position_keys);
452 }
453 }
454 joint.rotation_keys.sort_by_key(|k| k.time);
455 joint.position_keys.sort_by_key(|k| k.time);
456 }
457 self
458 }
459
460 pub fn joint(&self, name: &str) -> Option<&JointData> {
461 self.joints.iter().find(|joint| joint.name == name)
462 }
463
464 pub fn joint_mut(&mut self, name: &str) -> Option<&mut JointData> {
465 self.joints.iter_mut().find(|joint| joint.name == name)
466 }
467
468 pub fn position_reset_from_skeleton<'a>(
469 skeleton: &SkeletonDefinition,
470 joint_names: impl IntoIterator<Item = &'a str>,
471 priority: i32,
472 ) -> Result<Self> {
473 let mut bones = Vec::new();
474 for name in joint_names {
475 let bone = skeleton.bone(name).ok_or_else(|| {
476 AnimError::InvalidStructure(format!("Skeleton is missing required bone '{name}'"))
477 })?;
478 bones.push(bone);
479 }
480 Self::position_reset_from_bones(bones, priority)
481 }
482
483 pub fn position_reset_from_bones<'a>(
484 bones: impl IntoIterator<Item = &'a SkeletonBone>,
485 priority: i32,
486 ) -> Result<Self> {
487 let priority = priority.clamp(0, 7);
488 let mut animation = Self::default();
489 animation.header.base_priority = priority;
490
491 for bone in bones {
492 animation.joints.push(JointData {
493 name: bone.name.clone(),
494 priority,
495 rotation_keys: Vec::new(),
496 position_keys: vec![
497 PositionKey {
498 time: 0,
499 pos: bone.pos,
500 },
501 PositionKey {
502 time: u16::MAX,
503 pos: bone.pos,
504 },
505 ],
506 });
507 }
508
509 Ok(animation)
510 }
511
512 pub fn add_skeleton_positions(&mut self, skeleton: &SkeletonDefinition) -> Result<&mut Self> {
513 for joint in &mut self.joints {
514 if joint.position_keys.is_empty() {
515 continue;
516 }
517 let base_pos = skeleton.position(&joint.name).ok_or_else(|| {
518 AnimError::InvalidStructure(format!(
519 "Skeleton is missing position for joint '{}'",
520 joint.name
521 ))
522 })?;
523 for key in &mut joint.position_keys {
524 key.pos += base_pos;
525 }
526 }
527 Ok(self)
528 }
529
530 pub fn from_llsd(llsd: &Llsd, check_enabled: bool) -> Result<Self> {
566 let Some(joints) = llsd.as_map() else {
567 return Err(AnimError::InvalidStructure("LLSD must be a map".into()));
568 };
569 let mut animation = Self::default();
570 for (key, value) in joints {
571 let Some(inner) = value.as_map() else {
572 continue;
573 };
574 if check_enabled
575 && inner
576 .get("enabled")
577 .is_none_or(|e| e.as_boolean() != Some(&true))
578 {
579 continue;
580 }
581 let extract_key = |key: &str| -> Option<(f32, f32, f32)> {
582 inner.get(key).and_then(|e| e.as_array()).map(|arr| {
583 (
584 *arr.first().and_then(|e| e.as_real()).unwrap_or(&0.0f64) as f32,
585 *arr.get(1).and_then(|e| e.as_real()).unwrap_or(&0.0f64) as f32,
586 *arr.get(2).and_then(|e| e.as_real()).unwrap_or(&0.0f64) as f32,
587 )
588 })
589 };
590 let rotation = extract_key("rotation").map(|(roll, pitch, yaw)| RotationKey {
591 time: u16::MAX,
592 rot: Quat::from_euler(EulerRot::XYZ, roll, pitch, yaw).normalize(),
593 });
594 let position = extract_key("position").map(|(x, y, z)| PositionKey {
595 time: u16::MAX,
596 pos: Vec3::new(x, y, z),
597 });
598 animation.joints.push(JointData {
599 name: key.clone(),
600 rotation_keys: rotation.into_iter().collect(),
601 position_keys: position.into_iter().collect(),
602 ..Default::default()
603 });
604 }
605 Ok(animation)
606 }
607
608 pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
621 use binrw::BinRead;
622 use std::fs::File;
623 use std::io::BufReader;
624 let file = File::open(path).map_err(AnimError::Io)?;
625 let mut reader = BufReader::new(file);
626 Self::read_options(&mut reader, Endian::Little, ()).map_err(AnimError::BinRw)
627 }
628
629 pub fn to_file<P: AsRef<std::path::Path>>(&self, path: P) -> Result<()> {
643 use binrw::BinWrite;
644 use std::fs::File;
645 use std::io::BufWriter;
646 let file = File::create(path).map_err(AnimError::Io)?;
647 let mut writer = BufWriter::new(file);
648 self.write_options(&mut writer, Endian::Little, ())
649 .map_err(AnimError::BinRw)
650 }
651
652 pub fn from_llsd_file<P: AsRef<std::path::Path>>(path: P, check_enabled: bool) -> Result<Self> {
665 use std::fs::File;
666 use std::io::BufReader;
667 let file = File::open(path).map_err(AnimError::Io)?;
668 let reader = BufReader::new(file);
669 let llsd = llsd_rs::xml::from_reader(reader).map_err(|e| AnimError::Llsd(e.to_string()))?;
670 Self::from_llsd(&llsd, check_enabled)
671 }
672}