diff --git a/api/SHIM-COVERAGE.md b/api/SHIM-COVERAGE.md index 0416143..603d29c 100644 --- a/api/SHIM-COVERAGE.md +++ b/api/SHIM-COVERAGE.md @@ -4,7 +4,7 @@ Generated by `python3 tools/generate_api_shims.py`; do not edit by hand. | Assembly | Types | Members | Status | |---|---:|---:|---| -| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 207 types / 15,315 members; remaining surface is callable failure-only shims | +| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 225 types / 15,463 members; remaining surface is callable failure-only shims | | `LibreMetaverse.Imaging.Abstractions` | 3 | 20 | native implementation: 3 types / 20 members; no generated shims remain | | `LibreMetaverse.Imaging.Skia` | 1 | 3 | native implementation: 1 type / 3 members; no generated shims remain | | `LibreMetaverse.LslTools` | 164 | 768 | callable failure-only shim | diff --git a/crates/libremetaverse/src/animation.rs b/crates/libremetaverse/src/animation.rs new file mode 100644 index 0000000..f99ffa5 --- /dev/null +++ b/crates/libremetaverse/src/animation.rs @@ -0,0 +1,335 @@ +//! Bounded decoder for the simulator's binary BVH animation asset format. + +#![allow(clippy::needless_pass_by_value, clippy::unused_self)] // Mapped C# signatures are fixed. + +use libremetaverse_types::compat::Object; +use libremetaverse_types::{Error, Vector3}; +use std::hash::{Hash, Hasher}; + +const MAX_ANIMATION_BYTES: usize = 32 * 1024 * 1024; +const MAX_JOINTS: usize = 512; +const MAX_KEYS_PER_JOINT: usize = 10_000; +const MAX_JOINT_NAME_BYTES: usize = 255; + +#[derive(Clone, Debug)] +pub struct BinBVHJointKey { + pub key_element: Vector3, + pub time: f32, +} + +#[derive(Clone, Debug)] +pub struct BinBVHJoint { + pub name: String, + pub priority: i32, + pub tag: Object, + pub positionkeys: Vec, + pub rotationkeys: Vec, +} + +impl BinBVHJoint { + pub(crate) fn native_equals(&self, other: &Self) -> bool { + self.priority == other.priority + && self.name == other.name + && keys_equal(&self.rotationkeys, &other.rotationkeys) + && keys_equal(&self.positionkeys, &other.positionkeys) + } + + pub(crate) fn native_get_hash_code(&self) -> i32 { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + self.priority.hash(&mut hasher); + self.name.hash(&mut hasher); + for key in self.rotationkeys.iter().chain(&self.positionkeys) { + key.time.to_bits().hash(&mut hasher); + key.key_element.x.to_bits().hash(&mut hasher); + key.key_element.y.to_bits().hash(&mut hasher); + key.key_element.z.to_bits().hash(&mut hasher); + } + fold_hash(hasher.finish()) + } +} + +pub(crate) fn key_equal(left: &BinBVHJointKey, right: &BinBVHJointKey) -> bool { + left.time.to_bits() == right.time.to_bits() && Vector3::eq(left.key_element, right.key_element) +} + +pub(crate) fn keys_equal(left: &[BinBVHJointKey], right: &[BinBVHJointKey]) -> bool { + left.len() == right.len() + && left + .iter() + .zip(right) + .all(|(left, right)| key_equal(left, right)) +} + +pub(crate) fn joints_equal(left: &[BinBVHJoint], right: &[BinBVHJoint]) -> bool { + left.len() == right.len() + && left + .iter() + .zip(right) + .all(|(left, right)| left.native_equals(right)) +} + +#[derive(Clone, Debug)] +pub struct BinBVHAnimationReader { + pub ease_in_time: f32, + pub ease_out_time: f32, + pub expression_name: String, + pub hand_pose: u32, + pub in_point: f32, + pub joint_count: u32, + pub length: f32, + pub loop_: bool, + pub out_point: f32, + pub priority: i32, + pub joints: Vec, + pub unknown0: u16, + pub unknown1: u16, +} + +impl BinBVHAnimationReader { + pub(crate) fn native_new(data: Vec) -> Result { + if data.len() > MAX_ANIMATION_BYTES { + return Err(Error::Argument); + } + let mut cursor = Cursor::new(&data); + let unknown0 = cursor.u16()?; + let unknown1 = cursor.u16()?; + let priority = cursor.i32()?; + let length = cursor.f32()?; + let expression_name = cursor.string(MAX_JOINT_NAME_BYTES)?; + let in_point = cursor.f32()?; + let out_point = cursor.f32()?; + let loop_ = cursor.i32()? != 0; + let ease_in_time = cursor.f32()?; + let ease_out_time = cursor.f32()?; + let hand_pose = cursor.u32()?; + let joint_count = cursor.u32()?; + if length < 0.0 + || in_point < 0.0 + || out_point < in_point + || ease_in_time < 0.0 + || ease_out_time < 0.0 + { + return Err(Error::Argument); + } + let count = usize::try_from(joint_count).map_err(|_| Error::Argument)?; + if count > MAX_JOINTS { + return Err(Error::Argument); + } + let mut result = Self { + ease_in_time, + ease_out_time, + expression_name, + hand_pose, + in_point, + joint_count, + length, + loop_, + out_point, + priority, + joints: Vec::with_capacity(count), + unknown0, + unknown1, + }; + for _ in 0..count { + result.joints.push(result.read_joint_cursor(&mut cursor)?); + } + Ok(result) + } + + fn read_joint_cursor(&self, cursor: &mut Cursor<'_>) -> Result { + let name = cursor.string(MAX_JOINT_NAME_BYTES)?; + let priority = cursor.i32()?; + let rotation_count = cursor.count(MAX_KEYS_PER_JOINT)?; + let rotationkeys = self.read_keys_cursor(cursor, rotation_count, -1.0, 1.0)?; + let position_count = cursor.count(MAX_KEYS_PER_JOINT)?; + let positionkeys = self.read_keys_cursor(cursor, position_count, -0.5, 1.5)?; + Ok(BinBVHJoint { + name, + priority, + tag: Object::Undefined, + positionkeys, + rotationkeys, + }) + } + + fn read_keys_cursor( + &self, + cursor: &mut Cursor<'_>, + count: usize, + min: f32, + max: f32, + ) -> Result, Error> { + let mut keys = Vec::with_capacity(count); + for _ in 0..count { + keys.push(BinBVHJointKey { + time: decode_u16(cursor.u16()?, self.in_point, self.out_point), + key_element: Vector3 { + x: decode_u16(cursor.u16()?, min, max), + y: decode_u16(cursor.u16()?, min, max), + z: decode_u16(cursor.u16()?, min, max), + }, + }); + } + Ok(keys) + } + + pub(crate) fn native_read_bytes_until_null( + &self, + data: Vec, + offset: &mut i32, + ) -> Result { + let start = usize::try_from(*offset).map_err(|_| Error::Argument)?; + let mut cursor = Cursor::at(&data, start)?; + let value = cursor.string(MAX_JOINT_NAME_BYTES)?; + *offset = i32::try_from(cursor.position).map_err(|_| Error::Argument)?; + Ok(value) + } + + pub(crate) fn native_read_joint( + &self, + data: Vec, + offset: &mut i32, + ) -> Result { + let start = usize::try_from(*offset).map_err(|_| Error::Argument)?; + let mut cursor = Cursor::at(&data, start)?; + let joint = self.read_joint_cursor(&mut cursor)?; + *offset = i32::try_from(cursor.position).map_err(|_| Error::Argument)?; + Ok(joint) + } + + pub(crate) fn native_read_keys( + &self, + data: Vec, + offset: &mut i32, + key_count: i32, + min: f32, + max: f32, + ) -> Result, Error> { + let count = usize::try_from(key_count).map_err(|_| Error::Argument)?; + if count > MAX_KEYS_PER_JOINT || !min.is_finite() || !max.is_finite() || min > max { + return Err(Error::Argument); + } + let start = usize::try_from(*offset).map_err(|_| Error::Argument)?; + let mut cursor = Cursor::at(&data, start)?; + let keys = self.read_keys_cursor(&mut cursor, count, min, max)?; + *offset = i32::try_from(cursor.position).map_err(|_| Error::Argument)?; + Ok(keys) + } + + pub(crate) fn native_equals(&self, other: Option<&Self>) -> bool { + let Some(other) = other else { return false }; + self.loop_ == other.loop_ + && self.out_point.to_bits() == other.out_point.to_bits() + && self.in_point.to_bits() == other.in_point.to_bits() + && self.length.to_bits() == other.length.to_bits() + && self.hand_pose == other.hand_pose + && self.joint_count == other.joint_count + && self.ease_in_time.to_bits() == other.ease_in_time.to_bits() + && self.ease_out_time.to_bits() == other.ease_out_time.to_bits() + && self.priority == other.priority + && self.unknown0 == other.unknown0 + && self.unknown1 == other.unknown1 + && self.joints.len() == other.joints.len() + && self + .joints + .iter() + .zip(&other.joints) + .all(|(left, right)| left.native_equals(right)) + } + + pub(crate) fn native_get_hash_code(&self) -> i32 { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + self.loop_.hash(&mut hasher); + self.out_point.to_bits().hash(&mut hasher); + self.in_point.to_bits().hash(&mut hasher); + self.length.to_bits().hash(&mut hasher); + self.hand_pose.hash(&mut hasher); + self.priority.hash(&mut hasher); + for joint in &self.joints { + joint.native_get_hash_code().hash(&mut hasher); + } + fold_hash(hasher.finish()) + } +} + +fn fold_hash(hash: u64) -> i32 { + let folded = hash ^ (hash >> 32); + u32::try_from(folded & u64::from(u32::MAX)) + .unwrap_or_default() + .cast_signed() +} + +fn decode_u16(value: u16, min: f32, max: f32) -> f32 { + min + (f32::from(value) / f32::from(u16::MAX)) * (max - min) +} + +struct Cursor<'a> { + data: &'a [u8], + position: usize, +} + +impl<'a> Cursor<'a> { + const fn new(data: &'a [u8]) -> Self { + Self { data, position: 0 } + } + + fn at(data: &'a [u8], position: usize) -> Result { + if position > data.len() { + return Err(Error::IndexOutOfRange); + } + Ok(Self { data, position }) + } + + fn take(&mut self) -> Result<[u8; N], Error> { + let end = self.position.checked_add(N).ok_or(Error::IndexOutOfRange)?; + let bytes = self + .data + .get(self.position..end) + .ok_or(Error::IndexOutOfRange)?; + self.position = end; + bytes.try_into().map_err(|_| Error::IndexOutOfRange) + } + + fn u16(&mut self) -> Result { + Ok(u16::from_le_bytes(self.take()?)) + } + + fn u32(&mut self) -> Result { + Ok(u32::from_le_bytes(self.take()?)) + } + + fn i32(&mut self) -> Result { + Ok(i32::from_le_bytes(self.take()?)) + } + + fn f32(&mut self) -> Result { + let value = f32::from_le_bytes(self.take()?); + value.is_finite().then_some(value).ok_or(Error::Argument) + } + + fn count(&mut self, maximum: usize) -> Result { + let value = usize::try_from(self.i32()?).map_err(|_| Error::Argument)?; + (value <= maximum).then_some(value).ok_or(Error::Argument) + } + + fn string(&mut self, maximum: usize) -> Result { + let remaining = self + .data + .get(self.position..) + .ok_or(Error::IndexOutOfRange)?; + let length = remaining + .iter() + .take(maximum.saturating_add(1)) + .position(|byte| *byte == 0) + .ok_or(Error::Argument)?; + if length > maximum { + return Err(Error::Argument); + } + let bytes = &remaining[..length]; + self.position = self + .position + .checked_add(length + 1) + .ok_or(Error::IndexOutOfRange)?; + String::from_utf8(bytes.to_vec()).map_err(|_| Error::Argument) + } +} diff --git a/crates/libremetaverse/src/animesh.rs b/crates/libremetaverse/src/animesh.rs new file mode 100644 index 0000000..688c9ce --- /dev/null +++ b/crates/libremetaverse/src/animesh.rs @@ -0,0 +1,499 @@ +//! Deterministic animation playback and object-animation asset coordination. + +#![allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)] // Mapped constructors are fixed. + +use crate::animation::{BinBVHAnimationReader, BinBVHJointKey}; +use crate::client_core::ClientWeakHandle; +use crate::packet_catalog::PacketType; +use crate::packets::ObjectAnimationPacket; +use crate::{Error, GridClient}; +use libremetaverse_types::compat::{CancellationTokenSource, Subscription}; +use libremetaverse_types::{AssetType, Quaternion, UUID, Vector3}; +use std::collections::{HashMap, HashSet}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex, RwLock}; + +const MAX_TRACKS_PER_PLAYER: usize = 64; +const MAX_PLAYERS: usize = 4096; +const MAX_ANIMATIONS_PER_UPDATE: usize = 64; +const MAX_CONCURRENT_FETCHES: usize = 16; + +fn mutex(lock: &Mutex) -> std::sync::MutexGuard<'_, T> { + lock.lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) +} + +fn read(lock: &RwLock) -> std::sync::RwLockReadGuard<'_, T> { + lock.read() + .unwrap_or_else(std::sync::PoisonError::into_inner) +} + +fn write(lock: &RwLock) -> std::sync::RwLockWriteGuard<'_, T> { + lock.write() + .unwrap_or_else(std::sync::PoisonError::into_inner) +} + +#[derive(Clone, Copy, Debug)] +pub struct JointPose { + pub ease_weight: f32, + pub has_position: bool, + pub has_rotation: bool, + pub position: Vector3, + pub priority: i32, + pub rotation: Quaternion, +} + +#[derive(Default)] +struct TrackState { + data: Option, + current_time: f32, + is_finished: bool, +} + +pub struct AnimationTrack { + animation_id: UUID, + state: Mutex, +} + +impl AnimationTrack { + pub(crate) fn native_new(animation_id: UUID) -> Result { + Ok(Self { + animation_id, + state: Mutex::new(TrackState::default()), + }) + } + + pub(crate) fn native_animation_id(&self) -> UUID { + self.animation_id + } + + pub(crate) fn native_data(&self) -> Option { + mutex(&self.state).data.clone() + } + + pub(crate) fn native_set_data(&self, data: Option) { + let mut state = mutex(&self.state); + state.current_time = 0.0; + state.is_finished = false; + state.data = data; + } + + pub(crate) fn native_current_time(&self) -> f32 { + mutex(&self.state).current_time + } + + pub(crate) fn native_set_current_time(&self, value: f32) { + if value.is_finite() { + mutex(&self.state).current_time = value; + } + } + + pub(crate) fn native_is_finished(&self) -> bool { + mutex(&self.state).is_finished + } + + pub(crate) fn native_set_is_finished(&self, value: bool) { + mutex(&self.state).is_finished = value; + } + + pub(crate) fn native_advance(&self, dt: f32) -> Result<(), Error> { + if !dt.is_finite() || dt < 0.0 { + return Err(Error::Argument); + } + let mut state = mutex(&self.state); + if state.is_finished || state.data.is_none() { + return Ok(()); + } + let data = state.data.as_ref().expect("checked animation data"); + let (looping, in_point, out_point) = (data.loop_, data.in_point, data.out_point); + state.current_time += dt; + if looping { + let span = out_point - in_point; + if span > 0.0 && state.current_time > out_point { + state.current_time = in_point + (state.current_time - in_point) % span; + } + } else if state.current_time >= out_point { + state.current_time = out_point; + state.is_finished = true; + } + Ok(()) + } + + pub(crate) fn native_ease_weight(&self) -> f32 { + let state = mutex(&self.state); + ease_weight(&state) + } + + pub(crate) fn native_evaluate_pose( + &self, + pose: &mut HashMap, + ) -> Result<(), Error> { + let state = mutex(&self.state); + let Some(data) = &state.data else { + return Ok(()); + }; + let ease = ease_weight(&state); + for joint in data.joints.iter().take(MAX_TRACKS_PER_PLAYER * 8) { + let rotation = (!joint.rotationkeys.is_empty()) + .then(|| interpolate_rotation(&joint.rotationkeys, state.current_time)) + .transpose()?; + let position = (!joint.positionkeys.is_empty()) + .then(|| interpolate_position(&joint.positionkeys, state.current_time)) + .transpose()?; + if rotation.is_none() && position.is_none() { + continue; + } + let (rotation, position) = if let Some(existing) = pose.get(&joint.name).copied() { + if joint.priority < existing.priority { + continue; + } + if joint.priority == existing.priority { + let total = existing.ease_weight + ease; + let weight = if total > 0.0 { ease / total } else { 0.5 }; + ( + match (rotation, existing.has_rotation) { + (Some(value), true) => { + Some(Quaternion::slerp(existing.rotation, value, weight)?) + } + (Some(value), false) => Some(value), + (None, true) => Some(existing.rotation), + (None, false) => None, + }, + match (position, existing.has_position) { + (Some(value), true) => { + Some(Vector3::lerp(existing.position, value, weight)?) + } + (Some(value), false) => Some(value), + (None, true) => Some(existing.position), + (None, false) => None, + }, + ) + } else { + (rotation, position) + } + } else { + (rotation, position) + }; + pose.insert( + joint.name.clone(), + JointPose { + rotation: rotation.unwrap_or_else(Quaternion::identity), + has_rotation: rotation.is_some(), + position: position.unwrap_or_else(Vector3::zero), + has_position: position.is_some(), + priority: joint.priority, + ease_weight: ease, + }, + ); + } + Ok(()) + } + + pub(crate) fn native_decode_rotation(value: Vector3) -> Result { + let w_squared = 1.0 - value.x * value.x - value.y * value.y - value.z * value.z; + Quaternion::normalize(Quaternion { + x: value.x, + y: value.y, + z: value.z, + w: w_squared.max(0.0).sqrt(), + }) + } +} + +fn ease_weight(state: &TrackState) -> f32 { + let Some(data) = &state.data else { return 0.0 }; + if data.ease_in_time > 0.0 && state.current_time < data.ease_in_time { + return (state.current_time / data.ease_in_time).clamp(0.0, 1.0); + } + let start = data.out_point - data.ease_out_time; + if data.ease_out_time > 0.0 && state.current_time > start { + return ((data.out_point - state.current_time) / data.ease_out_time).clamp(0.0, 1.0); + } + 1.0 +} + +fn bracket(keys: &[BinBVHJointKey], time: f32) -> usize { + keys.partition_point(|key| key.time < time) +} + +fn interpolate_rotation(keys: &[BinBVHJointKey], time: f32) -> Result { + let upper = bracket(keys, time); + if upper == 0 { + return AnimationTrack::native_decode_rotation(keys[0].key_element); + } + if upper >= keys.len() { + return AnimationTrack::native_decode_rotation(keys[keys.len() - 1].key_element); + } + let (left, right) = (&keys[upper - 1], &keys[upper]); + let fraction = if right.time > left.time { + (time - left.time) / (right.time - left.time) + } else { + 0.0 + }; + Quaternion::slerp( + AnimationTrack::native_decode_rotation(left.key_element)?, + AnimationTrack::native_decode_rotation(right.key_element)?, + fraction, + ) +} + +fn interpolate_position(keys: &[BinBVHJointKey], time: f32) -> Result { + let upper = bracket(keys, time); + if upper == 0 { + return Ok(keys[0].key_element); + } + if upper >= keys.len() { + return Ok(keys[keys.len() - 1].key_element); + } + let (left, right) = (&keys[upper - 1], &keys[upper]); + let fraction = if right.time > left.time { + (time - left.time) / (right.time - left.time) + } else { + 0.0 + }; + Vector3::lerp(left.key_element, right.key_element, fraction) +} + +struct PlayerInner { + object_id: UUID, + tracks: RwLock>>, +} + +#[derive(Clone)] +pub struct AnimeshPlayer(Arc); + +impl AnimeshPlayer { + pub(crate) fn native_new(object_id: UUID) -> Result { + Ok(Self(Arc::new(PlayerInner { + object_id, + tracks: RwLock::new(HashMap::new()), + }))) + } + + pub(crate) fn native_object_id(&self) -> UUID { + self.0.object_id + } + + pub(crate) fn native_get_or_add_track( + &self, + animation_id: UUID, + ) -> Result, Error> { + if let Some(track) = read(&self.0.tracks).get(&animation_id).cloned() { + return Ok(track); + } + let mut tracks = write(&self.0.tracks); + if let Some(track) = tracks.get(&animation_id).cloned() { + return Ok(track); + } + if tracks.len() >= MAX_TRACKS_PER_PLAYER { + return Err(Error::InvalidOperation); + } + let track = Arc::new(AnimationTrack::native_new(animation_id)?); + tracks.insert(animation_id, Arc::clone(&track)); + Ok(track) + } + + fn native_track(&self, animation_id: UUID) -> Option> { + read(&self.0.tracks).get(&animation_id).cloned() + } + + pub(crate) fn native_retain_only(&self, active: &HashSet) { + write(&self.0.tracks).retain(|id, _| active.contains(id)); + } + + pub(crate) fn native_track_count(&self) -> i32 { + i32::try_from(read(&self.0.tracks).len()).unwrap_or(i32::MAX) + } + + pub(crate) fn native_update(&self, dt: f32) -> Result<(), Error> { + let mut tracks: Vec<_> = read(&self.0.tracks).values().cloned().collect(); + tracks.sort_by_key(|track| track.native_animation_id().to_string()); + for track in tracks { + track.native_advance(dt)?; + } + Ok(()) + } + + pub(crate) fn native_evaluate_pose(&self) -> Result, Error> { + let mut tracks: Vec<_> = read(&self.0.tracks).values().cloned().collect(); + tracks.sort_by_key(|track| track.native_animation_id().to_string()); + let mut pose = HashMap::new(); + for track in tracks { + track.native_evaluate_pose(&mut pose)?; + } + Ok(pose) + } +} + +pub(crate) struct AnimeshManagerInner { + client: ClientWeakHandle, + players: RwLock>, + cancellation: CancellationTokenSource, + fetches_in_flight: Arc, + subscriptions: Mutex>, +} + +impl Drop for AnimeshManagerInner { + fn drop(&mut self) { + self.cancellation.cancel(); + } +} + +#[derive(Clone)] +pub struct AnimeshManager(Arc); + +impl AnimeshManager { + pub(crate) fn native_new(client: GridClient) -> Result { + let inner = Arc::new(AnimeshManagerInner { + client: client.native_weak_handle(), + players: RwLock::new(HashMap::new()), + cancellation: CancellationTokenSource::new(), + fetches_in_flight: Arc::new(AtomicUsize::new(0)), + subscriptions: Mutex::new(Vec::new()), + }); + let weak = Arc::downgrade(&inner); + let subscription = client + .native_network()? + .subscribe_raw_packet(Arc::new(move |event| { + if event.packet_type != PacketType::ObjectAnimation { + return; + } + let Some(inner) = weak.upgrade() else { return }; + handle_object_animation(&inner, event.data); + })); + mutex(&inner.subscriptions).push(subscription); + Ok(Self(inner)) + } + + pub(crate) fn native_inner(&self) -> Arc { + Arc::clone(&self.0) + } + + pub(crate) fn native_from_inner(inner: Arc) -> Self { + Self(inner) + } + + pub(crate) fn native_get_player(&self, object_id: UUID) -> Option { + read(&self.0.players).get(&object_id).cloned() + } + + pub(crate) fn native_all_players(&self) -> Vec { + read(&self.0.players).values().cloned().collect() + } + + pub(crate) fn native_remove_player(&self, object_id: UUID) { + write(&self.0.players).remove(&object_id); + } + + pub(crate) fn native_update(&self, dt: f32) -> Result<(), Error> { + let players = self.native_all_players(); + for player in players { + player.native_update(dt)?; + } + Ok(()) + } +} + +fn handle_object_animation(inner: &Arc, data: Vec) { + let mut offset = 0; + let Ok(packet) = ObjectAnimationPacket::new_with_bytes_int32(data, &mut offset) else { + return; + }; + if packet.animation_list.len() > MAX_ANIMATIONS_PER_UPDATE { + return; + } + let object_id = packet.sender.id; + let player = { + let mut players = write(&inner.players); + if let Some(player) = players.get(&object_id).cloned() { + player + } else { + if players.len() >= MAX_PLAYERS { + return; + } + let Ok(player) = AnimeshPlayer::native_new(object_id) else { + return; + }; + players.insert(object_id, player.clone()); + player + } + }; + let active: HashSet<_> = packet + .animation_list + .iter() + .map(|value| value.anim_id) + .collect(); + player.native_retain_only(&active); + for animation in packet.animation_list { + let Ok(track) = player.native_get_or_add_track(animation.anim_id) else { + continue; + }; + if track.native_data().is_none() { + fetch_animation(inner, player.clone(), animation.anim_id); + } + } +} + +fn fetch_animation(inner: &Arc, player: AnimeshPlayer, animation_id: UUID) { + if inner + .fetches_in_flight + .fetch_update(Ordering::AcqRel, Ordering::Acquire, |count| { + (count < MAX_CONCURRENT_FETCHES).then_some(count + 1) + }) + .is_err() + { + return; + } + let weak = Arc::downgrade(inner); + let fetches_in_flight = Arc::clone(&inner.fetches_in_flight); + let spawned = std::thread::Builder::new() + .name("animesh-asset".to_owned()) + .spawn(move || { + struct FetchGuard(Arc); + impl Drop for FetchGuard { + fn drop(&mut self) { + self.0.fetch_sub(1, Ordering::AcqRel); + } + } + let _guard = FetchGuard(fetches_in_flight); + let Ok(runtime) = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + else { + return; + }; + runtime.block_on(async move { + let (token, assets) = { + let Some(inner) = weak.upgrade() else { return }; + let token = inner.cancellation.token(); + let Some(client) = inner.client.upgrade() else { + return; + }; + let Ok(assets) = client.native_assets() else { + return; + }; + (token, assets) + }; + let Ok(Some(asset)) = assets + .request_asset_with_uuid_asset_type_boolean_cancellation_token( + animation_id, + AssetType::Animation, + false, + Some(token), + ) + .await + else { + return; + }; + let Ok(decoded) = BinBVHAnimationReader::native_new(asset.asset_data) else { + return; + }; + if let Some(track) = player.native_track(animation_id) { + track.native_set_data(Some(decoded)); + } + }); + }); + if spawned.is_err() { + inner.fetches_in_flight.fetch_sub(1, Ordering::AcqRel); + } +} diff --git a/crates/libremetaverse/src/animesh_skinning.rs b/crates/libremetaverse/src/animesh_skinning.rs new file mode 100644 index 0000000..bf1d3b9 --- /dev/null +++ b/crates/libremetaverse/src/animesh_skinning.rs @@ -0,0 +1,238 @@ +//! CPU-side forward kinematics and bounded linear-blend skinning. + +#![allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)] // Mapped signatures are fixed. + +use crate::animesh_runtime::JointPose; +use crate::rendering::{Face, Joint, LindenSkeleton}; +use libremetaverse_types::{Error, Matrix4, Quaternion, Vector3}; +use std::collections::HashMap; + +const MAX_SKIN_JOINTS: usize = 512; +const MAX_VERTICES: usize = 1_000_000; + +#[derive(Clone, Debug, Default)] +pub struct MeshSkinData { + pub alt_inverse_bind_matrices: Vec, + pub bind_shape_matrix: Vec, + pub inverse_bind_matrices: Vec, + pub joint_names: Vec, + pub lock_scale_if_joint_position: bool, + pub pelvis_offset: f32, +} + +impl MeshSkinData { + pub(crate) fn native_new() -> Result { + Ok(Self::default()) + } +} + +pub struct AnimeshSkinning; + +impl AnimeshSkinning { + pub(crate) fn native_compute_skinning_matrices( + pose: Option>, + skeleton: Option, + skin_data: Option, + ) -> Result, Error> { + let pose = pose.ok_or(Error::ArgumentNull)?; + let skeleton = skeleton.ok_or(Error::ArgumentNull)?; + let skin_data = skin_data.ok_or(Error::ArgumentNull)?; + if skin_data.joint_names.len() > MAX_SKIN_JOINTS { + return Err(Error::Argument); + } + let mut world = HashMap::new(); + let mut visited = 0_usize; + compute_world( + &skeleton.bone(), + Matrix4::identity(), + &pose, + &mut world, + &mut visited, + )?; + skin_data + .joint_names + .iter() + .enumerate() + .map(|(index, name)| { + let Some(world) = world.get(name).copied() else { + return Ok(Matrix4::identity()); + }; + let inverse = extract_matrix(&skin_data.inverse_bind_matrices, index); + Matrix4::multiply_with_matrix4_matrix4(inverse, world) + }) + .collect() + } + + pub(crate) fn native_deform_vertices( + face: Face, + matrices: Vec, + bind_shape: Matrix4, + positions: &mut [Vector3], + mut normals: Option<&mut [Vector3]>, + ) -> Result<(), Error> { + let count = face.vertices.len(); + if count > MAX_VERTICES || positions.len() < count { + return Err(Error::Argument); + } + if normals.as_ref().is_some_and(|values| values.len() < count) { + return Err(Error::Argument); + } + for (index, vertex) in face.vertices.iter().enumerate() { + let Some(weights) = face.weights.as_ref().and_then(|values| values.get(index)) else { + positions[index] = vertex.position; + if let Some(output) = normals.as_deref_mut() { + output[index] = vertex.normal; + } + continue; + }; + if matrices.is_empty() { + positions[index] = vertex.position; + if let Some(output) = normals.as_deref_mut() { + output[index] = vertex.normal; + } + continue; + } + let position = Vector3::transform(vertex.position, bind_shape)?; + let influences = [ + (weights.joint0, weights.weight0), + (weights.joint1, weights.weight1), + (weights.joint2, weights.weight2), + (weights.joint3, weights.weight3), + ]; + let mut result = Vector3::zero(); + for (joint, weight) in influences { + if weight > 0.0 && weight.is_finite() { + let joint = usize::try_from(joint).map_err(|_| Error::Argument)?; + let matrix = matrices.get(joint).ok_or(Error::Argument)?; + result = Vector3::add_with_vector3_vector3( + result, + Vector3::multiply_with_vector3_single( + Vector3::transform(position, *matrix)?, + weight, + )?, + )?; + } + } + positions[index] = result; + if let Some(output) = normals.as_deref_mut() { + let mut result = Vector3::zero(); + for (joint, weight) in influences { + if weight > 0.0 && weight.is_finite() { + let joint = usize::try_from(joint).map_err(|_| Error::Argument)?; + let matrix = matrices.get(joint).ok_or(Error::Argument)?; + result = Vector3::add_with_vector3_vector3( + result, + Vector3::multiply_with_vector3_single( + Vector3::transform_normal(vertex.normal, *matrix)?, + weight, + )?, + )?; + } + } + output[index] = if result == Vector3::zero() { + result + } else { + Vector3::normalize(result)? + }; + } + } + Ok(()) + } +} + +fn compute_world( + joint: &Joint, + parent: Matrix4, + pose: &HashMap, + output: &mut HashMap, + visited: &mut usize, +) -> Result<(), Error> { + *visited = visited.checked_add(1).ok_or(Error::Argument)?; + if *visited > MAX_SKIN_JOINTS { + return Err(Error::Argument); + } + let local = local_transform(joint, pose)?; + let world = Matrix4::multiply_with_matrix4_matrix4(local, parent)?; + output.insert(joint.base.name(), world); + for alias in joint.get_aliases_list()? { + output.insert(alias, world); + } + for child in joint.bone().unwrap_or_default() { + compute_world(&child, world, pose, output, visited)?; + } + Ok(()) +} + +fn local_transform(joint: &Joint, pose: &HashMap) -> Result { + let mut translation = vector3(&joint.base.pos()); + let mut rotation = rotation(&joint.base.rot())?; + let override_pose = pose.get(&joint.base.name()).copied().or_else(|| { + joint + .get_aliases_list() + .ok()? + .into_iter() + .find_map(|alias| pose.get(&alias).copied()) + }); + if let Some(value) = override_pose { + if value.has_position { + translation = value.position; + } + if value.has_rotation { + rotation = value.rotation; + } + } + Matrix4::multiply_with_matrix4_matrix4( + Matrix4::create_from_quaternion(rotation)?, + Matrix4::create_translation(translation)?, + ) +} + +fn vector3(values: &[f32]) -> Vector3 { + if values.len() < 3 { + Vector3::zero() + } else { + Vector3 { + x: values[0], + y: values[1], + z: values[2], + } + } +} + +fn rotation(values: &[f32]) -> Result { + if values.len() < 3 { + return Ok(Quaternion::identity()); + } + Quaternion::create_from_eulers_with_single_single_single( + values[0].to_radians(), + values[1].to_radians(), + values[2].to_radians(), + ) +} + +fn extract_matrix(values: &[f32], index: usize) -> Matrix4 { + let Some(offset) = index.checked_mul(16) else { + return Matrix4::identity(); + }; + let Some(values) = values.get(offset..offset.saturating_add(16)) else { + return Matrix4::identity(); + }; + Matrix4 { + m11: values[0], + m12: values[1], + m13: values[2], + m14: values[3], + m21: values[4], + m22: values[5], + m23: values[6], + m24: values[7], + m31: values[8], + m32: values[9], + m33: values[10], + m34: values[11], + m41: values[12], + m42: values[13], + m43: values[14], + m44: values[15], + } +} diff --git a/crates/libremetaverse/src/avatar_animesh_semantics.rs b/crates/libremetaverse/src/avatar_animesh_semantics.rs index 68b3c64..e782530 100644 --- a/crates/libremetaverse/src/avatar_animesh_semantics.rs +++ b/crates/libremetaverse/src/avatar_animesh_semantics.rs @@ -374,7 +374,7 @@ fn make_single_joint_skeleton(joint_name: &str) -> LindenSkeleton { fn identity_floats() -> Vec { vec![ - 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, + 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, ] } diff --git a/crates/libremetaverse/src/avatar_manager.rs b/crates/libremetaverse/src/avatar_manager.rs new file mode 100644 index 0000000..bc50ff8 --- /dev/null +++ b/crates/libremetaverse/src/avatar_manager.rs @@ -0,0 +1,1021 @@ +//! Avatar profile and simulator-avatar state manager. + +#![allow( + clippy::needless_pass_by_value, + clippy::too_many_lines, + clippy::unnecessary_wraps, + clippy::unused_self +)] // Public compatibility signatures and profile wire schemas are fixed. + +use crate::client_core::ClientWeakHandle; +use crate::packet_catalog::PacketType; +use crate::packets::{ + AvatarAnimationPacket, AvatarPickerRequestPacket, AvatarPropertiesRequestPacket, + ClassifiedInfoRequestPacket, GenericMessagePacket, GenericMessagePacketParamListBlock, + TrackAgentPacket, UUIDNameRequestPacket, UUIDNameRequestPacketUUIDNameBlockBlock, +}; +use crate::{Error, GridClient}; +use libremetaverse_structured_data::{OSD, OSDMap, OSDParser}; +use libremetaverse_types::UUID; +use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription, Uri}; +use std::collections::HashMap; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::{Arc, Mutex}; + +struct EventSlot { + next: AtomicU64, + handlers: Mutex>>, +} + +impl Default for EventSlot { + fn default() -> Self { + Self { + next: AtomicU64::new(1), + handlers: Mutex::new(HashMap::new()), + } + } +} + +impl EventSlot { + fn subscribe(self: &Arc, handler: EventHandler) -> Subscription { + let id = self.next.fetch_add(1, Ordering::Relaxed); + self.handlers + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .insert(id, handler); + let weak = Arc::downgrade(self); + Subscription::new(move || { + if let Some(slot) = weak.upgrade() { + slot.handlers + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .remove(&id); + } + }) + } + + fn clear(&self) { + self.handlers + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clear(); + } +} + +impl EventSlot { + fn emit(&self, value: T) { + let handlers = self + .handlers + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .values() + .cloned() + .collect::>(); + for handler in handlers { + let value = value.clone(); + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| handler(value))); + } + } +} + +#[derive(Clone, Debug)] +pub struct Animation { + pub animation_id: UUID, + pub animation_sequence: i32, + pub animation_source_object_id: UUID, +} + +#[derive(Clone, Debug)] +pub struct AvatarAnimationEventArgs { + animations: Vec, + avatar_id: UUID, +} + +fn handle_avatar_animation(inner: &AvatarManagerInner, data: Vec) { + if inner.disposed.load(Ordering::Acquire) { + return; + } + let mut offset = 0; + let Ok(packet) = AvatarAnimationPacket::new_with_bytes_int32(data, &mut offset) else { + return; + }; + if packet.animation_list.len() > 256 { + return; + } + let animations = packet + .animation_list + .into_iter() + .enumerate() + .map(|(index, animation)| Animation { + animation_id: animation.anim_id, + animation_sequence: animation.anim_sequence_id, + animation_source_object_id: packet + .animation_source_list + .get(index) + .map_or_else(UUID::zero, |source| source.object_id), + }) + .collect(); + inner.avatar_animation.emit(AvatarAnimationEventArgs { + animations, + avatar_id: packet.sender.id, + }); +} + +impl AvatarAnimationEventArgs { + pub(crate) fn native_new(avatar_id: UUID, animations: Vec) -> Result { + if animations.len() > 256 { + return Err(Error::Argument); + } + Ok(Self { + animations, + avatar_id, + }) + } + + pub(crate) fn native_animations(&self) -> Vec { + self.animations.clone() + } + + pub(crate) fn native_avatar_id(&self) -> UUID { + self.avatar_id + } +} + +pub(crate) struct AvatarManagerInner { + client: ClientWeakHandle, + disposed: AtomicBool, + avatar_animation: Arc>, + avatar_appearance: Arc>, + avatar_classified_reply: Arc>, + avatar_groups_reply: Arc>, + avatar_interests_reply: Arc>, + avatar_notes_reply: Arc>, + avatar_picker_reply: Arc>, + avatar_picks_reply: Arc>, + avatar_properties_reply: Arc>, + classified_info_reply: Arc>, + display_name_update: Arc>, + pick_info_reply: Arc>, + uuid_name_reply: Arc>, + viewer_effect: Arc>, + viewer_effect_look_at: Arc>, + viewer_effect_point_at: Arc>, + subscriptions: Mutex>, +} + +#[derive(Clone)] +pub struct AvatarManager(Arc); + +impl AvatarManager { + pub(crate) fn native_new(client: Option>) -> Result { + let client = client.ok_or(Error::ArgumentNull)?; + let inner = Arc::new(AvatarManagerInner { + client: client.native_weak_handle(), + disposed: AtomicBool::new(false), + avatar_animation: Arc::default(), + avatar_appearance: Arc::default(), + avatar_classified_reply: Arc::default(), + avatar_groups_reply: Arc::default(), + avatar_interests_reply: Arc::default(), + avatar_notes_reply: Arc::default(), + avatar_picker_reply: Arc::default(), + avatar_picks_reply: Arc::default(), + avatar_properties_reply: Arc::default(), + classified_info_reply: Arc::default(), + display_name_update: Arc::default(), + pick_info_reply: Arc::default(), + uuid_name_reply: Arc::default(), + viewer_effect: Arc::default(), + viewer_effect_look_at: Arc::default(), + viewer_effect_point_at: Arc::default(), + subscriptions: Mutex::new(Vec::new()), + }); + let weak = Arc::downgrade(&inner); + let subscription = client + .native_network()? + .subscribe_raw_packet(Arc::new(move |event| { + if event.packet_type != PacketType::AvatarAnimation { + return; + } + let Some(inner) = weak.upgrade() else { return }; + handle_avatar_animation(&inner, event.data); + })); + inner + .subscriptions + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .push(subscription); + Ok(Self(inner)) + } + + pub(crate) fn native_inner(&self) -> Arc { + Arc::clone(&self.0) + } + + pub(crate) fn native_from_inner(inner: Arc) -> Self { + Self(inner) + } + + pub(crate) fn native_dispose(&self) -> Result<(), Error> { + self.0.disposed.store(true, Ordering::Release); + self.0 + .subscriptions + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clear(); + self.0.avatar_animation.clear(); + self.0.avatar_appearance.clear(); + self.0.avatar_classified_reply.clear(); + self.0.avatar_groups_reply.clear(); + self.0.avatar_interests_reply.clear(); + self.0.avatar_notes_reply.clear(); + self.0.avatar_picker_reply.clear(); + self.0.avatar_picks_reply.clear(); + self.0.avatar_properties_reply.clear(); + self.0.classified_info_reply.clear(); + self.0.display_name_update.clear(); + self.0.pick_info_reply.clear(); + self.0.uuid_name_reply.clear(); + self.0.viewer_effect.clear(); + self.0.viewer_effect_look_at.clear(); + self.0.viewer_effect_point_at.clear(); + Ok(()) + } + + pub(crate) fn native_subscribe_avatar_animation( + &self, + handler: EventHandler, + ) -> Subscription { + self.0.avatar_animation.subscribe(handler) + } + pub(crate) fn native_subscribe_avatar_appearance( + &self, + handler: EventHandler, + ) -> Subscription { + self.0.avatar_appearance.subscribe(handler) + } + pub(crate) fn native_subscribe_avatar_classified_reply( + &self, + handler: EventHandler, + ) -> Subscription { + self.0.avatar_classified_reply.subscribe(handler) + } + pub(crate) fn native_subscribe_avatar_groups_reply( + &self, + handler: EventHandler, + ) -> Subscription { + self.0.avatar_groups_reply.subscribe(handler) + } + pub(crate) fn native_subscribe_avatar_interests_reply( + &self, + handler: EventHandler, + ) -> Subscription { + self.0.avatar_interests_reply.subscribe(handler) + } + pub(crate) fn native_subscribe_avatar_notes_reply( + &self, + handler: EventHandler, + ) -> Subscription { + self.0.avatar_notes_reply.subscribe(handler) + } + pub(crate) fn native_subscribe_avatar_picker_reply( + &self, + handler: EventHandler, + ) -> Subscription { + self.0.avatar_picker_reply.subscribe(handler) + } + pub(crate) fn native_subscribe_avatar_picks_reply( + &self, + handler: EventHandler, + ) -> Subscription { + self.0.avatar_picks_reply.subscribe(handler) + } + pub(crate) fn native_subscribe_avatar_properties_reply( + &self, + handler: EventHandler, + ) -> Subscription { + self.0.avatar_properties_reply.subscribe(handler) + } + pub(crate) fn native_subscribe_classified_info_reply( + &self, + handler: EventHandler, + ) -> Subscription { + self.0.classified_info_reply.subscribe(handler) + } + pub(crate) fn native_subscribe_display_name_update( + &self, + handler: EventHandler, + ) -> Subscription { + self.0.display_name_update.subscribe(handler) + } + pub(crate) fn native_subscribe_pick_info_reply( + &self, + handler: EventHandler, + ) -> Subscription { + self.0.pick_info_reply.subscribe(handler) + } + pub(crate) fn native_subscribe_uuid_name_reply( + &self, + handler: EventHandler, + ) -> Subscription { + self.0.uuid_name_reply.subscribe(handler) + } + pub(crate) fn native_subscribe_viewer_effect( + &self, + handler: EventHandler, + ) -> Subscription { + self.0.viewer_effect.subscribe(handler) + } + pub(crate) fn native_subscribe_viewer_effect_look_at( + &self, + handler: EventHandler, + ) -> Subscription { + self.0.viewer_effect_look_at.subscribe(handler) + } + pub(crate) fn native_subscribe_viewer_effect_point_at( + &self, + handler: EventHandler, + ) -> Subscription { + self.0.viewer_effect_point_at.subscribe(handler) + } + + pub(crate) fn native_capability_available(&self, name: &str) -> Result { + if self.0.disposed.load(Ordering::Acquire) { + return Ok(false); + } + let Some(client) = self.0.client.upgrade() else { + return Ok(false); + }; + let network = client.native_network()?; + let Some(simulator) = network.native_current_sim() else { + return Ok(false); + }; + let Some(caps) = simulator.native_caps() else { + return Ok(false); + }; + Ok(caps.capability_uri(name.to_owned())?.is_some()) + } + + pub(crate) fn native_request_own_avatar_textures(&self) -> Result<(), Error> { + if self.0.disposed.load(Ordering::Acquire) { + return Err(Error::InvalidOperation); + } + let Some(client) = self.0.client.upgrade() else { + return Ok(()); + }; + let network = client.native_network()?; + if network.native_current_sim().is_none() { + return Ok(()); + } + self.send_generic( + &network, + "avatartexturesrequest", + &[network.native_agent_id()], + ) + } + + fn live_network(&self) -> Result { + if self.0.disposed.load(Ordering::Acquire) { + return Err(Error::InvalidOperation); + } + self.0 + .client + .upgrade() + .ok_or(Error::InvalidOperation)? + .native_network() + } + + fn send_bytes( + &self, + network: &crate::NetworkManager, + packet_type: PacketType, + bytes: Vec, + ) -> Result<(), Error> { + let simulator = network + .native_current_sim() + .ok_or(Error::InvalidOperation)?; + simulator.native_send_packet_data( + bytes.clone(), + i32::try_from(bytes.len()).map_err(|_| Error::Argument)?, + packet_type, + false, + ) + } + + fn send_generic( + &self, + network: &crate::NetworkManager, + method: &str, + parameters: &[UUID], + ) -> Result<(), Error> { + let mut packet = GenericMessagePacket::new_with_constructor()?; + let agent_id = network.native_agent_id(); + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = network.native_session_id(); + packet.agent_data.transaction_id = UUID::zero(); + packet.method_data.method = method.as_bytes().to_vec(); + packet.method_data.invoice = UUID::zero(); + packet.param_list = parameters + .iter() + .map(|value| GenericMessagePacketParamListBlock { + parameter: value.to_string().into_bytes(), + }) + .collect(); + self.send_bytes( + network, + PacketType::GenericMessage, + packet.to_bytes_with_method()?, + ) + } + + pub(crate) fn native_request_track_agent(&self, target: UUID) -> Result<(), Error> { + let network = self.live_network()?; + let mut packet = TrackAgentPacket::new_with_constructor()?; + packet.agent_data.agent_id = network.native_agent_id(); + packet.agent_data.session_id = network.native_session_id(); + packet.target_data.prey_id = target; + self.send_bytes( + &network, + PacketType::TrackAgent, + packet.to_bytes_with_method()?, + ) + } + + pub(crate) fn native_request_avatar_names(&self, ids: &[UUID]) -> Result<(), Error> { + const MAX_UUIDS_PER_PACKET: usize = 100; + if ids.len() > 100_000 { + return Err(Error::Argument); + } + let network = self.live_network()?; + for chunk in ids.chunks(MAX_UUIDS_PER_PACKET) { + let mut packet = UUIDNameRequestPacket::new_with_constructor()?; + packet.uuid_name_block = chunk + .iter() + .copied() + .map(|id| UUIDNameRequestPacketUUIDNameBlockBlock { id }) + .collect(); + self.send_bytes( + &network, + PacketType::UUIDNameRequest, + packet.to_bytes_with_method()?, + )?; + } + Ok(()) + } + + pub(crate) fn native_request_avatar_properties(&self, avatar_id: UUID) -> Result<(), Error> { + let network = self.live_network()?; + let mut packet = AvatarPropertiesRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = network.native_agent_id(); + packet.agent_data.session_id = network.native_session_id(); + packet.agent_data.avatar_id = avatar_id; + self.send_bytes( + &network, + PacketType::AvatarPropertiesRequest, + packet.to_bytes_with_method()?, + ) + } + + pub(crate) fn native_request_avatar_name_search( + &self, + name: String, + query_id: UUID, + ) -> Result<(), Error> { + if name.len() > 255 { + return Err(Error::Argument); + } + let network = self.live_network()?; + let mut packet = AvatarPickerRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = network.native_agent_id(); + packet.agent_data.session_id = network.native_session_id(); + packet.agent_data.query_id = query_id; + packet.data.name = name.into_bytes(); + self.send_bytes( + &network, + PacketType::AvatarPickerRequest, + packet.to_bytes_with_method()?, + ) + } + + pub(crate) fn native_request_classified_info(&self, classified_id: UUID) -> Result<(), Error> { + let network = self.live_network()?; + let mut packet = ClassifiedInfoRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = network.native_agent_id(); + packet.agent_data.session_id = network.native_session_id(); + packet.data.classified_id = classified_id; + self.send_bytes( + &network, + PacketType::ClassifiedInfoRequest, + packet.to_bytes_with_method()?, + ) + } + + pub(crate) fn native_request_generic( + &self, + method: &str, + parameters: &[UUID], + ) -> Result<(), Error> { + let network = self.live_network()?; + self.send_generic(&network, method, parameters) + } + + pub(crate) async fn native_get_display_names( + &self, + ids: Vec, + cancellation_token: Option, + ) -> Result< + ( + bool, + Option>, + Option>, + ), + Error, + > { + if ids.is_empty() || ids.len() > 10_000 { + return Ok((false, None, None)); + } + let Some(client) = self.0.client.upgrade() else { + return Ok((false, None, None)); + }; + let token = cancellation_token.unwrap_or_else(|| client.cancellation_token()); + token.throw_if_cancellation_requested()?; + let network = client.native_network()?; + let Some(simulator) = network.native_current_sim() else { + return Ok((false, None, None)); + }; + let Some(caps) = simulator.native_caps() else { + return Ok((false, None, None)); + }; + let Some(base) = caps.capability_uri("GetDisplayNames".to_owned())? else { + return Ok((false, None, None)); + }; + let http = client.native_http_caps_client(); + let mut names = Vec::new(); + let mut bad_ids = Vec::new(); + for chunk in ids.chunks(90) { + token.throw_if_cancellation_requested()?; + let query = chunk + .iter() + .map(ToString::to_string) + .collect::>() + .join("&ids="); + let separator = if base.0.contains('?') { '&' } else { '?' }; + let uri = Uri(format!("{}{separator}ids={query}", base.0)); + let (response, bytes) = http.get(uri, token.clone(), None).await?; + if !response.is_success_status_code() { + return Ok((false, None, None)); + } + let OSD::Map(map) = OSDParser::deserialize_with_bytes(bytes)? else { + return Ok((false, None, None)); + }; + let mut message = crate::messages::linden::GetDisplayNamesMessage::new()?; + message.deserialize(OSDMap::new_with_dictionary(map)?)?; + names.extend(message.agents); + bad_ids.extend(message.bad_i_ds); + } + Ok((true, Some(names), Some(bad_ids))) + } + + pub(crate) async fn native_request_agent_profile( + &self, + avatar_id: UUID, + cancellation_token: Option, + ) -> Result<(bool, Option), Error> { + let Some(client) = self.0.client.upgrade() else { + return Ok((false, None)); + }; + let token = cancellation_token.unwrap_or_else(|| client.cancellation_token()); + token.throw_if_cancellation_requested()?; + let network = client.native_network()?; + let Some(simulator) = network.native_current_sim() else { + return Ok((false, None)); + }; + let Some(caps) = simulator.native_caps() else { + return Ok((false, None)); + }; + let Some(base) = caps.capability_uri("AgentProfile".to_owned())? else { + return Ok((false, None)); + }; + let uri = Uri(format!("{}/{}", base.0.trim_end_matches('/'), avatar_id)); + let (response, bytes) = client + .native_http_caps_client() + .get(uri, token, None) + .await?; + if !response.is_success_status_code() { + return Ok((false, None)); + } + let OSD::Map(map) = OSDParser::deserialize_with_bytes(bytes)? else { + return Ok((false, None)); + }; + let mut profile = crate::messages::linden::AgentProfileMessage::new()?; + profile.deserialize(OSDMap::new_with_dictionary(map)?)?; + Ok((true, Some(profile))) + } +} + +pub(crate) fn agent_profile_group_defaults() -> crate::messages::linden::AgentProfileMessageGroupData +{ + crate::messages::linden::AgentProfileMessageGroupData { + description: String::new(), + enabled: false, + founder_id: UUID::zero(), + id: UUID::zero(), + image_id: UUID::zero(), + is_mature_publish: false, + is_open_enrollment: false, + is_shown_in_search: false, + name: String::new(), + } +} + +fn agent_profile_pick_defaults() -> crate::messages::linden::AgentProfileMessagePickData { + crate::messages::linden::AgentProfileMessagePickData { + description: String::new(), + enabled: false, + grid_x: 0.0, + grid_y: 0.0, + id: UUID::zero(), + name: String::new(), + parcel_id: UUID::zero(), + parcel_name: String::new(), + region_name: String::new(), + region_x: 0.0, + region_y: 0.0, + region_z: 0.0, + slurl: Uri("about:blank".to_owned()), + snapshot_id: UUID::zero(), + } +} + +pub(crate) fn agent_profile_defaults() -> crate::messages::linden::AgentProfileMessage { + crate::messages::linden::AgentProfileMessage { + allowed_maturity: crate::SimAccess(0), + avatar_id: UUID::zero(), + caption_index: None, + caption_text: None, + customer_type: String::new(), + display_name: String::new(), + display_name_next_update: std::time::UNIX_EPOCH, + first_life_about_text: String::new(), + first_life_image_id: UUID::zero(), + flags: crate::ProfileFlags(0), + groups: Vec::new(), + hide_age: None, + home_page: String::new(), + is_display_name_default: false, + is_mature_profile: false, + legacy_first_name: String::new(), + legacy_last_name: String::new(), + member_since: std::time::UNIX_EPOCH, + notes: String::new(), + partner_id: UUID::zero(), + picks: Vec::new(), + preferred_maturity: crate::SimAccess(0), + second_life_about_text: String::new(), + second_life_image_id: UUID::zero(), + title: String::new(), + username: String::new(), + } +} + +fn value(map: &OSDMap, key: &str) -> OSD { + map.get(key).unwrap_or(OSD::Undefined) +} + +pub(crate) fn deserialize_agent_profile( + profile: &mut crate::messages::linden::AgentProfileMessage, + map: &OSDMap, +) -> Result<(), Error> { + profile.avatar_id = value(map, "id").as_uuid()?; + profile.second_life_image_id = value(map, "sl_image_id").as_uuid()?; + profile.first_life_image_id = value(map, "fl_image_id").as_uuid()?; + profile.partner_id = value(map, "partner_id").as_uuid()?; + profile.second_life_about_text = value(map, "sl_about_text").as_string()?; + profile.first_life_about_text = value(map, "fl_about_text").as_string()?; + profile.member_since = value(map, "member_since").as_date()?; + profile.customer_type = value(map, "customer_type").as_string()?; + profile.notes = value(map, "notes").as_string()?; + profile.allowed_maturity = crate::SimAccess( + u8::try_from(value(map, "allowed_maturity").as_integer()?).map_err(|_| Error::Argument)?, + ); + profile.preferred_maturity = crate::SimAccess( + u8::try_from(value(map, "preferred_maturity").as_integer()?) + .map_err(|_| Error::Argument)?, + ); + profile.display_name = value(map, "display_name").as_string()?; + profile.display_name_next_update = value(map, "display_name_next_update").as_date()?; + profile.is_display_name_default = value(map, "is_display_name_default").as_boolean()?; + profile.username = value(map, "username").as_string()?; + profile.legacy_first_name = value(map, "legacy_first_name").as_string()?; + profile.legacy_last_name = value(map, "legacy_last_name").as_string()?; + profile.is_mature_profile = value(map, "mature_profile").as_boolean()?; + profile.title = value(map, "title").as_string()?; + profile.home_page = value(map, "home_page").as_string()?; + profile.hide_age = map + .get("hide_age") + .map(|entry| entry.as_boolean().map(Some)) + .transpose()?; + profile.caption_index = map + .get("charter_member") + .map(|entry| entry.as_integer().map(Some)) + .transpose()?; + profile.caption_text = map + .get("caption") + .map(|entry| entry.as_string()) + .transpose()?; + let mut flags = 0; + for (key, flag) in [ + ("online", crate::ProfileFlags::ONLINE.0), + ("allow_publish", crate::ProfileFlags::ALLOW_PUBLISH.0), + ("identified", crate::ProfileFlags::IDENTIFIED.0), + ("transacted", crate::ProfileFlags::TRANSACTED.0), + ] { + if value(map, key).as_boolean()? { + flags |= flag; + } + } + profile.flags = crate::ProfileFlags(flags); + profile.groups.clear(); + if let OSD::Array(groups) = value(map, "groups") { + if groups.len() > 512 { + return Err(Error::Argument); + } + for group in groups { + let OSD::Map(group) = group else { continue }; + let group = OSDMap::new_with_dictionary(group)?; + profile + .groups + .push(crate::messages::linden::AgentProfileMessageGroupData { + description: value(&group, "description").as_string()?, + enabled: value(&group, "enabled").as_boolean()?, + founder_id: value(&group, "founder_id").as_uuid()?, + id: value(&group, "id").as_uuid()?, + image_id: value(&group, "image_id").as_uuid()?, + is_mature_publish: value(&group, "mature_publish").as_boolean()?, + is_open_enrollment: value(&group, "open_enrollment").as_boolean()?, + is_shown_in_search: value(&group, "show_in_search").as_boolean()?, + name: value(&group, "name").as_string()?, + }); + } + } + profile.picks.clear(); + if let OSD::Array(picks) = value(map, "picks") { + if picks.len() > 512 { + return Err(Error::Argument); + } + for pick in picks { + let OSD::Map(pick) = pick else { continue }; + let pick = OSDMap::new_with_dictionary(pick)?; + let mut result = agent_profile_pick_defaults(); + result.description = value(&pick, "description").as_string()?; + result.enabled = value(&pick, "enabled").as_boolean()?; + result.grid_x = value(&pick, "grid_x").as_real()?; + result.grid_y = value(&pick, "grid_y").as_real()?; + result.id = value(&pick, "id").as_uuid()?; + result.name = value(&pick, "name").as_string()?; + result.parcel_id = value(&pick, "parcel_id").as_uuid()?; + result.parcel_name = value(&pick, "parcel_name").as_string()?; + result.region_name = value(&pick, "region_name").as_string()?; + result.region_x = value(&pick, "region_x").as_real()?; + result.region_y = value(&pick, "region_y").as_real()?; + result.region_z = value(&pick, "region_z").as_real()?; + result.slurl = value(&pick, "slurl") + .as_uri()? + .unwrap_or_else(|| Uri("about:blank".to_owned())); + result.snapshot_id = value(&pick, "snapshot_id").as_uuid()?; + profile.picks.push(result); + } + } + Ok(()) +} + +pub(crate) fn serialize_agent_profile( + profile: &crate::messages::linden::AgentProfileMessage, +) -> Result { + let mut map = std::collections::HashMap::from([ + ("id".to_owned(), OSD::UUID(profile.avatar_id)), + ( + "sl_image_id".to_owned(), + OSD::UUID(profile.second_life_image_id), + ), + ( + "fl_image_id".to_owned(), + OSD::UUID(profile.first_life_image_id), + ), + ("partner_id".to_owned(), OSD::UUID(profile.partner_id)), + ( + "sl_about_text".to_owned(), + OSD::String(profile.second_life_about_text.clone()), + ), + ( + "fl_about_text".to_owned(), + OSD::String(profile.first_life_about_text.clone()), + ), + ("member_since".to_owned(), OSD::Date(profile.member_since)), + ( + "customer_type".to_owned(), + OSD::String(profile.customer_type.clone()), + ), + ("notes".to_owned(), OSD::String(profile.notes.clone())), + ( + "allowed_maturity".to_owned(), + OSD::Integer(i32::from(profile.allowed_maturity.0)), + ), + ( + "preferred_maturity".to_owned(), + OSD::Integer(i32::from(profile.preferred_maturity.0)), + ), + ( + "display_name".to_owned(), + OSD::String(profile.display_name.clone()), + ), + ( + "display_name_next_update".to_owned(), + OSD::Date(profile.display_name_next_update), + ), + ( + "is_display_name_default".to_owned(), + OSD::Boolean(profile.is_display_name_default), + ), + ("username".to_owned(), OSD::String(profile.username.clone())), + ( + "legacy_first_name".to_owned(), + OSD::String(profile.legacy_first_name.clone()), + ), + ( + "legacy_last_name".to_owned(), + OSD::String(profile.legacy_last_name.clone()), + ), + ( + "mature_profile".to_owned(), + OSD::Boolean(profile.is_mature_profile), + ), + ("title".to_owned(), OSD::String(profile.title.clone())), + ( + "home_page".to_owned(), + OSD::String(profile.home_page.clone()), + ), + ]); + for (key, flag) in [ + ("online", crate::ProfileFlags::ONLINE.0), + ("allow_publish", crate::ProfileFlags::ALLOW_PUBLISH.0), + ("identified", crate::ProfileFlags::IDENTIFIED.0), + ("transacted", crate::ProfileFlags::TRANSACTED.0), + ] { + if profile.flags.0 & flag != 0 { + map.insert(key.to_owned(), OSD::Boolean(true)); + } + } + if let Some(Some(value)) = profile.hide_age { + map.insert("hide_age".to_owned(), OSD::Boolean(value)); + } + if let Some(Some(value)) = profile.caption_index { + map.insert("charter_member".to_owned(), OSD::Integer(value)); + } else if let Some(value) = &profile.caption_text { + map.insert("caption".to_owned(), OSD::String(value.clone())); + } + if profile.groups.len() > 512 || profile.picks.len() > 512 { + return Err(Error::Argument); + } + map.insert( + "groups".to_owned(), + OSD::Array( + profile + .groups + .iter() + .map(|group| { + OSD::Map(std::collections::HashMap::from([ + ( + "description".to_owned(), + OSD::String(group.description.clone()), + ), + ("enabled".to_owned(), OSD::Boolean(group.enabled)), + ("founder_id".to_owned(), OSD::UUID(group.founder_id)), + ("id".to_owned(), OSD::UUID(group.id)), + ("image_id".to_owned(), OSD::UUID(group.image_id)), + ( + "mature_publish".to_owned(), + OSD::Boolean(group.is_mature_publish), + ), + ("name".to_owned(), OSD::String(group.name.clone())), + ( + "open_enrollment".to_owned(), + OSD::Boolean(group.is_open_enrollment), + ), + ( + "show_in_search".to_owned(), + OSD::Boolean(group.is_shown_in_search), + ), + ])) + }) + .collect(), + ), + ); + map.insert( + "picks".to_owned(), + OSD::Array( + profile + .picks + .iter() + .map(|pick| { + OSD::Map(std::collections::HashMap::from([ + ( + "description".to_owned(), + OSD::String(pick.description.clone()), + ), + ("enabled".to_owned(), OSD::Boolean(pick.enabled)), + ("grid_x".to_owned(), OSD::Real(pick.grid_x)), + ("grid_y".to_owned(), OSD::Real(pick.grid_y)), + ("id".to_owned(), OSD::UUID(pick.id)), + ("name".to_owned(), OSD::String(pick.name.clone())), + ("parcel_id".to_owned(), OSD::UUID(pick.parcel_id)), + ( + "parcel_name".to_owned(), + OSD::String(pick.parcel_name.clone()), + ), + ( + "region_name".to_owned(), + OSD::String(pick.region_name.clone()), + ), + ("region_x".to_owned(), OSD::Real(pick.region_x)), + ("region_y".to_owned(), OSD::Real(pick.region_y)), + ("region_z".to_owned(), OSD::Real(pick.region_z)), + ("slurl".to_owned(), OSD::Uri(pick.slurl.clone())), + ("snapshot_id".to_owned(), OSD::UUID(pick.snapshot_id)), + ])) + }) + .collect(), + ), + ); + OSDMap::new_with_dictionary(map) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::packets::{ + AvatarAnimationPacketAnimationListBlock, AvatarAnimationPacketAnimationSourceListBlock, + }; + + #[test] + fn avatar_animation_packet_emits_complete_active_animation_list() { + let client = GridClient::new().expect("client"); + let manager = client.avatars(); + let received = Arc::new(Mutex::new(Vec::new())); + let values = Arc::clone(&received); + let _subscription = manager.native_subscribe_avatar_animation(Arc::new(move |event| { + values.lock().expect("event lock").push(event); + })); + let avatar_id = UUID::random().expect("avatar id"); + let animation_id = UUID::random().expect("animation id"); + let source_id = UUID::random().expect("source id"); + let mut packet = AvatarAnimationPacket::new_with_constructor().expect("packet"); + packet.sender.id = avatar_id; + let mut animation = + AvatarAnimationPacketAnimationListBlock::new_with_constructor().expect("animation"); + animation.anim_id = animation_id; + animation.anim_sequence_id = 42; + packet.animation_list.push(animation); + let mut source = + AvatarAnimationPacketAnimationSourceListBlock::new_with_constructor().expect("source"); + source.object_id = source_id; + packet.animation_source_list.push(source); + + handle_avatar_animation( + &manager.0, + packet.to_bytes_with_method().expect("packet bytes"), + ); + + let events = received.lock().expect("event lock"); + assert_eq!(events.len(), 1); + assert_eq!(events[0].native_avatar_id(), avatar_id); + let animations = events[0].native_animations(); + assert_eq!(animations.len(), 1); + assert_eq!(animations[0].animation_id, animation_id); + assert_eq!(animations[0].animation_sequence, 42); + assert_eq!(animations[0].animation_source_object_id, source_id); + } + + #[test] + fn avatar_animation_empty_list_signals_stopped_animations() { + let client = GridClient::new().expect("client"); + let manager = client.avatars(); + let received = Arc::new(Mutex::new(Vec::new())); + let values = Arc::clone(&received); + let _subscription = manager.native_subscribe_avatar_animation(Arc::new(move |event| { + values + .lock() + .expect("event lock") + .push(event.native_animations().len()); + })); + let mut packet = AvatarAnimationPacket::new_with_constructor().expect("packet"); + packet.sender.id = UUID::random().expect("avatar id"); + + handle_avatar_animation( + &manager.0, + packet.to_bytes_with_method().expect("packet bytes"), + ); + + assert_eq!(*received.lock().expect("event lock"), [0]); + } +} diff --git a/crates/libremetaverse/src/avatar_rig.rs b/crates/libremetaverse/src/avatar_rig.rs new file mode 100644 index 0000000..c5f7e39 --- /dev/null +++ b/crates/libremetaverse/src/avatar_rig.rs @@ -0,0 +1,776 @@ +//! Backend-independent avatar hierarchy and rigged-attachment math. + +#![allow( + clippy::needless_pass_by_value, + clippy::too_many_arguments, + clippy::unnecessary_wraps +)] // Mapped signatures and recursive hierarchy state are fixed. + +use crate::Error; +use crate::animesh_skinning::MeshSkinData; +use crate::rendering::{BoneTransform, Joint, LindenSkeleton}; +use crate::visual_catalog::VisualParams; +use libremetaverse_types::compat::{ + Matrix4x4, Quaternion as NumericsQuaternion, Vector3 as NumericsVector3, +}; +use libremetaverse_types::{Matrix4, Quaternion, UUID, Vector3}; +use roxmltree::Document; +use std::collections::HashMap; +use std::path::Path; + +const MAX_JOINTS: usize = 512; +const MAX_LAD_BYTES: u64 = 8 * 1024 * 1024; +const MAX_ATTACHMENT_POINTS: usize = 256; +const MAX_MESH_DEFINITIONS: usize = 512; + +#[derive(Clone, Debug, PartialEq)] +pub struct AvatarAttachmentPoint { + group: i32, + id: i32, + joint: String, + location: String, + name: String, + position: Vector3, + rotation: Vector3, + visible_in_first_person: bool, +} + +impl AvatarAttachmentPoint { + pub(crate) fn native_group(&self) -> i32 { + self.group + } + + pub(crate) fn native_id(&self) -> i32 { + self.id + } + + pub(crate) fn native_joint(&self) -> String { + self.joint.clone() + } + + pub(crate) fn native_location(&self) -> String { + self.location.clone() + } + + pub(crate) fn native_name(&self) -> String { + self.name.clone() + } + + pub(crate) fn native_position(&self) -> Vector3 { + self.position + } + + pub(crate) fn native_rotation(&self) -> Vector3 { + self.rotation + } + + pub(crate) fn native_visible_in_first_person(&self) -> bool { + self.visible_in_first_person + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct AvatarMeshDefinition { + file_name: String, + lod_level: i32, + min_pixel_width: i32, + type_: String, +} + +impl AvatarMeshDefinition { + pub(crate) fn native_file_name(&self) -> String { + self.file_name.clone() + } + + pub(crate) fn native_lod_level(&self) -> i32 { + self.lod_level + } + + pub(crate) fn native_min_pixel_width(&self) -> i32 { + self.min_pixel_width + } + + pub(crate) fn native_type(&self) -> String { + self.type_.clone() + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct LindenAvatarDefinition { + attachment_points: Vec, + mesh_definitions: Vec, + skeleton: LindenSkeleton, +} + +impl LindenAvatarDefinition { + pub(crate) fn native_load( + lad_file_name: Option, + skeleton_file_name: Option, + ) -> Result { + let skeleton = LindenSkeleton::load_with_string(skeleton_file_name)?; + let owned_xml; + let xml = if let Some(file_name) = lad_file_name { + owned_xml = read_bounded(&file_name)?; + owned_xml.as_str() + } else { + include_str!("../../../codegen/inputs/avatar_lad.xml") + }; + let document = Document::parse(xml).map_err(|_| Error::Argument)?; + let mut attachment_points = Vec::new(); + let mut mesh_definitions = Vec::new(); + for node in document.descendants().filter(roxmltree::Node::is_element) { + match node.tag_name().name() { + "attachment_point" => { + let Some(id) = parse_i32(node.attribute("id")) else { + continue; + }; + if attachment_points.len() >= MAX_ATTACHMENT_POINTS { + return Err(Error::Argument); + } + attachment_points.push(AvatarAttachmentPoint { + group: parse_i32(node.attribute("group")).unwrap_or_default(), + id, + joint: node.attribute("joint").unwrap_or_default().to_owned(), + location: node.attribute("location").unwrap_or_default().to_owned(), + name: node.attribute("name").unwrap_or_default().to_owned(), + position: parse_vector(node.attribute("position")), + rotation: parse_vector(node.attribute("rotation")), + visible_in_first_person: node + .attribute("visible_in_first_person") + .is_some_and(|value| value.eq_ignore_ascii_case("true")), + }); + } + "mesh" => { + let (Some(type_), Some(lod_level)) = + (node.attribute("type"), parse_i32(node.attribute("lod"))) + else { + continue; + }; + if mesh_definitions.len() >= MAX_MESH_DEFINITIONS { + return Err(Error::Argument); + } + mesh_definitions.push(AvatarMeshDefinition { + file_name: node.attribute("file_name").unwrap_or_default().to_owned(), + lod_level, + min_pixel_width: parse_i32(node.attribute("min_pixel_width")) + .unwrap_or_default(), + type_: type_.to_owned(), + }); + } + _ => {} + } + } + Ok(Self { + attachment_points, + mesh_definitions, + skeleton, + }) + } + + pub(crate) fn native_compute_bone_transforms( + &self, + param_values: &HashMap, + ) -> Result, Error> { + let mut result = HashMap::new(); + let joints = self.skeleton.get_all_joints()?.collect::>(); + if joints.len() > MAX_JOINTS { + return Err(Error::Argument); + } + for joint in joints { + seed_transform(&mut result, &joint.base); + for volume in joint.collision_volume() { + seed_transform(&mut result, &volume.base); + } + } + for param in VisualParams::params().0.into_values() { + let raw_value = param_values + .get(¶m.param_id) + .copied() + .unwrap_or(param.default_value); + let weight = raw_value.clamp(param.min_value, param.max_value); + if !weight.is_finite() { + return Err(Error::Argument); + } + for distortion in param.skeletal_distortions.unwrap_or_default() { + let Some(transform) = result.get_mut(&distortion.bone_name) else { + continue; + }; + transform.scale = add_scaled(transform.scale, distortion.scale_deformation, weight); + if distortion.has_position_deformation { + transform.position = + add_scaled(transform.position, distortion.position_deformation, weight); + } + } + for morph in param.volume_morphs.unwrap_or_default() { + let Some(transform) = result.get_mut(&morph.bone_name) else { + continue; + }; + if morph.has_scale { + transform.scale = add_scaled(transform.scale, morph.scale_delta, weight); + } + if morph.has_position { + transform.position = + add_scaled(transform.position, morph.position_delta, weight); + } + } + } + if result + .values() + .any(|value| !finite(value.position) || !finite(value.scale)) + { + return Err(Error::Argument); + } + Ok(result) + } + + pub(crate) fn native_get_attachment_point_by_id( + &self, + id: i32, + ) -> Option { + self.attachment_points + .iter() + .find(|point| point.id == id) + .cloned() + } + + pub(crate) fn native_get_attachment_point_by_name( + &self, + name: &str, + ) -> Option { + self.attachment_points + .iter() + .find(|point| point.name.eq_ignore_ascii_case(name)) + .cloned() + } + + pub(crate) fn native_attachment_points(&self) -> Vec { + self.attachment_points.clone() + } + + pub(crate) fn native_mesh_definitions(&self) -> Vec { + self.mesh_definitions.clone() + } + + pub(crate) fn native_skeleton(&self) -> LindenSkeleton { + self.skeleton.clone() + } +} + +#[derive(Clone, Debug, Default)] +pub struct AttachmentRiggedSkin { + pub inv_bind_matrices: Vec, + pub joint_names: Vec, + pub joint_position_overrides: Vec<(String, NumericsVector3)>, + pub joints: Vec, + pub lock_scale_if_joint_position: bool, + pub mesh_id: UUID, + pub weights: Vec, +} + +impl AttachmentRiggedSkin { + pub(crate) fn native_new() -> Result { + Ok(Self::default()) + } +} + +pub struct AvatarBoneMath; + +impl AvatarBoneMath { + pub(crate) fn native_build_bone_world_matrices( + skeleton: LindenSkeleton, + bone_transforms: HashMap, + ) -> Result, Error> { + let mut output = HashMap::new(); + let mut visited = 0; + build_joint( + &skeleton.bone(), + Quaternion::identity(), + Vector3::zero(), + Vector3::one(), + &bone_transforms, + None, + &mut output, + &mut visited, + )?; + Ok(output) + } + + pub(crate) fn native_compute_animated_bone_world_matrices( + avatar_definition: &LindenAvatarDefinition, + bone_transforms: &HashMap, + rotation_deltas: &HashMap, + ) -> Result, Error> { + let mut output = HashMap::new(); + let mut visited = 0; + build_joint( + &avatar_definition.skeleton.bone(), + Quaternion::identity(), + Vector3::zero(), + Vector3::one(), + bone_transforms, + Some(rotation_deltas), + &mut output, + &mut visited, + )?; + Ok(output) + } + + pub(crate) fn native_compute_attachment_bone_world_matrices( + avatar_definition: &LindenAvatarDefinition, + bone_transforms: &HashMap, + rotation_deltas: &HashMap, + ) -> Result, Error> { + Self::native_compute_animated_bone_world_matrices( + avatar_definition, + bone_transforms, + rotation_deltas, + ) + } + + pub(crate) fn native_strip_scale(matrix: Matrix4x4) -> Matrix4x4 { + let mut value = matrix.0; + for row in 0..3 { + let offset = row * 4; + let length = (value[offset].mul_add( + value[offset], + value[offset + 1].mul_add(value[offset + 1], value[offset + 2] * value[offset + 2]), + )) + .sqrt(); + if length > 1.0e-5 { + value[offset] /= length; + value[offset + 1] /= length; + value[offset + 2] /= length; + } + value[offset + 3] = 0.0; + } + Matrix4x4(value) + } + + pub(crate) fn native_compute_ground_adjustment( + transforms: Option<&HashMap>, + ) -> f32 { + let Some(transforms) = transforms else { + return 1.0; + }; + let names = [ + "mPelvis", + "mSkull", + "mNeck", + "mChest", + "mHead", + "mTorso", + "mHipLeft", + "mKneeLeft", + "mAnkleLeft", + "mFootLeft", + ]; + let Some(values) = names + .iter() + .map(|name| transforms.get(*name)) + .collect::>>() + else { + return 1.0; + }; + let [ + pelvis, + skull, + neck, + chest, + head, + torso, + hip, + knee, + ankle, + foot, + ] = values.as_slice() + else { + return 1.0; + }; + let pelvis_to_foot = hip.position.z * pelvis.scale.z + - knee.position.z * hip.scale.z + - ankle.position.z * knee.scale.z + - foot.position.z * ankle.scale.z; + let height = pelvis_to_foot + + std::f32::consts::SQRT_2 * skull.position.z * head.scale.z + + head.position.z * neck.scale.z + + neck.position.z * chest.scale.z + + chest.position.z * torso.scale.z + + torso.position.z * pelvis.scale.z; + let adjustment = height - pelvis_to_foot; + if adjustment.is_finite() && adjustment > 0.0 { + adjustment + } else { + 1.0 + } + } +} + +fn build_joint( + joint: &Joint, + parent_rotation: Quaternion, + parent_position: Vector3, + parent_scale: Vector3, + transforms: &HashMap, + rotation_deltas: Option<&HashMap>, + output: &mut HashMap, + visited: &mut usize, +) -> Result<(), Error> { + *visited = visited.checked_add(1).ok_or(Error::Argument)?; + if *visited > MAX_JOINTS { + return Err(Error::Argument); + } + let name = joint.base.name(); + let transform = transforms.get(&name); + let position = transform.map_or_else( + || vector(&joint.base.pos(), Vector3::zero()), + |v| v.position, + ); + let scale = transform.map_or_else(|| vector(&joint.base.scale(), Vector3::one()), |v| v.scale); + if !finite(position) || !finite(scale) { + return Err(Error::Argument); + } + let mut rotation = euler(&joint.base.rot())?; + if let Some(delta) = rotation_deltas.and_then(|values| values.get(&name)) { + rotation = Quaternion::multiply_with_quaternion_quaternion(rotation, quaternion(*delta)?)?; + } + let world_rotation = + Quaternion::multiply_with_quaternion_quaternion(parent_rotation, rotation)?; + let scaled_position = Vector3::multiply_with_vector3_vector3(position, parent_scale)?; + let rotated_position = Vector3::transform_normal( + scaled_position, + Matrix4::create_from_quaternion(parent_rotation)?, + )?; + let world_position = Vector3::add_with_vector3_vector3(rotated_position, parent_position)?; + let world = Matrix4::multiply_with_matrix4_matrix4( + Matrix4::multiply_with_matrix4_matrix4( + Matrix4::create_scale(scale)?, + Matrix4::create_from_quaternion(world_rotation)?, + )?, + Matrix4::create_translation(world_position)?, + )?; + let world = numerics(world); + if !name.is_empty() { + output.insert(name, world); + } + for alias in joint.get_aliases_list()? { + output.entry(alias).or_insert(world); + } + for volume in joint.collision_volume() { + *visited = visited.checked_add(1).ok_or(Error::Argument)?; + if *visited > MAX_JOINTS { + return Err(Error::Argument); + } + let volume_name = volume.base.name(); + let volume_transform = transforms.get(&volume_name); + let volume_position = volume_transform.map_or_else( + || vector(&volume.base.pos(), Vector3::zero()), + |value| value.position, + ); + let volume_scale = volume_transform.map_or_else( + || vector(&volume.base.scale(), Vector3::one()), + |value| value.scale, + ); + if !finite(volume_position) || !finite(volume_scale) { + return Err(Error::Argument); + } + let volume_rotation = euler(&volume.base.rot())?; + let volume_world_rotation = + Quaternion::multiply_with_quaternion_quaternion(world_rotation, volume_rotation)?; + let volume_scaled_position = + Vector3::multiply_with_vector3_vector3(volume_position, scale)?; + let volume_rotated_position = Vector3::transform_normal( + volume_scaled_position, + Matrix4::create_from_quaternion(world_rotation)?, + )?; + let volume_world_position = + Vector3::add_with_vector3_vector3(volume_rotated_position, world_position)?; + let volume_world = Matrix4::multiply_with_matrix4_matrix4( + Matrix4::multiply_with_matrix4_matrix4( + Matrix4::create_scale(volume_scale)?, + Matrix4::create_from_quaternion(volume_world_rotation)?, + )?, + Matrix4::create_translation(volume_world_position)?, + )?; + if !volume_name.is_empty() { + output.entry(volume_name).or_insert(numerics(volume_world)); + } + } + for child in joint.bone().unwrap_or_default() { + build_joint( + &child, + world_rotation, + world_position, + scale, + transforms, + rotation_deltas, + output, + visited, + )?; + } + Ok(()) +} + +fn vector(values: &[f32], fallback: Vector3) -> Vector3 { + if values.len() < 3 { + fallback + } else { + Vector3 { + x: values[0], + y: values[1], + z: values[2], + } + } +} + +fn finite(value: Vector3) -> bool { + value.x.is_finite() && value.y.is_finite() && value.z.is_finite() +} + +fn read_bounded(path: impl AsRef) -> Result { + let path = path.as_ref(); + if std::fs::metadata(path).map_err(|_| Error::Argument)?.len() > MAX_LAD_BYTES { + return Err(Error::Argument); + } + let contents = std::fs::read_to_string(path).map_err(|_| Error::Argument)?; + if contents.len() as u64 > MAX_LAD_BYTES { + return Err(Error::Argument); + } + Ok(contents) +} + +fn parse_i32(value: Option<&str>) -> Option { + value?.parse().ok() +} + +fn parse_vector(value: Option<&str>) -> Vector3 { + let mut values = value.unwrap_or_default().split_ascii_whitespace(); + let mut next = || { + values + .next() + .and_then(|part| part.parse::().ok()) + .unwrap_or_default() + }; + Vector3 { + x: next(), + y: next(), + z: next(), + } +} + +fn seed_transform( + output: &mut HashMap, + joint: &crate::rendering::JointBase, +) { + let name = joint.name(); + if name.is_empty() { + return; + } + output.insert( + name, + BoneTransform { + position: vector(&joint.pos(), Vector3::zero()), + scale: vector(&joint.scale(), Vector3::one()), + }, + ); +} + +fn add_scaled(value: Vector3, delta: Vector3, weight: f32) -> Vector3 { + Vector3 { + x: delta.x.mul_add(weight, value.x), + y: delta.y.mul_add(weight, value.y), + z: delta.z.mul_add(weight, value.z), + } +} + +fn quaternion(value: NumericsQuaternion) -> Result { + if value.0.iter().all(|component| component.is_finite()) { + Quaternion::new_with_single_single_single_single( + value.0[0], value.0[1], value.0[2], value.0[3], + ) + } else { + Err(Error::Argument) + } +} + +fn euler(values: &[f32]) -> Result { + let value = vector(values, Vector3::zero()); + Quaternion::create_from_eulers_with_single_single_single( + value.x.to_radians(), + value.y.to_radians(), + value.z.to_radians(), + ) +} + +fn numerics(value: Matrix4) -> Matrix4x4 { + Matrix4x4([ + value.m11, value.m12, value.m13, value.m14, value.m21, value.m22, value.m23, value.m24, + value.m31, value.m32, value.m33, value.m34, value.m41, value.m42, value.m43, value.m44, + ]) +} + +pub struct RiggedSkinMath; + +impl RiggedSkinMath { + pub(crate) fn native_floats_to_matrix(values: &[f32]) -> Matrix4x4 { + let Ok(values) = <&[f32; 16]>::try_from(values) else { + return Matrix4x4::default(); + }; + Matrix4x4(*values) + } + + pub(crate) fn native_build_inv_bind_matrices( + skin: &MeshSkinData, + ) -> Result, Error> { + if skin.joint_names.len() > MAX_JOINTS { + return Err(Error::Argument); + } + Ok((0..skin.joint_names.len()) + .map(|index| { + let start = index * 16; + skin.inverse_bind_matrices + .get(start..start + 16) + .map_or_else(identity, Self::native_floats_to_matrix) + }) + .collect()) + } + + pub(crate) fn native_extract_joint_position_overrides( + skin: &MeshSkinData, + ) -> Result, Error> { + let count = skin.joint_names.len(); + if count > MAX_JOINTS { + return Err(Error::Argument); + } + if skin.alt_inverse_bind_matrices.len() / 16 != count + || !skin.alt_inverse_bind_matrices.len().is_multiple_of(16) + { + return Ok(Vec::new()); + } + Ok(skin + .joint_names + .iter() + .enumerate() + .filter(|(_, name)| !name.is_empty()) + .map(|(index, name)| { + let offset = index * 16; + ( + name.clone(), + NumericsVector3([ + skin.alt_inverse_bind_matrices[offset + 12], + skin.alt_inverse_bind_matrices[offset + 13], + skin.alt_inverse_bind_matrices[offset + 14], + ]), + ) + }) + .collect()) + } + + pub(crate) fn native_normalize_skin_weights( + joint_count: i32, + joints: [&mut i32; 4], + weights: [&mut f32; 4], + ) { + let mut sum = 0.0; + for index in 0..4 { + if *joints[index] < 0 || *joints[index] >= joint_count { + *weights[index] = 0.0; + } + *weights[index] = if weights[index].is_finite() { + weights[index].clamp(0.0, 1.0) + } else { + 0.0 + }; + sum += *weights[index]; + } + if sum > 1.0e-6 { + for weight in weights { + *weight /= sum; + } + } else { + for index in 0..4 { + *joints[index] = 0; + *weights[index] = if index == 0 && joint_count > 0 { + 1.0 + } else { + 0.0 + }; + } + } + } +} + +fn identity() -> Matrix4x4 { + Matrix4x4([ + 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, + ]) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn embedded_avatar_definition_exposes_generated_skeleton_and_lad_records() { + let definition = + LindenAvatarDefinition::native_load(None, None).expect("avatar definition"); + assert!(!definition.native_attachment_points().is_empty()); + assert!(!definition.native_mesh_definitions().is_empty()); + assert!( + definition + .native_skeleton() + .get_all_joints() + .expect("joints") + .count() + > 1 + ); + let point = definition.native_attachment_points()[0].clone(); + assert_eq!( + definition + .native_get_attachment_point_by_id(point.native_id()) + .expect("point by id"), + point + ); + assert_eq!( + definition + .native_get_attachment_point_by_name(&point.native_name().to_uppercase()) + .expect("point by name"), + point + ); + } + + #[test] + fn animated_and_attachment_world_matrices_apply_rotation_deltas() { + let definition = + LindenAvatarDefinition::native_load(None, None).expect("avatar definition"); + let transforms = definition + .native_compute_bone_transforms(&HashMap::new()) + .expect("bone transforms"); + let bind = AvatarBoneMath::native_compute_animated_bone_world_matrices( + &definition, + &transforms, + &HashMap::new(), + ) + .expect("bind matrices"); + let delta = NumericsQuaternion([ + 0.0, + 0.0, + std::f32::consts::FRAC_1_SQRT_2, + std::f32::consts::FRAC_1_SQRT_2, + ]); + let deltas = HashMap::from([("mPelvis".to_owned(), delta)]); + let animated = AvatarBoneMath::native_compute_animated_bone_world_matrices( + &definition, + &transforms, + &deltas, + ) + .expect("animated matrices"); + let attachment = AvatarBoneMath::native_compute_attachment_bone_world_matrices( + &definition, + &transforms, + &deltas, + ) + .expect("attachment matrices"); + assert_ne!(animated["mPelvis"], bind["mPelvis"]); + assert_eq!(attachment, animated); + } +} diff --git a/crates/libremetaverse/src/client_core.rs b/crates/libremetaverse/src/client_core.rs index a0fd431..ea953f0 100644 --- a/crates/libremetaverse/src/client_core.rs +++ b/crates/libremetaverse/src/client_core.rs @@ -119,6 +119,8 @@ struct ClientRuntime { network_manager: Mutex>, agent_manager: Mutex>, appearance_manager: Mutex>>, + avatar_manager: Mutex>>, + animesh_manager: Mutex>>, inventory_manager: Mutex>>, inventory_ais_client: Mutex>, asset_manager: Mutex>>, @@ -151,6 +153,8 @@ impl ClientRuntime { network_manager: Mutex::new(std::sync::Weak::new()), agent_manager: Mutex::new(std::sync::Weak::new()), appearance_manager: Mutex::new(None), + avatar_manager: Mutex::new(None), + animesh_manager: Mutex::new(None), inventory_manager: Mutex::new(None), inventory_ais_client: Mutex::new(None), asset_manager: Mutex::new(None), @@ -491,6 +495,57 @@ impl GridClient { Ok(manager) } + pub(crate) fn native_avatars(&self) -> Result { + let mut cached = self + .runtime + .avatar_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if let Some(inner) = cached.as_ref() { + return Ok(crate::avatar_manager::AvatarManager::native_from_inner( + Arc::clone(inner), + )); + } + let manager = + crate::avatar_manager::AvatarManager::native_new(Some(Arc::new(self.clone())))?; + *cached = Some(manager.native_inner()); + Ok(manager) + } + + #[allow(clippy::needless_pass_by_value)] + pub(crate) fn native_set_avatars(&mut self, value: crate::AvatarManager) { + *self + .runtime + .avatar_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.native_inner()); + } + + pub(crate) fn native_animesh(&self) -> Result { + let mut cached = self + .runtime + .animesh_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if let Some(inner) = cached.as_ref() { + return Ok(crate::animesh_runtime::AnimeshManager::native_from_inner( + Arc::clone(inner), + )); + } + let manager = crate::animesh_runtime::AnimeshManager::native_new(self.clone())?; + *cached = Some(manager.native_inner()); + Ok(manager) + } + + #[allow(clippy::needless_pass_by_value)] + pub(crate) fn native_set_animesh(&mut self, value: crate::animesh::AnimeshManager) { + *self + .runtime + .animesh_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.native_inner()); + } + #[allow(clippy::needless_pass_by_value)] pub(crate) fn native_set_assets(&mut self, value: crate::AssetManager) { *self diff --git a/crates/libremetaverse/src/generated.rs b/crates/libremetaverse/src/generated.rs index d25aa7c..b4ef58b 100644 --- a/crates/libremetaverse/src/generated.rs +++ b/crates/libremetaverse/src/generated.rs @@ -2255,14 +2255,10 @@ impl AlertMessageEventArgs { } /// C# type: `T:LibreMetaverse.Animation`. -pub struct Animation { - /// C# member: `F:LibreMetaverse.Animation.AnimationID`. - pub animation_id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.Animation.AnimationSequence`. - pub animation_sequence: i32, - /// C# member: `F:LibreMetaverse.Animation.AnimationSourceObjectID`. - pub animation_source_object_id: libremetaverse_types::UUID, -} +pub use crate::avatar_manager::Animation; +/// C# member: `F:LibreMetaverse.Animation.AnimationID`. +/// C# member: `F:LibreMetaverse.Animation.AnimationSequence`. +/// C# member: `F:LibreMetaverse.Animation.AnimationSourceObjectID`. impl Animation {} /// C# type: `T:LibreMetaverse.Animations`. @@ -2825,7 +2821,7 @@ impl Animations { pub use crate::agent_messages::AnimationsChangedEventArgs; /// C# type: `T:LibreMetaverse.AnimeshSkinning`. -pub struct AnimeshSkinning; +pub use crate::animesh_skinning::AnimeshSkinning; impl AnimeshSkinning { /// C# member: `M:LibreMetaverse.AnimeshSkinning.ComputeSkinningMatrices(System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Animesh.JointPose},LibreMetaverse.Rendering.LindenSkeleton,LibreMetaverse.Rendering.MeshSkinData)`. pub fn compute_skinning_matrices( @@ -2833,8 +2829,8 @@ impl AnimeshSkinning { skeleton: Option, skin_data: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AnimeshSkinning.ComputeSkinningMatrices(System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Animesh.JointPose},LibreMetaverse.Rendering.LindenSkeleton,LibreMetaverse.Rendering.MeshSkinData)", + crate::animesh_skinning::AnimeshSkinning::native_compute_skinning_matrices( + pose, skeleton, skin_data, ) } /// C# member: `M:LibreMetaverse.AnimeshSkinning.DeformVertices(LibreMetaverse.Rendering.Face,LibreMetaverse.Matrix4[],LibreMetaverse.Matrix4,System.Span{LibreMetaverse.Vector3},System.Span{LibreMetaverse.Vector3})`. @@ -2845,8 +2841,12 @@ impl AnimeshSkinning { out_positions: &mut [libremetaverse_types::Vector3], out_normals: Option<&mut [libremetaverse_types::Vector3]>, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AnimeshSkinning.DeformVertices(LibreMetaverse.Rendering.Face,LibreMetaverse.Matrix4[],LibreMetaverse.Matrix4,System.Span{LibreMetaverse.Vector3},System.Span{LibreMetaverse.Vector3})", + crate::animesh_skinning::AnimeshSkinning::native_deform_vertices( + face, + skinning_matrices, + bind_shape_matrix, + out_positions, + out_normals, ) } } @@ -3890,28 +3890,22 @@ impl AvatarStatistics { } /// C# type: `T:LibreMetaverse.AvatarAnimationEventArgs`. -pub struct AvatarAnimationEventArgs; +pub use crate::avatar_manager::AvatarAnimationEventArgs; impl AvatarAnimationEventArgs { /// C# member: `M:LibreMetaverse.AvatarAnimationEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.Animation})`. pub fn new( avatar_id: libremetaverse_types::UUID, anims: Vec, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AvatarAnimationEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.Animation})", - ) + crate::avatar_manager::AvatarAnimationEventArgs::native_new(avatar_id, anims) } /// C# member: `P:LibreMetaverse.AvatarAnimationEventArgs.Animations`. pub fn animations(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AvatarAnimationEventArgs.Animations" - ) + self.native_animations() } /// C# member: `P:LibreMetaverse.AvatarAnimationEventArgs.AvatarID`. pub fn avatar_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AvatarAnimationEventArgs.AvatarID" - ) + self.native_avatar_id() } } @@ -4103,7 +4097,7 @@ impl AvatarInterestsReplyEventArgs { } /// C# type: `T:LibreMetaverse.AvatarManager`. -pub struct AvatarManager; +pub use crate::avatar_manager::AvatarManager; impl AvatarManager { /// C# member: `E:LibreMetaverse.AvatarManager.AvatarAnimation`. pub fn subscribe_avatar_animation( @@ -4112,7 +4106,7 @@ impl AvatarManager { libremetaverse::AvatarAnimationEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AvatarManager.AvatarAnimation") + self.native_subscribe_avatar_animation(handler) } /// C# member: `E:LibreMetaverse.AvatarManager.AvatarAppearance`. pub fn subscribe_avatar_appearance( @@ -4121,7 +4115,7 @@ impl AvatarManager { libremetaverse::AvatarAppearanceEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AvatarManager.AvatarAppearance") + self.native_subscribe_avatar_appearance(handler) } /// C# member: `E:LibreMetaverse.AvatarManager.AvatarClassifiedReply`. pub fn subscribe_avatar_classified_reply( @@ -4130,9 +4124,7 @@ impl AvatarManager { libremetaverse::AvatarClassifiedReplyEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.AvatarManager.AvatarClassifiedReply" - ) + self.native_subscribe_avatar_classified_reply(handler) } /// C# member: `E:LibreMetaverse.AvatarManager.AvatarGroupsReply`. pub fn subscribe_avatar_groups_reply( @@ -4141,7 +4133,7 @@ impl AvatarManager { libremetaverse::AvatarGroupsReplyEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AvatarManager.AvatarGroupsReply") + self.native_subscribe_avatar_groups_reply(handler) } /// C# member: `E:LibreMetaverse.AvatarManager.AvatarInterestsReply`. pub fn subscribe_avatar_interests_reply( @@ -4150,9 +4142,7 @@ impl AvatarManager { libremetaverse::AvatarInterestsReplyEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.AvatarManager.AvatarInterestsReply" - ) + self.native_subscribe_avatar_interests_reply(handler) } /// C# member: `E:LibreMetaverse.AvatarManager.AvatarNotesReply`. pub fn subscribe_avatar_notes_reply( @@ -4161,7 +4151,7 @@ impl AvatarManager { libremetaverse::AvatarNotesReplyEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AvatarManager.AvatarNotesReply") + self.native_subscribe_avatar_notes_reply(handler) } /// C# member: `E:LibreMetaverse.AvatarManager.AvatarPickerReply`. pub fn subscribe_avatar_picker_reply( @@ -4170,7 +4160,7 @@ impl AvatarManager { libremetaverse::AvatarPickerReplyEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AvatarManager.AvatarPickerReply") + self.native_subscribe_avatar_picker_reply(handler) } /// C# member: `E:LibreMetaverse.AvatarManager.AvatarPicksReply`. pub fn subscribe_avatar_picks_reply( @@ -4179,7 +4169,7 @@ impl AvatarManager { libremetaverse::AvatarPicksReplyEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AvatarManager.AvatarPicksReply") + self.native_subscribe_avatar_picks_reply(handler) } /// C# member: `E:LibreMetaverse.AvatarManager.AvatarPropertiesReply`. pub fn subscribe_avatar_properties_reply( @@ -4188,9 +4178,7 @@ impl AvatarManager { libremetaverse::AvatarPropertiesReplyEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.AvatarManager.AvatarPropertiesReply" - ) + self.native_subscribe_avatar_properties_reply(handler) } /// C# member: `E:LibreMetaverse.AvatarManager.ClassifiedInfoReply`. pub fn subscribe_classified_info_reply( @@ -4199,9 +4187,7 @@ impl AvatarManager { libremetaverse::ClassifiedInfoReplyEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.AvatarManager.ClassifiedInfoReply" - ) + self.native_subscribe_classified_info_reply(handler) } /// C# member: `E:LibreMetaverse.AvatarManager.DisplayNameUpdate`. pub fn subscribe_display_name_update( @@ -4210,28 +4196,28 @@ impl AvatarManager { libremetaverse::DisplayNameUpdateEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AvatarManager.DisplayNameUpdate") + self.native_subscribe_display_name_update(handler) } /// C# member: `E:LibreMetaverse.AvatarManager.PickInfoReply`. pub fn subscribe_pick_info_reply( &self, handler: libremetaverse_types::compat::EventHandler, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AvatarManager.PickInfoReply") + self.native_subscribe_pick_info_reply(handler) } /// C# member: `E:LibreMetaverse.AvatarManager.UUIDNameReply`. pub fn subscribe_uuid_name_reply( &self, handler: libremetaverse_types::compat::EventHandler, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AvatarManager.UUIDNameReply") + self.native_subscribe_uuid_name_reply(handler) } /// C# member: `E:LibreMetaverse.AvatarManager.ViewerEffect`. pub fn subscribe_viewer_effect( &self, handler: libremetaverse_types::compat::EventHandler, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AvatarManager.ViewerEffect") + self.native_subscribe_viewer_effect(handler) } /// C# member: `E:LibreMetaverse.AvatarManager.ViewerEffectLookAt`. pub fn subscribe_viewer_effect_look_at( @@ -4240,9 +4226,7 @@ impl AvatarManager { libremetaverse::ViewerEffectLookAtEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.AvatarManager.ViewerEffectLookAt" - ) + self.native_subscribe_viewer_effect_look_at(handler) } /// C# member: `E:LibreMetaverse.AvatarManager.ViewerEffectPointAt`. pub fn subscribe_viewer_effect_point_at( @@ -4251,33 +4235,25 @@ impl AvatarManager { libremetaverse::ViewerEffectPointAtEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.AvatarManager.ViewerEffectPointAt" - ) + self.native_subscribe_viewer_effect_point_at(handler) } /// C# member: `M:LibreMetaverse.AvatarManager.#ctor(LibreMetaverse.GridClient)`. pub fn new( client: Option>, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AvatarManager.#ctor(LibreMetaverse.GridClient)", - ) + crate::avatar_manager::AvatarManager::native_new(client) } /// C# member: `M:LibreMetaverse.AvatarManager.AgentProfileAvailable`. pub fn agent_profile_available(&self) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AvatarManager.AgentProfileAvailable", - ) + self.native_capability_available("AgentProfile") } /// C# member: `M:LibreMetaverse.AvatarManager.DisplayNamesAvailable`. pub fn display_names_available(&self) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AvatarManager.DisplayNamesAvailable", - ) + self.native_capability_available("GetDisplayNames") } /// C# member: `M:LibreMetaverse.AvatarManager.Dispose`. pub fn dispose(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.AvatarManager.Dispose") + self.native_dispose() } /// C# member: `M:LibreMetaverse.AvatarManager.GetDisplayNamesAsync(System.Collections.Generic.List{LibreMetaverse.UUID},System.Threading.CancellationToken)`. pub async fn get_display_names( @@ -4292,9 +4268,7 @@ impl AvatarManager { ), crate::Error, > { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AvatarManager.GetDisplayNamesAsync(System.Collections.Generic.List{LibreMetaverse.UUID},System.Threading.CancellationToken)", - ) + self.native_get_display_names(ids, cancellation_token).await } /// C# member: `M:LibreMetaverse.AvatarManager.RequestAgentProfileAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn request_agent_profile( @@ -4308,24 +4282,19 @@ impl AvatarManager { ), crate::Error, > { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AvatarManager.RequestAgentProfileAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) + self.native_request_agent_profile(avatarid, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AvatarManager.RequestAvatarClassified(LibreMetaverse.UUID)`. pub fn request_avatar_classified( &self, avatarid: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AvatarManager.RequestAvatarClassified(LibreMetaverse.UUID)", - ) + self.native_request_generic("avatarclassifiedsrequest", &[avatarid]) } /// C# member: `M:LibreMetaverse.AvatarManager.RequestAvatarName(LibreMetaverse.UUID)`. pub fn request_avatar_name(&self, id: libremetaverse_types::UUID) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AvatarManager.RequestAvatarName(LibreMetaverse.UUID)", - ) + self.native_request_avatar_names(&[id]) } /// C# member: `M:LibreMetaverse.AvatarManager.RequestAvatarNameSearch(System.String,LibreMetaverse.UUID)`. pub fn request_avatar_name_search( @@ -4333,60 +4302,46 @@ impl AvatarManager { name: String, query_id: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AvatarManager.RequestAvatarNameSearch(System.String,LibreMetaverse.UUID)", - ) + self.native_request_avatar_name_search(name, query_id) } /// C# member: `M:LibreMetaverse.AvatarManager.RequestAvatarNames(System.Collections.Generic.List{LibreMetaverse.UUID})`. pub fn request_avatar_names( &self, ids: Vec, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AvatarManager.RequestAvatarNames(System.Collections.Generic.List{LibreMetaverse.UUID})", - ) + self.native_request_avatar_names(&ids) } /// C# member: `M:LibreMetaverse.AvatarManager.RequestAvatarNotes(LibreMetaverse.UUID)`. pub fn request_avatar_notes( &self, avatarid: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AvatarManager.RequestAvatarNotes(LibreMetaverse.UUID)", - ) + self.native_request_generic("avatarnotesrequest", &[avatarid]) } /// C# member: `M:LibreMetaverse.AvatarManager.RequestAvatarPicks(LibreMetaverse.UUID)`. pub fn request_avatar_picks( &self, avatarid: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AvatarManager.RequestAvatarPicks(LibreMetaverse.UUID)", - ) + self.native_request_generic("avatarpicksrequest", &[avatarid]) } /// C# member: `M:LibreMetaverse.AvatarManager.RequestAvatarProperties(LibreMetaverse.UUID)`. pub fn request_avatar_properties( &self, avatarid: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AvatarManager.RequestAvatarProperties(LibreMetaverse.UUID)", - ) + self.native_request_avatar_properties(avatarid) } /// C# member: `M:LibreMetaverse.AvatarManager.RequestClassifiedInfo(LibreMetaverse.UUID)`. pub fn request_classified_info( &self, classifiedid: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AvatarManager.RequestClassifiedInfo(LibreMetaverse.UUID)", - ) + self.native_request_classified_info(classifiedid) } /// C# member: `M:LibreMetaverse.AvatarManager.RequestOwnAvatarTextures`. pub fn request_own_avatar_textures(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AvatarManager.RequestOwnAvatarTextures", - ) + self.native_request_own_avatar_textures() } /// C# member: `M:LibreMetaverse.AvatarManager.RequestPickInfo(LibreMetaverse.UUID,LibreMetaverse.UUID)`. pub fn request_pick_info( @@ -4394,18 +4349,14 @@ impl AvatarManager { avatarid: libremetaverse_types::UUID, pickid: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AvatarManager.RequestPickInfo(LibreMetaverse.UUID,LibreMetaverse.UUID)", - ) + self.native_request_generic("pickinforequest", &[avatarid, pickid]) } /// C# member: `M:LibreMetaverse.AvatarManager.RequestTrackAgent(LibreMetaverse.UUID)`. pub fn request_track_agent( &self, prey_id: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AvatarManager.RequestTrackAgent(LibreMetaverse.UUID)", - ) + self.native_request_track_agent(prey_id) } } @@ -4767,70 +4718,48 @@ impl BannedAgentsEventArgs { } /// C# type: `T:LibreMetaverse.BinBVHAnimationReader`. -pub struct BinBVHAnimationReader { - /// C# member: `F:LibreMetaverse.BinBVHAnimationReader.EaseInTime`. - pub ease_in_time: f32, - /// C# member: `F:LibreMetaverse.BinBVHAnimationReader.EaseOutTime`. - pub ease_out_time: f32, - /// C# member: `F:LibreMetaverse.BinBVHAnimationReader.ExpressionName`. - pub expression_name: String, - /// C# member: `F:LibreMetaverse.BinBVHAnimationReader.HandPose`. - pub hand_pose: u32, - /// C# member: `F:LibreMetaverse.BinBVHAnimationReader.InPoint`. - pub in_point: f32, - /// C# member: `F:LibreMetaverse.BinBVHAnimationReader.JointCount`. - pub joint_count: u32, - /// C# member: `F:LibreMetaverse.BinBVHAnimationReader.Length`. - pub length: f32, - /// C# member: `F:LibreMetaverse.BinBVHAnimationReader.Loop`. - pub loop_: bool, - /// C# member: `F:LibreMetaverse.BinBVHAnimationReader.OutPoint`. - pub out_point: f32, - /// C# member: `F:LibreMetaverse.BinBVHAnimationReader.Priority`. - pub priority: i32, - /// C# member: `F:LibreMetaverse.BinBVHAnimationReader.joints`. - pub joints: Vec, - /// C# member: `F:LibreMetaverse.BinBVHAnimationReader.unknown0`. - pub unknown0: u16, - /// C# member: `F:LibreMetaverse.BinBVHAnimationReader.unknown1`. - pub unknown1: u16, -} +pub use crate::animation::BinBVHAnimationReader; +/// C# member: `F:LibreMetaverse.BinBVHAnimationReader.EaseInTime`. +/// C# member: `F:LibreMetaverse.BinBVHAnimationReader.EaseOutTime`. +/// C# member: `F:LibreMetaverse.BinBVHAnimationReader.ExpressionName`. +/// C# member: `F:LibreMetaverse.BinBVHAnimationReader.HandPose`. +/// C# member: `F:LibreMetaverse.BinBVHAnimationReader.InPoint`. +/// C# member: `F:LibreMetaverse.BinBVHAnimationReader.JointCount`. +/// C# member: `F:LibreMetaverse.BinBVHAnimationReader.Length`. +/// C# member: `F:LibreMetaverse.BinBVHAnimationReader.Loop`. +/// C# member: `F:LibreMetaverse.BinBVHAnimationReader.OutPoint`. +/// C# member: `F:LibreMetaverse.BinBVHAnimationReader.Priority`. +/// C# member: `F:LibreMetaverse.BinBVHAnimationReader.joints`. +/// C# member: `F:LibreMetaverse.BinBVHAnimationReader.unknown0`. +/// C# member: `F:LibreMetaverse.BinBVHAnimationReader.unknown1`. impl BinBVHAnimationReader { /// C# member: `M:LibreMetaverse.BinBVHAnimationReader.#ctor(System.Byte[])`. pub fn new(animationdata: Vec) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.BinBVHAnimationReader.#ctor(System.Byte[])", - ) + crate::animation::BinBVHAnimationReader::native_new(animationdata) } /// C# member: `M:LibreMetaverse.BinBVHAnimationReader.Equals(LibreMetaverse.BinBVHAnimationReader)`. pub fn equals_with_bin_bvh_animation_reader( &self, other: Option, ) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.BinBVHAnimationReader.Equals(LibreMetaverse.BinBVHAnimationReader)" - ) + self.native_equals(other.as_ref()) } /// C# member: `M:LibreMetaverse.BinBVHAnimationReader.Equals(LibreMetaverse.binBVHJoint[],LibreMetaverse.binBVHJoint[])`. pub fn equals_with_bin_bvh_joint_array_bin_bvh_joint_array( arr1: Vec, arr2: Vec, ) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.BinBVHAnimationReader.Equals(LibreMetaverse.binBVHJoint[],LibreMetaverse.binBVHJoint[])" - ) + crate::animation::joints_equal(&arr1, &arr2) } /// C# member: `M:LibreMetaverse.BinBVHAnimationReader.Equals(System.Object)`. pub fn equals_with_object(&self, obj: Option) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.BinBVHAnimationReader.Equals(System.Object)" - ) + obj.as_ref() + .and_then(|value| value.downcast_ref::()) + .is_some_and(|other| self.native_equals(Some(other))) } /// C# member: `M:LibreMetaverse.BinBVHAnimationReader.GetHashCode`. pub fn get_hash_code(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.BinBVHAnimationReader.GetHashCode" - ) + self.native_get_hash_code() } /// C# member: `M:LibreMetaverse.BinBVHAnimationReader.ReadBytesUntilNull(System.Byte[],System.Int32@)`. pub fn read_bytes_until_null( @@ -4838,9 +4767,7 @@ impl BinBVHAnimationReader { data: Vec, i: &mut i32, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.BinBVHAnimationReader.ReadBytesUntilNull(System.Byte[],System.Int32@)", - ) + self.native_read_bytes_until_null(data, i) } /// C# member: `M:LibreMetaverse.BinBVHAnimationReader.readJoint(System.Byte[],System.Int32@)`. pub fn read_joint( @@ -4848,9 +4775,7 @@ impl BinBVHAnimationReader { data: Vec, i: &mut i32, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.BinBVHAnimationReader.readJoint(System.Byte[],System.Int32@)", - ) + self.native_read_joint(data, i) } /// C# member: `M:LibreMetaverse.BinBVHAnimationReader.readKeys(System.Byte[],System.Int32@,System.Int32,System.Single,System.Single)`. pub fn read_keys( @@ -4861,9 +4786,7 @@ impl BinBVHAnimationReader { min: f32, max: f32, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.BinBVHAnimationReader.readKeys(System.Byte[],System.Int32@,System.Int32,System.Single,System.Single)", - ) + self.native_read_keys(data, i, keycount, min, max) } } @@ -8663,11 +8586,14 @@ impl GridClient { } /// C# member: `P:LibreMetaverse.GridClient.Animesh`. pub fn animesh(&self) -> libremetaverse::animesh::AnimeshManager { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Animesh") + /* native client-core implementation */ + self.native_animesh() + .unwrap_or_else(|_| panic!("failed to construct AnimeshManager")) } /// Setter for C# member: `P:LibreMetaverse.GridClient.Animesh`. pub fn set_animesh(&mut self, value: libremetaverse::animesh::AnimeshManager) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Animesh") + /* native client-core implementation */ + self.native_set_animesh(value) } /// C# member: `P:LibreMetaverse.GridClient.Appearance`. pub fn appearance(&self) -> libremetaverse::AppearanceManager { @@ -8693,11 +8619,14 @@ impl GridClient { } /// C# member: `P:LibreMetaverse.GridClient.Avatars`. pub fn avatars(&self) -> libremetaverse::AvatarManager { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Avatars") + /* native client-core implementation */ + self.native_avatars() + .unwrap_or_else(|_| panic!("failed to construct AvatarManager")) } /// Setter for C# member: `P:LibreMetaverse.GridClient.Avatars`. pub fn set_avatars(&mut self, value: libremetaverse::AvatarManager) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Avatars") + /* native client-core implementation */ + self.native_set_avatars(value) } /// C# member: `P:LibreMetaverse.GridClient.CapsRateLimiter`. pub fn caps_rate_limiter(&self) -> libremetaverse::CapsRateLimiter { @@ -22732,180 +22661,138 @@ pub use crate::transfers::XferDownload; pub use crate::transfers::XferReceivedEventArgs; /// C# type: `T:LibreMetaverse.binBVHJoint`. -pub struct BinBVHJoint { - /// C# member: `F:LibreMetaverse.binBVHJoint.Name`. - pub name: String, - /// C# member: `F:LibreMetaverse.binBVHJoint.Priority`. - pub priority: i32, - /// C# member: `F:LibreMetaverse.binBVHJoint.Tag`. - pub tag: libremetaverse_types::compat::Object, - /// C# member: `F:LibreMetaverse.binBVHJoint.positionkeys`. - pub positionkeys: Vec, - /// C# member: `F:LibreMetaverse.binBVHJoint.rotationkeys`. - pub rotationkeys: Vec, -} +pub use crate::animation::BinBVHJoint; +/// C# member: `F:LibreMetaverse.binBVHJoint.Name`. +/// C# member: `F:LibreMetaverse.binBVHJoint.Priority`. +/// C# member: `F:LibreMetaverse.binBVHJoint.Tag`. +/// C# member: `F:LibreMetaverse.binBVHJoint.positionkeys`. +/// C# member: `F:LibreMetaverse.binBVHJoint.rotationkeys`. impl BinBVHJoint { /// C# member: `M:LibreMetaverse.binBVHJoint.Equals(LibreMetaverse.binBVHJoint)`. pub fn equals_with_bin_bvh_joint(&self, other: libremetaverse::BinBVHJoint) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.binBVHJoint.Equals(LibreMetaverse.binBVHJoint)" - ) + self.native_equals(&other) } /// C# member: `M:LibreMetaverse.binBVHJoint.Equals(LibreMetaverse.binBVHJointKey,LibreMetaverse.binBVHJointKey)`. pub fn equals_with_bin_bvh_joint_key_bin_bvh_joint_key( arr1: libremetaverse::BinBVHJointKey, arr2: libremetaverse::BinBVHJointKey, ) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.binBVHJoint.Equals(LibreMetaverse.binBVHJointKey,LibreMetaverse.binBVHJointKey)" - ) + crate::animation::key_equal(&arr1, &arr2) } /// C# member: `M:LibreMetaverse.binBVHJoint.Equals(LibreMetaverse.binBVHJointKey[],LibreMetaverse.binBVHJointKey[])`. pub fn equals_with_bin_bvh_joint_key_array_bin_bvh_joint_key_array( arr1: Vec, arr2: Vec, ) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.binBVHJoint.Equals(LibreMetaverse.binBVHJointKey[],LibreMetaverse.binBVHJointKey[])" - ) + crate::animation::keys_equal(&arr1, &arr2) } /// C# member: `M:LibreMetaverse.binBVHJoint.Equals(System.Object)`. pub fn equals_with_object(&self, obj: Option) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.binBVHJoint.Equals(System.Object)" - ) + obj.as_ref() + .and_then(|value| value.downcast_ref::()) + .is_some_and(|other| self.native_equals(other)) } /// C# member: `M:LibreMetaverse.binBVHJoint.GetHashCode`. pub fn get_hash_code(&self) -> i32 { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.binBVHJoint.GetHashCode") + self.native_get_hash_code() } /// C# member: `M:LibreMetaverse.binBVHJoint.op_Equality(LibreMetaverse.binBVHJoint,LibreMetaverse.binBVHJoint)`. pub fn eq(left: libremetaverse::BinBVHJoint, right: libremetaverse::BinBVHJoint) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.binBVHJoint.op_Equality(LibreMetaverse.binBVHJoint,LibreMetaverse.binBVHJoint)" - ) + left.native_equals(&right) } /// C# member: `M:LibreMetaverse.binBVHJoint.op_Inequality(LibreMetaverse.binBVHJoint,LibreMetaverse.binBVHJoint)`. pub fn ne(left: libremetaverse::BinBVHJoint, right: libremetaverse::BinBVHJoint) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.binBVHJoint.op_Inequality(LibreMetaverse.binBVHJoint,LibreMetaverse.binBVHJoint)" - ) + !left.native_equals(&right) } } /// C# type: `T:LibreMetaverse.binBVHJointKey`. -pub struct BinBVHJointKey { - /// C# member: `F:LibreMetaverse.binBVHJointKey.key_element`. - pub key_element: libremetaverse_types::Vector3, - /// C# member: `F:LibreMetaverse.binBVHJointKey.time`. - pub time: f32, -} +pub use crate::animation::BinBVHJointKey; +/// C# member: `F:LibreMetaverse.binBVHJointKey.key_element`. +/// C# member: `F:LibreMetaverse.binBVHJointKey.time`. impl BinBVHJointKey {} pub mod animesh { /// C# type: `T:LibreMetaverse.Animesh.AnimationTrack`. - pub struct AnimationTrack; + pub use crate::animesh_runtime::AnimationTrack; impl AnimationTrack { /// C# member: `M:LibreMetaverse.Animesh.AnimationTrack.Advance(System.Single)`. pub fn advance(&mut self, dt: f32) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Animesh.AnimationTrack.Advance(System.Single)", - ) + self.native_advance(dt) } /// C# member: `M:LibreMetaverse.Animesh.AnimationTrack.EvaluatePose(System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Animesh.JointPose})`. pub fn evaluate_pose( &self, pose: &mut std::collections::HashMap, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Animesh.AnimationTrack.EvaluatePose(System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Animesh.JointPose})", - ) + self.native_evaluate_pose(pose) } /// C# member: `P:LibreMetaverse.Animesh.AnimationTrack.AnimationID`. pub fn animation_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Animesh.AnimationTrack.AnimationID" - ) + self.native_animation_id() } /// C# member: `P:LibreMetaverse.Animesh.AnimationTrack.CurrentTime`. pub fn current_time(&self) -> f32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Animesh.AnimationTrack.CurrentTime" - ) + self.native_current_time() } /// Setter for C# member: `P:LibreMetaverse.Animesh.AnimationTrack.CurrentTime`. pub fn set_current_time(&mut self, value: f32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Animesh.AnimationTrack.CurrentTime" - ) + self.native_set_current_time(value) } /// C# member: `P:LibreMetaverse.Animesh.AnimationTrack.Data`. pub fn data(&self) -> Option { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Animesh.AnimationTrack.Data") + self.native_data() } /// Setter for C# member: `P:LibreMetaverse.Animesh.AnimationTrack.Data`. pub fn set_data(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Animesh.AnimationTrack.Data") + self.native_set_data(value) } /// C# member: `P:LibreMetaverse.Animesh.AnimationTrack.EaseWeight`. pub fn ease_weight(&self) -> f32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Animesh.AnimationTrack.EaseWeight" - ) + self.native_ease_weight() } /// C# member: `P:LibreMetaverse.Animesh.AnimationTrack.IsFinished`. pub fn is_finished(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Animesh.AnimationTrack.IsFinished" - ) + self.native_is_finished() } /// Setter for C# member: `P:LibreMetaverse.Animesh.AnimationTrack.IsFinished`. pub fn set_is_finished(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Animesh.AnimationTrack.IsFinished" - ) + self.native_set_is_finished(value) } } /// C# type: `T:LibreMetaverse.Animesh.AnimeshManager`. - pub struct AnimeshManager; + pub use crate::animesh_runtime::AnimeshManager; impl AnimeshManager { /// C# member: `M:LibreMetaverse.Animesh.AnimeshManager.GetPlayer(LibreMetaverse.UUID)`. pub fn get_player( &self, object_id: libremetaverse_types::UUID, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Animesh.AnimeshManager.GetPlayer(LibreMetaverse.UUID)", - ) + Ok(self.native_get_player(object_id)) } /// C# member: `M:LibreMetaverse.Animesh.AnimeshManager.RemovePlayer(LibreMetaverse.UUID)`. pub fn remove_player( &self, object_id: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Animesh.AnimeshManager.RemovePlayer(LibreMetaverse.UUID)", - ) + self.native_remove_player(object_id); + Ok(()) } /// C# member: `M:LibreMetaverse.Animesh.AnimeshManager.Update(System.Single)`. pub fn update(&self, dt: f32) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Animesh.AnimeshManager.Update(System.Single)", - ) + self.native_update(dt) } /// C# member: `P:LibreMetaverse.Animesh.AnimeshManager.AllPlayers`. pub fn all_players( &self, ) -> Box> { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Animesh.AnimeshManager.AllPlayers" - ) + Box::new(self.native_all_players().into_iter()) } } /// C# type: `T:LibreMetaverse.Animesh.AnimeshPlayer`. - pub struct AnimeshPlayer; + pub use crate::animesh_runtime::AnimeshPlayer; impl AnimeshPlayer { /// C# member: `M:LibreMetaverse.Animesh.AnimeshPlayer.EvaluatePose`. pub fn evaluate_pose( @@ -22914,45 +22801,30 @@ pub mod animesh { std::collections::HashMap, crate::Error, > { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Animesh.AnimeshPlayer.EvaluatePose", - ) + self.native_evaluate_pose() } /// C# member: `M:LibreMetaverse.Animesh.AnimeshPlayer.Update(System.Single)`. pub fn update(&mut self, dt: f32) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Animesh.AnimeshPlayer.Update(System.Single)", - ) + self.native_update(dt) } /// C# member: `P:LibreMetaverse.Animesh.AnimeshPlayer.ObjectID`. pub fn object_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Animesh.AnimeshPlayer.ObjectID" - ) + self.native_object_id() } /// C# member: `P:LibreMetaverse.Animesh.AnimeshPlayer.TrackCount`. pub fn track_count(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Animesh.AnimeshPlayer.TrackCount" - ) + self.native_track_count() } } /// C# type: `T:LibreMetaverse.Animesh.JointPose`. - pub struct JointPose { - /// C# member: `F:LibreMetaverse.Animesh.JointPose.EaseWeight`. - pub ease_weight: f32, - /// C# member: `F:LibreMetaverse.Animesh.JointPose.HasPosition`. - pub has_position: bool, - /// C# member: `F:LibreMetaverse.Animesh.JointPose.HasRotation`. - pub has_rotation: bool, - /// C# member: `F:LibreMetaverse.Animesh.JointPose.Position`. - pub position: libremetaverse_types::Vector3, - /// C# member: `F:LibreMetaverse.Animesh.JointPose.Priority`. - pub priority: i32, - /// C# member: `F:LibreMetaverse.Animesh.JointPose.Rotation`. - pub rotation: libremetaverse_types::Quaternion, - } + pub use crate::animesh_runtime::JointPose; + /// C# member: `F:LibreMetaverse.Animesh.JointPose.EaseWeight`. + /// C# member: `F:LibreMetaverse.Animesh.JointPose.HasPosition`. + /// C# member: `F:LibreMetaverse.Animesh.JointPose.HasRotation`. + /// C# member: `F:LibreMetaverse.Animesh.JointPose.Position`. + /// C# member: `F:LibreMetaverse.Animesh.JointPose.Priority`. + /// C# member: `F:LibreMetaverse.Animesh.JointPose.Rotation`. impl JointPose {} } @@ -58240,26 +58112,20 @@ pub mod messages { impl AgentProfileMessage { /// C# member: `M:LibreMetaverse.Messages.Linden.AgentProfileMessage.#ctor`. pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.AgentProfileMessage.#ctor", - ) + Ok(crate::avatar_manager::agent_profile_defaults()) } /// C# member: `M:LibreMetaverse.Messages.Linden.AgentProfileMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)`. pub fn deserialize( &mut self, map: libremetaverse_structured_data::OSDMap, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.AgentProfileMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)", - ) + crate::avatar_manager::deserialize_agent_profile(self, &map) } /// C# member: `M:LibreMetaverse.Messages.Linden.AgentProfileMessage.Serialize`. pub fn serialize( &self, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.AgentProfileMessage.Serialize", - ) + crate::avatar_manager::serialize_agent_profile(self) } } impl libremetaverse::interfaces::IMessage for AgentProfileMessage {} @@ -58288,9 +58154,7 @@ pub mod messages { impl AgentProfileMessageGroupData { /// C# member: `M:LibreMetaverse.Messages.Linden.AgentProfileMessage.GroupData.#ctor`. pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.AgentProfileMessage.GroupData.#ctor", - ) + Ok(crate::avatar_manager::agent_profile_group_defaults()) } } @@ -157417,86 +157281,60 @@ pub mod packets { pub mod rendering { /// C# type: `T:LibreMetaverse.Rendering.AttachmentRiggedSkin`. - pub struct AttachmentRiggedSkin { - /// C# member: `F:LibreMetaverse.Rendering.AttachmentRiggedSkin.InvBindMatrices`. - pub inv_bind_matrices: Vec, - /// C# member: `F:LibreMetaverse.Rendering.AttachmentRiggedSkin.JointNames`. - pub joint_names: Vec, - /// C# member: `F:LibreMetaverse.Rendering.AttachmentRiggedSkin.JointPositionOverrides`. - pub joint_position_overrides: Vec<(String, libremetaverse_types::compat::Vector3)>, - /// C# member: `F:LibreMetaverse.Rendering.AttachmentRiggedSkin.Joints`. - pub joints: Vec, - /// C# member: `F:LibreMetaverse.Rendering.AttachmentRiggedSkin.LockScaleIfJointPosition`. - pub lock_scale_if_joint_position: bool, - /// C# member: `F:LibreMetaverse.Rendering.AttachmentRiggedSkin.MeshId`. - pub mesh_id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.Rendering.AttachmentRiggedSkin.Weights`. - pub weights: Vec, - } + pub use crate::avatar_rig::AttachmentRiggedSkin; + /// C# member: `F:LibreMetaverse.Rendering.AttachmentRiggedSkin.InvBindMatrices`. + /// C# member: `F:LibreMetaverse.Rendering.AttachmentRiggedSkin.JointNames`. + /// C# member: `F:LibreMetaverse.Rendering.AttachmentRiggedSkin.JointPositionOverrides`. + /// C# member: `F:LibreMetaverse.Rendering.AttachmentRiggedSkin.Joints`. + /// C# member: `F:LibreMetaverse.Rendering.AttachmentRiggedSkin.LockScaleIfJointPosition`. + /// C# member: `F:LibreMetaverse.Rendering.AttachmentRiggedSkin.MeshId`. + /// C# member: `F:LibreMetaverse.Rendering.AttachmentRiggedSkin.Weights`. impl AttachmentRiggedSkin { /// C# member: `M:LibreMetaverse.Rendering.AttachmentRiggedSkin.#ctor`. pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Rendering.AttachmentRiggedSkin.#ctor", - ) + crate::avatar_rig::AttachmentRiggedSkin::native_new() } } /// C# type: `T:LibreMetaverse.Rendering.AvatarAttachmentPoint`. - pub struct AvatarAttachmentPoint; + pub use crate::avatar_rig::AvatarAttachmentPoint; impl AvatarAttachmentPoint { /// C# member: `P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Group`. pub fn group(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Group" - ) + self.native_group() } /// C# member: `P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Id`. pub fn id(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Id" - ) + self.native_id() } /// C# member: `P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Joint`. pub fn joint(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Joint" - ) + self.native_joint() } /// C# member: `P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Location`. pub fn location(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Location" - ) + self.native_location() } /// C# member: `P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Name`. pub fn name(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Name" - ) + self.native_name() } /// C# member: `P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Position`. pub fn position(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Position" - ) + self.native_position() } /// C# member: `P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Rotation`. pub fn rotation(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Rotation" - ) + self.native_rotation() } /// C# member: `P:LibreMetaverse.Rendering.AvatarAttachmentPoint.VisibleInFirstPerson`. pub fn visible_in_first_person(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Rendering.AvatarAttachmentPoint.VisibleInFirstPerson" - ) + self.native_visible_in_first_person() } } /// C# type: `T:LibreMetaverse.Rendering.AvatarBoneMath`. - pub struct AvatarBoneMath; + pub use crate::avatar_rig::AvatarBoneMath; impl AvatarBoneMath { /// C# member: `M:LibreMetaverse.Rendering.AvatarBoneMath.BuildBoneWorldMatrices(LibreMetaverse.Rendering.LindenSkeleton,System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Rendering.BoneTransform})`. pub fn build_bone_world_matrices( @@ -157509,8 +157347,9 @@ pub mod rendering { std::collections::HashMap, crate::Error, > { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Rendering.AvatarBoneMath.BuildBoneWorldMatrices(LibreMetaverse.Rendering.LindenSkeleton,System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Rendering.BoneTransform})", + crate::avatar_rig::AvatarBoneMath::native_build_bone_world_matrices( + skeleton, + bone_transforms, ) } /// C# member: `M:LibreMetaverse.Rendering.AvatarBoneMath.ComputeAnimatedBoneWorldMatrices(LibreMetaverse.Rendering.LindenAvatarDefinition,System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Rendering.BoneTransform},System.Collections.Generic.IReadOnlyDictionary{System.String,System.Numerics.Quaternion})`. @@ -157525,8 +157364,10 @@ pub mod rendering { std::collections::HashMap, crate::Error, > { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Rendering.AvatarBoneMath.ComputeAnimatedBoneWorldMatrices(LibreMetaverse.Rendering.LindenAvatarDefinition,System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Rendering.BoneTransform},System.Collections.Generic.IReadOnlyDictionary{System.String,System.Numerics.Quaternion})", + crate::avatar_rig::AvatarBoneMath::native_compute_animated_bone_world_matrices( + &avatar_def, + &bone_transforms, + &rot_deltas, ) } /// C# member: `M:LibreMetaverse.Rendering.AvatarBoneMath.ComputeAnimatedBoneWorldMatrices(LibreMetaverse.Rendering.LindenAvatarDefinition,System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Rendering.BoneTransform},System.Collections.Generic.IReadOnlyDictionary{System.String,System.Numerics.Quaternion},System.Collections.Generic.Dictionary{System.String,System.Numerics.Matrix4x4})`. @@ -157539,9 +157380,16 @@ pub mod rendering { rot_deltas: std::collections::HashMap, result: std::collections::HashMap, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Rendering.AvatarBoneMath.ComputeAnimatedBoneWorldMatrices(LibreMetaverse.Rendering.LindenAvatarDefinition,System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Rendering.BoneTransform},System.Collections.Generic.IReadOnlyDictionary{System.String,System.Numerics.Quaternion},System.Collections.Generic.Dictionary{System.String,System.Numerics.Matrix4x4})", - ) + let computed = + crate::avatar_rig::AvatarBoneMath::native_compute_animated_bone_world_matrices( + &avatar_def, + &bone_transforms, + &rot_deltas, + )?; + let mut result = result; + result.clear(); + result.extend(computed); + Ok(()) } /// C# member: `M:LibreMetaverse.Rendering.AvatarBoneMath.ComputeAttachmentBoneWorldMatrices(LibreMetaverse.Rendering.LindenAvatarDefinition,System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Rendering.BoneTransform},System.Collections.Generic.IReadOnlyDictionary{System.String,System.Numerics.Quaternion})`. pub fn compute_attachment_bone_world_matrices_with_linden_avatar_definition_dictionary_i_read_only_dictionary( @@ -157555,8 +157403,10 @@ pub mod rendering { std::collections::HashMap, crate::Error, > { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Rendering.AvatarBoneMath.ComputeAttachmentBoneWorldMatrices(LibreMetaverse.Rendering.LindenAvatarDefinition,System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Rendering.BoneTransform},System.Collections.Generic.IReadOnlyDictionary{System.String,System.Numerics.Quaternion})", + crate::avatar_rig::AvatarBoneMath::native_compute_attachment_bone_world_matrices( + &avatar_def, + &bone_transforms, + &rot_deltas, ) } /// C# member: `M:LibreMetaverse.Rendering.AvatarBoneMath.ComputeAttachmentBoneWorldMatrices(LibreMetaverse.Rendering.LindenAvatarDefinition,System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Rendering.BoneTransform},System.Collections.Generic.IReadOnlyDictionary{System.String,System.Numerics.Quaternion},System.Collections.Generic.Dictionary{System.String,System.Numerics.Matrix4x4})`. @@ -157569,9 +157419,16 @@ pub mod rendering { rot_deltas: std::collections::HashMap, result: std::collections::HashMap, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Rendering.AvatarBoneMath.ComputeAttachmentBoneWorldMatrices(LibreMetaverse.Rendering.LindenAvatarDefinition,System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Rendering.BoneTransform},System.Collections.Generic.IReadOnlyDictionary{System.String,System.Numerics.Quaternion},System.Collections.Generic.Dictionary{System.String,System.Numerics.Matrix4x4})", - ) + let computed = + crate::avatar_rig::AvatarBoneMath::native_compute_attachment_bone_world_matrices( + &avatar_def, + &bone_transforms, + &rot_deltas, + )?; + let mut result = result; + result.clear(); + result.extend(computed); + Ok(()) } /// C# member: `M:LibreMetaverse.Rendering.AvatarBoneMath.ComputeGroundAdjustment(System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Rendering.BoneTransform})`. pub fn compute_ground_adjustment( @@ -157579,46 +157436,38 @@ pub mod rendering { std::collections::HashMap, >, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Rendering.AvatarBoneMath.ComputeGroundAdjustment(System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Rendering.BoneTransform})", + Ok( + crate::avatar_rig::AvatarBoneMath::native_compute_ground_adjustment( + bone_transforms.as_ref(), + ), ) } /// C# member: `M:LibreMetaverse.Rendering.AvatarBoneMath.StripScale(System.Numerics.Matrix4x4)`. pub fn strip_scale( m: libremetaverse_types::compat::Matrix4x4, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Rendering.AvatarBoneMath.StripScale(System.Numerics.Matrix4x4)", - ) + Ok(crate::avatar_rig::AvatarBoneMath::native_strip_scale(m)) } } /// C# type: `T:LibreMetaverse.Rendering.AvatarMeshDefinition`. - pub struct AvatarMeshDefinition; + pub use crate::avatar_rig::AvatarMeshDefinition; impl AvatarMeshDefinition { /// C# member: `P:LibreMetaverse.Rendering.AvatarMeshDefinition.FileName`. pub fn file_name(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Rendering.AvatarMeshDefinition.FileName" - ) + self.native_file_name() } /// C# member: `P:LibreMetaverse.Rendering.AvatarMeshDefinition.LodLevel`. pub fn lod_level(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Rendering.AvatarMeshDefinition.LodLevel" - ) + self.native_lod_level() } /// C# member: `P:LibreMetaverse.Rendering.AvatarMeshDefinition.MinPixelWidth`. pub fn min_pixel_width(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Rendering.AvatarMeshDefinition.MinPixelWidth" - ) + self.native_min_pixel_width() } /// C# member: `P:LibreMetaverse.Rendering.AvatarMeshDefinition.Type`. pub fn type_(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Rendering.AvatarMeshDefinition.Type" - ) + self.native_type() } } @@ -157980,7 +157829,7 @@ pub mod rendering { } /// C# type: `T:LibreMetaverse.Rendering.LindenAvatarDefinition`. - pub struct LindenAvatarDefinition; + pub use crate::avatar_rig::LindenAvatarDefinition; impl LindenAvatarDefinition { /// C# member: `M:LibreMetaverse.Rendering.LindenAvatarDefinition.ComputeBoneTransforms(System.Collections.Generic.IReadOnlyDictionary{System.Int32,System.Single})`. pub fn compute_bone_transforms( @@ -157990,9 +157839,7 @@ pub mod rendering { std::collections::HashMap, crate::Error, > { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Rendering.LindenAvatarDefinition.ComputeBoneTransforms(System.Collections.Generic.IReadOnlyDictionary{System.Int32,System.Single})", - ) + self.native_compute_bone_transforms(¶m_values) } /// C# member: `M:LibreMetaverse.Rendering.LindenAvatarDefinition.GetAttachmentPoint(System.Int32)`. pub fn get_attachment_point_with_int32( @@ -158000,9 +157847,7 @@ pub mod rendering { id: i32, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Rendering.LindenAvatarDefinition.GetAttachmentPoint(System.Int32)", - ) + Ok(self.native_get_attachment_point_by_id(id)) } /// C# member: `M:LibreMetaverse.Rendering.LindenAvatarDefinition.GetAttachmentPoint(System.String)`. pub fn get_attachment_point_with_string( @@ -158010,36 +157855,29 @@ pub mod rendering { name: String, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Rendering.LindenAvatarDefinition.GetAttachmentPoint(System.String)", - ) + Ok(self.native_get_attachment_point_by_name(&name)) } /// C# member: `M:LibreMetaverse.Rendering.LindenAvatarDefinition.Load(System.String,System.String)`. pub fn load( lad_file_name: Option, skeleton_file_name: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Rendering.LindenAvatarDefinition.Load(System.String,System.String)", + crate::avatar_rig::LindenAvatarDefinition::native_load( + lad_file_name, + skeleton_file_name, ) } /// C# member: `P:LibreMetaverse.Rendering.LindenAvatarDefinition.AttachmentPoints`. pub fn attachment_points(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Rendering.LindenAvatarDefinition.AttachmentPoints" - ) + self.native_attachment_points() } /// C# member: `P:LibreMetaverse.Rendering.LindenAvatarDefinition.MeshDefinitions`. pub fn mesh_definitions(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Rendering.LindenAvatarDefinition.MeshDefinitions" - ) + self.native_mesh_definitions() } /// C# member: `P:LibreMetaverse.Rendering.LindenAvatarDefinition.Skeleton`. pub fn skeleton(&self) -> libremetaverse::rendering::LindenSkeleton { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Rendering.LindenAvatarDefinition.Skeleton" - ) + self.native_skeleton() } } @@ -158477,24 +158315,17 @@ pub mod rendering { } /// C# type: `T:LibreMetaverse.Rendering.MeshSkinData`. - pub struct MeshSkinData { - /// C# member: `F:LibreMetaverse.Rendering.MeshSkinData.AltInverseBindMatrices`. - pub alt_inverse_bind_matrices: Vec, - /// C# member: `F:LibreMetaverse.Rendering.MeshSkinData.BindShapeMatrix`. - pub bind_shape_matrix: Vec, - /// C# member: `F:LibreMetaverse.Rendering.MeshSkinData.InverseBindMatrices`. - pub inverse_bind_matrices: Vec, - /// C# member: `F:LibreMetaverse.Rendering.MeshSkinData.JointNames`. - pub joint_names: Vec, - /// C# member: `F:LibreMetaverse.Rendering.MeshSkinData.LockScaleIfJointPosition`. - pub lock_scale_if_joint_position: bool, - /// C# member: `F:LibreMetaverse.Rendering.MeshSkinData.PelvisOffset`. - pub pelvis_offset: f32, - } + pub use crate::animesh_skinning::MeshSkinData; + /// C# member: `F:LibreMetaverse.Rendering.MeshSkinData.AltInverseBindMatrices`. + /// C# member: `F:LibreMetaverse.Rendering.MeshSkinData.BindShapeMatrix`. + /// C# member: `F:LibreMetaverse.Rendering.MeshSkinData.InverseBindMatrices`. + /// C# member: `F:LibreMetaverse.Rendering.MeshSkinData.JointNames`. + /// C# member: `F:LibreMetaverse.Rendering.MeshSkinData.LockScaleIfJointPosition`. + /// C# member: `F:LibreMetaverse.Rendering.MeshSkinData.PelvisOffset`. impl MeshSkinData { /// C# member: `M:LibreMetaverse.Rendering.MeshSkinData.#ctor`. pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Rendering.MeshSkinData.#ctor") + crate::animesh_skinning::MeshSkinData::native_new() } } @@ -158730,31 +158561,27 @@ pub mod rendering { } /// C# type: `T:LibreMetaverse.Rendering.RiggedSkinMath`. - pub struct RiggedSkinMath; + pub use crate::avatar_rig::RiggedSkinMath; impl RiggedSkinMath { /// C# member: `M:LibreMetaverse.Rendering.RiggedSkinMath.BuildInvBindMatrices(LibreMetaverse.Rendering.MeshSkinData)`. pub fn build_inv_bind_matrices( skin: libremetaverse::rendering::MeshSkinData, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Rendering.RiggedSkinMath.BuildInvBindMatrices(LibreMetaverse.Rendering.MeshSkinData)", - ) + crate::avatar_rig::RiggedSkinMath::native_build_inv_bind_matrices(&skin) } /// C# member: `M:LibreMetaverse.Rendering.RiggedSkinMath.ExtractJointPositionOverrides(LibreMetaverse.Rendering.MeshSkinData)`. pub fn extract_joint_position_overrides( skin: libremetaverse::rendering::MeshSkinData, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Rendering.RiggedSkinMath.ExtractJointPositionOverrides(LibreMetaverse.Rendering.MeshSkinData)", - ) + crate::avatar_rig::RiggedSkinMath::native_extract_joint_position_overrides(&skin) } /// C# member: `M:LibreMetaverse.Rendering.RiggedSkinMath.FloatsToMatrix(System.Single[])`. pub fn floats_to_matrix( f: Vec, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Rendering.RiggedSkinMath.FloatsToMatrix(System.Single[])", - ) + Ok(crate::avatar_rig::RiggedSkinMath::native_floats_to_matrix( + &f, + )) } /// C# member: `M:LibreMetaverse.Rendering.RiggedSkinMath.NormalizeSkinWeights(System.Int32,System.Int32@,System.Single@,System.Int32@,System.Single@,System.Int32@,System.Single@,System.Int32@,System.Single@)`. pub fn normalize_skin_weights( @@ -158768,9 +158595,12 @@ pub mod rendering { j3: &mut i32, w3: &mut f32, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Rendering.RiggedSkinMath.NormalizeSkinWeights(System.Int32,System.Int32@,System.Single@,System.Int32@,System.Single@,System.Int32@,System.Single@,System.Int32@,System.Single@)", - ) + crate::avatar_rig::RiggedSkinMath::native_normalize_skin_weights( + joint_count, + [j0, j1, j2, j3], + [w0, w1, w2, w3], + ); + Ok(()) } } diff --git a/crates/libremetaverse/src/lib.rs b/crates/libremetaverse/src/lib.rs index b6aa86a..7ed5508 100644 --- a/crates/libremetaverse/src/lib.rs +++ b/crates/libremetaverse/src/lib.rs @@ -7,12 +7,18 @@ mod attention_catalog; mod agent_manager; mod agent_messages; mod agent_movement; +mod animation; +#[path = "animesh.rs"] +mod animesh_runtime; +mod animesh_skinning; mod appearance_baker; mod appearance_manager; mod asset_cache; mod asset_manager; mod asset_material; mod asset_models; +mod avatar_manager; +mod avatar_rig; mod baking_texture_provider; mod bit_pack; mod caps; @@ -85,45 +91,41 @@ impl assets::OarFile { #[allow(dead_code)] impl animesh::AnimationTrack { - fn new(_animation_id: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Animesh.AnimationTrack.#ctor(LibreMetaverse.UUID)", - ) + fn new(animation_id: libremetaverse_types::UUID) -> Result { + Self::native_new(animation_id) } fn decode_rotation( - _value: libremetaverse_types::Vector3, + value: libremetaverse_types::Vector3, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Animesh.AnimationTrack.DecodeRotation(LibreMetaverse.Vector3)", - ) + Self::native_decode_rotation(value) } } -#[allow(clippy::unused_self, dead_code)] +#[allow( + clippy::needless_pass_by_value, + clippy::unnecessary_wraps, + clippy::unused_self, + dead_code +)] impl animesh::AnimeshPlayer { - fn new(_object_id: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Animesh.AnimeshPlayer.#ctor(LibreMetaverse.UUID)", - ) + fn new(object_id: libremetaverse_types::UUID) -> Result { + Self::native_new(object_id) } fn get_or_add_track( &mut self, - _animation_id: libremetaverse_types::UUID, + animation_id: libremetaverse_types::UUID, ) -> Result, Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Animesh.AnimeshPlayer.GetOrAddTrack(LibreMetaverse.UUID)", - ) + self.native_get_or_add_track(animation_id) } fn retain_only( &mut self, - _animation_ids: std::collections::HashSet, + animation_ids: std::collections::HashSet, ) -> Result<(), Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Animesh.AnimeshPlayer.RetainOnly(System.Collections.Generic.ISet{LibreMetaverse.UUID})", - ) + self.native_retain_only(&animation_ids); + Ok(()) } } diff --git a/crates/libremetaverse/src/skeleton.rs b/crates/libremetaverse/src/skeleton.rs index 6a4d00e..7345f62 100644 --- a/crates/libremetaverse/src/skeleton.rs +++ b/crates/libremetaverse/src/skeleton.rs @@ -9,6 +9,11 @@ use roxmltree::{Document, Node}; use std::collections::{HashMap, HashSet}; use std::sync::Arc; +const MAX_SKELETON_BYTES: u64 = 8 * 1024 * 1024; +const MAX_SKELETON_BONES: usize = 512; +const MAX_COLLISION_VOLUMES: usize = 512; +const MAX_SKELETON_DEPTH: usize = 128; + #[derive(Clone, Debug, Default, PartialEq)] pub struct JointBase { name: String, @@ -310,7 +315,17 @@ impl LindenSkeleton { let Some(file_name) = file_name else { return Self::get_default(); }; + if std::fs::metadata(&file_name) + .map_err(|_| crate::Error::Argument)? + .len() + > MAX_SKELETON_BYTES + { + return Err(crate::Error::Argument); + } let text = std::fs::read_to_string(file_name).map_err(|_| crate::Error::Argument)?; + if text.len() as u64 > MAX_SKELETON_BYTES { + return Err(crate::Error::Argument); + } parse_skeleton_xml(&text) } @@ -469,10 +484,20 @@ fn parse_base(node: Node<'_, '_>) -> Result { )) } -fn parse_joint(node: Node<'_, '_>, names: &mut HashSet) -> Result { +fn parse_joint( + node: Node<'_, '_>, + names: &mut HashSet, + depth: usize, + bone_count: &mut usize, + collision_volume_count: &mut usize, +) -> Result { if !node.has_tag_name("bone") { return Err(parse_error(node_position(node))); } + if depth > MAX_SKELETON_DEPTH || *bone_count >= MAX_SKELETON_BONES { + return Err(parse_error(node_position(node))); + } + *bone_count += 1; let base = parse_base(node)?; if !names.insert(base.name.clone()) { return Err(parse_error(node_position(node))); @@ -487,12 +512,22 @@ fn parse_joint(node: Node<'_, '_>, names: &mut HashSet) -> Result= MAX_COLLISION_VOLUMES { + return Err(parse_error(node_position(child))); + } if child.children().any(|node| node.is_element()) { return Err(parse_error(node_position(child))); } collision_volumes.push(CollisionVolume::from_base(parse_base(child)?)); + *collision_volume_count += 1; } else if child.has_tag_name("bone") { - bones.push(parse_joint(child, names)?); + bones.push(parse_joint( + child, + names, + depth + 1, + bone_count, + collision_volume_count, + )?); } else { return Err(parse_error(node_position(child))); } @@ -508,6 +543,9 @@ fn parse_joint(node: Node<'_, '_>, names: &mut HashSet) -> Result Result { + if text.len() as u64 > MAX_SKELETON_BYTES { + return Err(parse_error(0)); + } let document = Document::parse(text).map_err(|_| parse_error(0))?; let root = document.root_element(); if !root.has_tag_name("linden_skeleton") { @@ -528,12 +566,23 @@ pub(crate) fn parse_skeleton_xml(text: &str) -> Result() .map_err(|_| parse_error(node_position(root)))?; + if expected_bones > MAX_SKELETON_BONES || expected_collision_volumes > MAX_COLLISION_VOLUMES { + return Err(parse_error(node_position(root))); + } let root_bones = root.children().filter(Node::is_element).collect::>(); if root_bones.len() != 1 || !root_bones[0].has_tag_name("bone") { return Err(parse_error(node_position(root))); } let mut names = HashSet::new(); - let bone = parse_joint(root_bones[0], &mut names)?; + let mut bone_count = 0; + let mut collision_volume_count = 0; + let bone = parse_joint( + root_bones[0], + &mut names, + 0, + &mut bone_count, + &mut collision_volume_count, + )?; let (actual_bones, actual_collision_volumes) = skeleton_counts(&bone); if actual_bones != expected_bones || actual_collision_volumes != expected_collision_volumes { return Err(parse_error(node_position(root))); diff --git a/tools/generate_api_shims.py b/tools/generate_api_shims.py index 3a61e48..2b6ca0d 100644 --- a/tools/generate_api_shims.py +++ b/tools/generate_api_shims.py @@ -291,7 +291,15 @@ NATIVE_TYPES = { # hand-written, while its fixed public methods remain generator-audited. NATIVE_DECLARATIONS = { "T:LibreMetaverse.AgentManager": "crate::agent_manager::AgentManager", + "T:LibreMetaverse.Animation": "crate::avatar_manager::Animation", + "T:LibreMetaverse.Animesh.AnimationTrack": "crate::animesh_runtime::AnimationTrack", + "T:LibreMetaverse.Animesh.AnimeshManager": "crate::animesh_runtime::AnimeshManager", + "T:LibreMetaverse.Animesh.AnimeshPlayer": "crate::animesh_runtime::AnimeshPlayer", + "T:LibreMetaverse.Animesh.JointPose": "crate::animesh_runtime::JointPose", + "T:LibreMetaverse.AnimeshSkinning": "crate::animesh_skinning::AnimeshSkinning", "T:LibreMetaverse.AppearanceManager": "crate::appearance_manager::AppearanceManager", + "T:LibreMetaverse.AvatarManager": "crate::avatar_manager::AvatarManager", + "T:LibreMetaverse.AvatarAnimationEventArgs": "crate::avatar_manager::AvatarAnimationEventArgs", "T:LibreMetaverse.AppearanceSetEventArgs": "crate::appearance_manager::AppearanceSetEventArgs", "T:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy": "crate::current_outfit::CompositeCurrentOutfitPolicy", "T:LibreMetaverse.Appearance.CurrentOutfitFolder": "crate::current_outfit::CurrentOutfitFolder", @@ -301,10 +309,20 @@ NATIVE_DECLARATIONS = { "T:LibreMetaverse.GridClientBakingTextureProvider": "crate::baking_texture_provider::GridClientBakingTextureProvider", "T:LibreMetaverse.Imaging.Baker": "crate::appearance_baker::Baker", "T:LibreMetaverse.GridClient": "crate::client_core::GridClient", + "T:LibreMetaverse.BinBVHAnimationReader": "crate::animation::BinBVHAnimationReader", + "T:LibreMetaverse.binBVHJoint": "crate::animation::BinBVHJoint", + "T:LibreMetaverse.binBVHJointKey": "crate::animation::BinBVHJointKey", "T:LibreMetaverse.InventoryManager": "crate::inventory_manager::InventoryManager", "T:LibreMetaverse.InventoryAISClient": "crate::inventory_ais::InventoryAISClient", "T:LibreMetaverse.NetworkManager": "crate::network_manager::NetworkManager", "T:LibreMetaverse.ObjectManager": "crate::object_material::ObjectManager", + "T:LibreMetaverse.Rendering.MeshSkinData": "crate::animesh_skinning::MeshSkinData", + "T:LibreMetaverse.Rendering.AttachmentRiggedSkin": "crate::avatar_rig::AttachmentRiggedSkin", + "T:LibreMetaverse.Rendering.AvatarAttachmentPoint": "crate::avatar_rig::AvatarAttachmentPoint", + "T:LibreMetaverse.Rendering.AvatarBoneMath": "crate::avatar_rig::AvatarBoneMath", + "T:LibreMetaverse.Rendering.AvatarMeshDefinition": "crate::avatar_rig::AvatarMeshDefinition", + "T:LibreMetaverse.Rendering.LindenAvatarDefinition": "crate::avatar_rig::LindenAvatarDefinition", + "T:LibreMetaverse.Rendering.RiggedSkinMath": "crate::avatar_rig::RiggedSkinMath", "T:LibreMetaverse.RebakeAvatarTexturesEventArgs": "crate::appearance_manager::RebakeAvatarTexturesEventArgs", "T:LibreMetaverse.Settings": "crate::client_core::Settings", "T:LibreMetaverse.Simulator": "crate::network_manager::Simulator", @@ -312,6 +330,162 @@ NATIVE_DECLARATIONS = { } NATIVE_MEMBER_BODIES = { + "M:LibreMetaverse.AvatarAnimationEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.Animation})": + "crate::avatar_manager::AvatarAnimationEventArgs::native_new(avatar_id, anims)", + "P:LibreMetaverse.AvatarAnimationEventArgs.Animations": + "self.native_animations()", + "P:LibreMetaverse.AvatarAnimationEventArgs.AvatarID": + "self.native_avatar_id()", + "E:LibreMetaverse.AvatarManager.AvatarAnimation": "self.native_subscribe_avatar_animation(handler)", + "E:LibreMetaverse.AvatarManager.AvatarAppearance": "self.native_subscribe_avatar_appearance(handler)", + "E:LibreMetaverse.AvatarManager.AvatarClassifiedReply": "self.native_subscribe_avatar_classified_reply(handler)", + "E:LibreMetaverse.AvatarManager.AvatarGroupsReply": "self.native_subscribe_avatar_groups_reply(handler)", + "E:LibreMetaverse.AvatarManager.AvatarInterestsReply": "self.native_subscribe_avatar_interests_reply(handler)", + "E:LibreMetaverse.AvatarManager.AvatarNotesReply": "self.native_subscribe_avatar_notes_reply(handler)", + "E:LibreMetaverse.AvatarManager.AvatarPickerReply": "self.native_subscribe_avatar_picker_reply(handler)", + "E:LibreMetaverse.AvatarManager.AvatarPicksReply": "self.native_subscribe_avatar_picks_reply(handler)", + "E:LibreMetaverse.AvatarManager.AvatarPropertiesReply": "self.native_subscribe_avatar_properties_reply(handler)", + "E:LibreMetaverse.AvatarManager.ClassifiedInfoReply": "self.native_subscribe_classified_info_reply(handler)", + "E:LibreMetaverse.AvatarManager.DisplayNameUpdate": "self.native_subscribe_display_name_update(handler)", + "E:LibreMetaverse.AvatarManager.PickInfoReply": "self.native_subscribe_pick_info_reply(handler)", + "E:LibreMetaverse.AvatarManager.UUIDNameReply": "self.native_subscribe_uuid_name_reply(handler)", + "E:LibreMetaverse.AvatarManager.ViewerEffect": "self.native_subscribe_viewer_effect(handler)", + "E:LibreMetaverse.AvatarManager.ViewerEffectLookAt": "self.native_subscribe_viewer_effect_look_at(handler)", + "E:LibreMetaverse.AvatarManager.ViewerEffectPointAt": "self.native_subscribe_viewer_effect_point_at(handler)", + "M:LibreMetaverse.Animesh.AnimationTrack.Advance(System.Single)": + "self.native_advance(dt)", + "M:LibreMetaverse.Animesh.AnimationTrack.EvaluatePose(System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Animesh.JointPose})": + "self.native_evaluate_pose(pose)", + "P:LibreMetaverse.Animesh.AnimationTrack.AnimationID": + "self.native_animation_id()", + "P:LibreMetaverse.Animesh.AnimationTrack.CurrentTime": + "self.native_current_time()", + "P:LibreMetaverse.Animesh.AnimationTrack.CurrentTime#set": + "self.native_set_current_time(value)", + "P:LibreMetaverse.Animesh.AnimationTrack.Data": + "self.native_data()", + "P:LibreMetaverse.Animesh.AnimationTrack.Data#set": + "self.native_set_data(value)", + "P:LibreMetaverse.Animesh.AnimationTrack.EaseWeight": + "self.native_ease_weight()", + "P:LibreMetaverse.Animesh.AnimationTrack.IsFinished": + "self.native_is_finished()", + "P:LibreMetaverse.Animesh.AnimationTrack.IsFinished#set": + "self.native_set_is_finished(value)", + "M:LibreMetaverse.Animesh.AnimeshManager.GetPlayer(LibreMetaverse.UUID)": + "Ok(self.native_get_player(object_id))", + "M:LibreMetaverse.Animesh.AnimeshManager.RemovePlayer(LibreMetaverse.UUID)": + "self.native_remove_player(object_id); Ok(())", + "M:LibreMetaverse.Animesh.AnimeshManager.Update(System.Single)": + "self.native_update(dt)", + "P:LibreMetaverse.Animesh.AnimeshManager.AllPlayers": + "Box::new(self.native_all_players().into_iter())", + "M:LibreMetaverse.Animesh.AnimeshPlayer.EvaluatePose": + "self.native_evaluate_pose()", + "M:LibreMetaverse.Animesh.AnimeshPlayer.Update(System.Single)": + "self.native_update(dt)", + "P:LibreMetaverse.Animesh.AnimeshPlayer.ObjectID": + "self.native_object_id()", + "P:LibreMetaverse.Animesh.AnimeshPlayer.TrackCount": + "self.native_track_count()", + "M:LibreMetaverse.AnimeshSkinning.ComputeSkinningMatrices(System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Animesh.JointPose},LibreMetaverse.Rendering.LindenSkeleton,LibreMetaverse.Rendering.MeshSkinData)": + "crate::animesh_skinning::AnimeshSkinning::native_compute_skinning_matrices(pose, skeleton, skin_data)", + "M:LibreMetaverse.AnimeshSkinning.DeformVertices(LibreMetaverse.Rendering.Face,LibreMetaverse.Matrix4[],LibreMetaverse.Matrix4,System.Span{LibreMetaverse.Vector3},System.Span{LibreMetaverse.Vector3})": + "crate::animesh_skinning::AnimeshSkinning::native_deform_vertices(face, skinning_matrices, bind_shape_matrix, out_positions, out_normals)", + "M:LibreMetaverse.BinBVHAnimationReader.#ctor(System.Byte[])": + "crate::animation::BinBVHAnimationReader::native_new(animationdata)", + "M:LibreMetaverse.BinBVHAnimationReader.Equals(LibreMetaverse.BinBVHAnimationReader)": + "self.native_equals(other.as_ref())", + "M:LibreMetaverse.BinBVHAnimationReader.Equals(LibreMetaverse.binBVHJoint[],LibreMetaverse.binBVHJoint[])": + "crate::animation::joints_equal(&arr1, &arr2)", + "M:LibreMetaverse.BinBVHAnimationReader.Equals(System.Object)": + "obj.as_ref().and_then(|value| value.downcast_ref::()).is_some_and(|other| self.native_equals(Some(other)))", + "M:LibreMetaverse.BinBVHAnimationReader.GetHashCode": + "self.native_get_hash_code()", + "M:LibreMetaverse.BinBVHAnimationReader.ReadBytesUntilNull(System.Byte[],System.Int32@)": + "self.native_read_bytes_until_null(data, i)", + "M:LibreMetaverse.BinBVHAnimationReader.readJoint(System.Byte[],System.Int32@)": + "self.native_read_joint(data, i)", + "M:LibreMetaverse.BinBVHAnimationReader.readKeys(System.Byte[],System.Int32@,System.Int32,System.Single,System.Single)": + "self.native_read_keys(data, i, keycount, min, max)", + "M:LibreMetaverse.binBVHJoint.Equals(LibreMetaverse.binBVHJoint)": + "self.native_equals(&other)", + "M:LibreMetaverse.binBVHJoint.Equals(LibreMetaverse.binBVHJointKey,LibreMetaverse.binBVHJointKey)": + "crate::animation::key_equal(&arr1, &arr2)", + "M:LibreMetaverse.binBVHJoint.Equals(LibreMetaverse.binBVHJointKey[],LibreMetaverse.binBVHJointKey[])": + "crate::animation::keys_equal(&arr1, &arr2)", + "M:LibreMetaverse.binBVHJoint.Equals(System.Object)": + "obj.as_ref().and_then(|value| value.downcast_ref::()).is_some_and(|other| self.native_equals(other))", + "M:LibreMetaverse.binBVHJoint.GetHashCode": + "self.native_get_hash_code()", + "M:LibreMetaverse.binBVHJoint.op_Equality(LibreMetaverse.binBVHJoint,LibreMetaverse.binBVHJoint)": + "left.native_equals(&right)", + "M:LibreMetaverse.binBVHJoint.op_Inequality(LibreMetaverse.binBVHJoint,LibreMetaverse.binBVHJoint)": + "!left.native_equals(&right)", + "M:LibreMetaverse.Rendering.MeshSkinData.#ctor": + "crate::animesh_skinning::MeshSkinData::native_new()", + "M:LibreMetaverse.Rendering.AttachmentRiggedSkin.#ctor": + "crate::avatar_rig::AttachmentRiggedSkin::native_new()", + "P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Group": + "self.native_group()", + "P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Id": + "self.native_id()", + "P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Joint": + "self.native_joint()", + "P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Location": + "self.native_location()", + "P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Name": + "self.native_name()", + "P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Position": + "self.native_position()", + "P:LibreMetaverse.Rendering.AvatarAttachmentPoint.Rotation": + "self.native_rotation()", + "P:LibreMetaverse.Rendering.AvatarAttachmentPoint.VisibleInFirstPerson": + "self.native_visible_in_first_person()", + "M:LibreMetaverse.Rendering.AvatarBoneMath.BuildBoneWorldMatrices(LibreMetaverse.Rendering.LindenSkeleton,System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Rendering.BoneTransform})": + "crate::avatar_rig::AvatarBoneMath::native_build_bone_world_matrices(skeleton, bone_transforms)", + "M:LibreMetaverse.Rendering.AvatarBoneMath.ComputeAnimatedBoneWorldMatrices(LibreMetaverse.Rendering.LindenAvatarDefinition,System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Rendering.BoneTransform},System.Collections.Generic.IReadOnlyDictionary{System.String,System.Numerics.Quaternion})": + "crate::avatar_rig::AvatarBoneMath::native_compute_animated_bone_world_matrices(&avatar_def, &bone_transforms, &rot_deltas)", + "M:LibreMetaverse.Rendering.AvatarBoneMath.ComputeAnimatedBoneWorldMatrices(LibreMetaverse.Rendering.LindenAvatarDefinition,System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Rendering.BoneTransform},System.Collections.Generic.IReadOnlyDictionary{System.String,System.Numerics.Quaternion},System.Collections.Generic.Dictionary{System.String,System.Numerics.Matrix4x4})": + "let computed = crate::avatar_rig::AvatarBoneMath::native_compute_animated_bone_world_matrices(&avatar_def, &bone_transforms, &rot_deltas)?; let mut result = result; result.clear(); result.extend(computed); Ok(())", + "M:LibreMetaverse.Rendering.AvatarBoneMath.ComputeAttachmentBoneWorldMatrices(LibreMetaverse.Rendering.LindenAvatarDefinition,System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Rendering.BoneTransform},System.Collections.Generic.IReadOnlyDictionary{System.String,System.Numerics.Quaternion})": + "crate::avatar_rig::AvatarBoneMath::native_compute_attachment_bone_world_matrices(&avatar_def, &bone_transforms, &rot_deltas)", + "M:LibreMetaverse.Rendering.AvatarBoneMath.ComputeAttachmentBoneWorldMatrices(LibreMetaverse.Rendering.LindenAvatarDefinition,System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Rendering.BoneTransform},System.Collections.Generic.IReadOnlyDictionary{System.String,System.Numerics.Quaternion},System.Collections.Generic.Dictionary{System.String,System.Numerics.Matrix4x4})": + "let computed = crate::avatar_rig::AvatarBoneMath::native_compute_attachment_bone_world_matrices(&avatar_def, &bone_transforms, &rot_deltas)?; let mut result = result; result.clear(); result.extend(computed); Ok(())", + "M:LibreMetaverse.Rendering.AvatarBoneMath.ComputeGroundAdjustment(System.Collections.Generic.Dictionary{System.String,LibreMetaverse.Rendering.BoneTransform})": + "Ok(crate::avatar_rig::AvatarBoneMath::native_compute_ground_adjustment(bone_transforms.as_ref()))", + "M:LibreMetaverse.Rendering.AvatarBoneMath.StripScale(System.Numerics.Matrix4x4)": + "Ok(crate::avatar_rig::AvatarBoneMath::native_strip_scale(m))", + "P:LibreMetaverse.Rendering.AvatarMeshDefinition.FileName": + "self.native_file_name()", + "P:LibreMetaverse.Rendering.AvatarMeshDefinition.LodLevel": + "self.native_lod_level()", + "P:LibreMetaverse.Rendering.AvatarMeshDefinition.MinPixelWidth": + "self.native_min_pixel_width()", + "P:LibreMetaverse.Rendering.AvatarMeshDefinition.Type": + "self.native_type()", + "M:LibreMetaverse.Rendering.LindenAvatarDefinition.ComputeBoneTransforms(System.Collections.Generic.IReadOnlyDictionary{System.Int32,System.Single})": + "self.native_compute_bone_transforms(¶m_values)", + "M:LibreMetaverse.Rendering.LindenAvatarDefinition.GetAttachmentPoint(System.Int32)": + "Ok(self.native_get_attachment_point_by_id(id))", + "M:LibreMetaverse.Rendering.LindenAvatarDefinition.GetAttachmentPoint(System.String)": + "Ok(self.native_get_attachment_point_by_name(&name))", + "M:LibreMetaverse.Rendering.LindenAvatarDefinition.Load(System.String,System.String)": + "crate::avatar_rig::LindenAvatarDefinition::native_load(lad_file_name, skeleton_file_name)", + "P:LibreMetaverse.Rendering.LindenAvatarDefinition.AttachmentPoints": + "self.native_attachment_points()", + "P:LibreMetaverse.Rendering.LindenAvatarDefinition.MeshDefinitions": + "self.native_mesh_definitions()", + "P:LibreMetaverse.Rendering.LindenAvatarDefinition.Skeleton": + "self.native_skeleton()", + "M:LibreMetaverse.Rendering.RiggedSkinMath.BuildInvBindMatrices(LibreMetaverse.Rendering.MeshSkinData)": + "crate::avatar_rig::RiggedSkinMath::native_build_inv_bind_matrices(&skin)", + "M:LibreMetaverse.Rendering.RiggedSkinMath.ExtractJointPositionOverrides(LibreMetaverse.Rendering.MeshSkinData)": + "crate::avatar_rig::RiggedSkinMath::native_extract_joint_position_overrides(&skin)", + "M:LibreMetaverse.Rendering.RiggedSkinMath.FloatsToMatrix(System.Single[])": + "Ok(crate::avatar_rig::RiggedSkinMath::native_floats_to_matrix(&f))", + "M:LibreMetaverse.Rendering.RiggedSkinMath.NormalizeSkinWeights(System.Int32,System.Int32@,System.Single@,System.Int32@,System.Single@,System.Int32@,System.Single@,System.Int32@,System.Single@)": + "crate::avatar_rig::RiggedSkinMath::native_normalize_skin_weights(joint_count, [j0, j1, j2, j3], [w0, w1, w2, w3]); Ok(())", "M:LibreMetaverse.NameValue.#ctor(System.String,LibreMetaverse.NameValue.ValueType,LibreMetaverse.NameValue.ClassType,LibreMetaverse.NameValue.SendtoType,System.Object)": "Ok(crate::NameValue { class_: class_type, name, sendto: sendto_type, type_: value_type, value: Some(value) })", "M:LibreMetaverse.Primitive.#ctor": @@ -592,6 +766,56 @@ NATIVE_MEMBER_BODIES = { "self.native_appearance().unwrap_or_else(|_| panic!(\"failed to construct AppearanceManager\"))", "P:LibreMetaverse.GridClient.Appearance#set": "self.native_set_appearance(value)", + "P:LibreMetaverse.GridClient.Animesh": + "self.native_animesh().unwrap_or_else(|_| panic!(\"failed to construct AnimeshManager\"))", + "P:LibreMetaverse.GridClient.Animesh#set": + "self.native_set_animesh(value)", + "P:LibreMetaverse.GridClient.Avatars": + "self.native_avatars().unwrap_or_else(|_| panic!(\"failed to construct AvatarManager\"))", + "P:LibreMetaverse.GridClient.Avatars#set": + "self.native_set_avatars(value)", + "M:LibreMetaverse.AvatarManager.#ctor(LibreMetaverse.GridClient)": + "crate::avatar_manager::AvatarManager::native_new(client)", + "M:LibreMetaverse.AvatarManager.AgentProfileAvailable": + "self.native_capability_available(\"AgentProfile\")", + "M:LibreMetaverse.AvatarManager.DisplayNamesAvailable": + "self.native_capability_available(\"GetDisplayNames\")", + "M:LibreMetaverse.AvatarManager.Dispose": + "self.native_dispose()", + "M:LibreMetaverse.AvatarManager.RequestOwnAvatarTextures": + "self.native_request_own_avatar_textures()", + "M:LibreMetaverse.AvatarManager.RequestTrackAgent(LibreMetaverse.UUID)": + "self.native_request_track_agent(prey_id)", + "M:LibreMetaverse.AvatarManager.RequestAvatarName(LibreMetaverse.UUID)": + "self.native_request_avatar_names(&[id])", + "M:LibreMetaverse.AvatarManager.RequestAvatarNames(System.Collections.Generic.List{LibreMetaverse.UUID})": + "self.native_request_avatar_names(&ids)", + "M:LibreMetaverse.AvatarManager.RequestAvatarProperties(LibreMetaverse.UUID)": + "self.native_request_avatar_properties(avatarid)", + "M:LibreMetaverse.AvatarManager.RequestAvatarNameSearch(System.String,LibreMetaverse.UUID)": + "self.native_request_avatar_name_search(name, query_id)", + "M:LibreMetaverse.AvatarManager.RequestAvatarNotes(LibreMetaverse.UUID)": + "self.native_request_generic(\"avatarnotesrequest\", &[avatarid])", + "M:LibreMetaverse.AvatarManager.RequestAvatarPicks(LibreMetaverse.UUID)": + "self.native_request_generic(\"avatarpicksrequest\", &[avatarid])", + "M:LibreMetaverse.AvatarManager.RequestAvatarClassified(LibreMetaverse.UUID)": + "self.native_request_generic(\"avatarclassifiedsrequest\", &[avatarid])", + "M:LibreMetaverse.AvatarManager.RequestPickInfo(LibreMetaverse.UUID,LibreMetaverse.UUID)": + "self.native_request_generic(\"pickinforequest\", &[avatarid, pickid])", + "M:LibreMetaverse.AvatarManager.RequestClassifiedInfo(LibreMetaverse.UUID)": + "self.native_request_classified_info(classifiedid)", + "M:LibreMetaverse.AvatarManager.GetDisplayNamesAsync(System.Collections.Generic.List{LibreMetaverse.UUID},System.Threading.CancellationToken)": + "self.native_get_display_names(ids, cancellation_token).await", + "M:LibreMetaverse.AvatarManager.RequestAgentProfileAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_request_agent_profile(avatarid, cancellation_token).await", + "M:LibreMetaverse.Messages.Linden.AgentProfileMessage.#ctor": + "Ok(crate::avatar_manager::agent_profile_defaults())", + "M:LibreMetaverse.Messages.Linden.AgentProfileMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)": + "crate::avatar_manager::deserialize_agent_profile(self, &map)", + "M:LibreMetaverse.Messages.Linden.AgentProfileMessage.Serialize": + "crate::avatar_manager::serialize_agent_profile(self)", + "M:LibreMetaverse.Messages.Linden.AgentProfileMessage.GroupData.#ctor": + "Ok(crate::avatar_manager::agent_profile_group_defaults())", "M:LibreMetaverse.Avatar.AvatarProperties.FromOSD(LibreMetaverse.StructuredData.OSD)": "crate::agent_messages::avatar_properties_from_osd(o)", "M:LibreMetaverse.Avatar.AvatarProperties.GetOSD": diff --git a/tools/generate_rust_mapping.py b/tools/generate_rust_mapping.py index 53ae8aa..92f6c06 100644 --- a/tools/generate_rust_mapping.py +++ b/tools/generate_rust_mapping.py @@ -985,12 +985,17 @@ def validate_generated_shims() -> None: "crate::agent_manager::", "crate::agent_messages::", "crate::agent_movement::", + "crate::animation::", + "crate::animesh_runtime::", + "crate::animesh_skinning::", "crate::asset_cache::", "crate::asset_manager::", "crate::asset_material::", "crate::asset_models::", "crate::appearance_baker::", "crate::appearance_manager::", + "crate::avatar_manager::", + "crate::avatar_rig::", "crate::baking_texture_provider::", "crate::byte_order::", "crate::attention_catalog::", @@ -1021,6 +1026,7 @@ def validate_generated_shims() -> None: "crate::xml_codec::", "stored_visual_params", "self.native_", + "left.native_equals(", ) ) if uses_standardized_or_native_path: