use crate::{errors::ParseError, utils::skeleton::JointName};
use flate2::bufread::ZlibDecoder;
use glam::{Mat4, Vec3};
use serde::{Deserialize, Serialize};
use serde_llsd_benthic::{de::binary, LLSDValue};
use std::{collections::HashMap, io::Read, str::FromStr};
const ZLIB_MAGIC_NUMBER: u8 = 120;
const ZLIB_DECODING_TYPE: u8 = 218;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Mesh {
pub position: Option<Vec3>,
pub high_level_of_detail: MeshGeometry,
pub medium_level_of_detail: Option<MeshGeometry>,
pub low_level_of_detail: Option<MeshGeometry>,
pub lowest_level_of_detail: Option<MeshGeometry>,
pub physics_convex: Option<Vec<u8>>,
pub skin: Option<Skin>,
}
impl Mesh {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
let mut mesh = Mesh {
..Default::default()
};
let data = binary::from_bytes(bytes)?;
let sentinel_location = bytes
.windows(2)
.position(|w| w == [ZLIB_MAGIC_NUMBER, ZLIB_DECODING_TYPE])
.expect("Zlib header not found");
let compressed_data = &bytes[sentinel_location..];
let map_data = data.into_map()?;
let get_offset_size = |key: &str| -> Result<Option<(usize, usize)>, ParseError> {
map_data.get(key).map(extract_offset_size).transpose()
};
let (high_lod_offset, high_lod_size) = extract_offset_size(
map_data
.get("high_lod")
.ok_or(ParseError::MissingField("high_lod".into()))?,
)?;
let (medium_lod_offset, medium_lod_size) = get_offset_size("medium_lod")?.unwrap_or((0, 0));
let (low_lod_offset, low_lod_size) = get_offset_size("low_lod")?.unwrap_or((0, 0));
let (lowest_lod_offset, lowest_lod_size) = get_offset_size("lowest_lod")?.unwrap_or((0, 0));
let (physics_convex_offset, physics_convex_size) =
get_offset_size("physics_convex")?.unwrap_or((0, 0));
let (skin_offset, skin_size) = get_offset_size("skin")?.unwrap_or((0, 0));
let high_level_of_detail =
decompress_slice(&compressed_data[high_lod_offset..high_lod_offset + high_lod_size])?;
let medium_level_of_detail = if medium_lod_size > 0 {
Some(decompress_slice(
&compressed_data[medium_lod_offset..medium_lod_offset + medium_lod_size],
)?)
} else {
None
};
let low_level_of_detail = if low_lod_size > 0 {
Some(decompress_slice(
&compressed_data[low_lod_offset..low_lod_offset + low_lod_size],
)?)
} else {
None
};
let lowest_level_of_detail = if lowest_lod_size > 0 {
Some(decompress_slice(
&compressed_data[lowest_lod_offset..lowest_lod_offset + lowest_lod_size],
)?)
} else {
None
};
if physics_convex_size > 0 {
let physics_convex = decompress_slice(
&compressed_data
[physics_convex_offset..physics_convex_offset + physics_convex_size],
)?;
mesh.physics_convex = Some(physics_convex);
}
if skin_size > 0 {
let skin = decompress_slice(&compressed_data[skin_offset..skin_offset + skin_size])?;
mesh.skin = Some(Skin::from_llsd(binary::from_bytes(&skin)?)?);
}
mesh.high_level_of_detail =
MeshGeometry::from_llsd(binary::from_bytes(&high_level_of_detail)?, &mesh.skin)?;
mesh.medium_level_of_detail = medium_level_of_detail
.as_ref()
.map(|bytes| MeshGeometry::from_llsd(binary::from_bytes(bytes).unwrap(), &mesh.skin))
.transpose()?;
mesh.low_level_of_detail = low_level_of_detail
.as_ref()
.map(|bytes| MeshGeometry::from_llsd(binary::from_bytes(bytes).unwrap(), &mesh.skin))
.transpose()?;
mesh.lowest_level_of_detail = lowest_level_of_detail
.as_ref()
.map(|bytes| MeshGeometry::from_llsd(binary::from_bytes(bytes).unwrap(), &mesh.skin))
.transpose()?;
Ok(mesh)
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct MeshGeometry {
pub no_geometry: bool,
pub position_domain: Option<PositionDomain>,
pub weights: Option<Vec<JointWeight>>,
pub texture_coordinate: Vec<TextureCoordinate>,
pub texture_coordinate_domain: TextureCoordinateDomain,
pub triangles: Option<Vec<Vec3>>,
pub vertices: Vec<Vec3>,
pub indices: Vec<u16>,
}
impl MeshGeometry {
fn from_llsd(data: LLSDValue, skin: &Option<Skin>) -> Result<Self, ParseError> {
let array = data
.as_array()
.ok_or_else(|| ParseError::MissingField("Expected top level array".into()))?;
let map = array
.first()
.and_then(LLSDValue::as_map)
.ok_or_else(|| ParseError::MissingField("Expected map inside array".into()))?;
let position_domain = map
.get("PositionDomain")
.and_then(LLSDValue::as_map)
.ok_or_else(|| ParseError::MissingField("PositionDomain".into()))?;
let min = position_domain
.get("Min")
.and_then(LLSDValue::as_array)
.ok_or_else(|| ParseError::MissingField("PositionDomain Min".into()))?;
let max = position_domain
.get("Max")
.and_then(LLSDValue::as_array)
.ok_or_else(|| ParseError::MissingField("PositionDomain Max".into()))?;
let position_domain_min = Vec3::new(
parse_f32(&min[0])
.ok_or_else(|| ParseError::InvalidField("position domain min x".into()))?,
parse_f32(&min[1])
.ok_or_else(|| ParseError::InvalidField("position domain min y".into()))?,
parse_f32(&min[2])
.ok_or_else(|| ParseError::InvalidField("position domain min z".into()))?,
);
let position_domain_max = Vec3::new(
parse_f32(&max[0])
.ok_or_else(|| ParseError::InvalidField("position domain max x".into()))?,
parse_f32(&max[1])
.ok_or_else(|| ParseError::InvalidField("Invalid position domain max y".into()))?,
parse_f32(&max[2])
.ok_or_else(|| ParseError::InvalidField("Invalid position domain max z".into()))?,
);
let position_bytes = parse_binary(map, "Position")?;
let mut positions = Vec::new();
if position_bytes.len() % 6 != 0 {
return Err(ParseError::MeshError(
"Position data length is not a multiple of 6".into(),
));
}
for chunk in position_bytes.chunks_exact(6) {
let x = u16::from_le_bytes([chunk[0], chunk[1]]);
let y = u16::from_le_bytes([chunk[2], chunk[3]]);
let z = u16::from_le_bytes([chunk[4], chunk[5]]);
let xf = position_domain_min.x
+ (x as f32 / 65535.0) * (position_domain_max.x - position_domain_min.x);
let yf = position_domain_min.y
+ (y as f32 / 65535.0) * (position_domain_max.y - position_domain_min.y);
let zf = position_domain_min.z
+ (z as f32 / 65535.0) * (position_domain_max.z - position_domain_min.z);
positions.push(Vec3::new(xf, yf, zf));
}
let triangle_bytes = parse_binary(map, "TriangleList")?;
if triangle_bytes.len() % 2 != 0 {
return Err(ParseError::MeshError(
"TriangleList data has odd length (should be even)".into(),
));
}
let mut triangle_indices = Vec::new();
for chunk in triangle_bytes.chunks_exact(2) {
triangle_indices.push(u16::from_le_bytes([chunk[0], chunk[1]]));
}
let weights = map
.get("Weights")
.and_then(|weights_llsd| skin.as_ref().map(|skin| (weights_llsd, skin)))
.map(|(weights_llsd, skin)| handle_skin(weights_llsd, skin.joint_names.as_ref()))
.transpose()?;
let data = parse_binary(map, "TexCoord0")?;
if data.len() % 4 != 0 {
return Err(ParseError::InvalidField(
"TexCoord0 is not a multiple of 4".into(),
));
}
let texture_coordinate = data
.chunks_exact(4)
.map(|chunk| {
let u = u16::from_le_bytes([chunk[0], chunk[1]]);
let v = u16::from_le_bytes([chunk[2], chunk[3]]);
TextureCoordinate { u, v }
})
.collect::<Vec<_>>();
let domain_value = map
.get("TexCoord0Domain")
.ok_or_else(|| ParseError::MissingField("TexCoord0Domain".into()))?;
let domain_map = match domain_value {
LLSDValue::Map(m) => m,
_ => {
return Err(ParseError::InvalidField(
"TexCoord0Domain is not a Map".into(),
));
}
};
let min = match domain_map.get("Min") {
Some(LLSDValue::Array(arr)) if arr.len() == 2 => {
let x = match &arr[1] {
LLSDValue::Real(f) => *f as f32,
_ => return Err(ParseError::InvalidField("Invalid Min value".into())),
};
let y = match &arr[0] {
LLSDValue::Real(f) => *f as f32,
_ => return Err(ParseError::InvalidField("Invalid Min value".into())),
};
[x, y]
}
_ => return Err(ParseError::InvalidField("Min is missing or invalid".into())),
};
let max = match domain_map.get("Max") {
Some(LLSDValue::Array(arr)) if arr.len() == 2 => {
let x = match &arr[0] {
LLSDValue::Real(f) => *f as f32,
_ => return Err(ParseError::InvalidField("Invalid Max value".into())),
};
let y = match &arr[1] {
LLSDValue::Real(f) => *f as f32,
_ => return Err(ParseError::InvalidField("Invalid Max value".into())),
};
[x, y]
}
_ => return Err(ParseError::InvalidField("Max is missing or invalid".into())),
};
let texture_coordinate_domain = TextureCoordinateDomain { min, max };
Ok(MeshGeometry {
no_geometry: false,
position_domain: Some(PositionDomain {
min: position_domain_min,
max: position_domain_max,
}),
texture_coordinate_domain,
weights,
texture_coordinate,
triangles: None,
vertices: positions,
indices: triangle_indices,
})
}
}
fn handle_skin(data: &LLSDValue, joints: &[JointName]) -> Result<Vec<JointWeight>, ParseError> {
let map = match data {
LLSDValue::Binary(map) => map,
_ => return Err(ParseError::InvalidField("Weights".into())),
};
let mut weights = Vec::new();
let mut iter = map.iter().cloned(); while let Some(_) = iter.clone().next() {
let mut joint_indices = [0u8; 4];
let mut joint_weights = [0u16; 4];
for i in 0..4 {
let joint = match iter.next() {
Some(j) => j,
None => {
return Err(ParseError::InvalidField(
"Unexpected end of weight data".into(),
));
}
};
if joint == 0xFF {
break; }
let w1 = match iter.next() {
Some(b) => b,
None => {
return Err(ParseError::InvalidField(
"Unexpected end while reading weight value".into(),
));
}
};
let w2 = match iter.next() {
Some(b) => b,
None => {
return Err(ParseError::InvalidField(
"Unexpected end while reading weight value".into(),
));
}
};
let weight = u16::from_le_bytes([w1, w2]);
joint_indices[i] = joint;
joint_weights[i] = weight;
}
let raw_f32: [f32; 4] = joint_weights.map(|w| w as f32);
let total: f32 = raw_f32.iter().sum();
let normalized_weights: [f32; 4] = if total > 0.0 {
raw_f32.map(|w| w / total)
} else {
[0.25, 0.25, 0.25, 0.25]
};
weights.push(JointWeight {
indices: joint_indices,
weights: normalized_weights,
joint_name: [
joints[joint_indices[0] as usize],
joints[joint_indices[1] as usize],
joints[joint_indices[2] as usize],
joints[joint_indices[3] as usize],
],
});
}
Ok(weights)
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct PositionDomain {
pub min: Vec3,
pub max: Vec3,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct JointWeight {
pub indices: [u8; 4],
pub weights: [f32; 4],
pub joint_name: [JointName; 4],
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct TextureCoordinate {
pub u: u16,
pub v: u16,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct TextureCoordinateDomain {
pub min: [f32; 2],
pub max: [f32; 2],
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Skin {
pub joint_names: Vec<JointName>,
pub inverse_bind_matrices: Vec<Mat4>,
pub bind_shape_matrix: Mat4,
}
impl Skin {
fn from_llsd(data: LLSDValue) -> Result<Self, ParseError> {
let map = data
.as_map()
.ok_or_else(|| ParseError::MissingField("Expected top level map".into()))?;
let joint_names: Vec<JointName> = map
.get("joint_names")
.and_then(LLSDValue::as_array)
.ok_or_else(|| ParseError::MissingField("joint_names".into()))?
.iter()
.map(|v| {
v.as_string()
.ok_or_else(|| ParseError::InvalidField("joint name (not a string)".into()))
.and_then(|s| {
JointName::from_str(s).map_err(|e| {
ParseError::InvalidField(format!("Unknown joint name: {}, {}", s, e))
})
})
})
.collect::<Result<Vec<_>, _>>()?;
let inverse_bind_matrices =
map.get("inverse_bind_matrix")
.and_then(LLSDValue::as_array)
.ok_or_else(|| ParseError::MissingField("inverse_bind_matrix".into()))?
.iter()
.map(|matrix_val| {
let flat = matrix_val
.as_array()
.ok_or_else(|| ParseError::InvalidField("Expected matrix array".into()))?;
if flat.len() != 16 {
return Err(ParseError::InvalidField(
"Matrix must have 16 elements".into(),
));
}
let mut floats = [0.0f32; 16];
for (i, val) in flat.iter().enumerate() {
floats[i] = *val.as_real().ok_or_else(|| {
ParseError::InvalidField("Matrix element not real".into())
})? as f32;
}
Ok(Mat4::from_cols_array(&floats))
})
.collect::<Result<Vec<_>, _>>()?;
let bind_shape_vals = map
.get("bind_shape_matrix")
.and_then(LLSDValue::as_array)
.ok_or_else(|| ParseError::MissingField("bind_shape_matrix".into()))?;
if bind_shape_vals.len() != 16 {
return Err(ParseError::InvalidField(
"bind_shape_matrix must have 16 elements".into(),
));
}
let mut bind_shape_array = [0.0f32; 16];
for (i, val) in bind_shape_vals.iter().enumerate() {
bind_shape_array[i] = *val.as_real().ok_or_else(|| {
ParseError::InvalidField("Invalid bind shape matrix element".into())
})? as f32;
}
let bind_shape_matrix = Mat4::from_cols_array(&bind_shape_array);
Ok(Self {
joint_names,
inverse_bind_matrices,
bind_shape_matrix,
})
}
}
fn decompress_slice(slice: &[u8]) -> Result<Vec<u8>, ParseError> {
let mut decoder = ZlibDecoder::new(slice);
let mut decoded = Vec::new();
decoder.read_to_end(&mut decoded)?;
Ok(decoded)
}
fn extract_offset_size(map: &LLSDValue) -> Result<(usize, usize), ParseError> {
if let LLSDValue::Map(inner) = map {
let offset = match inner.get("offset") {
Some(LLSDValue::Integer(val)) => *val as usize,
_ => return Err(ParseError::MissingField("offset".into())),
};
let size = match inner.get("size") {
Some(LLSDValue::Integer(val)) => *val as usize,
_ => return Err(ParseError::MissingField("size".into())),
};
Ok((offset, size))
} else {
Err(ParseError::MissingField("Expected a map".into()))
}
}
fn parse_f32(value: &LLSDValue) -> Option<f32> {
match value {
LLSDValue::Real(n) => Some(*n as f32),
LLSDValue::Integer(n) => Some(*n as f32),
_ => None,
}
}
fn parse_binary(map: &HashMap<String, LLSDValue>, key: &str) -> Result<Vec<u8>, ParseError> {
match map.get(key) {
Some(LLSDValue::Binary(data)) => Ok(data.clone()),
_ => Err(ParseError::InvalidField(format!(
"Missing or invalid binary data for key: {}",
key
))),
}
}