use std::{collections::HashMap, io::Read};
use crate::json_extra::JsonExt;
#[inline]
fn read_n<R: Read, const N: usize>(data: &mut R) -> std::io::Result<[u8; N]> {
let mut buf = [0_u8; N];
data.read_exact(&mut buf)?;
Ok(buf)
}
#[inline]
fn read_u8<R: Read>(data: &mut R) -> std::io::Result<u8> {
let buf = read_n::<_, 1>(data)?;
Ok(u8::from_ne_bytes(buf))
}
#[inline]
fn read_be_u32<R: Read>(data: &mut R) -> std::io::Result<u32> {
let buf = read_n::<_, 4>(data)?;
Ok(u32::from_be_bytes(buf))
}
#[inline]
fn read_vec<R: Read>(data: &mut R, n: usize) -> std::io::Result<Vec<u8>> {
let mut buf = Vec::new();
data.take(n as u64).read_to_end(&mut buf)?;
if buf.len() != n {
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
format!("expected {} bytes, got {}", n, buf.len()),
));
}
Ok(buf)
}
#[derive(Clone, Debug)]
pub struct Puppet {
pub meta: Meta,
pub physics: Physics,
pub nodes: Node,
pub params: HashMap<u32, Param>,
pub automation: Automation,
pub animations: HashMap<String, Animation>,
pub groups: Vec<Group>,
pub vendors: Vec<VendorData>,
pub textures: Vec<Texture>,
}
impl Puppet {
pub fn open<P>(path: P) -> std::io::Result<Self>
where
P: AsRef<std::path::Path>,
{
let mut file = std::fs::File::open(path)?;
Self::from_reader(&mut file)
}
pub fn from_bytes(bytes: &[u8]) -> std::io::Result<Self> {
let mut cursor = std::io::Cursor::new(bytes);
Self::from_reader(&mut cursor)
}
fn from_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
let magic = read_n::<_, 8>(reader)?;
if !magic.starts_with(b"TRNSRTS\0") {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid magic: expected TRNSRTS",
));
}
let size = read_be_u32(reader)?;
let json_buffer = read_vec(reader, size as usize)?;
let json_data = std::str::from_utf8(&json_buffer)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
let values = json::parse(json_data)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
let mut puppet = Puppet::from_json(&values)?;
let tex_magic = read_n::<_, 8>(reader)?;
if !tex_magic.starts_with(b"TEX_SECT") {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid magic: expected TEX_SECT",
));
}
let texture_count = read_be_u32(reader)?;
for id in 0..texture_count {
let tex_len = read_be_u32(reader)?;
let format_byte = read_u8(reader)?;
let format = TextureFormat::from_byte(format_byte).ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Invalid texture format: {}", format_byte),
)
})?;
let tex_data = read_vec(reader, tex_len as usize)?;
let (width, height) = format.get_img_dim(&tex_data)?;
puppet
.textures
.push(Texture::new(id, width, height, format, tex_data));
}
let ext_magic = match read_n::<_, 8>(reader) {
Ok(m) => m,
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
return Ok(puppet);
}
Err(e) => return Err(e),
};
if !ext_magic.starts_with(b"EXT_SECT") {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid magic: expected EXT_SECT",
));
}
let ext_count = read_be_u32(reader)?;
for _ in 0..ext_count {
let name_len = read_be_u32(reader)?;
let name_bytes = read_vec(reader, name_len as usize)?;
let name = String::from_utf8_lossy(&name_bytes).into_owned();
let payload_len = read_be_u32(reader)?;
let payload_bytes = read_vec(reader, payload_len as usize)?;
let data = json::parse(&String::from_utf8_lossy(&payload_bytes))
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
puppet.vendors.push(VendorData { name, data });
}
Ok(puppet)
}
fn from_json(root: &json::JsonValue) -> std::io::Result<Self> {
let missing = |field: &str| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("missing '{}' in puppet JSON", field),
)
};
let meta_v = root.get("meta").ok_or_else(|| missing("meta"))?;
let physics_v = root.get("physics").ok_or_else(|| missing("physics"))?;
let nodes_v = root.get("nodes").ok_or_else(|| missing("nodes"))?;
Ok(Self {
meta: Meta::from_json(meta_v),
physics: Physics::from_json(physics_v),
nodes: Node::from_json(nodes_v),
params: parse_params(root),
automation: Automation {},
animations: parse_animations(root),
groups: parse_groups(root),
textures: Vec::new(),
vendors: Vec::new(),
})
}
}
#[derive(Clone, Debug)]
pub struct Meta {
pub name: Option<String>,
pub version: String,
pub rigger: Option<String>,
pub artist: Option<String>,
pub rights: Option<String>,
pub copyright: Option<String>,
pub license_url: Option<String>,
pub contact: Option<String>,
pub reference: Option<String>,
pub thumbnail_id: u32,
pub preserve_pixels: bool,
}
impl Meta {
fn from_json(v: &json::JsonValue) -> Self {
Self {
name: v.get_str("name").map(str::to_owned),
version: v.get_str("version").unwrap_or("1.0").to_owned(),
rigger: v.get_str("rigger").map(str::to_owned),
artist: v.get_str("artist").map(str::to_owned),
rights: v.get_str("rights").map(str::to_owned),
copyright: v.get_str("copyright").map(str::to_owned),
license_url: v.get_str("licenseURL").map(str::to_owned),
contact: v.get_str("contact").map(str::to_owned),
reference: v.get_str("reference").map(str::to_owned),
thumbnail_id: v.get_u32("thumbnailId").unwrap_or(u32::MAX),
preserve_pixels: v.get_bool("preservePixels", false),
}
}
}
#[derive(Clone, Debug)]
pub struct Physics {
pub pixels_per_meter: f32,
pub gravity: f32,
}
impl Physics {
fn from_json(v: &json::JsonValue) -> Self {
Self {
pixels_per_meter: v.get_f32("pixelsPerMeter", 1000.0),
gravity: v.get_f32("gravity", 9.8),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Node {
pub uuid: u32,
pub name: String,
pub type_node: NodeDataType,
pub enabled: bool,
pub zsort: f32,
pub transform: Transform,
pub lock_to_root: bool,
pub children: Vec<Node>,
}
impl Node {
pub fn iter(&self) -> NodeIter<'_> {
NodeIter { stack: vec![self] }
}
pub fn find_by_uuid(&self, uuid: u32) -> Option<&Node> {
self.iter().find(|n| n.uuid == uuid)
}
fn from_json(v: &json::JsonValue) -> Self {
let type_str = v.get_str("type").unwrap_or("generic");
Self {
uuid: v.get_u32("uuid").unwrap_or(u32::MAX),
name: v.get_str("name").unwrap_or("").to_owned(),
type_node: NodeDataType::from_json(type_str, v),
enabled: v.get_bool("enabled", true),
zsort: v.get_f32("zsort", 0.0),
transform: Transform::from_json(v),
lock_to_root: v.get_bool("lockToRoot", false),
children: v
.get_array("children")
.unwrap_or(&[])
.iter()
.map(Node::from_json)
.collect(),
}
}
}
pub struct NodeIter<'a> {
stack: Vec<&'a Node>,
}
impl<'a> Iterator for NodeIter<'a> {
type Item = &'a Node;
fn next(&mut self) -> Option<Self::Item> {
let node = self.stack.pop()?;
self.stack.extend(node.children.iter().rev());
Some(node)
}
}
#[derive(Debug, Clone, Default, Copy)]
pub struct Transform {
pub translation: [f32; 3],
pub rotation: [f32; 3],
pub scale: [f32; 2],
}
impl Transform {
fn from_json(v: &json::JsonValue) -> Self {
match v.get("transform") {
Some(t) => Self {
translation: t.get_vec3("trans").unwrap_or_default(),
rotation: t.get_vec3("rot").unwrap_or_default(),
scale: t.get_vec2("scale").unwrap_or([1.0, 1.0]),
},
None => Self::default(),
}
}
}
#[derive(Clone, Debug, Default)]
pub enum NodeDataType {
Part(PartData),
Camera(CameraData),
SimplePhysics(SimplePhysicsData),
Composite(CompositeData),
Mask(MaskData),
MeshGroup(MeshGroupData),
#[default]
Generic,
}
impl NodeDataType {
fn from_json(type_str: &str, v: &json::JsonValue) -> Self {
match type_str.to_ascii_lowercase().as_str() {
"part" => Self::Part(PartData::from_json(v)),
"camera" => Self::Camera(CameraData::from_json(v)),
"simplephysics" => Self::SimplePhysics(SimplePhysicsData::from_json(v)),
"composite" => Self::Composite(CompositeData::from_json(v)),
"mask" => Self::Mask(MaskData::from_json(v)),
"meshgroup" => Self::MeshGroup(MeshGroupData::from_json(v)),
_ => Self::Generic,
}
}
pub fn as_part(&self) -> Option<&PartData> {
match self {
Self::Part(d) => Some(d),
_ => None,
}
}
pub fn as_composite(&self) -> Option<&CompositeData> {
match self {
Self::Composite(d) => Some(d),
_ => None,
}
}
pub fn as_mask(&self) -> Option<&MaskData> {
match self {
Self::Mask(d) => Some(d),
_ => None,
}
}
pub fn as_mesh_group(&self) -> Option<&MeshGroupData> {
match self {
Self::MeshGroup(d) => Some(d),
_ => None,
}
}
pub fn as_camera(&self) -> Option<&CameraData> {
match self {
Self::Camera(d) => Some(d),
_ => None,
}
}
pub fn as_simple_physics(&self) -> Option<&SimplePhysicsData> {
match self {
Self::SimplePhysics(d) => Some(d),
_ => None,
}
}
pub fn is_generic(&self) -> bool {
matches!(self, Self::Generic)
}
}
#[derive(Debug, Default, Clone)]
pub struct Mesh {
pub vertices: Vec<f32>,
pub indices: Vec<u32>,
pub uvs: Vec<f32>,
pub origin: [f32; 2],
}
fn parse_mesh(v: &json::JsonValue) -> Option<Mesh> {
let mesh = v.get("mesh")?;
let verts = mesh.get_array("verts")?;
if verts.is_empty() {
return None;
}
let indices = mesh.get_array("indices")?;
let uvs = mesh.get_array("uvs")?;
let origin = mesh.get_vec2("origin")?;
Some(Mesh {
vertices: verts.iter().map(|v| v.as_f32().unwrap_or(0.0)).collect(),
indices: indices.iter().map(|i| i.as_u32().unwrap_or(0)).collect(),
uvs: uvs.iter().map(|v| v.as_f32().unwrap_or(0.0)).collect(),
origin,
})
}
#[derive(Clone, Debug, Default)]
pub struct MaskData {
pub mesh: Option<Mesh>,
}
impl MaskData {
fn from_json(v: &json::JsonValue) -> Self {
Self {
mesh: parse_mesh(v),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Mask {
pub source: u32,
pub mode: MaskMode,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MaskMode {
#[default]
Mask,
Dodge,
}
fn parse_masks(v: &json::JsonValue) -> Vec<Mask> {
let Some(masks_val) = v.get("masks") else {
return Vec::new();
};
let arr = masks_val.as_array().unwrap_or(&[]);
let mut result = Vec::with_capacity(arr.len());
for m in arr {
let Some(obj) = m.as_object() else { continue };
let (Some(source), Some(mode)) = (obj.get("source"), obj.get("mode")) else {
continue;
};
result.push(Mask {
source: source.as_u32().unwrap_or(u32::MAX),
mode: match mode.as_str().unwrap_or("mask") {
v if v.eq_ignore_ascii_case("dodgemask") => MaskMode::Dodge,
_ => MaskMode::Mask,
},
});
}
result
}
#[derive(Clone, Debug, Default)]
pub struct PartData {
pub mesh: Option<Mesh>,
pub textures: [u32; 3],
pub blend_mode: BlendMode,
pub tint: [f32; 3],
pub screen_tint: [f32; 3],
pub emission_strength: f32,
pub mask: Vec<Mask>,
pub mask_threshold: f32,
pub opacity: f32,
pub psd_layer_path: Option<String>,
}
impl PartData {
fn from_json(v: &json::JsonValue) -> Self {
let textures_arr = v.get_array("textures").unwrap_or(&[]);
Self {
mesh: parse_mesh(v),
textures: [
textures_arr
.first()
.and_then(|t| t.as_u32())
.unwrap_or(u32::MAX),
textures_arr
.get(1)
.and_then(|t| t.as_u32())
.unwrap_or(u32::MAX),
textures_arr
.get(2)
.and_then(|t| t.as_u32())
.unwrap_or(u32::MAX),
],
blend_mode: BlendMode::from_str(v.get_str("blend_mode").unwrap_or("normal"))
.unwrap_or_default(),
tint: v.get_vec3("tint").unwrap_or([1.0, 1.0, 1.0]),
screen_tint: v.get_vec3("screenTint").unwrap_or([0.0, 0.0, 0.0]),
emission_strength: v.get_f32("emissionStrength", 0.0),
mask: parse_masks(v),
mask_threshold: v.get_f32("mask_threshold", 0.5),
opacity: v.get_f32("opacity", 1.0),
psd_layer_path: v.get_str("psdLayerPath").map(str::to_owned),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct CameraData {
pub viewport: [f32; 2],
}
impl CameraData {
fn from_json(v: &json::JsonValue) -> Self {
Self {
viewport: v.get_vec2("viewport").unwrap_or([1280.0, 720.0]),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct SimplePhysicsData {
pub param: u32,
pub model_type: PhysicsModelType,
pub map_mode: PhysicsMapMode,
pub gravity: f32,
pub length: f32,
pub frequency: f32,
pub angle_damping: f32,
pub length_damping: f32,
pub output_scale: [f32; 2],
pub local_only: Option<bool>,
}
impl SimplePhysicsData {
fn from_json(v: &json::JsonValue) -> Self {
Self {
param: v.get_u32("param").unwrap_or(u32::MAX),
model_type: match v.get_str("model_type") {
Some(s) if s.eq_ignore_ascii_case("springpendulum") => {
PhysicsModelType::SpringPendulum
}
_ => PhysicsModelType::Pendulum,
},
map_mode: match v.get_str("map_mode") {
Some(s) if s.eq_ignore_ascii_case("xy") => PhysicsMapMode::XY,
Some(s) if s.eq_ignore_ascii_case("lengthangle") => PhysicsMapMode::LengthAngle,
Some(s) if s.eq_ignore_ascii_case("yx") => PhysicsMapMode::YX,
_ => PhysicsMapMode::AngleLength,
},
gravity: v.get_f32("gravity", 1.0),
length: v.get_f32("length", 100.0),
frequency: v.get_f32("frequency", 1.0),
angle_damping: v.get_f32("angle_damping", 0.5),
length_damping: v.get_f32("length_damping", 0.5),
output_scale: v.get_vec2("output_scale").unwrap_or([1.0, 1.0]),
local_only: v.get_as_bool("local_only"),
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PhysicsModelType {
#[default]
Pendulum,
SpringPendulum,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PhysicsMapMode {
#[default]
AngleLength,
XY,
LengthAngle,
YX,
}
#[derive(Clone, Debug, Default)]
pub struct CompositeData {
pub blend_mode: BlendMode,
pub tint: [f32; 3],
pub screen_tint: [f32; 3],
pub opacity: f32,
pub mask: Vec<Mask>,
pub mask_threshold: f32,
pub propagate_meshgroup: Option<bool>,
}
impl CompositeData {
fn from_json(v: &json::JsonValue) -> Self {
Self {
blend_mode: BlendMode::from_str(v.get_str("blend_mode").unwrap_or("normal"))
.unwrap_or_default(),
tint: v.get_vec3("tint").unwrap_or([1.0, 1.0, 1.0]),
screen_tint: v.get_vec3("screenTint").unwrap_or([0.0, 0.0, 0.0]),
opacity: v.get_f32("opacity", 1.0),
mask: parse_masks(v),
mask_threshold: v.get_f32("mask_threshold", 0.5),
propagate_meshgroup: v.get_as_bool("propagate_meshgroup"),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct MeshGroupData {
pub mesh: Option<Mesh>,
pub dynamic_deformation: bool,
pub translate_children: bool,
}
impl MeshGroupData {
fn from_json(v: &json::JsonValue) -> Self {
Self {
mesh: parse_mesh(v),
dynamic_deformation: v.get_bool("dynamic_deformation", false),
translate_children: v.get_bool("translate_children", true),
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BlendMode {
#[default]
Normal,
Multiply,
Screen,
Overlay,
Darken,
Lighten,
ColorDodge,
LinearDodge,
Add,
ColorBurn,
HardLight,
SoftLight,
Subtract,
Difference,
Exclusion,
Inverse,
DestinationIn,
ClipToLower,
SliceFromLower,
}
impl BlendMode {
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Option<Self> {
match s {
s if s.eq_ignore_ascii_case("normal") => Some(Self::Normal),
s if s.eq_ignore_ascii_case("multiply") => Some(Self::Multiply),
s if s.eq_ignore_ascii_case("screen") => Some(Self::Screen),
s if s.eq_ignore_ascii_case("overlay") => Some(Self::Overlay),
s if s.eq_ignore_ascii_case("darken") => Some(Self::Darken),
s if s.eq_ignore_ascii_case("lighten") => Some(Self::Lighten),
s if s.eq_ignore_ascii_case("colordodge") => Some(Self::ColorDodge),
s if s.eq_ignore_ascii_case("colorburn") => Some(Self::ColorBurn),
s if s.eq_ignore_ascii_case("hardlight") => Some(Self::HardLight),
s if s.eq_ignore_ascii_case("softlight") => Some(Self::SoftLight),
s if s.eq_ignore_ascii_case("lineardodge") => Some(Self::LinearDodge),
s if s.eq_ignore_ascii_case("difference") => Some(Self::Difference),
s if s.eq_ignore_ascii_case("exclusion") => Some(Self::Exclusion),
s if s.eq_ignore_ascii_case("add") => Some(Self::Add),
s if s.eq_ignore_ascii_case("subtract") => Some(Self::Subtract),
s if s.eq_ignore_ascii_case("cliptolower") => Some(Self::ClipToLower),
s if s.eq_ignore_ascii_case("slicefromlower") => Some(Self::SliceFromLower),
s if s.eq_ignore_ascii_case("inverse") => Some(Self::Inverse),
s if s.eq_ignore_ascii_case("destinationin") => Some(Self::DestinationIn),
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub struct Param {
pub parent_uuid: Option<u32>,
pub uuid: u32,
pub name: String,
pub is_vec2: bool,
pub min: [f32; 2],
pub max: [f32; 2],
pub defaults: [f32; 2],
pub axis_points: [Vec<f32>; 2],
pub merge_mode: MergeMode,
pub bindings: Vec<ParamBinding>,
}
fn parse_params(root: &json::JsonValue) -> HashMap<u32, Param> {
let params = root.get_array("param").unwrap_or(&[]);
let mut map = HashMap::default();
for p in params {
let uuid = p.get_u32("uuid").unwrap_or(u32::MAX);
map.insert(uuid, Param::from_json(p));
}
map
}
impl Param {
fn from_json(v: &json::JsonValue) -> Self {
Self {
parent_uuid: v.get_u32("parentUUID"),
uuid: v.get_u32("uuid").unwrap_or(u32::MAX),
name: v.get_str("name").unwrap_or("").to_owned(),
is_vec2: v.get_bool("is_vec2", false),
min: v.get_vec2("min").unwrap_or([0.0, 0.0]),
max: v.get_vec2("max").unwrap_or([1.0, 1.0]),
defaults: v.get_vec2("defaults").unwrap_or([0.0, 1.0]),
axis_points: parse_axis_points(v),
merge_mode: parse_merge_mode(v.get_str("merge_mode")),
bindings: parse_bindings(v),
}
}
}
fn parse_axis_points(v: &json::JsonValue) -> [Vec<f32>; 2] {
let axis = v.get_array("axis_points").unwrap_or(&[]);
if axis.len() != 2 {
return [Vec::new(), Vec::new()];
}
let parse_axis = |a: &json::JsonValue| -> Vec<f32> {
a.as_array()
.unwrap_or(&[])
.iter()
.map(|v| v.as_f32().unwrap_or(0.0))
.collect()
};
[parse_axis(&axis[0]), parse_axis(&axis[1])]
}
#[derive(Debug, Clone)]
pub struct ParamBinding {
pub node: u32,
pub param_name: ParamName,
pub values: BindingValues,
pub is_set: Vec<Vec<bool>>,
pub interpolate_mode: Interpolation,
}
fn parse_bindings(v: &json::JsonValue) -> Vec<ParamBinding> {
let bindings = v.get_array("bindings").unwrap_or(&[]);
bindings.iter().map(ParamBinding::from_json).collect()
}
impl ParamBinding {
fn from_json(v: &json::JsonValue) -> Self {
let param_name = parse_param_name(v.get_str("param_name"));
let values_v = v.get("values");
Self {
node: v.get_u32("node").unwrap_or(u32::MAX),
values: match values_v {
Some(vals) => match ¶m_name {
ParamName::Deform => BindingValues::Deform(FlatDeformValues::new(vals)),
ParamName::Other(_) => BindingValues::Other(vals.clone()),
_ => BindingValues::Transform(FlatTransformValues::new(vals)),
},
None => BindingValues::Transform(FlatTransformValues {
data: Vec::new(),
frames: 0,
values_per_frame: 0,
}),
},
param_name,
is_set: parse_is_set(v),
interpolate_mode: parse_interpolation(v.get_str("interpolate_mode")),
}
}
}
fn parse_is_set(v: &json::JsonValue) -> Vec<Vec<bool>> {
let is_set = v.get_array("isSet").unwrap_or(&[]);
is_set
.iter()
.map(|row| {
row.as_array()
.unwrap_or(&[])
.iter()
.map(|b| b.as_bool().unwrap_or(false))
.collect()
})
.collect()
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
pub enum ParamName {
TransformTX,
TransformTY,
TransformTZ,
TransformSX,
TransformSY,
TransformRX,
TransformRY,
TransformRZ,
Deform,
#[default]
Opacity,
Other(String),
}
fn parse_param_name(s: Option<&str>) -> ParamName {
match s {
Some("transform.t.x") => ParamName::TransformTX,
Some("transform.t.y") => ParamName::TransformTY,
Some("transform.t.z") => ParamName::TransformTZ,
Some("transform.s.x") => ParamName::TransformSX,
Some("transform.s.y") => ParamName::TransformSY,
Some("transform.r.x") => ParamName::TransformRX,
Some("transform.r.y") => ParamName::TransformRY,
Some("transform.r.z") => ParamName::TransformRZ,
Some("deform") => ParamName::Deform,
Some("opacity") => ParamName::Opacity,
Some(other) => ParamName::Other(other.to_owned()),
None => ParamName::Other(String::new()),
}
}
#[derive(Debug, Clone)]
pub enum BindingValues {
Transform(FlatTransformValues),
Deform(FlatDeformValues),
Other(json::JsonValue),
}
#[derive(Debug, Clone)]
pub struct FlatTransformValues {
pub data: Vec<f32>,
pub frames: usize,
pub values_per_frame: usize,
}
impl FlatTransformValues {
pub fn new(values: &json::JsonValue) -> Self {
let parsed: Vec<Vec<f32>> = values
.as_array()
.unwrap_or(&[])
.iter()
.filter_map(|frame| {
frame
.as_array()
.map(|f| f.iter().filter_map(|v| v.as_f32()).collect())
})
.collect();
if parsed.is_empty() {
return Self {
data: Vec::new(),
frames: 0,
values_per_frame: 0,
};
}
let values_per_frame = parsed[0].len();
debug_assert!(
parsed.iter().all(|v| v.len() == values_per_frame),
"Inconsistent values per frame"
);
let frames = parsed.len();
let data = parsed.into_iter().flatten().collect();
Self {
data,
frames,
values_per_frame,
}
}
pub fn get(&self, frame: usize, index: usize) -> Option<f32> {
if frame >= self.frames || index >= self.values_per_frame {
return None;
}
self.data
.get(frame * self.values_per_frame + index)
.copied()
}
pub fn frames(&self) -> usize {
self.frames
}
pub fn values_per_frame(&self) -> usize {
self.values_per_frame
}
}
#[derive(Debug, Clone)]
pub struct FlatDeformValues {
pub data: Vec<[f32; 2]>,
pub frames: usize,
pub vertices_per_frame: usize,
}
impl FlatDeformValues {
pub fn new(values: &json::JsonValue) -> Self {
let frames_data: Vec<Vec<[f32; 2]>> = values
.as_array()
.unwrap_or(&[])
.iter()
.map(|frame| {
frame
.as_array()
.unwrap_or(&[])
.iter()
.flat_map(|vertex| {
vertex
.as_array()
.unwrap_or(&[])
.iter()
.filter_map(|coords| {
let pair = coords.as_array()?;
Some([pair.first()?.as_f32()?, pair.get(1)?.as_f32()?])
})
})
.collect()
})
.collect();
if frames_data.is_empty() {
return Self {
data: Vec::new(),
frames: 0,
vertices_per_frame: 0,
};
}
let frames = frames_data.len();
let vertices_per_frame = frames_data[0].len();
debug_assert!(
frames_data.iter().all(|f| f.len() == vertices_per_frame),
"Inconsistent vertices per frame"
);
let data = frames_data.into_iter().flatten().collect();
Self {
data,
frames,
vertices_per_frame,
}
}
pub fn get(&self, frame: usize, vertex: usize) -> Option<[f32; 2]> {
if frame >= self.frames || vertex >= self.vertices_per_frame {
return None;
}
let idx = frame * self.vertices_per_frame + vertex;
self.data.get(idx).copied()
}
pub fn frames(&self) -> usize {
self.frames
}
pub fn vertices_per_frame(&self) -> usize {
self.vertices_per_frame
}
pub fn get_frame(&self, frame: usize) -> Option<&[[f32; 2]]> {
if frame >= self.frames {
return None;
}
let start = frame * self.vertices_per_frame;
self.data.get(start..start + self.vertices_per_frame)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MergeMode {
Additive,
Multiplicative,
Override,
Forced,
}
fn parse_merge_mode(s: Option<&str>) -> MergeMode {
match s {
Some(s) if s.eq_ignore_ascii_case("multiply") => MergeMode::Multiplicative,
Some(s) if s.eq_ignore_ascii_case("override") => MergeMode::Override,
Some(s) if s.eq_ignore_ascii_case("forced") => MergeMode::Forced,
_ => MergeMode::Additive,
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Interpolation {
#[default]
Linear,
Stepped,
Nearest,
Cubic,
}
fn parse_interpolation(s: Option<&str>) -> Interpolation {
match s {
Some(s) if s.eq_ignore_ascii_case("stepped") => Interpolation::Stepped,
Some(s) if s.eq_ignore_ascii_case("nearest") => Interpolation::Nearest,
Some(s) if s.eq_ignore_ascii_case("cubic") => Interpolation::Cubic,
Some(s) if s.eq_ignore_ascii_case("linear") => Interpolation::Linear,
_ => Interpolation::Linear,
}
}
#[derive(Clone, Debug)]
pub struct Automation {}
#[derive(Debug, Clone)]
pub struct Animation {
pub name: String,
pub timestep: f32,
pub additive: bool,
pub length: u32,
pub lead_in: u32,
pub lead_out: u32,
pub weight: f32,
pub lanes: Vec<AnimationLane>,
}
impl Animation {
#[inline]
pub fn duration(&self) -> f32 {
self.length as f32 * self.timestep
}
#[inline]
pub fn time_to_frame(&self, time: f32) -> f32 {
time / self.timestep
}
#[inline]
pub fn frame_to_time(&self, frame: f32) -> f32 {
frame * self.timestep
}
}
fn parse_animations(root: &json::JsonValue) -> HashMap<String, Animation> {
let Some(anims) = root.get("animations") else {
return HashMap::default();
};
let Some(obj) = anims.as_object() else {
return HashMap::default();
};
let mut map = HashMap::default();
for (name, data) in obj.iter() {
map.insert(
name.to_owned(),
Animation {
name: name.to_owned(),
timestep: data.get_f32("timestep", 0.016666668),
additive: data.get_bool("additive", false),
length: data.get_u32("length").unwrap_or(0),
lead_in: data.get_u32("leadIn").unwrap_or(0),
lead_out: data.get_u32("leadOut").unwrap_or(0),
weight: data.get_f32("animationWeight", 1.0),
lanes: parse_lanes(data),
},
);
}
map
}
#[derive(Debug, Clone)]
pub struct AnimationLane {
pub interpolation: Interpolation,
pub param_uuid: u32,
pub target: u8,
pub merge_mode: MergeMode,
pub keyframes: Vec<Keyframe>,
}
impl AnimationLane {
pub fn evaluate(&self, frame: f32) -> f32 {
if self.keyframes.is_empty() {
return 0.0;
}
if frame <= self.keyframes[0].frame as f32 {
return self.keyframes[0].value;
}
let last = &self.keyframes[self.keyframes.len() - 1];
if frame >= last.frame as f32 {
return last.value;
}
let mut prev_idx = 0;
for (i, kf) in self.keyframes.iter().enumerate() {
if kf.frame as f32 > frame {
break;
}
prev_idx = i;
}
let prev = &self.keyframes[prev_idx];
let next = &self.keyframes[prev_idx + 1];
let t = (frame - prev.frame as f32) / (next.frame as f32 - prev.frame as f32);
match self.interpolation {
Interpolation::Stepped => prev.value,
Interpolation::Nearest => {
if t < 0.5 {
prev.value
} else {
next.value
}
}
Interpolation::Linear => lerp(prev.value, next.value, t),
Interpolation::Cubic => {
let tension = (prev.tension + next.tension) * 0.5;
cubic_interpolate(prev.value, next.value, t, tension)
}
}
}
}
fn parse_lanes(v: &json::JsonValue) -> Vec<AnimationLane> {
let lanes = v.get_array("lanes").unwrap_or(&[]);
lanes
.iter()
.map(|lane| AnimationLane {
interpolation: parse_interpolation(lane.get_str("interpolation")),
param_uuid: lane.get_u32("uuid").unwrap_or(u32::MAX),
target: lane.get_u32("target").unwrap_or(0) as u8,
merge_mode: parse_merge_mode(lane.get_str("merge_mode")),
keyframes: parse_keyframes(lane),
})
.collect()
}
#[derive(Debug, Clone, Copy)]
pub struct Keyframe {
pub frame: u32,
pub value: f32,
pub tension: f32,
}
fn parse_keyframes(v: &json::JsonValue) -> Vec<Keyframe> {
let kfs = v.get_array("keyframes").unwrap_or(&[]);
kfs.iter()
.map(|kf| Keyframe {
frame: kf.get_u32("frame").unwrap_or(0),
value: kf.get_f32("value", 0.0),
tension: kf.get_f32("tension", 0.5),
})
.collect()
}
#[derive(Clone, Debug)]
pub struct Group {
pub group_uuid: u32,
pub name: String,
pub color: [f32; 3],
}
fn parse_groups(root: &json::JsonValue) -> Vec<Group> {
let groups = root.get_array("groups").unwrap_or(&[]);
groups
.iter()
.map(|g| Group {
group_uuid: g.get_u32("groupUUID").unwrap_or(u32::MAX),
name: g.get_str("name").unwrap_or("").to_owned(),
color: g.get_vec3("color").unwrap_or([0.0, 0.0, 0.0]),
})
.collect()
}
#[derive(Debug, Clone)]
pub struct VendorData {
pub name: String,
pub data: json::JsonValue,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum TextureFormat {
#[default]
Png = 0,
Tga = 1,
Bc7 = 2,
}
impl TextureFormat {
pub fn from_byte(b: u8) -> Option<Self> {
match b {
0 => Some(Self::Png),
1 => Some(Self::Tga),
2 => Some(Self::Bc7),
_ => None,
}
}
pub fn extension(&self) -> &'static str {
match self {
Self::Png => "png",
Self::Tga => "tga",
Self::Bc7 => "bc7",
}
}
pub fn get_img_dim(&self, data: &[u8]) -> std::io::Result<(u32, u32)> {
match self {
TextureFormat::Png => {
if data.len() < 24 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid PNG data (too short)",
));
}
let width = u32::from_be_bytes([data[16], data[17], data[18], data[19]]);
let height = u32::from_be_bytes([data[20], data[21], data[22], data[23]]);
Ok((width, height))
}
TextureFormat::Tga => {
if data.len() < 18 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid TGA data (too short)",
));
}
let width = u16::from_le_bytes([data[12], data[13]]) as u32;
let height = u16::from_le_bytes([data[14], data[15]]) as u32;
Ok((width, height))
}
TextureFormat::Bc7 => {
Ok((0, 0))
}
}
}
}
#[derive(Debug, Clone)]
pub enum TextureData {
Encoded(Vec<u8>),
Rgba(Vec<u8>),
}
#[derive(Debug, Clone)]
pub struct Texture {
pub id: u32,
pub width: u32,
pub height: u32,
pub format: TextureFormat,
pub data: TextureData,
}
impl Texture {
pub fn new(id: u32, width: u32, height: u32, format: TextureFormat, data: Vec<u8>) -> Self {
Self {
id,
width,
height,
format,
data: TextureData::Encoded(data),
}
}
pub fn dimensions_from_data(&self) -> std::io::Result<(u32, u32)> {
match &self.data {
TextureData::Encoded(bytes) => self.format.get_img_dim(bytes),
TextureData::Rgba(_) => {
Ok((self.width, self.height))
}
}
}
}
#[inline]
fn lerp(a: f32, b: f32, t: f32) -> f32 {
a + (b - a) * t
}
#[inline]
fn cubic_interpolate(a: f32, b: f32, t: f32, tension: f32) -> f32 {
let t2 = t * t;
let t3 = t2 * t;
let m = (1.0 - tension) * (b - a);
let h1 = 2.0 * t3 - 3.0 * t2 + 1.0;
let h2 = t3 - 2.0 * t2 + t;
let h3 = -2.0 * t3 + 3.0 * t2;
let h4 = t3 - t2;
h1 * a + h2 * m + h3 * b + h4 * m
}