diff --git a/Cargo.lock b/Cargo.lock index b7ab250..4d37b58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -852,6 +852,7 @@ dependencies = [ name = "libremetaverse" version = "0.0.1" dependencies = [ + "base64", "bcdec_rs", "flate2", "futures-channel", @@ -864,6 +865,7 @@ dependencies = [ "reqwest", "roxmltree", "serde_json", + "tar", "tokio", "vorbis_rs", ] diff --git a/README.md b/README.md index 9c3d149..cde26e5 100644 --- a/README.md +++ b/README.md @@ -365,6 +365,11 @@ deduplicated HTTP subscribers, and atomic size-pruned disk caching. The ownershi correlation, and corruption contracts are documented in [`docs/assets.md`](docs/assets.md). +Deterministic OAR/tar IO, bounded GLTF/GLB and Collada conversion, material +resolution, and opt-in mesh pricing/upload now use the same native asset and +capability layers. Their archive safety, offline determinism, and live-upload +boundaries are also documented in [`docs/assets.md`](docs/assets.md). + The client-owned `InventoryManager` now implements the packet-level create, update, move, copy, remove, fetch, search, give, rez/derez, script-state, and task-inventory workflows. Callback IDs and concurrent item/task waiters are diff --git a/api/SHIM-COVERAGE.md b/api/SHIM-COVERAGE.md index d80e8b8..7c24ffc 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: 369 types / 16,774 members; remaining surface is callable failure-only shims | +| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 401 types / 17,023 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/Cargo.toml b/crates/libremetaverse/Cargo.toml index c7aa540..0ceac71 100644 --- a/crates/libremetaverse/Cargo.toml +++ b/crates/libremetaverse/Cargo.toml @@ -13,6 +13,7 @@ dds-bc67 = ["dep:bcdec_rs"] jpeg2000 = ["libremetaverse-imaging/jpeg2000"] [dependencies] +base64 = "0.22.1" bcdec_rs = { version = "0.2.0", optional = true } flate2 = "1.1.2" futures-channel = "0.3.31" @@ -25,6 +26,7 @@ os_info = { version = "3.15.0", default-features = false } reqwest = { version = "0.13.4", default-features = false, features = ["rustls", "stream"] } roxmltree = "0.21.1" serde_json = "1.0.143" +tar = "0.4.46" tokio = { version = "1.47.1", features = ["macros", "net", "rt", "sync", "time"] } vorbis_rs = "0.5.5" diff --git a/crates/libremetaverse/src/asset_archive.rs b/crates/libremetaverse/src/asset_archive.rs new file mode 100644 index 0000000..486e989 --- /dev/null +++ b/crates/libremetaverse/src/asset_archive.rs @@ -0,0 +1,1261 @@ +//! Deterministic and bounded tar/OAR archive handling. + +// Public signatures mirror the fixed C# compatibility surface. Several own +// inputs intentionally and RegionSettings intentionally exposes boolean fields. +#![allow(clippy::pedantic)] + +use std::fs::{self, File}; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::path::{Component, Path, PathBuf}; +use std::sync::Mutex; + +use flate2::{Compression, GzBuilder, read::GzDecoder}; +use libremetaverse_types::compat::ReadWrite; +use libremetaverse_types::{AssetType, Error, UUID}; + +use crate::assets::TarArchiveReaderTarEntryType; + +const BLOCK: usize = 512; +const MAX_ENTRY_BYTES: usize = 64 * 1024 * 1024; +const MAX_TOTAL_BYTES: usize = 1024 * 1024 * 1024; +const MAX_ENTRIES: usize = 100_000; +const MAX_PATH_BYTES: usize = 4096; +const MAX_PATH_DEPTH: usize = 64; +const ARCHIVE_XML: &str = "\n"; +const OAR_DIRECTORIES: [&str; 5] = ["assets", "objects", "terrains", "landdata", "settings"]; + +type AsyncCallback = Box; +type TerrainCallback = Box>, i64, i64) + Send + Sync>; + +fn archive_error(context: &'static str) -> Error { + Error::Parse { + position: 0, + context, + } +} + +fn io_error(_: std::io::Error) -> Error { + Error::InvalidOperation +} + +fn validate_archive_path(path: &str) -> Result<(), Error> { + if path.is_empty() + || path.len() > MAX_PATH_BYTES + || path.contains('\0') + || path.contains('\\') + || path.starts_with('/') + { + return Err(archive_error("unsafe archive path")); + } + let mut depth = 0usize; + for component in Path::new(path).components() { + match component { + Component::Normal(_) => depth += 1, + Component::CurDir => {} + Component::ParentDir | Component::RootDir | Component::Prefix(_) => { + return Err(archive_error("archive path traversal")); + } + } + } + if depth == 0 || depth > MAX_PATH_DEPTH { + return Err(archive_error("archive path depth limit")); + } + Ok(()) +} + +fn octal(bytes: &[u8]) -> Result { + let text = std::str::from_utf8(bytes) + .map_err(|_| archive_error("tar octal UTF-8"))? + .trim_matches(['\0', ' ']); + if text.is_empty() { + return Ok(0); + } + if !text.bytes().all(|byte| matches!(byte, b'0'..=b'7')) { + return Err(archive_error("tar octal field")); + } + u64::from_str_radix(text, 8).map_err(|_| archive_error("tar octal overflow")) +} + +fn padded_octal(value: u64, width: usize) -> Result, Error> { + if width < 2 { + return Err(Error::Argument); + } + let digits = format!("{value:o}"); + if digits.len() + 1 > width { + return Err(Error::Argument); + } + let mut result = vec![b'0'; width]; + let start = width - 1 - digits.len(); + result[start..width - 1].copy_from_slice(digits.as_bytes()); + result[width - 1] = 0; + Ok(result) +} + +fn split_ustar_path(path: &str) -> Result<(&str, &str), Error> { + validate_archive_path(path)?; + if path.len() <= 100 { + return Ok(("", path)); + } + for (index, _) in path.match_indices('/').rev() { + let prefix = &path[..index]; + let name = &path[index + 1..]; + if prefix.len() <= 155 && name.len() <= 100 && !name.is_empty() { + return Ok((prefix, name)); + } + } + Err(archive_error("tar path exceeds ustar limits")) +} + +fn write_header( + writer: &mut dyn Write, + path: &str, + size: usize, + entry_type: u8, +) -> Result<(), Error> { + if size > MAX_ENTRY_BYTES { + return Err(archive_error("tar entry size limit")); + } + let (prefix, name) = split_ustar_path(path)?; + let mut header = [0u8; BLOCK]; + header[..name.len()].copy_from_slice(name.as_bytes()); + header[100..108].copy_from_slice(&padded_octal( + if entry_type == b'5' { 0o755 } else { 0o644 }, + 8, + )?); + header[108..116].copy_from_slice(&padded_octal(0, 8)?); + header[116..124].copy_from_slice(&padded_octal(0, 8)?); + header[124..136].copy_from_slice(&padded_octal(size as u64, 12)?); + header[136..148].copy_from_slice(&padded_octal(0, 12)?); + header[148..156].fill(b' '); + header[156] = entry_type; + header[257..263].copy_from_slice(b"ustar\0"); + header[263..265].copy_from_slice(b"00"); + header[345..345 + prefix.len()].copy_from_slice(prefix.as_bytes()); + let checksum = header.iter().map(|byte| u64::from(*byte)).sum(); + header[148..156].copy_from_slice(&padded_octal(checksum, 8)?); + writer.write_all(&header).map_err(io_error) +} + +fn write_entry( + writer: &mut dyn Write, + path: &str, + data: &[u8], + entry_type: u8, +) -> Result<(), Error> { + write_header(writer, path, data.len(), entry_type)?; + writer.write_all(data).map_err(io_error)?; + let padding = (BLOCK - data.len() % BLOCK) % BLOCK; + if padding != 0 { + writer.write_all(&vec![0; padding]).map_err(io_error)?; + } + Ok(()) +} + +struct ReaderState { + stream: Box, + entries: usize, + total_bytes: usize, + finished: bool, +} + +pub struct TarArchiveReader { + state: Mutex, +} + +impl std::fmt::Debug for TarArchiveReader { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter + .debug_struct("TarArchiveReader") + .finish_non_exhaustive() + } +} + +impl TarArchiveReader { + pub fn new(stream: Box) -> Result { + Ok(Self { + state: Mutex::new(ReaderState { + stream, + entries: 0, + total_bytes: 0, + finished: false, + }), + }) + } + + pub fn close(&self) -> Result<(), Error> { + self.state + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .finished = true; + Ok(()) + } + + pub fn convert_octal_bytes_to_decimal( + bytes: Vec, + start_index: i32, + count: i32, + ) -> Result { + let start = usize::try_from(start_index).map_err(|_| Error::IndexOutOfRange)?; + let count = usize::try_from(count).map_err(|_| Error::IndexOutOfRange)?; + let end = start.checked_add(count).ok_or(Error::IndexOutOfRange)?; + let value = octal(bytes.get(start..end).ok_or(Error::IndexOutOfRange)?)?; + i32::try_from(value).map_err(|_| Error::IndexOutOfRange) + } + + pub fn read_entry( + &self, + file_path: &mut String, + entry_type: &mut TarArchiveReaderTarEntryType, + ) -> Result>, Error> { + let mut state = self + .state + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if state.finished { + return Ok(None); + } + let mut header = [0u8; BLOCK]; + state.stream.read_exact(&mut header).map_err(io_error)?; + if header.iter().all(|byte| *byte == 0) { + state.finished = true; + return Ok(None); + } + let recorded = octal(&header[148..156])?; + let mut checksum_header = header; + checksum_header[148..156].fill(b' '); + let computed: u64 = checksum_header.iter().map(|byte| u64::from(*byte)).sum(); + if recorded != computed { + return Err(archive_error("tar checksum")); + } + let name = nul_string(&header[..100])?; + let prefix = nul_string(&header[345..500])?; + let path = if prefix.is_empty() { + name + } else { + format!("{prefix}/{name}") + }; + validate_archive_path(&path)?; + let size = usize::try_from(octal(&header[124..136])?) + .map_err(|_| archive_error("tar size overflow"))?; + if size > MAX_ENTRY_BYTES { + return Err(archive_error("tar entry size limit")); + } + state.entries = state + .entries + .checked_add(1) + .ok_or_else(|| archive_error("tar entry count overflow"))?; + state.total_bytes = state + .total_bytes + .checked_add(size) + .ok_or_else(|| archive_error("tar total size overflow"))?; + if state.entries > MAX_ENTRIES || state.total_bytes > MAX_TOTAL_BYTES { + return Err(archive_error("tar archive limits")); + } + let kind = match header[156] { + 0 | b'0' => TarArchiveReaderTarEntryType::TYPE_NORMAL_FILE, + b'1' => TarArchiveReaderTarEntryType::TYPE_HARD_LINK, + b'2' => TarArchiveReaderTarEntryType::TYPE_SYMBOLIC_LINK, + b'3' => TarArchiveReaderTarEntryType::TYPE_CHAR_SPECIAL, + b'4' => TarArchiveReaderTarEntryType::TYPE_BLOCK_SPECIAL, + b'5' => TarArchiveReaderTarEntryType::TYPE_DIRECTORY, + b'6' => TarArchiveReaderTarEntryType::TYPE_FIFO, + b'7' => TarArchiveReaderTarEntryType::TYPE_CONTIGUOUS_FILE, + _ => TarArchiveReaderTarEntryType::TYPE_UNKNOWN, + }; + if matches!( + kind, + TarArchiveReaderTarEntryType::TYPE_SYMBOLIC_LINK + | TarArchiveReaderTarEntryType::TYPE_HARD_LINK + ) { + return Err(archive_error("tar links are not permitted")); + } + let mut data = vec![0u8; size]; + state.stream.read_exact(&mut data).map_err(io_error)?; + let padding = (BLOCK - size % BLOCK) % BLOCK; + if padding != 0 { + state + .stream + .seek(SeekFrom::Current(padding as i64)) + .map_err(io_error)?; + } + *file_path = path; + *entry_type = kind; + Ok(Some(data)) + } +} + +fn nul_string(bytes: &[u8]) -> Result { + let end = bytes + .iter() + .position(|byte| *byte == 0) + .unwrap_or(bytes.len()); + std::str::from_utf8(&bytes[..end]) + .map(str::to_owned) + .map_err(|_| archive_error("tar path UTF-8")) +} + +struct WriterState { + stream: Box, + entries: usize, + total_bytes: usize, + closed: bool, +} + +pub struct TarArchiveWriter { + state: Mutex, +} + +#[derive(Clone)] +pub struct OarFileAssetLoadedCallback( + std::sync::Arc, +); +impl OarFileAssetLoadedCallback { + pub fn from_callback( + callback: impl Fn(crate::assets::Asset, i64, i64) + Send + Sync + 'static, + ) -> Self { + Self(std::sync::Arc::new(callback)) + } + pub fn new( + _object: libremetaverse_types::compat::Object, + _method: isize, + ) -> Result { + Err(Error::Argument) + } + pub fn invoke( + &self, + asset: crate::assets::Asset, + bytes_read: i64, + total_bytes: i64, + ) -> Result<(), Error> { + (self.0)(asset, bytes_read, total_bytes); + Ok(()) + } + pub fn begin_invoke( + &self, + asset: crate::assets::Asset, + bytes_read: i64, + total_bytes: i64, + callback: AsyncCallback, + _object: libremetaverse_types::compat::Object, + ) -> Result, Error> { + self.invoke(asset, bytes_read, total_bytes)?; + callback(&()); + Ok(Box::new(())) + } + pub fn end_invoke(&self, _result: Box) -> Result<(), Error> { + Ok(()) + } +} + +#[derive(Clone)] +pub struct OarFileTerrainLoadedCallback(std::sync::Arc, i64, i64) + Send + Sync>); +impl OarFileTerrainLoadedCallback { + pub fn from_callback(callback: impl Fn(Vec, i64, i64) + Send + Sync + 'static) -> Self { + Self(std::sync::Arc::new(callback)) + } + pub fn new( + _object: libremetaverse_types::compat::Object, + _method: isize, + ) -> Result { + Err(Error::Argument) + } + pub fn invoke( + &self, + terrain: Vec, + bytes_read: i64, + total_bytes: i64, + ) -> Result<(), Error> { + (self.0)(terrain, bytes_read, total_bytes); + Ok(()) + } + pub fn begin_invoke( + &self, + terrain: Vec, + bytes_read: i64, + total_bytes: i64, + callback: AsyncCallback, + _object: libremetaverse_types::compat::Object, + ) -> Result, Error> { + self.invoke(terrain, bytes_read, total_bytes)?; + callback(&()); + Ok(Box::new(())) + } + pub fn end_invoke(&self, _result: Box) -> Result<(), Error> { + Ok(()) + } +} + +#[derive(Clone)] +pub struct OarFileSettingsLoadedCallback( + std::sync::Arc, +); +impl OarFileSettingsLoadedCallback { + pub fn from_callback( + callback: impl Fn(String, RegionSettings) + Send + Sync + 'static, + ) -> Self { + Self(std::sync::Arc::new(callback)) + } + pub fn new( + _object: libremetaverse_types::compat::Object, + _method: isize, + ) -> Result { + Err(Error::Argument) + } + pub fn invoke(&self, region_name: String, settings: RegionSettings) -> Result<(), Error> { + (self.0)(region_name, settings); + Ok(()) + } + pub fn begin_invoke( + &self, + region_name: String, + settings: RegionSettings, + callback: AsyncCallback, + _object: libremetaverse_types::compat::Object, + ) -> Result, Error> { + self.invoke(region_name, settings)?; + callback(&()); + Ok(Box::new(())) + } + pub fn end_invoke(&self, _result: Box) -> Result<(), Error> { + Ok(()) + } +} + +#[derive(Clone)] +pub struct OarFileSceneObjectLoadedCallback( + std::sync::Arc, +); +impl OarFileSceneObjectLoadedCallback { + pub fn from_callback( + callback: impl Fn(crate::assets::AssetPrim, i64, i64) + Send + Sync + 'static, + ) -> Self { + Self(std::sync::Arc::new(callback)) + } + pub fn new( + _object: libremetaverse_types::compat::Object, + _method: isize, + ) -> Result { + Err(Error::Argument) + } + pub fn invoke( + &self, + linkset: crate::assets::AssetPrim, + bytes_read: i64, + total_bytes: i64, + ) -> Result<(), Error> { + (self.0)(linkset, bytes_read, total_bytes); + Ok(()) + } + pub fn begin_invoke( + &self, + linkset: crate::assets::AssetPrim, + bytes_read: i64, + total_bytes: i64, + callback: AsyncCallback, + _object: libremetaverse_types::compat::Object, + ) -> Result, Error> { + self.invoke(linkset, bytes_read, total_bytes)?; + callback(&()); + Ok(Box::new(())) + } + pub fn end_invoke(&self, _result: Box) -> Result<(), Error> { + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct RegionSettings { + pub agent_limit: i32, + pub allow_damage: bool, + pub allow_land_join_divide: bool, + pub allow_land_resell: bool, + pub block_fly: bool, + pub block_land_show_in_search: bool, + pub block_terraform: bool, + pub disable_collisions: bool, + pub disable_physics: bool, + pub disable_scripts: bool, + pub fixed_sun: bool, + pub maturity_rating: i32, + pub object_bonus: f32, + pub restrict_pushing: bool, + pub terrain_detail0: UUID, + pub terrain_detail1: UUID, + pub terrain_detail2: UUID, + pub terrain_detail3: UUID, + pub terrain_height_range00: f32, + pub terrain_height_range01: f32, + pub terrain_height_range10: f32, + pub terrain_height_range11: f32, + pub terrain_lower_limit: f32, + pub terrain_raise_limit: f32, + pub terrain_start_height00: f32, + pub terrain_start_height01: f32, + pub terrain_start_height10: f32, + pub terrain_start_height11: f32, + pub use_estate_sun: bool, + pub water_height: f32, +} +impl RegionSettings { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn from_stream(mut stream: Box) -> Result { + stream.seek(SeekFrom::Start(0)).map_err(io_error)?; + let mut bytes = Vec::new(); + stream + .take((MAX_ENTRY_BYTES + 1) as u64) + .read_to_end(&mut bytes) + .map_err(io_error)?; + if bytes.len() > MAX_ENTRY_BYTES { + return Err(Error::Argument); + } + let text = + std::str::from_utf8(&bytes).map_err(|_| archive_error("region settings UTF-8"))?; + let document = + roxmltree::Document::parse(text).map_err(|_| archive_error("region settings XML"))?; + if !document.root_element().has_tag_name("RegionSettings") { + return Err(archive_error("region settings root")); + } + let mut settings = Self::default(); + for node in document.descendants().filter(|node| node.is_element()) { + let text = node.text().unwrap_or_default().trim(); + match node.tag_name().name() { + "AllowDamage" => settings.allow_damage = parse_bool(text)?, + "AllowLandResell" => settings.allow_land_resell = parse_bool(text)?, + "AllowLandJoinDivide" => settings.allow_land_join_divide = parse_bool(text)?, + "BlockFly" => settings.block_fly = parse_bool(text)?, + "BlockLandShowInSearch" => settings.block_land_show_in_search = parse_bool(text)?, + "BlockTerraform" => settings.block_terraform = parse_bool(text)?, + "DisableCollisions" => settings.disable_collisions = parse_bool(text)?, + "DisablePhysics" => settings.disable_physics = parse_bool(text)?, + "DisableScripts" => settings.disable_scripts = parse_bool(text)?, + "FixedSun" => settings.fixed_sun = parse_bool(text)?, + "RestrictPushing" => settings.restrict_pushing = parse_bool(text)?, + "UseEstateSun" => settings.use_estate_sun = parse_bool(text)?, + "MaturityRating" => settings.maturity_rating = parse_i32(text)?, + "AgentLimit" => settings.agent_limit = parse_i32(text)?, + "ObjectBonus" => settings.object_bonus = parse_f32(text)?, + "WaterHeight" => settings.water_height = parse_f32(text)?, + "TerrainRaiseLimit" => settings.terrain_raise_limit = parse_f32(text)?, + "TerrainLowerLimit" => settings.terrain_lower_limit = parse_f32(text)?, + "Texture1" => settings.terrain_detail0 = UUID::new_with_string(text.to_owned())?, + "Texture2" => settings.terrain_detail1 = UUID::new_with_string(text.to_owned())?, + "Texture3" => settings.terrain_detail2 = UUID::new_with_string(text.to_owned())?, + "Texture4" => settings.terrain_detail3 = UUID::new_with_string(text.to_owned())?, + "ElevationLowSW" => settings.terrain_start_height00 = parse_f32(text)?, + "ElevationLowNW" => settings.terrain_start_height01 = parse_f32(text)?, + "ElevationLowSE" => settings.terrain_start_height10 = parse_f32(text)?, + "ElevationLowNE" => settings.terrain_start_height11 = parse_f32(text)?, + "ElevationHighSW" => settings.terrain_height_range00 = parse_f32(text)?, + "ElevationHighNW" => settings.terrain_height_range01 = parse_f32(text)?, + "ElevationHighSE" => settings.terrain_height_range10 = parse_f32(text)?, + "ElevationHighNE" => settings.terrain_height_range11 = parse_f32(text)?, + _ => {} + } + } + Ok(settings) + } + pub fn to_xml(&self, filename: String) -> Result<(), Error> { + fs::write(filename, self.xml()).map_err(io_error) + } + fn xml(&self) -> String { + format!( + "\n{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}\n", + self.allow_damage, + self.allow_land_resell, + self.allow_land_join_divide, + self.block_fly, + self.block_land_show_in_search, + self.block_terraform, + self.disable_collisions, + self.disable_physics, + self.disable_scripts, + self.maturity_rating, + self.restrict_pushing, + self.agent_limit, + self.object_bonus, + self.terrain_detail0.to_string(), + self.terrain_detail1.to_string(), + self.terrain_detail2.to_string(), + self.terrain_detail3.to_string(), + self.terrain_start_height00, + self.terrain_start_height01, + self.terrain_start_height10, + self.terrain_start_height11, + self.terrain_height_range00, + self.terrain_height_range01, + self.terrain_height_range10, + self.terrain_height_range11, + self.water_height, + self.terrain_raise_limit, + self.terrain_lower_limit, + self.use_estate_sun, + self.fixed_sun + ) + } +} +impl Default for RegionSettings { + fn default() -> Self { + Self { + agent_limit: 0, + allow_damage: false, + allow_land_join_divide: false, + allow_land_resell: false, + block_fly: false, + block_land_show_in_search: false, + block_terraform: false, + disable_collisions: false, + disable_physics: false, + disable_scripts: false, + fixed_sun: false, + maturity_rating: 0, + object_bonus: 0.0, + restrict_pushing: false, + terrain_detail0: UUID::zero(), + terrain_detail1: UUID::zero(), + terrain_detail2: UUID::zero(), + terrain_detail3: UUID::zero(), + terrain_height_range00: 0.0, + terrain_height_range01: 0.0, + terrain_height_range10: 0.0, + terrain_height_range11: 0.0, + terrain_lower_limit: 0.0, + terrain_raise_limit: 0.0, + terrain_start_height00: 0.0, + terrain_start_height01: 0.0, + terrain_start_height10: 0.0, + terrain_start_height11: 0.0, + use_estate_sun: false, + water_height: 0.0, + } + } +} +fn parse_bool(value: &str) -> Result { + match value.to_ascii_lowercase().as_str() { + "true" => Ok(true), + "false" => Ok(false), + _ => Err(archive_error("region settings boolean")), + } +} +fn parse_i32(value: &str) -> Result { + value + .parse() + .map_err(|_| archive_error("region settings integer")) +} +fn parse_f32(value: &str) -> Result { + value + .parse::() + .ok() + .filter(|value| value.is_finite()) + .ok_or_else(|| archive_error("region settings number")) +} +impl std::fmt::Debug for TarArchiveWriter { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter + .debug_struct("TarArchiveWriter") + .finish_non_exhaustive() + } +} +impl TarArchiveWriter { + pub fn new(stream: Box) -> Result { + Ok(Self { + state: Mutex::new(WriterState { + stream, + entries: 0, + total_bytes: 0, + closed: false, + }), + }) + } + pub fn convert_decimal_to_padded_octal_bytes( + value: i32, + padding: i32, + ) -> Result, Error> { + padded_octal( + u64::try_from(value).map_err(|_| Error::Argument)?, + usize::try_from(padding).map_err(|_| Error::Argument)?, + ) + } + pub fn write_dir(&self, dir_name: String) -> Result<(), Error> { + self.write(&dir_name, &[], b'5') + } + pub fn write_file_with_string_bytes( + &self, + file_path: String, + data: Vec, + ) -> Result<(), Error> { + self.write(&file_path, &data, b'0') + } + pub fn write_file_with_string_string( + &self, + file_path: String, + data: String, + ) -> Result<(), Error> { + self.write(&file_path, data.as_bytes(), b'0') + } + fn write(&self, path: &str, data: &[u8], kind: u8) -> Result<(), Error> { + let mut state = self + .state + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if state.closed { + return Err(Error::InvalidOperation); + } + state.entries = state + .entries + .checked_add(1) + .ok_or(Error::InvalidOperation)?; + state.total_bytes = state + .total_bytes + .checked_add(data.len()) + .ok_or(Error::InvalidOperation)?; + if state.entries > MAX_ENTRIES || state.total_bytes > MAX_TOTAL_BYTES { + return Err(archive_error("tar archive limits")); + } + write_entry(&mut state.stream, path, data, kind) + } + pub fn close(&self) -> Result<(), Error> { + let mut state = self + .state + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if !state.closed { + state + .stream + .write_all(&[0u8; BLOCK * 2]) + .map_err(io_error)?; + state.stream.flush().map_err(io_error)?; + state.closed = true; + } + Ok(()) + } +} + +pub(crate) fn clear_asset_folder(assets_path: String) -> Result<(), Error> { + let root = PathBuf::from(assets_path); + if !root.is_dir() { + return fs::create_dir_all(root).map_err(io_error); + } + for entry in fs::read_dir(&root).map_err(io_error)? { + let entry = entry.map_err(io_error)?; + let metadata = fs::symlink_metadata(entry.path()).map_err(io_error)?; + if metadata.file_type().is_symlink() || metadata.is_file() { + fs::remove_file(entry.path()).map_err(io_error)?; + } else if metadata.is_dir() { + fs::remove_dir_all(entry.path()).map_err(io_error)?; + } + } + Ok(()) +} + +fn asset_extension(asset_type: AssetType) -> Option<&'static str> { + match asset_type { + AssetType::Animation => Some("_animation.bvh"), + AssetType::Bodypart => Some("_bodypart.txt"), + AssetType::CallingCard => Some("_callingcard.txt"), + AssetType::Clothing => Some("_clothing.txt"), + AssetType::Folder => Some("_folder.txt"), + AssetType::Gesture => Some("_gesture.txt"), + AssetType::ImageJPEG => Some("_image.jpg"), + AssetType::ImageTGA => Some("_image.tga"), + AssetType::Landmark => Some("_landmark.txt"), + AssetType::LSLBytecode => Some("_bytecode.lso"), + AssetType::LSLText => Some("_script.lsl"), + AssetType::Notecard => Some("_notecard.txt"), + AssetType::Object => Some("_object.xml"), + AssetType::Simstate => Some("_simstate.bin"), + AssetType::Sound => Some("_sound.ogg"), + AssetType::SoundWAV => Some("_sound.wav"), + AssetType::Texture => Some("_texture.jp2"), + AssetType::TextureTGA => Some("_texture.tga"), + AssetType::Mesh => Some("_mesh.llmesh"), + AssetType::Material => Some("_material.llsd"), + AssetType::Settings => Some("_settings.llsd"), + _ => None, + } +} + +pub(crate) fn asset_type_to_extension() +-> libremetaverse_types::compat::FrozenDictionary { + let types = [ + AssetType::Animation, + AssetType::Bodypart, + AssetType::CallingCard, + AssetType::Clothing, + AssetType::Folder, + AssetType::Gesture, + AssetType::ImageJPEG, + AssetType::ImageTGA, + AssetType::Landmark, + AssetType::LSLBytecode, + AssetType::LSLText, + AssetType::Notecard, + AssetType::Object, + AssetType::Simstate, + AssetType::Sound, + AssetType::SoundWAV, + AssetType::Texture, + AssetType::TextureTGA, + AssetType::Mesh, + AssetType::Material, + AssetType::Settings, + ]; + libremetaverse_types::compat::FrozenDictionary( + types + .into_iter() + .filter_map(|kind| asset_extension(kind).map(|suffix| (kind, suffix.to_owned()))) + .collect(), + ) +} + +pub(crate) fn extension_to_asset_type() +-> libremetaverse_types::compat::FrozenDictionary { + libremetaverse_types::compat::FrozenDictionary( + asset_type_to_extension() + .0 + .into_iter() + .map(|(kind, suffix)| (suffix, kind)) + .collect(), + ) +} + +fn prepare_output_directory(path: &str) -> Result { + let root = PathBuf::from(path); + if root.exists() { + let metadata = fs::symlink_metadata(&root).map_err(io_error)?; + if metadata.file_type().is_symlink() || !metadata.is_dir() { + return Err(archive_error("archive output root")); + } + } else { + fs::create_dir_all(&root).map_err(io_error)?; + } + Ok(root) +} + +fn write_asset(root: &Path, id: UUID, extension: &str, data: &[u8]) -> Result<(), Error> { + if data.len() > MAX_ENTRY_BYTES { + return Err(archive_error("asset size limit")); + } + let name = format!("{}{extension}", id); + validate_archive_path(&name)?; + fs::write(root.join(name), data).map_err(io_error) +} + +pub(crate) async fn save_assets( + asset_manager: crate::AssetManager, + asset_type: AssetType, + mut assets: Vec, + assets_path: String, +) -> Result<(), Error> { + if assets.len() > MAX_ENTRIES { + return Err(archive_error("asset count limit")); + } + let extension = asset_extension(asset_type).ok_or(Error::Argument)?; + let root = prepare_output_directory(&assets_path)?; + assets.sort_by_key(UUID::to_string); + assets.dedup(); + let mut total = 0usize; + for id in assets { + if id == UUID::zero() { + continue; + } + let bytes = if asset_type == AssetType::Texture { + asset_manager + .request_image(id, None, None) + .await? + .map(|asset| asset.native_bytes()) + } else { + asset_manager + .request_asset_with_uuid_asset_type_boolean_cancellation_token( + id, asset_type, false, None, + ) + .await? + .map(|asset| asset.asset_data) + }; + if let Some(bytes) = bytes { + total = total + .checked_add(bytes.len()) + .ok_or_else(|| archive_error("asset total size overflow"))?; + if total > MAX_TOTAL_BYTES { + return Err(archive_error("asset total size limit")); + } + write_asset(&root, id, extension, &bytes)?; + } + } + Ok(()) +} + +pub(crate) async fn save_sim_asset( + asset_manager: crate::AssetManager, + asset_type: AssetType, + asset_id: UUID, + item_id: UUID, + prim_id: UUID, + assets_path: String, +) -> Result<(), Error> { + let extension = asset_extension(asset_type).ok_or(Error::Argument)?; + let root = prepare_output_directory(&assets_path)?; + let asset = asset_manager + .request_asset_with_uuid_uuid_uuid_asset_type_boolean_source_type_uuid_cancellation_token( + asset_id, + item_id, + prim_id, + asset_type, + false, + crate::SourceType::SimInventoryItem, + UUID::random()?, + None, + ) + .await?; + if let Some(asset) = asset { + write_asset(&root, asset_id, extension, &asset.asset_data)?; + } + Ok(()) +} + +pub(crate) fn package_archive(directory_name: String, filename: String) -> Result<(), Error> { + let root = fs::canonicalize(&directory_name).map_err(io_error)?; + if !root.is_dir() { + return Err(Error::Argument); + } + let output = File::create(filename).map_err(io_error)?; + let mut gzip = GzBuilder::new() + .mtime(0) + .write(output, Compression::default()); + write_entry(&mut gzip, "archive.xml", ARCHIVE_XML.as_bytes(), b'0')?; + let mut entries = Vec::new(); + for directory in OAR_DIRECTORIES { + let path = root.join(directory); + if path.is_dir() { + collect_files(&root, &path, 1, &mut entries)?; + } + } + entries.sort_by(|left, right| left.0.cmp(&right.0)); + let mut total = ARCHIVE_XML.len(); + if entries.len() + 1 > MAX_ENTRIES { + return Err(archive_error("OAR entry count limit")); + } + for (archive_path, disk_path) in entries { + let data = fs::read(disk_path).map_err(io_error)?; + total = total + .checked_add(data.len()) + .ok_or_else(|| archive_error("OAR total size overflow"))?; + if data.len() > MAX_ENTRY_BYTES || total > MAX_TOTAL_BYTES { + return Err(archive_error("OAR size limit")); + } + write_entry(&mut gzip, &archive_path, &data, b'0')?; + } + gzip.write_all(&[0u8; BLOCK * 2]).map_err(io_error)?; + gzip.finish().map_err(io_error)?; + Ok(()) +} + +fn collect_files( + root: &Path, + directory: &Path, + depth: usize, + result: &mut Vec<(String, PathBuf)>, +) -> Result<(), Error> { + if depth > MAX_PATH_DEPTH { + return Err(archive_error("OAR directory depth limit")); + } + let mut children = fs::read_dir(directory) + .map_err(io_error)? + .collect::, _>>() + .map_err(io_error)?; + children.sort_by_key(std::fs::DirEntry::file_name); + for child in children { + let metadata = fs::symlink_metadata(child.path()).map_err(io_error)?; + if metadata.file_type().is_symlink() { + return Err(archive_error("OAR symlink rejected")); + } + if metadata.is_dir() { + collect_files(root, &child.path(), depth + 1, result)?; + } else if metadata.is_file() { + let relative = child + .path() + .strip_prefix(root) + .map_err(|_| archive_error("OAR extraction root"))? + .to_string_lossy() + .replace('\\', "/"); + validate_archive_path(&relative)?; + result.push((relative, child.path())); + } + } + Ok(()) +} + +pub(crate) fn unpackage_archive( + filename: String, + asset_callback: OarFileAssetLoadedCallback, + terrain_callback: OarFileTerrainLoadedCallback, + _object_callback: OarFileSceneObjectLoadedCallback, + settings_callback: OarFileSettingsLoadedCallback, +) -> Result<(), Error> { + let file = File::open(filename).map_err(io_error)?; + let total = i64::try_from(file.metadata().map_err(io_error)?.len()) + .map_err(|_| Error::InvalidOperation)?; + let decoder = GzDecoder::new(file); + let mut archive = tar::Archive::new(decoder); + let mut count = 0usize; + let mut expanded = 0usize; + for item in archive.entries().map_err(io_error)? { + let entry = item.map_err(io_error)?; + count += 1; + if count > MAX_ENTRIES { + return Err(archive_error("OAR entry count limit")); + } + let path = entry + .path() + .map_err(io_error)? + .to_string_lossy() + .replace('\\', "/"); + validate_archive_path(&path)?; + let size = usize::try_from(entry.size()).map_err(|_| archive_error("OAR entry size"))?; + expanded = expanded + .checked_add(size) + .ok_or_else(|| archive_error("OAR expanded size overflow"))?; + if size > MAX_ENTRY_BYTES || expanded > MAX_TOTAL_BYTES { + return Err(archive_error("OAR expanded size limit")); + } + if entry.header().entry_type().is_symlink() || entry.header().entry_type().is_hard_link() { + return Err(archive_error("OAR links are not permitted")); + } + if !entry.header().entry_type().is_file() { + continue; + } + let mut data = Vec::with_capacity(size); + entry + .take((MAX_ENTRY_BYTES + 1) as u64) + .read_to_end(&mut data) + .map_err(io_error)?; + if data.len() != size { + return Err(archive_error("OAR entry length")); + } + if path.starts_with("terrains/") { + let flattened = decode_terrain(&path, &data)?.unwrap_or_default(); + if !flattened.is_empty() { + terrain_callback.invoke(flattened, expanded as i64, total)?; + } + } else if path.starts_with("assets/") { + if let Some(asset) = load_generic_asset(&path, data)? { + asset_callback.invoke(asset, expanded as i64, total)?; + } + } else if path.starts_with("settings/") && path.ends_with(".xml") { + let settings = RegionSettings::from_stream(Box::new(std::io::Cursor::new(data)))?; + let name = Path::new(&path) + .file_stem() + .and_then(|value| value.to_str()) + .ok_or_else(|| archive_error("OAR settings name"))? + .to_owned(); + settings_callback.invoke(name, settings)?; + } + } + Ok(()) +} + +fn load_generic_asset(path: &str, data: Vec) -> Result, Error> { + let filename = path + .rsplit('/') + .next() + .ok_or_else(|| archive_error("OAR asset name"))?; + let Some((suffix, asset_type)) = extension_asset_type(filename) else { + return Ok(None); + }; + let Some(id_text) = filename.strip_suffix(suffix) else { + return Ok(None); + }; + let id = UUID::new_with_string(id_text.to_owned())?; + crate::asset_models::Asset::native_new(asset_type, id, data).map(Some) +} + +fn extension_asset_type(filename: &str) -> Option<(&'static str, AssetType)> { + asset_type_to_extension() + .0 + .into_iter() + .find_map(|(kind, _)| { + asset_extension(kind) + .filter(|suffix| filename.ends_with(suffix)) + .map(|suffix| (suffix, kind)) + }) +} + +fn decode_terrain(path: &str, data: &[u8]) -> Result>, Error> { + if !path.ends_with(".r32") && !path.ends_with(".f32") { + return Ok(None); + } + if data.is_empty() || !data.len().is_multiple_of(4) { + return Ok(None); + } + let posts = data.len() / 4; + let side = (posts as f64).sqrt() as usize; + if side == 0 || side.checked_mul(side) != Some(posts) { + return Ok(None); + } + let mut terrain = Vec::with_capacity(posts); + for bytes in data.chunks_exact(4) { + let value = f32::from_le_bytes( + bytes + .try_into() + .map_err(|_| archive_error("terrain float"))?, + ); + if !value.is_finite() { + return Err(archive_error("terrain non-finite height")); + } + terrain.push(value.clamp(0.0, 255.0)); + } + Ok(Some(terrain)) +} + +pub(crate) fn load_terrain( + path: &str, + data: &[u8], + callback: TerrainCallback, + bytes_read: i64, + total_bytes: i64, +) -> Result { + let Some(flat) = decode_terrain(path, data)? else { + return Ok(false); + }; + let side = (flat.len() as f64).sqrt() as usize; + let rows = flat.chunks_exact(side).map(<[f32]>::to_vec).collect(); + callback(rows, bytes_read, total_bytes); + Ok(true) +} + +pub(crate) fn save_terrain(sim: crate::Simulator, terrain_path: String) -> Result<(), Error> { + let root = PathBuf::from(terrain_path); + if root.exists() { + fs::remove_dir_all(&root).map_err(io_error)?; + } + fs::create_dir_all(&root).map_err(io_error)?; + let name = sim.name.clone(); + let safe_name = name.replace(['/', '\\'], "_"); + let mut file = File::create(root.join(format!("{safe_name}.r32"))).map_err(io_error)?; + for y in 0..sim.size_y { + for x in 0..sim.size_x { + let mut height = 0.0; + if !sim.terrain_height_at_point(x as i32, y as i32, &mut height)? { + height = 0.0; + } + file.write_all(&height.to_le_bytes()).map_err(io_error)?; + } + } + file.flush().map_err(io_error) +} + +fn has_region_flag(flags: crate::RegionFlags, flag: crate::RegionFlags) -> bool { + flags.0 & flag.0 == flag.0 +} + +pub(crate) fn save_region_settings( + sim: crate::Simulator, + settings_path: String, +) -> Result<(), Error> { + let root = PathBuf::from(&settings_path); + if root.exists() { + let metadata = fs::symlink_metadata(&root).map_err(io_error)?; + if metadata.file_type().is_symlink() || !metadata.is_dir() { + return Err(archive_error("settings output root")); + } + fs::remove_dir_all(&root).map_err(io_error)?; + } + fs::create_dir_all(&root).map_err(io_error)?; + let flags = sim.flags; + let settings = RegionSettings { + allow_damage: has_region_flag(flags, crate::RegionFlags::ALLOW_DAMAGE), + allow_land_resell: !has_region_flag(flags, crate::RegionFlags::BLOCK_LAND_RESELL), + block_fly: has_region_flag(flags, crate::RegionFlags::NO_FLY), + block_land_show_in_search: has_region_flag(flags, crate::RegionFlags::BLOCK_PARCEL_SEARCH), + block_terraform: has_region_flag(flags, crate::RegionFlags::BLOCK_TERRAFORM), + disable_collisions: has_region_flag(flags, crate::RegionFlags::SKIP_COLLISIONS), + disable_physics: has_region_flag(flags, crate::RegionFlags::SKIP_PHYSICS), + disable_scripts: has_region_flag(flags, crate::RegionFlags::SKIP_SCRIPTS), + fixed_sun: has_region_flag(flags, crate::RegionFlags::SUN_FIXED), + maturity_rating: i32::from(sim.access.0), + restrict_pushing: has_region_flag(flags, crate::RegionFlags::RESTRICT_PUSH_OBJECT), + terrain_detail0: sim.terrain_detail0, + terrain_detail1: sim.terrain_detail1, + terrain_detail2: sim.terrain_detail2, + terrain_detail3: sim.terrain_detail3, + terrain_height_range00: sim.terrain_height_range00, + terrain_height_range01: sim.terrain_height_range01, + terrain_height_range10: sim.terrain_height_range10, + terrain_height_range11: sim.terrain_height_range11, + terrain_start_height00: sim.terrain_start_height00, + terrain_start_height01: sim.terrain_start_height01, + terrain_start_height10: sim.terrain_start_height10, + terrain_start_height11: sim.terrain_start_height11, + water_height: sim.water_height, + ..RegionSettings::default() + }; + let safe_name = sim.name.replace(['/', '\\'], "_"); + settings.to_xml( + root.join(format!("{safe_name}.xml")) + .to_string_lossy() + .into_owned(), + ) +} + +#[cfg(test)] +mod tests { + use super::{ + BLOCK, MAX_ENTRY_BYTES, MAX_PATH_DEPTH, TarArchiveReader, padded_octal, + validate_archive_path, + }; + use crate::assets::TarArchiveReaderTarEntryType; + + #[test] + fn rejects_absolute_parent_and_windows_style_archive_paths() { + for path in [ + "/etc/passwd", + "../escape", + "assets/../../escape", + "C:\\escape", + "assets\\file", + ] { + assert!( + validate_archive_path(path).is_err(), + "accepted unsafe path {path}" + ); + } + } + + #[test] + fn rejects_archive_paths_over_depth_limit() { + let path = std::iter::repeat_n("directory", MAX_PATH_DEPTH + 1) + .collect::>() + .join("/"); + assert!(validate_archive_path(&path).is_err()); + } + + #[test] + fn octal_fields_are_fixed_width_and_nul_terminated() { + let encoded = padded_octal(0o1234, 8).expect("octal field"); + assert_eq!(encoded.len(), 8); + assert_eq!(encoded.last(), Some(&0)); + assert_eq!(&encoded[3..7], b"1234"); + } + + #[test] + fn rejects_declared_entry_over_size_limit_before_reading_payload() { + let mut header = [0u8; BLOCK]; + header[..9].copy_from_slice(b"large.bin"); + header[100..108].copy_from_slice(&padded_octal(0o644, 8).expect("mode")); + header[124..136].copy_from_slice( + &padded_octal((MAX_ENTRY_BYTES + 1) as u64, 12).expect("oversized length"), + ); + header[148..156].fill(b' '); + header[156] = b'0'; + header[257..263].copy_from_slice(b"ustar\0"); + header[263..265].copy_from_slice(b"00"); + let checksum = header.iter().map(|byte| u64::from(*byte)).sum(); + header[148..156].copy_from_slice(&padded_octal(checksum, 8).expect("checksum")); + let reader = TarArchiveReader::new(Box::new(std::io::Cursor::new(header.to_vec()))) + .expect("tar reader"); + let mut path = String::new(); + let mut kind = TarArchiveReaderTarEntryType::TYPE_UNKNOWN; + assert!(reader.read_entry(&mut path, &mut kind).is_err()); + } +} diff --git a/crates/libremetaverse/src/generated.rs b/crates/libremetaverse/src/generated.rs index 1303956..97df278 100644 --- a/crates/libremetaverse/src/generated.rs +++ b/crates/libremetaverse/src/generated.rs @@ -17620,9 +17620,7 @@ pub mod assets { pub fn asset_type_to_extension() -> libremetaverse_types::compat::FrozenDictionary { - libremetaverse_types::unimplemented_api!( - "F:LibreMetaverse.Assets.ArchiveConstants.ASSET_TYPE_TO_EXTENSION" - ) + crate::asset_archive::asset_type_to_extension() } /// C# member: `F:LibreMetaverse.Assets.ArchiveConstants.CONTROL_FILE_PATH`. pub const CONTROL_FILE_PATH: &'static str = "archive.xml"; @@ -17630,9 +17628,7 @@ pub mod assets { pub fn extension_to_asset_type() -> libremetaverse_types::compat::FrozenDictionary { - libremetaverse_types::unimplemented_api!( - "F:LibreMetaverse.Assets.ArchiveConstants.EXTENSION_TO_ASSET_TYPE" - ) + crate::asset_archive::extension_to_asset_type() } /// C# member: `F:LibreMetaverse.Assets.ArchiveConstants.LANDDATA_PATH`. pub const LANDDATA_PATH: &'static str = "landdata/"; @@ -17644,7 +17640,7 @@ pub mod assets { pub const TERRAINS_PATH: &'static str = "terrains/"; /// C# member: `M:LibreMetaverse.Assets.ArchiveConstants.#ctor`. pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.ArchiveConstants.#ctor") + Ok(Self) } } @@ -18025,9 +18021,7 @@ pub mod assets { impl OarFile { /// C# member: `M:LibreMetaverse.Assets.OarFile.ClearAssetFolder(System.String)`. pub fn clear_asset_folder(assets_path: String) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.ClearAssetFolder(System.String)", - ) + crate::asset_archive::clear_asset_folder(assets_path) } /// C# member: `M:LibreMetaverse.Assets.OarFile.LoadObjects(System.Byte[],LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback,System.Int64,System.Int64)`. pub fn load_objects( @@ -18045,9 +18039,7 @@ pub mod assets { directory_name: String, filename: String, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.PackageArchive(System.String,System.String)", - ) + crate::asset_archive::package_archive(directory_name, filename) } /// C# member: `M:LibreMetaverse.Assets.OarFile.SOGToXml2(System.Xml.XmlTextWriter,LibreMetaverse.Assets.AssetPrim)`. pub fn sog_to_xml2( @@ -18065,9 +18057,7 @@ pub mod assets { assets: Vec, assets_path: String, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.SaveAssetsAsync(LibreMetaverse.AssetManager,LibreMetaverse.AssetType,System.Collections.Generic.IList{LibreMetaverse.UUID},System.String)", - ) + crate::asset_archive::save_assets(asset_manager, asset_type, assets, assets_path).await } /// C# member: `M:LibreMetaverse.Assets.OarFile.SaveParcels(LibreMetaverse.Simulator,System.String)`. pub fn save_parcels( @@ -18094,9 +18084,7 @@ pub mod assets { sim: libremetaverse::Simulator, settings_path: String, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.SaveRegionSettings(LibreMetaverse.Simulator,System.String)", - ) + crate::asset_archive::save_region_settings(sim, settings_path) } /// C# member: `M:LibreMetaverse.Assets.OarFile.SaveSimAssetsAsync(LibreMetaverse.AssetManager,LibreMetaverse.AssetType,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,System.String)`. pub async fn save_sim_assets( @@ -18107,18 +18095,22 @@ pub mod assets { prim_id: libremetaverse_types::UUID, assets_path: String, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.SaveSimAssetsAsync(LibreMetaverse.AssetManager,LibreMetaverse.AssetType,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,System.String)", + crate::asset_archive::save_sim_asset( + asset_manager, + asset_type, + asset_id, + item_id, + prim_id, + assets_path, ) + .await } /// C# member: `M:LibreMetaverse.Assets.OarFile.SaveTerrain(LibreMetaverse.Simulator,System.String)`. pub fn save_terrain( sim: libremetaverse::Simulator, terrain_path: String, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.SaveTerrain(LibreMetaverse.Simulator,System.String)", - ) + crate::asset_archive::save_terrain(sim, terrain_path) } /// C# member: `M:LibreMetaverse.Assets.OarFile.UnpackageArchive(System.String,LibreMetaverse.Assets.OarFile.AssetLoadedCallback,LibreMetaverse.Assets.OarFile.TerrainLoadedCallback,LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback,LibreMetaverse.Assets.OarFile.SettingsLoadedCallback)`. pub fn unpackage_archive( @@ -18128,197 +18120,43 @@ pub mod assets { object_callback: libremetaverse::assets::OarFileSceneObjectLoadedCallback, settings_callback: libremetaverse::assets::OarFileSettingsLoadedCallback, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.UnpackageArchive(System.String,LibreMetaverse.Assets.OarFile.AssetLoadedCallback,LibreMetaverse.Assets.OarFile.TerrainLoadedCallback,LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback,LibreMetaverse.Assets.OarFile.SettingsLoadedCallback)", + crate::asset_archive::unpackage_archive( + filename, + asset_callback, + terrain_callback, + object_callback, + settings_callback, ) } } /// C# type: `T:LibreMetaverse.Assets.OarFile.AssetLoadedCallback`. - pub struct OarFileAssetLoadedCallback; - impl OarFileAssetLoadedCallback { - /// C# member: `M:LibreMetaverse.Assets.OarFile.AssetLoadedCallback.#ctor(System.Object,System.IntPtr)`. - pub fn new( - object: libremetaverse_types::compat::Object, - method: isize, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.AssetLoadedCallback.#ctor(System.Object,System.IntPtr)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.OarFile.AssetLoadedCallback.BeginInvoke(LibreMetaverse.Assets.Asset,System.Int64,System.Int64,System.AsyncCallback,System.Object)`. - pub fn begin_invoke( - &self, - asset: libremetaverse::assets::Asset, - bytes_read: i64, - total_bytes: i64, - callback: Box, - object: libremetaverse_types::compat::Object, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.AssetLoadedCallback.BeginInvoke(LibreMetaverse.Assets.Asset,System.Int64,System.Int64,System.AsyncCallback,System.Object)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.OarFile.AssetLoadedCallback.EndInvoke(System.IAsyncResult)`. - pub fn end_invoke( - &self, - result: Box, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.AssetLoadedCallback.EndInvoke(System.IAsyncResult)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.OarFile.AssetLoadedCallback.Invoke(LibreMetaverse.Assets.Asset,System.Int64,System.Int64)`. - pub fn invoke( - &self, - asset: libremetaverse::assets::Asset, - bytes_read: i64, - total_bytes: i64, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.AssetLoadedCallback.Invoke(LibreMetaverse.Assets.Asset,System.Int64,System.Int64)", - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.OarFile.AssetLoadedCallback.#ctor(System.Object,System.IntPtr)`. + /// C# member: `M:LibreMetaverse.Assets.OarFile.AssetLoadedCallback.BeginInvoke(LibreMetaverse.Assets.Asset,System.Int64,System.Int64,System.AsyncCallback,System.Object)`. + /// C# member: `M:LibreMetaverse.Assets.OarFile.AssetLoadedCallback.EndInvoke(System.IAsyncResult)`. + /// C# member: `M:LibreMetaverse.Assets.OarFile.AssetLoadedCallback.Invoke(LibreMetaverse.Assets.Asset,System.Int64,System.Int64)`. + pub use crate::asset_archive::OarFileAssetLoadedCallback; /// C# type: `T:LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback`. - pub struct OarFileSceneObjectLoadedCallback; - impl OarFileSceneObjectLoadedCallback { - /// C# member: `M:LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback.#ctor(System.Object,System.IntPtr)`. - pub fn new( - object: libremetaverse_types::compat::Object, - method: isize, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback.#ctor(System.Object,System.IntPtr)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback.BeginInvoke(LibreMetaverse.Assets.AssetPrim,System.Int64,System.Int64,System.AsyncCallback,System.Object)`. - pub fn begin_invoke( - &self, - linkset: libremetaverse::assets::AssetPrim, - bytes_read: i64, - total_bytes: i64, - callback: Box, - object: libremetaverse_types::compat::Object, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback.BeginInvoke(LibreMetaverse.Assets.AssetPrim,System.Int64,System.Int64,System.AsyncCallback,System.Object)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback.EndInvoke(System.IAsyncResult)`. - pub fn end_invoke( - &self, - result: Box, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback.EndInvoke(System.IAsyncResult)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback.Invoke(LibreMetaverse.Assets.AssetPrim,System.Int64,System.Int64)`. - pub fn invoke( - &self, - linkset: libremetaverse::assets::AssetPrim, - bytes_read: i64, - total_bytes: i64, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback.Invoke(LibreMetaverse.Assets.AssetPrim,System.Int64,System.Int64)", - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback.#ctor(System.Object,System.IntPtr)`. + /// C# member: `M:LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback.BeginInvoke(LibreMetaverse.Assets.AssetPrim,System.Int64,System.Int64,System.AsyncCallback,System.Object)`. + /// C# member: `M:LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback.EndInvoke(System.IAsyncResult)`. + /// C# member: `M:LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback.Invoke(LibreMetaverse.Assets.AssetPrim,System.Int64,System.Int64)`. + pub use crate::asset_archive::OarFileSceneObjectLoadedCallback; /// C# type: `T:LibreMetaverse.Assets.OarFile.SettingsLoadedCallback`. - pub struct OarFileSettingsLoadedCallback; - impl OarFileSettingsLoadedCallback { - /// C# member: `M:LibreMetaverse.Assets.OarFile.SettingsLoadedCallback.#ctor(System.Object,System.IntPtr)`. - pub fn new( - object: libremetaverse_types::compat::Object, - method: isize, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.SettingsLoadedCallback.#ctor(System.Object,System.IntPtr)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.OarFile.SettingsLoadedCallback.BeginInvoke(System.String,LibreMetaverse.Assets.RegionSettings,System.AsyncCallback,System.Object)`. - pub fn begin_invoke( - &self, - region_name: String, - settings: libremetaverse::assets::RegionSettings, - callback: Box, - object: libremetaverse_types::compat::Object, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.SettingsLoadedCallback.BeginInvoke(System.String,LibreMetaverse.Assets.RegionSettings,System.AsyncCallback,System.Object)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.OarFile.SettingsLoadedCallback.EndInvoke(System.IAsyncResult)`. - pub fn end_invoke( - &self, - result: Box, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.SettingsLoadedCallback.EndInvoke(System.IAsyncResult)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.OarFile.SettingsLoadedCallback.Invoke(System.String,LibreMetaverse.Assets.RegionSettings)`. - pub fn invoke( - &self, - region_name: String, - settings: libremetaverse::assets::RegionSettings, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.SettingsLoadedCallback.Invoke(System.String,LibreMetaverse.Assets.RegionSettings)", - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.OarFile.SettingsLoadedCallback.#ctor(System.Object,System.IntPtr)`. + /// C# member: `M:LibreMetaverse.Assets.OarFile.SettingsLoadedCallback.BeginInvoke(System.String,LibreMetaverse.Assets.RegionSettings,System.AsyncCallback,System.Object)`. + /// C# member: `M:LibreMetaverse.Assets.OarFile.SettingsLoadedCallback.EndInvoke(System.IAsyncResult)`. + /// C# member: `M:LibreMetaverse.Assets.OarFile.SettingsLoadedCallback.Invoke(System.String,LibreMetaverse.Assets.RegionSettings)`. + pub use crate::asset_archive::OarFileSettingsLoadedCallback; /// C# type: `T:LibreMetaverse.Assets.OarFile.TerrainLoadedCallback`. - pub struct OarFileTerrainLoadedCallback; - impl OarFileTerrainLoadedCallback { - /// C# member: `M:LibreMetaverse.Assets.OarFile.TerrainLoadedCallback.#ctor(System.Object,System.IntPtr)`. - pub fn new( - object: libremetaverse_types::compat::Object, - method: isize, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.TerrainLoadedCallback.#ctor(System.Object,System.IntPtr)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.OarFile.TerrainLoadedCallback.BeginInvoke(System.Single[0:,0:],System.Int64,System.Int64,System.AsyncCallback,System.Object)`. - pub fn begin_invoke( - &self, - terrain: Vec, - bytes_read: i64, - total_bytes: i64, - callback: Box, - object: libremetaverse_types::compat::Object, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.TerrainLoadedCallback.BeginInvoke(System.Single[0:,0:],System.Int64,System.Int64,System.AsyncCallback,System.Object)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.OarFile.TerrainLoadedCallback.EndInvoke(System.IAsyncResult)`. - pub fn end_invoke( - &self, - result: Box, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.TerrainLoadedCallback.EndInvoke(System.IAsyncResult)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.OarFile.TerrainLoadedCallback.Invoke(System.Single[0:,0:],System.Int64,System.Int64)`. - pub fn invoke( - &self, - terrain: Vec, - bytes_read: i64, - total_bytes: i64, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.TerrainLoadedCallback.Invoke(System.Single[0:,0:],System.Int64,System.Int64)", - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.OarFile.TerrainLoadedCallback.#ctor(System.Object,System.IntPtr)`. + /// C# member: `M:LibreMetaverse.Assets.OarFile.TerrainLoadedCallback.BeginInvoke(System.Single[0:,0:],System.Int64,System.Int64,System.AsyncCallback,System.Object)`. + /// C# member: `M:LibreMetaverse.Assets.OarFile.TerrainLoadedCallback.EndInvoke(System.IAsyncResult)`. + /// C# member: `M:LibreMetaverse.Assets.OarFile.TerrainLoadedCallback.Invoke(System.Single[0:,0:],System.Int64,System.Int64)`. + pub use crate::asset_archive::OarFileTerrainLoadedCallback; /// C# type: `T:LibreMetaverse.Assets.PrimObject`. pub struct PrimObject { @@ -18836,125 +18674,47 @@ pub mod assets { } /// C# type: `T:LibreMetaverse.Assets.RegionSettings`. - pub struct RegionSettings { - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.AgentLimit`. - pub agent_limit: i32, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.AllowDamage`. - pub allow_damage: bool, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.AllowLandJoinDivide`. - pub allow_land_join_divide: bool, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.AllowLandResell`. - pub allow_land_resell: bool, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.BlockFly`. - pub block_fly: bool, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.BlockLandShowInSearch`. - pub block_land_show_in_search: bool, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.BlockTerraform`. - pub block_terraform: bool, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.DisableCollisions`. - pub disable_collisions: bool, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.DisablePhysics`. - pub disable_physics: bool, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.DisableScripts`. - pub disable_scripts: bool, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.FixedSun`. - pub fixed_sun: bool, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.MaturityRating`. - pub maturity_rating: i32, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.ObjectBonus`. - pub object_bonus: f32, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.RestrictPushing`. - pub restrict_pushing: bool, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainDetail0`. - pub terrain_detail0: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainDetail1`. - pub terrain_detail1: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainDetail2`. - pub terrain_detail2: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainDetail3`. - pub terrain_detail3: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainHeightRange00`. - pub terrain_height_range00: f32, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainHeightRange01`. - pub terrain_height_range01: f32, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainHeightRange10`. - pub terrain_height_range10: f32, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainHeightRange11`. - pub terrain_height_range11: f32, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainLowerLimit`. - pub terrain_lower_limit: f32, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainRaiseLimit`. - pub terrain_raise_limit: f32, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainStartHeight00`. - pub terrain_start_height00: f32, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainStartHeight01`. - pub terrain_start_height01: f32, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainStartHeight10`. - pub terrain_start_height10: f32, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainStartHeight11`. - pub terrain_start_height11: f32, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.UseEstateSun`. - pub use_estate_sun: bool, - /// C# member: `F:LibreMetaverse.Assets.RegionSettings.WaterHeight`. - pub water_height: f32, - } - impl RegionSettings { - /// C# member: `M:LibreMetaverse.Assets.RegionSettings.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.RegionSettings.#ctor") - } - /// C# member: `M:LibreMetaverse.Assets.RegionSettings.FromStream(System.IO.Stream)`. - pub fn from_stream( - stream: Box, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.RegionSettings.FromStream(System.IO.Stream)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.RegionSettings.ToXML(System.String)`. - pub fn to_xml(&self, filename: String) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.RegionSettings.ToXML(System.String)", - ) - } - } + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.AgentLimit`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.AllowDamage`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.AllowLandJoinDivide`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.AllowLandResell`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.BlockFly`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.BlockLandShowInSearch`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.BlockTerraform`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.DisableCollisions`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.DisablePhysics`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.DisableScripts`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.FixedSun`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.MaturityRating`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.ObjectBonus`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.RestrictPushing`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainDetail0`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainDetail1`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainDetail2`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainDetail3`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainHeightRange00`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainHeightRange01`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainHeightRange10`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainHeightRange11`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainLowerLimit`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainRaiseLimit`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainStartHeight00`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainStartHeight01`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainStartHeight10`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.TerrainStartHeight11`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.UseEstateSun`. + /// C# member: `F:LibreMetaverse.Assets.RegionSettings.WaterHeight`. + /// C# member: `M:LibreMetaverse.Assets.RegionSettings.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.RegionSettings.FromStream(System.IO.Stream)`. + /// C# member: `M:LibreMetaverse.Assets.RegionSettings.ToXML(System.String)`. + pub use crate::asset_archive::RegionSettings; /// C# type: `T:LibreMetaverse.Assets.TarArchiveReader`. - pub struct TarArchiveReader; - impl TarArchiveReader { - /// C# member: `M:LibreMetaverse.Assets.TarArchiveReader.#ctor(System.IO.Stream)`. - pub fn new( - s: Box, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.TarArchiveReader.#ctor(System.IO.Stream)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.TarArchiveReader.Close`. - pub fn close(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.TarArchiveReader.Close") - } - /// C# member: `M:LibreMetaverse.Assets.TarArchiveReader.ConvertOctalBytesToDecimal(System.Byte[],System.Int32,System.Int32)`. - pub fn convert_octal_bytes_to_decimal( - bytes: Vec, - start_index: i32, - count: i32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.TarArchiveReader.ConvertOctalBytesToDecimal(System.Byte[],System.Int32,System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.TarArchiveReader.ReadEntry(System.String@,LibreMetaverse.Assets.TarArchiveReader.TarEntryType@)`. - pub fn read_entry( - &self, - file_path: &mut String, - entry_type: &mut libremetaverse::assets::TarArchiveReaderTarEntryType, - ) -> Result>, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.TarArchiveReader.ReadEntry(System.String@,LibreMetaverse.Assets.TarArchiveReader.TarEntryType@)", - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.TarArchiveReader.#ctor(System.IO.Stream)`. + /// C# member: `M:LibreMetaverse.Assets.TarArchiveReader.Close`. + /// C# member: `M:LibreMetaverse.Assets.TarArchiveReader.ConvertOctalBytesToDecimal(System.Byte[],System.Int32,System.Int32)`. + /// C# member: `M:LibreMetaverse.Assets.TarArchiveReader.ReadEntry(System.String@,LibreMetaverse.Assets.TarArchiveReader.TarEntryType@)`. + pub use crate::asset_archive::TarArchiveReader; /// C# type: `T:LibreMetaverse.Assets.TarArchiveReader.TarEntryType`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -18981,56 +18741,13 @@ pub mod assets { } /// C# type: `T:LibreMetaverse.Assets.TarArchiveWriter`. - pub struct TarArchiveWriter; - impl TarArchiveWriter { - /// C# member: `M:LibreMetaverse.Assets.TarArchiveWriter.#ctor(System.IO.Stream)`. - pub fn new( - s: Box, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.TarArchiveWriter.#ctor(System.IO.Stream)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.TarArchiveWriter.Close`. - pub fn close(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.TarArchiveWriter.Close") - } - /// C# member: `M:LibreMetaverse.Assets.TarArchiveWriter.ConvertDecimalToPaddedOctalBytes(System.Int32,System.Int32)`. - pub fn convert_decimal_to_padded_octal_bytes( - d: i32, - padding: i32, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.TarArchiveWriter.ConvertDecimalToPaddedOctalBytes(System.Int32,System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.TarArchiveWriter.WriteDir(System.String)`. - pub fn write_dir(&self, dir_name: String) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.TarArchiveWriter.WriteDir(System.String)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.TarArchiveWriter.WriteFile(System.String,System.Byte[])`. - pub fn write_file_with_string_bytes( - &self, - file_path: String, - data: Vec, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.TarArchiveWriter.WriteFile(System.String,System.Byte[])", - ) - } - /// C# member: `M:LibreMetaverse.Assets.TarArchiveWriter.WriteFile(System.String,System.String)`. - pub fn write_file_with_string_string( - &self, - file_path: String, - data: String, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.TarArchiveWriter.WriteFile(System.String,System.String)", - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.TarArchiveWriter.#ctor(System.IO.Stream)`. + /// C# member: `M:LibreMetaverse.Assets.TarArchiveWriter.Close`. + /// C# member: `M:LibreMetaverse.Assets.TarArchiveWriter.ConvertDecimalToPaddedOctalBytes(System.Int32,System.Int32)`. + /// C# member: `M:LibreMetaverse.Assets.TarArchiveWriter.WriteDir(System.String)`. + /// C# member: `M:LibreMetaverse.Assets.TarArchiveWriter.WriteFile(System.String,System.Byte[])`. + /// C# member: `M:LibreMetaverse.Assets.TarArchiveWriter.WriteFile(System.String,System.String)`. + pub use crate::asset_archive::TarArchiveWriter; /// C# type: `T:LibreMetaverse.Assets.TarHeader`. pub struct TarHeader { @@ -19050,144 +18767,20 @@ pub mod assets { pub mod gltf { /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfAccessor`. - pub struct GltfAccessor; - impl GltfAccessor { - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfAccessor.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfAccessor.#ctor", - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.BufferView`. - pub fn buffer_view(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.BufferView" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.BufferView`. - pub fn set_buffer_view(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.BufferView" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.ByteOffset`. - pub fn byte_offset(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.ByteOffset" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.ByteOffset`. - pub fn set_byte_offset(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.ByteOffset" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.ComponentByteSize`. - pub fn component_byte_size(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.ComponentByteSize" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.ComponentCount`. - pub fn component_count(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.ComponentCount" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.ComponentType`. - pub fn component_type(&self) -> libremetaverse::assets::gltf::GltfComponentType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.ComponentType" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.ComponentType`. - pub fn set_component_type( - &mut self, - value: libremetaverse::assets::gltf::GltfComponentType, - ) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.ComponentType" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.Count`. - pub fn count(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.Count" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.Count`. - pub fn set_count(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.Count" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.DefaultStride`. - pub fn default_stride(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.DefaultStride" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.Max`. - pub fn max(&self) -> Option> { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.Max" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.Max`. - pub fn set_max(&mut self, value: Option>) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.Max" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.Min`. - pub fn min(&self) -> Option> { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.Min" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.Min`. - pub fn set_min(&mut self, value: Option>) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.Min" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.Name`. - pub fn name(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.Name" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.Name`. - pub fn set_name(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.Name" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.Normalized`. - pub fn normalized(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.Normalized" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.Normalized`. - pub fn set_normalized(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.Normalized" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.Type`. - pub fn type_(&self) -> libremetaverse::assets::gltf::GltfAccessorType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.Type" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.Type`. - pub fn set_type_(&mut self, value: libremetaverse::assets::gltf::GltfAccessorType) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAccessor.Type" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfAccessor.#ctor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.BufferView`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.ByteOffset`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.ComponentByteSize`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.ComponentCount`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.ComponentType`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.Count`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.DefaultStride`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.Max`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.Min`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.Name`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.Normalized`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAccessor.Type`. + pub use crate::gltf::GltfAccessor; /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfAccessorType`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -19210,106 +18803,23 @@ pub mod assets { } /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfAnimation`. - pub struct GltfAnimation; - impl GltfAnimation { - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfAnimation.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfAnimation.#ctor", - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimation.Channels`. - pub fn channels(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAnimation.Channels" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimation.Name`. - pub fn name(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAnimation.Name" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimation.Name`. - pub fn set_name(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAnimation.Name" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimation.Samplers`. - pub fn samplers(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAnimation.Samplers" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfAnimation.#ctor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimation.Channels`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimation.Name`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimation.Samplers`. + pub use crate::gltf::GltfAnimation; /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfAnimationChannel`. - pub struct GltfAnimationChannel; - impl GltfAnimationChannel { - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfAnimationChannel.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfAnimationChannel.#ctor", - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationChannel.Sampler`. - pub fn sampler(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAnimationChannel.Sampler" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationChannel.Sampler`. - pub fn set_sampler(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAnimationChannel.Sampler" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationChannel.Target`. - pub fn target(&self) -> libremetaverse::assets::gltf::GltfAnimationChannelTarget { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAnimationChannel.Target" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfAnimationChannel.#ctor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationChannel.Sampler`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationChannel.Target`. + pub use crate::gltf::GltfAnimationChannel; /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfAnimationChannelTarget`. - pub struct GltfAnimationChannelTarget; - impl GltfAnimationChannelTarget { - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfAnimationChannelTarget.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfAnimationChannelTarget.#ctor", - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationChannelTarget.Node`. - pub fn node(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAnimationChannelTarget.Node" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationChannelTarget.Node`. - pub fn set_node(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAnimationChannelTarget.Node" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationChannelTarget.Path`. - pub fn path(&self) -> libremetaverse::assets::gltf::GltfAnimationTargetPath { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAnimationChannelTarget.Path" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationChannelTarget.Path`. - pub fn set_path( - &mut self, - value: libremetaverse::assets::gltf::GltfAnimationTargetPath, - ) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAnimationChannelTarget.Path" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfAnimationChannelTarget.#ctor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationChannelTarget.Node`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationChannelTarget.Path`. + pub use crate::gltf::GltfAnimationChannelTarget; /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfAnimationInterpolation`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -19324,56 +18834,11 @@ pub mod assets { } /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfAnimationSampler`. - pub struct GltfAnimationSampler; - impl GltfAnimationSampler { - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfAnimationSampler.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfAnimationSampler.#ctor", - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationSampler.Input`. - pub fn input(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAnimationSampler.Input" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationSampler.Input`. - pub fn set_input(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAnimationSampler.Input" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationSampler.Interpolation`. - pub fn interpolation( - &self, - ) -> libremetaverse::assets::gltf::GltfAnimationInterpolation { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAnimationSampler.Interpolation" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationSampler.Interpolation`. - pub fn set_interpolation( - &mut self, - value: libremetaverse::assets::gltf::GltfAnimationInterpolation, - ) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAnimationSampler.Interpolation" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationSampler.Output`. - pub fn output(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAnimationSampler.Output" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationSampler.Output`. - pub fn set_output(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfAnimationSampler.Output" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfAnimationSampler.#ctor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationSampler.Input`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationSampler.Interpolation`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfAnimationSampler.Output`. + pub use crate::gltf::GltfAnimationSampler; /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfAnimationTargetPath`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -19390,146 +18855,22 @@ pub mod assets { } /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfBuffer`. - pub struct GltfBuffer; - impl GltfBuffer { - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfBuffer.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfBuffer.#ctor", - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBuffer.ByteLength`. - pub fn byte_length(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBuffer.ByteLength" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfBuffer.ByteLength`. - pub fn set_byte_length(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBuffer.ByteLength" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBuffer.Data`. - pub fn data(&self) -> Option> { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBuffer.Data" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfBuffer.Data`. - pub fn set_data(&mut self, value: Option>) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBuffer.Data" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBuffer.Name`. - pub fn name(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBuffer.Name" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfBuffer.Name`. - pub fn set_name(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBuffer.Name" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBuffer.Uri`. - pub fn uri(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBuffer.Uri" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfBuffer.Uri`. - pub fn set_uri(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBuffer.Uri" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfBuffer.#ctor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBuffer.ByteLength`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBuffer.Data`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBuffer.Name`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBuffer.Uri`. + pub use crate::gltf::GltfBuffer; /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfBufferView`. - pub struct GltfBufferView; - impl GltfBufferView { - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfBufferView.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfBufferView.#ctor", - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBufferView.Buffer`. - pub fn buffer(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBufferView.Buffer" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfBufferView.Buffer`. - pub fn set_buffer(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBufferView.Buffer" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBufferView.ByteLength`. - pub fn byte_length(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBufferView.ByteLength" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfBufferView.ByteLength`. - pub fn set_byte_length(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBufferView.ByteLength" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBufferView.ByteOffset`. - pub fn byte_offset(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBufferView.ByteOffset" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfBufferView.ByteOffset`. - pub fn set_byte_offset(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBufferView.ByteOffset" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBufferView.ByteStride`. - pub fn byte_stride(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBufferView.ByteStride" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfBufferView.ByteStride`. - pub fn set_byte_stride(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBufferView.ByteStride" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBufferView.Name`. - pub fn name(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBufferView.Name" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfBufferView.Name`. - pub fn set_name(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBufferView.Name" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBufferView.Target`. - pub fn target(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBufferView.Target" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfBufferView.Target`. - pub fn set_target(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfBufferView.Target" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfBufferView.#ctor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBufferView.Buffer`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBufferView.ByteLength`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBufferView.ByteOffset`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBufferView.ByteStride`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBufferView.Name`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfBufferView.Target`. + pub use crate::gltf::GltfBufferView; /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfComponentType`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -19550,832 +18891,116 @@ pub mod assets { } /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfDocument`. - pub struct GltfDocument; - impl GltfDocument { - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocument.#ctor", - ) - } - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetColors(LibreMetaverse.Assets.Gltf.GltfPrimitive,System.Int32)`. - pub fn get_colors( - &self, - primitive: libremetaverse::assets::gltf::GltfPrimitive, - set: Option, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocument.GetColors(LibreMetaverse.Assets.Gltf.GltfPrimitive,System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetIndices(LibreMetaverse.Assets.Gltf.GltfPrimitive)`. - pub fn get_indices( - &self, - primitive: libremetaverse::assets::gltf::GltfPrimitive, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocument.GetIndices(LibreMetaverse.Assets.Gltf.GltfPrimitive)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetInverseBindMatrices(LibreMetaverse.Assets.Gltf.GltfSkin)`. - pub fn get_inverse_bind_matrices( - &self, - skin: libremetaverse::assets::gltf::GltfSkin, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocument.GetInverseBindMatrices(LibreMetaverse.Assets.Gltf.GltfSkin)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetJoints(LibreMetaverse.Assets.Gltf.GltfPrimitive,System.Int32)`. - pub fn get_joints( - &self, - primitive: libremetaverse::assets::gltf::GltfPrimitive, - set: Option, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocument.GetJoints(LibreMetaverse.Assets.Gltf.GltfPrimitive,System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetNormals(LibreMetaverse.Assets.Gltf.GltfPrimitive)`. - pub fn get_normals( - &self, - primitive: libremetaverse::assets::gltf::GltfPrimitive, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocument.GetNormals(LibreMetaverse.Assets.Gltf.GltfPrimitive)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetPositions(LibreMetaverse.Assets.Gltf.GltfPrimitive)`. - pub fn get_positions( - &self, - primitive: libremetaverse::assets::gltf::GltfPrimitive, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocument.GetPositions(LibreMetaverse.Assets.Gltf.GltfPrimitive)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetSamplerQuaternion(LibreMetaverse.Assets.Gltf.GltfAnimationSampler)`. - pub fn get_sampler_quaternion( - &self, - sampler: libremetaverse::assets::gltf::GltfAnimationSampler, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocument.GetSamplerQuaternion(LibreMetaverse.Assets.Gltf.GltfAnimationSampler)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetSamplerTimestamps(LibreMetaverse.Assets.Gltf.GltfAnimationSampler)`. - pub fn get_sampler_timestamps( - &self, - sampler: libremetaverse::assets::gltf::GltfAnimationSampler, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocument.GetSamplerTimestamps(LibreMetaverse.Assets.Gltf.GltfAnimationSampler)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetSamplerVec3(LibreMetaverse.Assets.Gltf.GltfAnimationSampler)`. - pub fn get_sampler_vec3( - &self, - sampler: libremetaverse::assets::gltf::GltfAnimationSampler, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocument.GetSamplerVec3(LibreMetaverse.Assets.Gltf.GltfAnimationSampler)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetSamplerWeights(LibreMetaverse.Assets.Gltf.GltfAnimationSampler)`. - pub fn get_sampler_weights( - &self, - sampler: libremetaverse::assets::gltf::GltfAnimationSampler, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocument.GetSamplerWeights(LibreMetaverse.Assets.Gltf.GltfAnimationSampler)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetTangents(LibreMetaverse.Assets.Gltf.GltfPrimitive)`. - pub fn get_tangents( - &self, - primitive: libremetaverse::assets::gltf::GltfPrimitive, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocument.GetTangents(LibreMetaverse.Assets.Gltf.GltfPrimitive)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetTexCoords(LibreMetaverse.Assets.Gltf.GltfPrimitive,System.Int32)`. - pub fn get_tex_coords( - &self, - primitive: libremetaverse::assets::gltf::GltfPrimitive, - channel: Option, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocument.GetTexCoords(LibreMetaverse.Assets.Gltf.GltfPrimitive,System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetWeights(LibreMetaverse.Assets.Gltf.GltfPrimitive,System.Int32)`. - pub fn get_weights( - &self, - primitive: libremetaverse::assets::gltf::GltfPrimitive, - set: Option, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocument.GetWeights(LibreMetaverse.Assets.Gltf.GltfPrimitive,System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.Load(System.Byte[],System.Func{System.String,System.Byte[]})`. - pub fn load( - data: Vec, - buffer_loader: Option Vec + Send + Sync>>, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocument.Load(System.Byte[],System.Func{System.String,System.Byte[]})", - ) - } - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.LoadGlb(System.Byte[])`. - pub fn load_glb( - data: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocument.LoadGlb(System.Byte[])", - ) - } - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.LoadGltf(System.String,System.Func{System.String,System.Byte[]})`. - pub fn load_gltf( - json: String, - buffer_loader: Option Vec + Send + Sync>>, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocument.LoadGltf(System.String,System.Func{System.String,System.Byte[]})", - ) - } - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.ToGlb`. - pub fn to_glb(&self) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocument.ToGlb", - ) - } - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.ToJson(System.Boolean)`. - pub fn to_json(&self, pretty: Option) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocument.ToJson(System.Boolean)", - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Accessors`. - pub fn accessors(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.Accessors" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Animations`. - pub fn animations(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.Animations" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.BufferViews`. - pub fn buffer_views(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.BufferViews" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Buffers`. - pub fn buffers(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.Buffers" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Copyright`. - pub fn copyright(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.Copyright" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Copyright`. - pub fn set_copyright(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.Copyright" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.DefaultScene`. - pub fn default_scene(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.DefaultScene" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.DefaultScene`. - pub fn set_default_scene(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.DefaultScene" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.ExtensionsRequired`. - pub fn extensions_required(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.ExtensionsRequired" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.ExtensionsUsed`. - pub fn extensions_used(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.ExtensionsUsed" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Generator`. - pub fn generator(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.Generator" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Generator`. - pub fn set_generator(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.Generator" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Images`. - pub fn images(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.Images" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Materials`. - pub fn materials(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.Materials" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Meshes`. - pub fn meshes(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.Meshes" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.MinVersion`. - pub fn min_version(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.MinVersion" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.MinVersion`. - pub fn set_min_version(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.MinVersion" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Nodes`. - pub fn nodes(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.Nodes" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Samplers`. - pub fn samplers(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.Samplers" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Scenes`. - pub fn scenes(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.Scenes" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Skins`. - pub fn skins(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.Skins" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Textures`. - pub fn textures(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.Textures" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Version`. - pub fn version(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.Version" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Version`. - pub fn set_version(&mut self, value: String) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocument.Version" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetColors(LibreMetaverse.Assets.Gltf.GltfPrimitive,System.Int32)`. + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetIndices(LibreMetaverse.Assets.Gltf.GltfPrimitive)`. + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetInverseBindMatrices(LibreMetaverse.Assets.Gltf.GltfSkin)`. + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetJoints(LibreMetaverse.Assets.Gltf.GltfPrimitive,System.Int32)`. + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetNormals(LibreMetaverse.Assets.Gltf.GltfPrimitive)`. + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetPositions(LibreMetaverse.Assets.Gltf.GltfPrimitive)`. + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetSamplerQuaternion(LibreMetaverse.Assets.Gltf.GltfAnimationSampler)`. + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetSamplerTimestamps(LibreMetaverse.Assets.Gltf.GltfAnimationSampler)`. + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetSamplerVec3(LibreMetaverse.Assets.Gltf.GltfAnimationSampler)`. + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetSamplerWeights(LibreMetaverse.Assets.Gltf.GltfAnimationSampler)`. + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetTangents(LibreMetaverse.Assets.Gltf.GltfPrimitive)`. + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetTexCoords(LibreMetaverse.Assets.Gltf.GltfPrimitive,System.Int32)`. + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.GetWeights(LibreMetaverse.Assets.Gltf.GltfPrimitive,System.Int32)`. + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.Load(System.Byte[],System.Func{System.String,System.Byte[]})`. + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.LoadGlb(System.Byte[])`. + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.LoadGltf(System.String,System.Func{System.String,System.Byte[]})`. + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.ToGlb`. + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocument.ToJson(System.Boolean)`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Accessors`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Animations`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.BufferViews`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Buffers`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Copyright`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.DefaultScene`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.ExtensionsRequired`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.ExtensionsUsed`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Generator`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Images`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Materials`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Meshes`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.MinVersion`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Nodes`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Samplers`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Scenes`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Skins`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Textures`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocument.Version`. + pub use crate::gltf::GltfDocument; /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial`. - pub struct GltfDocumentMaterial; - impl GltfDocumentMaterial { - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.#ctor", - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.AlphaCutoff`. - pub fn alpha_cutoff(&self) -> f32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.AlphaCutoff" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.AlphaCutoff`. - pub fn set_alpha_cutoff(&mut self, value: f32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.AlphaCutoff" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.AlphaMode`. - pub fn alpha_mode(&self) -> libremetaverse::assets::GltfAlphaMode { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.AlphaMode" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.AlphaMode`. - pub fn set_alpha_mode(&mut self, value: libremetaverse::assets::GltfAlphaMode) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.AlphaMode" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.BaseColorFactor`. - pub fn base_color_factor(&self) -> libremetaverse_types::Color4 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.BaseColorFactor" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.BaseColorFactor`. - pub fn set_base_color_factor(&mut self, value: libremetaverse_types::Color4) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.BaseColorFactor" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.BaseColorTexture`. - pub fn base_color_texture( - &self, - ) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.BaseColorTexture" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.BaseColorTexture`. - pub fn set_base_color_texture( - &mut self, - value: Option, - ) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.BaseColorTexture" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.DoubleSided`. - pub fn double_sided(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.DoubleSided" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.DoubleSided`. - pub fn set_double_sided(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.DoubleSided" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.EmissiveFactor`. - pub fn emissive_factor(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.EmissiveFactor" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.EmissiveFactor`. - pub fn set_emissive_factor(&mut self, value: libremetaverse_types::Vector3) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.EmissiveFactor" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.EmissiveTexture`. - pub fn emissive_texture(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.EmissiveTexture" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.EmissiveTexture`. - pub fn set_emissive_texture( - &mut self, - value: Option, - ) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.EmissiveTexture" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.MetallicFactor`. - pub fn metallic_factor(&self) -> f32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.MetallicFactor" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.MetallicFactor`. - pub fn set_metallic_factor(&mut self, value: f32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.MetallicFactor" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.MetallicRoughnessTexture`. - pub fn metallic_roughness_texture( - &self, - ) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.MetallicRoughnessTexture" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.MetallicRoughnessTexture`. - pub fn set_metallic_roughness_texture( - &mut self, - value: Option, - ) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.MetallicRoughnessTexture" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.Name`. - pub fn name(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.Name" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.Name`. - pub fn set_name(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.Name" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.NormalTexture`. - pub fn normal_texture( - &self, - ) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.NormalTexture" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.NormalTexture`. - pub fn set_normal_texture( - &mut self, - value: Option, - ) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.NormalTexture" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.OcclusionTexture`. - pub fn occlusion_texture( - &self, - ) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.OcclusionTexture" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.OcclusionTexture`. - pub fn set_occlusion_texture( - &mut self, - value: Option, - ) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.OcclusionTexture" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.RoughnessFactor`. - pub fn roughness_factor(&self) -> f32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.RoughnessFactor" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.RoughnessFactor`. - pub fn set_roughness_factor(&mut self, value: f32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.RoughnessFactor" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.#ctor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.AlphaCutoff`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.AlphaMode`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.BaseColorFactor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.BaseColorTexture`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.DoubleSided`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.EmissiveFactor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.EmissiveTexture`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.MetallicFactor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.MetallicRoughnessTexture`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.Name`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.NormalTexture`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.OcclusionTexture`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial.RoughnessFactor`. + pub use crate::gltf::GltfDocumentMaterial; /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfImage`. - pub struct GltfImage; - impl GltfImage { - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfImage.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfImage.#ctor", - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfImage.BufferView`. - pub fn buffer_view(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfImage.BufferView" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfImage.BufferView`. - pub fn set_buffer_view(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfImage.BufferView" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfImage.MimeType`. - pub fn mime_type(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfImage.MimeType" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfImage.MimeType`. - pub fn set_mime_type(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfImage.MimeType" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfImage.Name`. - pub fn name(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfImage.Name" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfImage.Name`. - pub fn set_name(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfImage.Name" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfImage.Uri`. - pub fn uri(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfImage.Uri" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfImage.Uri`. - pub fn set_uri(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfImage.Uri" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfImage.#ctor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfImage.BufferView`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfImage.MimeType`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfImage.Name`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfImage.Uri`. + pub use crate::gltf::GltfImage; /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfMesh`. - pub struct GltfMesh; - impl GltfMesh { - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfMesh.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.Gltf.GltfMesh.#ctor") - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfMesh.Name`. - pub fn name(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfMesh.Name" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfMesh.Name`. - pub fn set_name(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfMesh.Name" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfMesh.Primitives`. - pub fn primitives(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfMesh.Primitives" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfMesh.Weights`. - pub fn weights(&self) -> Option> { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfMesh.Weights" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfMesh.Weights`. - pub fn set_weights(&mut self, value: Option>) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfMesh.Weights" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfMesh.#ctor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfMesh.Name`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfMesh.Primitives`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfMesh.Weights`. + pub use crate::gltf::GltfMesh; /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfNode`. - pub struct GltfNode; - impl GltfNode { - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfNode.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.Gltf.GltfNode.#ctor") - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Children`. - pub fn children(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfNode.Children" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Matrix`. - pub fn matrix(&self) -> Option> { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfNode.Matrix" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Matrix`. - pub fn set_matrix(&mut self, value: Option>) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfNode.Matrix" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Mesh`. - pub fn mesh(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfNode.Mesh" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Mesh`. - pub fn set_mesh(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfNode.Mesh" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Name`. - pub fn name(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfNode.Name" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Name`. - pub fn set_name(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfNode.Name" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Rotation`. - pub fn rotation(&self) -> libremetaverse_types::Quaternion { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfNode.Rotation" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Rotation`. - pub fn set_rotation(&mut self, value: libremetaverse_types::Quaternion) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfNode.Rotation" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Scale`. - pub fn scale(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfNode.Scale" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Scale`. - pub fn set_scale(&mut self, value: libremetaverse_types::Vector3) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfNode.Scale" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Skin`. - pub fn skin(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfNode.Skin" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Skin`. - pub fn set_skin(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfNode.Skin" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Translation`. - pub fn translation(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfNode.Translation" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Translation`. - pub fn set_translation(&mut self, value: libremetaverse_types::Vector3) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfNode.Translation" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Weights`. - pub fn weights(&self) -> Option> { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfNode.Weights" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Weights`. - pub fn set_weights(&mut self, value: Option>) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfNode.Weights" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfNode.#ctor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Children`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Matrix`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Mesh`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Name`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Rotation`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Scale`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Skin`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Translation`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNode.Weights`. + pub use crate::gltf::GltfNode; /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfNormalTextureRef`. - pub struct GltfNormalTextureRef; - impl GltfNormalTextureRef { - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfNormalTextureRef.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfNormalTextureRef.#ctor", - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNormalTextureRef.Scale`. - pub fn scale(&self) -> f32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfNormalTextureRef.Scale" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfNormalTextureRef.Scale`. - pub fn set_scale(&mut self, value: f32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfNormalTextureRef.Scale" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfNormalTextureRef.#ctor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfNormalTextureRef.Scale`. + pub use crate::gltf::GltfNormalTextureRef; /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfOcclusionTextureRef`. - pub struct GltfOcclusionTextureRef; - impl GltfOcclusionTextureRef { - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfOcclusionTextureRef.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfOcclusionTextureRef.#ctor", - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfOcclusionTextureRef.Strength`. - pub fn strength(&self) -> f32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfOcclusionTextureRef.Strength" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfOcclusionTextureRef.Strength`. - pub fn set_strength(&mut self, value: f32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfOcclusionTextureRef.Strength" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfOcclusionTextureRef.#ctor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfOcclusionTextureRef.Strength`. + pub use crate::gltf::GltfOcclusionTextureRef; /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfPrimitive`. - pub struct GltfPrimitive; - impl GltfPrimitive { - /// C# member: `F:LibreMetaverse.Assets.Gltf.GltfPrimitive.ATTR_COLOR_0`. - pub const ATTR_COLOR_0: &'static str = "COLOR_0"; - /// C# member: `F:LibreMetaverse.Assets.Gltf.GltfPrimitive.ATTR_JOINTS_0`. - pub const ATTR_JOINTS_0: &'static str = "JOINTS_0"; - /// C# member: `F:LibreMetaverse.Assets.Gltf.GltfPrimitive.ATTR_NORMAL`. - pub const ATTR_NORMAL: &'static str = "NORMAL"; - /// C# member: `F:LibreMetaverse.Assets.Gltf.GltfPrimitive.ATTR_POSITION`. - pub const ATTR_POSITION: &'static str = "POSITION"; - /// C# member: `F:LibreMetaverse.Assets.Gltf.GltfPrimitive.ATTR_TANGENT`. - pub const ATTR_TANGENT: &'static str = "TANGENT"; - /// C# member: `F:LibreMetaverse.Assets.Gltf.GltfPrimitive.ATTR_TEXCOORD_0`. - pub const ATTR_TEXCOORD_0: &'static str = "TEXCOORD_0"; - /// C# member: `F:LibreMetaverse.Assets.Gltf.GltfPrimitive.ATTR_TEXCOORD_1`. - pub const ATTR_TEXCOORD_1: &'static str = "TEXCOORD_1"; - /// C# member: `F:LibreMetaverse.Assets.Gltf.GltfPrimitive.ATTR_WEIGHTS_0`. - pub const ATTR_WEIGHTS_0: &'static str = "WEIGHTS_0"; - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfPrimitive.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfPrimitive.#ctor", - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfPrimitive.Attributes`. - pub fn attributes(&self) -> std::collections::HashMap { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfPrimitive.Attributes" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfPrimitive.Indices`. - pub fn indices(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfPrimitive.Indices" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfPrimitive.Indices`. - pub fn set_indices(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfPrimitive.Indices" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfPrimitive.Material`. - pub fn material(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfPrimitive.Material" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfPrimitive.Material`. - pub fn set_material(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfPrimitive.Material" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfPrimitive.Mode`. - pub fn mode(&self) -> libremetaverse::assets::gltf::GltfPrimitiveMode { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfPrimitive.Mode" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfPrimitive.Mode`. - pub fn set_mode(&mut self, value: libremetaverse::assets::gltf::GltfPrimitiveMode) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfPrimitive.Mode" - ) - } - } + /// C# member: `F:LibreMetaverse.Assets.Gltf.GltfPrimitive.ATTR_COLOR_0`. + /// C# member: `F:LibreMetaverse.Assets.Gltf.GltfPrimitive.ATTR_JOINTS_0`. + /// C# member: `F:LibreMetaverse.Assets.Gltf.GltfPrimitive.ATTR_NORMAL`. + /// C# member: `F:LibreMetaverse.Assets.Gltf.GltfPrimitive.ATTR_POSITION`. + /// C# member: `F:LibreMetaverse.Assets.Gltf.GltfPrimitive.ATTR_TANGENT`. + /// C# member: `F:LibreMetaverse.Assets.Gltf.GltfPrimitive.ATTR_TEXCOORD_0`. + /// C# member: `F:LibreMetaverse.Assets.Gltf.GltfPrimitive.ATTR_TEXCOORD_1`. + /// C# member: `F:LibreMetaverse.Assets.Gltf.GltfPrimitive.ATTR_WEIGHTS_0`. + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfPrimitive.#ctor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfPrimitive.Attributes`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfPrimitive.Indices`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfPrimitive.Material`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfPrimitive.Mode`. + pub use crate::gltf::GltfPrimitive; /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfPrimitiveMode`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -20398,81 +19023,13 @@ pub mod assets { } /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfSampler`. - pub struct GltfSampler; - impl GltfSampler { - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfSampler.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfSampler.#ctor", - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfSampler.MagFilter`. - pub fn mag_filter(&self) -> libremetaverse::assets::gltf::GltfSamplerFilter { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfSampler.MagFilter" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfSampler.MagFilter`. - pub fn set_mag_filter( - &mut self, - value: libremetaverse::assets::gltf::GltfSamplerFilter, - ) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfSampler.MagFilter" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfSampler.MinFilter`. - pub fn min_filter(&self) -> libremetaverse::assets::gltf::GltfSamplerFilter { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfSampler.MinFilter" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfSampler.MinFilter`. - pub fn set_min_filter( - &mut self, - value: libremetaverse::assets::gltf::GltfSamplerFilter, - ) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfSampler.MinFilter" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfSampler.Name`. - pub fn name(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfSampler.Name" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfSampler.Name`. - pub fn set_name(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfSampler.Name" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfSampler.WrapS`. - pub fn wrap_s(&self) -> libremetaverse::assets::gltf::GltfSamplerWrap { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfSampler.WrapS" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfSampler.WrapS`. - pub fn set_wrap_s(&mut self, value: libremetaverse::assets::gltf::GltfSamplerWrap) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfSampler.WrapS" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfSampler.WrapT`. - pub fn wrap_t(&self) -> libremetaverse::assets::gltf::GltfSamplerWrap { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfSampler.WrapT" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfSampler.WrapT`. - pub fn set_wrap_t(&mut self, value: libremetaverse::assets::gltf::GltfSamplerWrap) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfSampler.WrapT" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfSampler.#ctor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfSampler.MagFilter`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfSampler.MinFilter`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfSampler.Name`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfSampler.WrapS`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfSampler.WrapT`. + pub use crate::gltf::GltfSampler; /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfSamplerFilter`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -20507,178 +19064,32 @@ pub mod assets { } /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfScene`. - pub struct GltfScene; - impl GltfScene { - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfScene.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfScene.#ctor", - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfScene.Name`. - pub fn name(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfScene.Name" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfScene.Name`. - pub fn set_name(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfScene.Name" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfScene.Nodes`. - pub fn nodes(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfScene.Nodes" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfScene.#ctor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfScene.Name`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfScene.Nodes`. + pub use crate::gltf::GltfScene; /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfSkin`. - pub struct GltfSkin; - impl GltfSkin { - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfSkin.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.Gltf.GltfSkin.#ctor") - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfSkin.InverseBindMatrices`. - pub fn inverse_bind_matrices(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfSkin.InverseBindMatrices" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfSkin.InverseBindMatrices`. - pub fn set_inverse_bind_matrices(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfSkin.InverseBindMatrices" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfSkin.Joints`. - pub fn joints(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfSkin.Joints" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfSkin.Name`. - pub fn name(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfSkin.Name" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfSkin.Name`. - pub fn set_name(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfSkin.Name" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfSkin.Skeleton`. - pub fn skeleton(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfSkin.Skeleton" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfSkin.Skeleton`. - pub fn set_skeleton(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfSkin.Skeleton" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfSkin.#ctor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfSkin.InverseBindMatrices`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfSkin.Joints`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfSkin.Name`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfSkin.Skeleton`. + pub use crate::gltf::GltfSkin; /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfTexture`. - pub struct GltfTexture; - impl GltfTexture { - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfTexture.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfTexture.#ctor", - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfTexture.Name`. - pub fn name(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfTexture.Name" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfTexture.Name`. - pub fn set_name(&mut self, value: Option) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfTexture.Name" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfTexture.Sampler`. - pub fn sampler(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfTexture.Sampler" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfTexture.Sampler`. - pub fn set_sampler(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfTexture.Sampler" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfTexture.Source`. - pub fn source(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfTexture.Source" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfTexture.Source`. - pub fn set_source(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfTexture.Source" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfTexture.#ctor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfTexture.Name`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfTexture.Sampler`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfTexture.Source`. + pub use crate::gltf::GltfTexture; /// C# type: `T:LibreMetaverse.Assets.Gltf.GltfTextureRef`. - pub struct GltfTextureRef; - impl GltfTextureRef { - /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfTextureRef.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.Gltf.GltfTextureRef.#ctor", - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfTextureRef.Index`. - pub fn index(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfTextureRef.Index" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfTextureRef.Index`. - pub fn set_index(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfTextureRef.Index" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfTextureRef.TexCoord`. - pub fn tex_coord(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfTextureRef.TexCoord" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfTextureRef.TexCoord`. - pub fn set_tex_coord(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfTextureRef.TexCoord" - ) - } - /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfTextureRef.Transform`. - pub fn transform(&self) -> libremetaverse::assets::GltfTextureTransform { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfTextureRef.Transform" - ) - } - /// Setter for C# member: `P:LibreMetaverse.Assets.Gltf.GltfTextureRef.Transform`. - pub fn set_transform(&mut self, value: libremetaverse::assets::GltfTextureTransform) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.Gltf.GltfTextureRef.Transform" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.Gltf.GltfTextureRef.#ctor`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfTextureRef.Index`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfTextureRef.TexCoord`. + /// C# member: `P:LibreMetaverse.Assets.Gltf.GltfTextureRef.Transform`. + pub use crate::gltf::GltfTextureRef; } } @@ -20841,163 +19252,50 @@ pub mod imaging { pub mod import_export { /// C# type: `T:LibreMetaverse.ImportExport.ColladaLoader`. - pub struct ColladaLoader; - impl ColladaLoader { - /// C# member: `M:LibreMetaverse.ImportExport.ColladaLoader.#ctor(LibreMetaverse.Imaging.ITextureCodec)`. - pub fn new( - texture_codec: Option>, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ImportExport.ColladaLoader.#ctor(LibreMetaverse.Imaging.ITextureCodec)", - ) - } - /// C# member: `M:LibreMetaverse.ImportExport.ColladaLoader.Load(System.String,System.Boolean)`. - pub fn load( - &self, - filename: String, - load_images: bool, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ImportExport.ColladaLoader.Load(System.String,System.Boolean)", - ) - } - } + /// C# member: `M:LibreMetaverse.ImportExport.ColladaLoader.#ctor(LibreMetaverse.Imaging.ITextureCodec)`. + /// C# member: `M:LibreMetaverse.ImportExport.ColladaLoader.Load(System.String,System.Boolean)`. + pub use crate::model_workflow::ColladaLoader; /// C# type: `T:LibreMetaverse.ImportExport.ModelFace`. - pub struct ModelFace { - /// C# member: `F:LibreMetaverse.ImportExport.ModelFace.Indices`. - pub indices: Vec, - /// C# member: `F:LibreMetaverse.ImportExport.ModelFace.Material`. - pub material: libremetaverse::import_export::ModelMaterial, - /// C# member: `F:LibreMetaverse.ImportExport.ModelFace.MaterialID`. - pub material_id: String, - /// C# member: `F:LibreMetaverse.ImportExport.ModelFace.Vertices`. - pub vertices: Vec, - } - impl ModelFace { - /// C# member: `M:LibreMetaverse.ImportExport.ModelFace.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.ImportExport.ModelFace.#ctor") - } - /// C# member: `M:LibreMetaverse.ImportExport.ModelFace.AddVertex(LibreMetaverse.Rendering.Vertex)`. - pub fn add_vertex(&self, v: libremetaverse::rendering::Vertex) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ImportExport.ModelFace.AddVertex(LibreMetaverse.Rendering.Vertex)", - ) - } - } + /// C# member: `F:LibreMetaverse.ImportExport.ModelFace.Indices`. + /// C# member: `F:LibreMetaverse.ImportExport.ModelFace.Material`. + /// C# member: `F:LibreMetaverse.ImportExport.ModelFace.MaterialID`. + /// C# member: `F:LibreMetaverse.ImportExport.ModelFace.Vertices`. + /// C# member: `M:LibreMetaverse.ImportExport.ModelFace.#ctor`. + /// C# member: `M:LibreMetaverse.ImportExport.ModelFace.AddVertex(LibreMetaverse.Rendering.Vertex)`. + pub use crate::model_workflow::ModelFace; /// C# type: `T:LibreMetaverse.ImportExport.ModelMaterial`. - pub struct ModelMaterial { - /// C# member: `F:LibreMetaverse.ImportExport.ModelMaterial.DiffuseColor`. - pub diffuse_color: libremetaverse_types::Color4, - /// C# member: `F:LibreMetaverse.ImportExport.ModelMaterial.ID`. - pub id: String, - /// C# member: `F:LibreMetaverse.ImportExport.ModelMaterial.Texture`. - pub texture: String, - /// C# member: `F:LibreMetaverse.ImportExport.ModelMaterial.TextureData`. - pub texture_data: Vec, - } - impl ModelMaterial { - /// C# member: `M:LibreMetaverse.ImportExport.ModelMaterial.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ImportExport.ModelMaterial.#ctor", - ) - } - } + /// C# member: `F:LibreMetaverse.ImportExport.ModelMaterial.DiffuseColor`. + /// C# member: `F:LibreMetaverse.ImportExport.ModelMaterial.ID`. + /// C# member: `F:LibreMetaverse.ImportExport.ModelMaterial.Texture`. + /// C# member: `F:LibreMetaverse.ImportExport.ModelMaterial.TextureData`. + /// C# member: `M:LibreMetaverse.ImportExport.ModelMaterial.#ctor`. + pub use crate::model_workflow::ModelMaterial; /// C# type: `T:LibreMetaverse.ImportExport.ModelPrim`. - pub struct ModelPrim { - /// C# member: `F:LibreMetaverse.ImportExport.ModelPrim.Asset`. - pub asset: Vec, - /// C# member: `F:LibreMetaverse.ImportExport.ModelPrim.BoundMax`. - pub bound_max: libremetaverse_types::Vector3, - /// C# member: `F:LibreMetaverse.ImportExport.ModelPrim.BoundMin`. - pub bound_min: libremetaverse_types::Vector3, - /// C# member: `F:LibreMetaverse.ImportExport.ModelPrim.Faces`. - pub faces: Vec, - /// C# member: `F:LibreMetaverse.ImportExport.ModelPrim.ID`. - pub id: String, - /// C# member: `F:LibreMetaverse.ImportExport.ModelPrim.Position`. - pub position: libremetaverse_types::Vector3, - /// C# member: `F:LibreMetaverse.ImportExport.ModelPrim.Positions`. - pub positions: Vec, - /// C# member: `F:LibreMetaverse.ImportExport.ModelPrim.Rotation`. - pub rotation: libremetaverse_types::Quaternion, - /// C# member: `F:LibreMetaverse.ImportExport.ModelPrim.Scale`. - pub scale: libremetaverse_types::Vector3, - } - impl ModelPrim { - /// C# member: `M:LibreMetaverse.ImportExport.ModelPrim.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.ImportExport.ModelPrim.#ctor") - } - /// C# member: `M:LibreMetaverse.ImportExport.ModelPrim.CreateAsset(LibreMetaverse.UUID)`. - pub fn create_asset( - &self, - creator: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ImportExport.ModelPrim.CreateAsset(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.ImportExport.ModelPrim.PhysicsStub`. - pub fn physics_stub() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ImportExport.ModelPrim.PhysicsStub", - ) - } - } + /// C# member: `F:LibreMetaverse.ImportExport.ModelPrim.Asset`. + /// C# member: `F:LibreMetaverse.ImportExport.ModelPrim.BoundMax`. + /// C# member: `F:LibreMetaverse.ImportExport.ModelPrim.BoundMin`. + /// C# member: `F:LibreMetaverse.ImportExport.ModelPrim.Faces`. + /// C# member: `F:LibreMetaverse.ImportExport.ModelPrim.ID`. + /// C# member: `F:LibreMetaverse.ImportExport.ModelPrim.Position`. + /// C# member: `F:LibreMetaverse.ImportExport.ModelPrim.Positions`. + /// C# member: `F:LibreMetaverse.ImportExport.ModelPrim.Rotation`. + /// C# member: `F:LibreMetaverse.ImportExport.ModelPrim.Scale`. + /// C# member: `M:LibreMetaverse.ImportExport.ModelPrim.#ctor`. + /// C# member: `M:LibreMetaverse.ImportExport.ModelPrim.CreateAsset(LibreMetaverse.UUID)`. + /// C# member: `M:LibreMetaverse.ImportExport.ModelPrim.PhysicsStub`. + pub use crate::model_workflow::ModelPrim; /// C# type: `T:LibreMetaverse.ImportExport.ModelUploader`. - pub struct ModelUploader { - /// C# member: `F:LibreMetaverse.ImportExport.ModelUploader.IncludePhysicsStub`. - pub include_physics_stub: bool, - /// C# member: `F:LibreMetaverse.ImportExport.ModelUploader.UseModelAsPhysics`. - pub use_model_as_physics: bool, - } - impl ModelUploader { - /// C# member: `M:LibreMetaverse.ImportExport.ModelUploader.#ctor(LibreMetaverse.GridClient,System.Collections.Generic.List{LibreMetaverse.ImportExport.ModelPrim},System.String,System.String)`. - pub fn new( - client: libremetaverse::GridClient, - prims: Vec, - new_inv_name: String, - new_inv_desc: String, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ImportExport.ModelUploader.#ctor(LibreMetaverse.GridClient,System.Collections.Generic.List{LibreMetaverse.ImportExport.ModelPrim},System.String,System.String)", - ) - } - /// C# member: `M:LibreMetaverse.ImportExport.ModelUploader.PerformUploadAsync(System.Uri,System.Threading.CancellationToken)`. - pub async fn perform_upload( - &self, - uploader: libremetaverse_types::compat::Uri, - cancellation_token: libremetaverse_types::compat::CancellationToken, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ImportExport.ModelUploader.PerformUploadAsync(System.Uri,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.ImportExport.ModelUploader.PrepareUploadAsync(System.Threading.CancellationToken)`. - pub async fn prepare_upload( - &self, - cancellation_token: libremetaverse_types::compat::CancellationToken, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ImportExport.ModelUploader.PrepareUploadAsync(System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.ImportExport.ModelUploader.UploadAsync(System.Threading.CancellationToken)`. - pub async fn upload( - &self, - cancellation_token: libremetaverse_types::compat::CancellationToken, - ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ImportExport.ModelUploader.UploadAsync(System.Threading.CancellationToken)", - ) - } - } + /// C# member: `F:LibreMetaverse.ImportExport.ModelUploader.IncludePhysicsStub`. + /// C# member: `F:LibreMetaverse.ImportExport.ModelUploader.UseModelAsPhysics`. + /// C# member: `M:LibreMetaverse.ImportExport.ModelUploader.#ctor(LibreMetaverse.GridClient,System.Collections.Generic.List{LibreMetaverse.ImportExport.ModelPrim},System.String,System.String)`. + /// C# member: `M:LibreMetaverse.ImportExport.ModelUploader.PerformUploadAsync(System.Uri,System.Threading.CancellationToken)`. + /// C# member: `M:LibreMetaverse.ImportExport.ModelUploader.PrepareUploadAsync(System.Threading.CancellationToken)`. + /// C# member: `M:LibreMetaverse.ImportExport.ModelUploader.UploadAsync(System.Threading.CancellationToken)`. + pub use crate::model_workflow::ModelUploader; pub mod collada14 { /// C# type: `T:LibreMetaverse.ImportExport.Collada14.COLLADA`. diff --git a/crates/libremetaverse/src/gltf.rs b/crates/libremetaverse/src/gltf.rs new file mode 100644 index 0000000..d62b788 --- /dev/null +++ b/crates/libremetaverse/src/gltf.rs @@ -0,0 +1,2599 @@ +//! Bounded, native glTF 2.0 document and binary-buffer support. + +// Accessor methods and owned DTO parameters are fixed by the compatibility map; +// explicit numeric conversions are guarded by the format bounds below. +#![allow(clippy::pedantic)] + +use std::collections::HashMap; + +use base64::Engine as _; +use libremetaverse_types::{Color4, Error, Matrix4, Quaternion, Vector2, Vector3, Vector4}; +use serde_json::{Map, Value, json}; + +use crate::assets::GltfAlphaMode; +use crate::assets::GltfTextureTransform; +use crate::assets::gltf::{ + GltfAccessorType, GltfAnimationInterpolation, GltfAnimationTargetPath, GltfComponentType, + GltfPrimitiveMode, GltfSamplerFilter, GltfSamplerWrap, +}; + +const MAX_DOCUMENT_BYTES: usize = 64 * 1024 * 1024; +const MAX_BUFFER_BYTES: usize = 256 * 1024 * 1024; +const MAX_COLLECTION_ITEMS: usize = 1_000_000; + +fn parse_error(context: &'static str) -> Error { + Error::Parse { + position: 0, + context, + } +} + +fn usize_from_i32(value: i32, context: &'static str) -> Result { + usize::try_from(value).map_err(|_| parse_error(context)) +} + +fn i32_value(value: Option<&Value>, default: i32) -> Result { + match value { + None | Some(Value::Null) => Ok(default), + Some(value) => value + .as_i64() + .and_then(|v| i32::try_from(v).ok()) + .ok_or_else(|| parse_error("glTF integer")), + } +} + +fn f32_value(value: Option<&Value>, default: f32) -> Result { + match value { + None | Some(Value::Null) => Ok(default), + Some(value) => value + .as_f64() + .filter(|v| v.is_finite()) + .map(|v| v as f32) + .ok_or_else(|| parse_error("glTF finite number")), + } +} + +fn optional_string(value: Option<&Value>) -> Result, Error> { + match value { + None | Some(Value::Null) => Ok(None), + Some(value) => value + .as_str() + .map(|v| Some(v.to_owned())) + .ok_or_else(|| parse_error("glTF string")), + } +} + +fn number_array(value: Option<&Value>) -> Result>, Error> { + let Some(value) = value else { return Ok(None) }; + let values = value + .as_array() + .ok_or_else(|| parse_error("glTF number array"))?; + if values.len() > 16 { + return Err(parse_error("glTF number array limit")); + } + values + .iter() + .map(|v| { + v.as_f64() + .filter(|n| n.is_finite()) + .ok_or_else(|| parse_error("glTF finite number array")) + }) + .collect::, _>>() + .map(Some) +} + +fn int_array(value: Option<&Value>) -> Result, Error> { + let Some(value) = value else { + return Ok(Vec::new()); + }; + let values = value + .as_array() + .ok_or_else(|| parse_error("glTF integer array"))?; + if values.len() > MAX_COLLECTION_ITEMS { + return Err(parse_error("glTF collection limit")); + } + values + .iter() + .map(|v| { + v.as_i64() + .and_then(|n| i32::try_from(n).ok()) + .ok_or_else(|| parse_error("glTF integer array")) + }) + .collect() +} + +fn finite_vec(value: Option<&Value>, count: usize, defaults: &[f32]) -> Result, Error> { + let Some(value) = value else { + return Ok(defaults.to_vec()); + }; + let values = value.as_array().ok_or_else(|| parse_error("glTF vector"))?; + if values.len() != count { + return Err(parse_error("glTF vector length")); + } + values + .iter() + .map(|v| { + v.as_f64() + .filter(|n| n.is_finite()) + .map(|n| n as f32) + .ok_or_else(|| parse_error("glTF finite vector")) + }) + .collect() +} + +#[derive(Clone, Debug)] +pub struct GltfAccessor { + buffer_view: i32, + byte_offset: i32, + component_type: GltfComponentType, + normalized: bool, + count: i32, + type_: GltfAccessorType, + max: Option>, + min: Option>, + name: Option, +} + +impl GltfAccessor { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn buffer_view(&self) -> i32 { + self.buffer_view + } + pub fn set_buffer_view(&mut self, value: i32) { + self.buffer_view = value; + } + pub fn byte_offset(&self) -> i32 { + self.byte_offset + } + pub fn set_byte_offset(&mut self, value: i32) { + self.byte_offset = value; + } + pub fn component_byte_size(&self) -> i32 { + component_size(self.component_type) as i32 + } + pub fn component_count(&self) -> i32 { + accessor_components(self.type_) as i32 + } + pub fn component_type(&self) -> GltfComponentType { + self.component_type + } + pub fn set_component_type(&mut self, value: GltfComponentType) { + self.component_type = value; + } + pub fn count(&self) -> i32 { + self.count + } + pub fn set_count(&mut self, value: i32) { + self.count = value; + } + pub fn default_stride(&self) -> i32 { + self.component_byte_size() * self.component_count() + } + pub fn max(&self) -> Option> { + self.max.clone() + } + pub fn set_max(&mut self, value: Option>) { + self.max = value; + } + pub fn min(&self) -> Option> { + self.min.clone() + } + pub fn set_min(&mut self, value: Option>) { + self.min = value; + } + pub fn name(&self) -> Option { + self.name.clone() + } + pub fn set_name(&mut self, value: Option) { + self.name = value; + } + pub fn normalized(&self) -> bool { + self.normalized + } + pub fn set_normalized(&mut self, value: bool) { + self.normalized = value; + } + pub fn type_(&self) -> GltfAccessorType { + self.type_ + } + pub fn set_type_(&mut self, value: GltfAccessorType) { + self.type_ = value; + } +} + +impl Default for GltfAccessor { + fn default() -> Self { + Self { + buffer_view: -1, + byte_offset: 0, + component_type: GltfComponentType::Float, + normalized: false, + count: 0, + type_: GltfAccessorType::Scalar, + max: None, + min: None, + name: None, + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct GltfAnimation { + channels: Vec, + name: Option, + samplers: Vec, +} +impl GltfAnimation { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn channels(&self) -> Vec { + self.channels.clone() + } + pub fn name(&self) -> Option { + self.name.clone() + } + pub fn set_name(&mut self, value: Option) { + self.name = value; + } + pub fn samplers(&self) -> Vec { + self.samplers.clone() + } +} + +#[derive(Clone, Debug)] +pub struct GltfAnimationChannel { + sampler: i32, + target: GltfAnimationChannelTarget, +} +impl Default for GltfAnimationChannel { + fn default() -> Self { + Self { + sampler: -1, + target: GltfAnimationChannelTarget::default(), + } + } +} +impl GltfAnimationChannel { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn sampler(&self) -> i32 { + self.sampler + } + pub fn set_sampler(&mut self, value: i32) { + self.sampler = value; + } + pub fn target(&self) -> GltfAnimationChannelTarget { + self.target.clone() + } +} + +#[derive(Clone, Debug)] +pub struct GltfAnimationChannelTarget { + node: i32, + path: GltfAnimationTargetPath, +} +impl Default for GltfAnimationChannelTarget { + fn default() -> Self { + Self { + node: -1, + path: GltfAnimationTargetPath::Translation, + } + } +} +impl GltfAnimationChannelTarget { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn node(&self) -> i32 { + self.node + } + pub fn set_node(&mut self, value: i32) { + self.node = value; + } + pub fn path(&self) -> GltfAnimationTargetPath { + self.path + } + pub fn set_path(&mut self, value: GltfAnimationTargetPath) { + self.path = value; + } +} + +#[derive(Clone, Debug)] +pub struct GltfAnimationSampler { + input: i32, + interpolation: GltfAnimationInterpolation, + output: i32, +} +impl Default for GltfAnimationSampler { + fn default() -> Self { + Self { + input: -1, + interpolation: GltfAnimationInterpolation::Linear, + output: -1, + } + } +} +impl GltfAnimationSampler { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn input(&self) -> i32 { + self.input + } + pub fn set_input(&mut self, value: i32) { + self.input = value; + } + pub fn interpolation(&self) -> GltfAnimationInterpolation { + self.interpolation + } + pub fn set_interpolation(&mut self, value: GltfAnimationInterpolation) { + self.interpolation = value; + } + pub fn output(&self) -> i32 { + self.output + } + pub fn set_output(&mut self, value: i32) { + self.output = value; + } +} + +#[derive(Clone, Debug, Default)] +pub struct GltfBuffer { + byte_length: i32, + data: Option>, + name: Option, + uri: Option, +} +impl GltfBuffer { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn byte_length(&self) -> i32 { + self.byte_length + } + pub fn set_byte_length(&mut self, value: i32) { + self.byte_length = value; + } + pub fn data(&self) -> Option> { + self.data.clone() + } + pub fn set_data(&mut self, value: Option>) { + self.data = value; + } + pub fn name(&self) -> Option { + self.name.clone() + } + pub fn set_name(&mut self, value: Option) { + self.name = value; + } + pub fn uri(&self) -> Option { + self.uri.clone() + } + pub fn set_uri(&mut self, value: Option) { + self.uri = value; + } +} + +#[derive(Clone, Debug)] +pub struct GltfBufferView { + buffer: i32, + byte_length: i32, + byte_offset: i32, + byte_stride: i32, + name: Option, + target: i32, +} +impl Default for GltfBufferView { + fn default() -> Self { + Self { + buffer: -1, + byte_length: 0, + byte_offset: 0, + byte_stride: 0, + name: None, + target: -1, + } + } +} +impl GltfBufferView { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn buffer(&self) -> i32 { + self.buffer + } + pub fn set_buffer(&mut self, value: i32) { + self.buffer = value; + } + pub fn byte_length(&self) -> i32 { + self.byte_length + } + pub fn set_byte_length(&mut self, value: i32) { + self.byte_length = value; + } + pub fn byte_offset(&self) -> i32 { + self.byte_offset + } + pub fn set_byte_offset(&mut self, value: i32) { + self.byte_offset = value; + } + pub fn byte_stride(&self) -> i32 { + self.byte_stride + } + pub fn set_byte_stride(&mut self, value: i32) { + self.byte_stride = value; + } + pub fn name(&self) -> Option { + self.name.clone() + } + pub fn set_name(&mut self, value: Option) { + self.name = value; + } + pub fn target(&self) -> i32 { + self.target + } + pub fn set_target(&mut self, value: i32) { + self.target = value; + } +} + +#[derive(Clone, Debug)] +pub struct GltfTextureRef { + index: i32, + tex_coord: i32, + transform: GltfTextureTransform, +} +impl Default for GltfTextureRef { + fn default() -> Self { + Self { + index: -1, + tex_coord: 0, + transform: GltfTextureTransform::default(), + } + } +} +impl GltfTextureRef { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn index(&self) -> i32 { + self.index + } + pub fn set_index(&mut self, value: i32) { + self.index = value; + } + pub fn tex_coord(&self) -> i32 { + self.tex_coord + } + pub fn set_tex_coord(&mut self, value: i32) { + self.tex_coord = value; + } + pub fn transform(&self) -> GltfTextureTransform { + self.transform + } + pub fn set_transform(&mut self, value: GltfTextureTransform) { + self.transform = value; + } +} + +#[derive(Clone, Debug)] +pub struct GltfNormalTextureRef { + inner: GltfTextureRef, + scale: f32, +} +impl Default for GltfNormalTextureRef { + fn default() -> Self { + Self { + inner: GltfTextureRef::default(), + scale: 1.0, + } + } +} +impl GltfNormalTextureRef { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn scale(&self) -> f32 { + self.scale + } + pub fn set_scale(&mut self, value: f32) { + self.scale = value; + } +} + +#[derive(Clone, Debug)] +pub struct GltfOcclusionTextureRef { + inner: GltfTextureRef, + strength: f32, +} +impl Default for GltfOcclusionTextureRef { + fn default() -> Self { + Self { + inner: GltfTextureRef::default(), + strength: 1.0, + } + } +} +impl GltfOcclusionTextureRef { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn strength(&self) -> f32 { + self.strength + } + pub fn set_strength(&mut self, value: f32) { + self.strength = value; + } +} + +#[derive(Clone, Debug)] +pub struct GltfDocumentMaterial { + name: Option, + base_color_factor: Color4, + base_color_texture: Option, + metallic_factor: f32, + metallic_roughness_texture: Option, + roughness_factor: f32, + normal_texture: Option, + occlusion_texture: Option, + emissive_texture: Option, + emissive_factor: Vector3, + alpha_mode: GltfAlphaMode, + alpha_cutoff: f32, + double_sided: bool, +} +impl Default for GltfDocumentMaterial { + fn default() -> Self { + Self { + name: None, + base_color_factor: Color4::white(), + base_color_texture: None, + metallic_factor: 1.0, + metallic_roughness_texture: None, + roughness_factor: 1.0, + normal_texture: None, + occlusion_texture: None, + emissive_texture: None, + emissive_factor: Vector3::zero(), + alpha_mode: GltfAlphaMode::Opaque, + alpha_cutoff: 0.5, + double_sided: false, + } + } +} +impl GltfDocumentMaterial { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn name(&self) -> Option { + self.name.clone() + } + pub fn set_name(&mut self, value: Option) { + self.name = value; + } + pub fn base_color_factor(&self) -> Color4 { + self.base_color_factor + } + pub fn set_base_color_factor(&mut self, value: Color4) { + self.base_color_factor = value; + } + pub fn base_color_texture(&self) -> Option { + self.base_color_texture.clone() + } + pub fn set_base_color_texture(&mut self, value: Option) { + self.base_color_texture = value; + } + pub fn metallic_factor(&self) -> f32 { + self.metallic_factor + } + pub fn set_metallic_factor(&mut self, value: f32) { + self.metallic_factor = value; + } + pub fn metallic_roughness_texture(&self) -> Option { + self.metallic_roughness_texture.clone() + } + pub fn set_metallic_roughness_texture(&mut self, value: Option) { + self.metallic_roughness_texture = value; + } + pub fn roughness_factor(&self) -> f32 { + self.roughness_factor + } + pub fn set_roughness_factor(&mut self, value: f32) { + self.roughness_factor = value; + } + pub fn normal_texture(&self) -> Option { + self.normal_texture.clone() + } + pub fn set_normal_texture(&mut self, value: Option) { + self.normal_texture = value; + } + pub fn occlusion_texture(&self) -> Option { + self.occlusion_texture.clone() + } + pub fn set_occlusion_texture(&mut self, value: Option) { + self.occlusion_texture = value; + } + pub fn emissive_texture(&self) -> Option { + self.emissive_texture.clone() + } + pub fn set_emissive_texture(&mut self, value: Option) { + self.emissive_texture = value; + } + pub fn emissive_factor(&self) -> Vector3 { + self.emissive_factor + } + pub fn set_emissive_factor(&mut self, value: Vector3) { + self.emissive_factor = value; + } + pub fn alpha_mode(&self) -> GltfAlphaMode { + self.alpha_mode + } + pub fn set_alpha_mode(&mut self, value: GltfAlphaMode) { + self.alpha_mode = value; + } + pub fn alpha_cutoff(&self) -> f32 { + self.alpha_cutoff + } + pub fn set_alpha_cutoff(&mut self, value: f32) { + self.alpha_cutoff = value; + } + pub fn double_sided(&self) -> bool { + self.double_sided + } + pub fn set_double_sided(&mut self, value: bool) { + self.double_sided = value; + } +} + +#[derive(Clone, Debug)] +pub struct GltfImage { + buffer_view: i32, + mime_type: Option, + name: Option, + uri: Option, +} +impl Default for GltfImage { + fn default() -> Self { + Self { + buffer_view: -1, + mime_type: None, + name: None, + uri: None, + } + } +} +impl GltfImage { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn buffer_view(&self) -> i32 { + self.buffer_view + } + pub fn set_buffer_view(&mut self, value: i32) { + self.buffer_view = value; + } + pub fn mime_type(&self) -> Option { + self.mime_type.clone() + } + pub fn set_mime_type(&mut self, value: Option) { + self.mime_type = value; + } + pub fn name(&self) -> Option { + self.name.clone() + } + pub fn set_name(&mut self, value: Option) { + self.name = value; + } + pub fn uri(&self) -> Option { + self.uri.clone() + } + pub fn set_uri(&mut self, value: Option) { + self.uri = value; + } +} + +#[derive(Clone, Debug)] +pub struct GltfPrimitive { + attributes: HashMap, + indices: i32, + material: i32, + mode: GltfPrimitiveMode, +} +impl Default for GltfPrimitive { + fn default() -> Self { + Self { + attributes: HashMap::new(), + indices: -1, + material: -1, + mode: GltfPrimitiveMode::Triangles, + } + } +} +impl GltfPrimitive { + pub const ATTR_COLOR_0: &'static str = "COLOR_0"; + pub const ATTR_JOINTS_0: &'static str = "JOINTS_0"; + pub const ATTR_NORMAL: &'static str = "NORMAL"; + pub const ATTR_POSITION: &'static str = "POSITION"; + pub const ATTR_TANGENT: &'static str = "TANGENT"; + pub const ATTR_TEXCOORD_0: &'static str = "TEXCOORD_0"; + pub const ATTR_TEXCOORD_1: &'static str = "TEXCOORD_1"; + pub const ATTR_WEIGHTS_0: &'static str = "WEIGHTS_0"; + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn attributes(&self) -> HashMap { + self.attributes.clone() + } + pub fn indices(&self) -> i32 { + self.indices + } + pub fn set_indices(&mut self, value: i32) { + self.indices = value; + } + pub fn material(&self) -> i32 { + self.material + } + pub fn set_material(&mut self, value: i32) { + self.material = value; + } + pub fn mode(&self) -> GltfPrimitiveMode { + self.mode + } + pub fn set_mode(&mut self, value: GltfPrimitiveMode) { + self.mode = value; + } +} + +#[derive(Clone, Debug, Default)] +pub struct GltfMesh { + name: Option, + primitives: Vec, + weights: Option>, +} +impl GltfMesh { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn name(&self) -> Option { + self.name.clone() + } + pub fn set_name(&mut self, value: Option) { + self.name = value; + } + pub fn primitives(&self) -> Vec { + self.primitives.clone() + } + pub fn weights(&self) -> Option> { + self.weights.clone() + } + pub fn set_weights(&mut self, value: Option>) { + self.weights = value; + } +} + +#[derive(Clone, Debug)] +pub struct GltfNode { + children: Vec, + matrix: Option>, + mesh: i32, + name: Option, + rotation: Quaternion, + scale: Vector3, + skin: i32, + translation: Vector3, + weights: Option>, +} +impl Default for GltfNode { + fn default() -> Self { + Self { + children: Vec::new(), + matrix: None, + mesh: -1, + name: None, + rotation: Quaternion::identity(), + scale: Vector3::one(), + skin: -1, + translation: Vector3::zero(), + weights: None, + } + } +} +impl GltfNode { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn children(&self) -> Vec { + self.children.clone() + } + pub fn matrix(&self) -> Option> { + self.matrix + } + pub fn set_matrix(&mut self, value: Option>) { + self.matrix = value; + } + pub fn mesh(&self) -> i32 { + self.mesh + } + pub fn set_mesh(&mut self, value: i32) { + self.mesh = value; + } + pub fn name(&self) -> Option { + self.name.clone() + } + pub fn set_name(&mut self, value: Option) { + self.name = value; + } + pub fn rotation(&self) -> Quaternion { + self.rotation + } + pub fn set_rotation(&mut self, value: Quaternion) { + self.rotation = value; + } + pub fn scale(&self) -> Vector3 { + self.scale + } + pub fn set_scale(&mut self, value: Vector3) { + self.scale = value; + } + pub fn skin(&self) -> i32 { + self.skin + } + pub fn set_skin(&mut self, value: i32) { + self.skin = value; + } + pub fn translation(&self) -> Vector3 { + self.translation + } + pub fn set_translation(&mut self, value: Vector3) { + self.translation = value; + } + pub fn weights(&self) -> Option> { + self.weights.clone() + } + pub fn set_weights(&mut self, value: Option>) { + self.weights = value; + } +} + +#[derive(Clone, Debug)] +pub struct GltfSampler { + mag_filter: GltfSamplerFilter, + min_filter: GltfSamplerFilter, + name: Option, + wrap_s: GltfSamplerWrap, + wrap_t: GltfSamplerWrap, +} +impl Default for GltfSampler { + fn default() -> Self { + Self { + mag_filter: GltfSamplerFilter::Linear, + min_filter: GltfSamplerFilter::LinearMipmapLinear, + name: None, + wrap_s: GltfSamplerWrap::Repeat, + wrap_t: GltfSamplerWrap::Repeat, + } + } +} +impl GltfSampler { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn mag_filter(&self) -> GltfSamplerFilter { + self.mag_filter + } + pub fn set_mag_filter(&mut self, value: GltfSamplerFilter) { + self.mag_filter = value; + } + pub fn min_filter(&self) -> GltfSamplerFilter { + self.min_filter + } + pub fn set_min_filter(&mut self, value: GltfSamplerFilter) { + self.min_filter = value; + } + pub fn name(&self) -> Option { + self.name.clone() + } + pub fn set_name(&mut self, value: Option) { + self.name = value; + } + pub fn wrap_s(&self) -> GltfSamplerWrap { + self.wrap_s + } + pub fn set_wrap_s(&mut self, value: GltfSamplerWrap) { + self.wrap_s = value; + } + pub fn wrap_t(&self) -> GltfSamplerWrap { + self.wrap_t + } + pub fn set_wrap_t(&mut self, value: GltfSamplerWrap) { + self.wrap_t = value; + } +} + +#[derive(Clone, Debug, Default)] +pub struct GltfScene { + name: Option, + nodes: Vec, +} +impl GltfScene { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn name(&self) -> Option { + self.name.clone() + } + pub fn set_name(&mut self, value: Option) { + self.name = value; + } + pub fn nodes(&self) -> Vec { + self.nodes.clone() + } +} + +#[derive(Clone, Debug)] +pub struct GltfSkin { + inverse_bind_matrices: i32, + joints: Vec, + name: Option, + skeleton: i32, +} +impl Default for GltfSkin { + fn default() -> Self { + Self { + inverse_bind_matrices: -1, + joints: Vec::new(), + name: None, + skeleton: -1, + } + } +} +impl GltfSkin { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn inverse_bind_matrices(&self) -> i32 { + self.inverse_bind_matrices + } + pub fn set_inverse_bind_matrices(&mut self, value: i32) { + self.inverse_bind_matrices = value; + } + pub fn joints(&self) -> Vec { + self.joints.clone() + } + pub fn name(&self) -> Option { + self.name.clone() + } + pub fn set_name(&mut self, value: Option) { + self.name = value; + } + pub fn skeleton(&self) -> i32 { + self.skeleton + } + pub fn set_skeleton(&mut self, value: i32) { + self.skeleton = value; + } +} + +#[derive(Clone, Debug)] +pub struct GltfTexture { + name: Option, + sampler: i32, + source: i32, +} +impl Default for GltfTexture { + fn default() -> Self { + Self { + name: None, + sampler: -1, + source: -1, + } + } +} +impl GltfTexture { + pub fn new() -> Result { + Ok(Self::default()) + } + pub fn name(&self) -> Option { + self.name.clone() + } + pub fn set_name(&mut self, value: Option) { + self.name = value; + } + pub fn sampler(&self) -> i32 { + self.sampler + } + pub fn set_sampler(&mut self, value: i32) { + self.sampler = value; + } + pub fn source(&self) -> i32 { + self.source + } + pub fn set_source(&mut self, value: i32) { + self.source = value; + } +} + +#[derive(Clone, Debug)] +pub struct GltfDocument { + accessors: Vec, + animations: Vec, + buffer_views: Vec, + buffers: Vec, + copyright: Option, + default_scene: i32, + extensions_required: Vec, + extensions_used: Vec, + generator: Option, + images: Vec, + materials: Vec, + meshes: Vec, + min_version: Option, + nodes: Vec, + samplers: Vec, + scenes: Vec, + skins: Vec, + textures: Vec, + version: String, +} + +impl Default for GltfDocument { + fn default() -> Self { + Self { + accessors: Vec::new(), + animations: Vec::new(), + buffer_views: Vec::new(), + buffers: Vec::new(), + copyright: None, + default_scene: -1, + extensions_required: Vec::new(), + extensions_used: Vec::new(), + generator: None, + images: Vec::new(), + materials: Vec::new(), + meshes: Vec::new(), + min_version: None, + nodes: Vec::new(), + samplers: Vec::new(), + scenes: Vec::new(), + skins: Vec::new(), + textures: Vec::new(), + version: "2.0".to_owned(), + } + } +} + +impl GltfDocument { + pub fn new() -> Result { + Ok(Self::default()) + } + + pub fn load( + data: Vec, + buffer_loader: Option Vec + Send + Sync>>, + ) -> Result { + if data.len() > MAX_DOCUMENT_BYTES { + return Err(parse_error("glTF document size limit")); + } + if data.starts_with(b"glTF") { + Self::load_glb(data) + } else { + Self::load_gltf( + String::from_utf8(data).map_err(|_| parse_error("glTF UTF-8"))?, + buffer_loader, + ) + } + } + + pub fn load_glb(data: Vec) -> Result { + if data.len() < 20 || data.len() > MAX_DOCUMENT_BYTES || &data[..4] != b"glTF" { + return Err(parse_error("GLB header")); + } + let version = u32::from_le_bytes( + data[4..8] + .try_into() + .map_err(|_| parse_error("GLB version"))?, + ); + let length = u32::from_le_bytes( + data[8..12] + .try_into() + .map_err(|_| parse_error("GLB length"))?, + ) as usize; + if version != 2 || length != data.len() { + return Err(parse_error("GLB version or length")); + } + let mut offset = 12usize; + let mut json_chunk = None; + let mut bin_chunk = None; + while offset < data.len() { + let header_end = offset + .checked_add(8) + .ok_or_else(|| parse_error("GLB chunk overflow"))?; + if header_end > data.len() { + return Err(parse_error("GLB chunk header")); + } + let size = u32::from_le_bytes( + data[offset..offset + 4] + .try_into() + .map_err(|_| parse_error("GLB chunk size"))?, + ) as usize; + let kind = &data[offset + 4..offset + 8]; + offset = header_end; + let end = offset + .checked_add(size) + .ok_or_else(|| parse_error("GLB chunk overflow"))?; + if end > data.len() || size > MAX_BUFFER_BYTES { + return Err(parse_error("GLB chunk bounds")); + } + match kind { + b"JSON" if json_chunk.is_none() => json_chunk = Some(&data[offset..end]), + b"BIN\0" if bin_chunk.is_none() => bin_chunk = Some(data[offset..end].to_vec()), + _ => {} + } + offset = end; + } + let json = json_chunk.ok_or_else(|| parse_error("GLB JSON chunk"))?; + let json = std::str::from_utf8(json) + .map_err(|_| parse_error("GLB JSON UTF-8"))? + .trim_end_matches([' ', '\0']); + let mut document = Self::load_gltf(json.to_owned(), None)?; + if let Some(bin) = bin_chunk { + let buffer = document + .buffers + .get_mut(0) + .ok_or_else(|| parse_error("GLB missing buffer"))?; + if usize_from_i32(buffer.byte_length, "GLB buffer length")? > bin.len() { + return Err(parse_error("GLB BIN too short")); + } + buffer.data = Some(bin); + buffer.uri = None; + } + Ok(document) + } + + pub fn load_gltf( + json_text: String, + buffer_loader: Option Vec + Send + Sync>>, + ) -> Result { + if json_text.len() > MAX_DOCUMENT_BYTES { + return Err(parse_error("glTF JSON size limit")); + } + let root: Value = serde_json::from_str(&json_text).map_err(|_| parse_error("glTF JSON"))?; + let object = root + .as_object() + .ok_or_else(|| parse_error("glTF root object"))?; + let mut document = Self::default(); + let asset = object + .get("asset") + .and_then(Value::as_object) + .ok_or_else(|| parse_error("glTF asset"))?; + document.version = asset + .get("version") + .and_then(Value::as_str) + .ok_or_else(|| parse_error("glTF asset version"))? + .to_owned(); + if document.version != "2.0" { + return Err(parse_error("unsupported glTF version")); + } + document.generator = optional_string(asset.get("generator"))?; + document.copyright = optional_string(asset.get("copyright"))?; + document.min_version = optional_string(asset.get("minVersion"))?; + document.default_scene = i32_value(object.get("scene"), -1)?; + document.extensions_required = parse_strings(object.get("extensionsRequired"))?; + document.extensions_used = parse_strings(object.get("extensionsUsed"))?; + document.buffer_views = parse_array(object.get("bufferViews"), parse_buffer_view)?; + document.accessors = parse_array(object.get("accessors"), parse_accessor)?; + document.images = parse_array(object.get("images"), parse_image)?; + document.samplers = parse_array(object.get("samplers"), parse_sampler)?; + document.textures = parse_array(object.get("textures"), parse_texture)?; + document.materials = parse_array(object.get("materials"), parse_material)?; + document.meshes = parse_array(object.get("meshes"), parse_mesh)?; + document.nodes = parse_array(object.get("nodes"), parse_node)?; + document.scenes = parse_array(object.get("scenes"), parse_scene)?; + document.skins = parse_array(object.get("skins"), parse_skin)?; + document.animations = parse_array(object.get("animations"), parse_animation)?; + document.buffers = parse_array(object.get("buffers"), |v| { + parse_buffer(v, buffer_loader.as_deref()) + })?; + document.validate()?; + Ok(document) + } + + fn validate(&self) -> Result<(), Error> { + let mut total = 0usize; + for buffer in &self.buffers { + let expected = usize_from_i32(buffer.byte_length, "glTF buffer byteLength")?; + if expected > MAX_BUFFER_BYTES { + return Err(parse_error("glTF buffer size limit")); + } + if let Some(data) = &buffer.data { + if data.len() < expected || data.len() > MAX_BUFFER_BYTES { + return Err(parse_error("glTF buffer data length")); + } + total = total + .checked_add(data.len()) + .ok_or_else(|| parse_error("glTF total buffer overflow"))?; + } + } + if total > MAX_BUFFER_BYTES { + return Err(parse_error("glTF total buffer size limit")); + } + for view in &self.buffer_views { + let buffer = self + .buffers + .get(usize_from_i32(view.buffer, "glTF buffer index")?) + .ok_or_else(|| parse_error("glTF buffer index"))?; + let start = usize_from_i32(view.byte_offset, "glTF bufferView offset")?; + let len = usize_from_i32(view.byte_length, "glTF bufferView length")?; + let end = start + .checked_add(len) + .ok_or_else(|| parse_error("glTF bufferView overflow"))?; + if end > usize_from_i32(buffer.byte_length, "glTF buffer byteLength")? { + return Err(parse_error("glTF bufferView bounds")); + } + } + for (index, _) in self.nodes.iter().enumerate() { + self.validate_node_depth(index, &mut Vec::new(), 0)?; + } + Ok(()) + } + + fn validate_node_depth( + &self, + index: usize, + stack: &mut Vec, + depth: usize, + ) -> Result<(), Error> { + if depth > 256 || stack.contains(&index) { + return Err(parse_error("glTF node depth or cycle")); + } + let node = self + .nodes + .get(index) + .ok_or_else(|| parse_error("glTF node index"))?; + stack.push(index); + for child in &node.children { + self.validate_node_depth( + usize_from_i32(*child, "glTF child index")?, + stack, + depth + 1, + )?; + } + stack.pop(); + Ok(()) + } + + fn accessor_values(&self, index: i32) -> Result>, Error> { + let accessor = self + .accessors + .get(usize_from_i32(index, "glTF accessor index")?) + .ok_or_else(|| parse_error("glTF accessor index"))?; + let view = self + .buffer_views + .get(usize_from_i32( + accessor.buffer_view, + "glTF accessor bufferView", + )?) + .ok_or_else(|| parse_error("glTF accessor bufferView"))?; + let buffer = self + .buffers + .get(usize_from_i32(view.buffer, "glTF accessor buffer")?) + .ok_or_else(|| parse_error("glTF accessor buffer"))?; + let data = buffer + .data + .as_ref() + .ok_or_else(|| parse_error("glTF unresolved buffer"))?; + let components = accessor_components(accessor.type_); + let component_bytes = component_size(accessor.component_type); + let packed = components + .checked_mul(component_bytes) + .ok_or_else(|| parse_error("glTF accessor stride overflow"))?; + let stride = if view.byte_stride == 0 { + packed + } else { + usize_from_i32(view.byte_stride, "glTF accessor stride")? + }; + if stride < packed || stride > 252 { + return Err(parse_error("glTF accessor stride")); + } + let base = usize_from_i32(view.byte_offset, "glTF view offset")? + .checked_add(usize_from_i32( + accessor.byte_offset, + "glTF accessor offset", + )?) + .ok_or_else(|| parse_error("glTF accessor offset overflow"))?; + let count = usize_from_i32(accessor.count, "glTF accessor count")?; + if count > MAX_COLLECTION_ITEMS { + return Err(parse_error("glTF accessor count limit")); + } + let mut result = Vec::with_capacity(count); + for row in 0..count { + let start = base + .checked_add( + row.checked_mul(stride) + .ok_or_else(|| parse_error("glTF accessor offset overflow"))?, + ) + .ok_or_else(|| parse_error("glTF accessor offset overflow"))?; + let end = start + .checked_add(packed) + .ok_or_else(|| parse_error("glTF accessor bounds overflow"))?; + if end > data.len() { + return Err(parse_error("glTF accessor bounds")); + } + let mut values = Vec::with_capacity(components); + for component in 0..components { + let offset = start + component * component_bytes; + values.push(read_component( + &data[offset..offset + component_bytes], + accessor.component_type, + accessor.normalized, + )?); + } + result.push(values); + } + Ok(result) + } + + fn attribute_values( + &self, + primitive: &GltfPrimitive, + semantic: &str, + ) -> Result>, Error> { + match primitive.attributes.get(semantic) { + Some(index) => self.accessor_values(*index), + None => Ok(Vec::new()), + } + } + + pub fn get_positions(&self, primitive: GltfPrimitive) -> Result, Error> { + vectors3(self.attribute_values(&primitive, "POSITION")?) + } + pub fn get_normals(&self, primitive: GltfPrimitive) -> Result, Error> { + vectors3(self.attribute_values(&primitive, "NORMAL")?) + } + pub fn get_tangents(&self, primitive: GltfPrimitive) -> Result, Error> { + vectors4(self.attribute_values(&primitive, "TANGENT")?) + } + pub fn get_tex_coords( + &self, + primitive: GltfPrimitive, + channel: Option, + ) -> Result, Error> { + vectors2(self.attribute_values(&primitive, &format!("TEXCOORD_{}", channel.unwrap_or(0)))?) + } + pub fn get_colors( + &self, + primitive: GltfPrimitive, + set: Option, + ) -> Result, Error> { + self.attribute_values(&primitive, &format!("COLOR_{}", set.unwrap_or(0)))? + .into_iter() + .map(|v| { + if v.len() == 3 { + Ok(Color4 { + r: v[0], + g: v[1], + b: v[2], + a: 1.0, + }) + } else if v.len() == 4 { + Ok(Color4 { + r: v[0], + g: v[1], + b: v[2], + a: v[3], + }) + } else { + Err(parse_error("glTF color accessor type")) + } + }) + .collect() + } + pub fn get_joints( + &self, + primitive: GltfPrimitive, + set: Option, + ) -> Result, Error> { + vectors4(self.attribute_values(&primitive, &format!("JOINTS_{}", set.unwrap_or(0)))?) + } + pub fn get_weights( + &self, + primitive: GltfPrimitive, + set: Option, + ) -> Result, Error> { + vectors4(self.attribute_values(&primitive, &format!("WEIGHTS_{}", set.unwrap_or(0)))?) + } + pub fn get_indices(&self, primitive: GltfPrimitive) -> Result, Error> { + if primitive.indices < 0 { + return Ok(Vec::new()); + } + self.accessor_values(primitive.indices)? + .into_iter() + .map(|v| { + if v.len() != 1 || v[0] < 0.0 || v[0] > i32::MAX as f32 { + Err(parse_error("glTF index accessor")) + } else { + Ok(v[0] as i32) + } + }) + .collect() + } + pub fn get_inverse_bind_matrices(&self, skin: GltfSkin) -> Result, Error> { + if skin.inverse_bind_matrices < 0 { + return Ok(Vec::new()); + } + self.accessor_values(skin.inverse_bind_matrices)? + .into_iter() + .map(matrix_from_gltf) + .collect() + } + pub fn get_sampler_timestamps(&self, sampler: GltfAnimationSampler) -> Result, Error> { + self.accessor_values(sampler.input)? + .into_iter() + .map(|v| { + v.first() + .copied() + .ok_or_else(|| parse_error("glTF timestamp accessor")) + }) + .collect() + } + pub fn get_sampler_vec3(&self, sampler: GltfAnimationSampler) -> Result, Error> { + vectors3(self.accessor_values(sampler.output)?) + } + pub fn get_sampler_quaternion( + &self, + sampler: GltfAnimationSampler, + ) -> Result, Error> { + self.accessor_values(sampler.output)? + .into_iter() + .map(|v| { + if v.len() == 4 { + Ok(Quaternion { + x: v[0], + y: v[1], + z: v[2], + w: v[3], + }) + } else { + Err(parse_error("glTF quaternion accessor")) + } + }) + .collect() + } + pub fn get_sampler_weights(&self, sampler: GltfAnimationSampler) -> Result, Error> { + Ok(self + .accessor_values(sampler.output)? + .into_iter() + .flatten() + .collect()) + } + + pub fn to_json(&self, pretty: Option) -> Result { + let value = self.json_value(true)?; + if pretty.unwrap_or(false) { + serde_json::to_string_pretty(&value) + } else { + serde_json::to_string(&value) + } + .map_err(|_| parse_error("glTF JSON serialization")) + } + + pub fn to_glb(&self) -> Result, Error> { + let mut clone = self.clone(); + let mut binary = Vec::new(); + if !clone.buffers.is_empty() { + let mut offsets = Vec::with_capacity(clone.buffers.len()); + for buffer in &clone.buffers { + while binary.len() % 4 != 0 { + binary.push(0); + } + offsets.push(binary.len()); + let data = buffer + .data + .as_ref() + .ok_or_else(|| parse_error("glTF unresolved buffer"))?; + binary.extend_from_slice(data); + } + for view in &mut clone.buffer_views { + let buffer_index = usize_from_i32(view.buffer, "GLB buffer view index")?; + let base = *offsets + .get(buffer_index) + .ok_or_else(|| parse_error("GLB buffer view index"))?; + let adjusted = base + .checked_add(usize_from_i32(view.byte_offset, "GLB buffer view offset")?) + .ok_or_else(|| parse_error("GLB buffer view offset overflow"))?; + view.buffer = 0; + view.byte_offset = + i32::try_from(adjusted).map_err(|_| parse_error("GLB buffer view offset"))?; + } + clone.buffers = vec![GltfBuffer { + byte_length: i32::try_from(binary.len()) + .map_err(|_| parse_error("GLB buffer length"))?, + data: Some(binary.clone()), + name: clone.buffers[0].name.clone(), + uri: None, + }]; + } + let mut json = serde_json::to_vec(&clone.json_value(false)?) + .map_err(|_| parse_error("GLB JSON serialization"))?; + while json.len() % 4 != 0 { + json.push(b' '); + } + while binary.len() % 4 != 0 { + binary.push(0); + } + let total = 12usize + .checked_add(8 + json.len()) + .and_then(|v| { + v.checked_add(if binary.is_empty() { + 0 + } else { + 8 + binary.len() + }) + }) + .ok_or_else(|| parse_error("GLB size overflow"))?; + if total > MAX_DOCUMENT_BYTES { + return Err(parse_error("GLB size limit")); + } + let mut out = Vec::with_capacity(total); + out.extend_from_slice(b"glTF"); + out.extend_from_slice(&2u32.to_le_bytes()); + out.extend_from_slice(&(total as u32).to_le_bytes()); + out.extend_from_slice(&(json.len() as u32).to_le_bytes()); + out.extend_from_slice(b"JSON"); + out.extend_from_slice(&json); + if !binary.is_empty() { + out.extend_from_slice(&(binary.len() as u32).to_le_bytes()); + out.extend_from_slice(b"BIN\0"); + out.extend_from_slice(&binary); + } + Ok(out) + } + + fn json_value(&self, include_data_uris: bool) -> Result { + let mut root = Map::new(); + let mut asset = Map::new(); + asset.insert("version".into(), json!(self.version)); + insert_opt(&mut asset, "generator", self.generator.clone()); + insert_opt(&mut asset, "copyright", self.copyright.clone()); + insert_opt(&mut asset, "minVersion", self.min_version.clone()); + root.insert("asset".into(), Value::Object(asset)); + if self.default_scene >= 0 { + root.insert("scene".into(), json!(self.default_scene)); + } + insert_strings(&mut root, "extensionsRequired", &self.extensions_required); + insert_strings(&mut root, "extensionsUsed", &self.extensions_used); + insert_values( + &mut root, + "scenes", + self.scenes.iter().map(scene_json).collect(), + ); + insert_values( + &mut root, + "nodes", + self.nodes.iter().map(node_json).collect(), + ); + insert_values( + &mut root, + "meshes", + self.meshes.iter().map(mesh_json).collect(), + ); + insert_values( + &mut root, + "materials", + self.materials.iter().map(material_json).collect(), + ); + insert_values( + &mut root, + "images", + self.images.iter().map(image_json).collect(), + ); + insert_values( + &mut root, + "samplers", + self.samplers.iter().map(sampler_json).collect(), + ); + insert_values( + &mut root, + "textures", + self.textures.iter().map(texture_json).collect(), + ); + insert_values( + &mut root, + "skins", + self.skins.iter().map(skin_json).collect(), + ); + insert_values( + &mut root, + "animations", + self.animations.iter().map(animation_json).collect(), + ); + insert_values( + &mut root, + "accessors", + self.accessors.iter().map(accessor_json).collect(), + ); + insert_values( + &mut root, + "bufferViews", + self.buffer_views.iter().map(buffer_view_json).collect(), + ); + let buffers = self + .buffers + .iter() + .map(|b| buffer_json(b, include_data_uris)) + .collect::, _>>()?; + insert_values(&mut root, "buffers", buffers); + Ok(Value::Object(root)) + } + + pub fn accessors(&self) -> Vec { + self.accessors.clone() + } + pub fn animations(&self) -> Vec { + self.animations.clone() + } + pub fn buffer_views(&self) -> Vec { + self.buffer_views.clone() + } + pub fn buffers(&self) -> Vec { + self.buffers.clone() + } + pub fn copyright(&self) -> Option { + self.copyright.clone() + } + pub fn set_copyright(&mut self, value: Option) { + self.copyright = value; + } + pub fn default_scene(&self) -> i32 { + self.default_scene + } + pub fn set_default_scene(&mut self, value: i32) { + self.default_scene = value; + } + pub fn extensions_required(&self) -> Vec { + self.extensions_required.clone() + } + pub fn extensions_used(&self) -> Vec { + self.extensions_used.clone() + } + pub fn generator(&self) -> Option { + self.generator.clone() + } + pub fn set_generator(&mut self, value: Option) { + self.generator = value; + } + pub fn images(&self) -> Vec { + self.images.clone() + } + pub fn materials(&self) -> Vec { + self.materials.clone() + } + pub fn meshes(&self) -> Vec { + self.meshes.clone() + } + pub fn min_version(&self) -> Option { + self.min_version.clone() + } + pub fn set_min_version(&mut self, value: Option) { + self.min_version = value; + } + pub fn nodes(&self) -> Vec { + self.nodes.clone() + } + pub fn samplers(&self) -> Vec { + self.samplers.clone() + } + pub fn scenes(&self) -> Vec { + self.scenes.clone() + } + pub fn skins(&self) -> Vec { + self.skins.clone() + } + pub fn textures(&self) -> Vec { + self.textures.clone() + } + pub fn version(&self) -> String { + self.version.clone() + } + pub fn set_version(&mut self, value: String) { + self.version = value; + } +} + +fn parse_array( + value: Option<&Value>, + parser: impl Fn(&Value) -> Result, +) -> Result, Error> { + let Some(value) = value else { + return Ok(Vec::new()); + }; + let array = value + .as_array() + .ok_or_else(|| parse_error("glTF collection"))?; + if array.len() > MAX_COLLECTION_ITEMS { + return Err(parse_error("glTF collection limit")); + } + array.iter().map(parser).collect() +} +fn parse_strings(value: Option<&Value>) -> Result, Error> { + parse_array(value, |v| { + v.as_str() + .map(str::to_owned) + .ok_or_else(|| parse_error("glTF string collection")) + }) +} +fn object(value: &Value) -> Result<&Map, Error> { + value.as_object().ok_or_else(|| parse_error("glTF object")) +} + +fn parse_buffer( + value: &Value, + loader: Option<&(dyn Fn(String) -> Vec + Send + Sync)>, +) -> Result { + let o = object(value)?; + let byte_length = i32_value(o.get("byteLength"), 0)?; + let uri = optional_string(o.get("uri"))?; + let data = if let Some(uri_value) = &uri { + if let Some(encoded) = uri_value + .strip_prefix("data:") + .and_then(|v| v.split_once(',')) + .filter(|(meta, _)| meta.ends_with(";base64")) + .map(|(_, data)| data) + { + Some( + base64::engine::general_purpose::STANDARD + .decode(encoded) + .map_err(|_| parse_error("glTF base64 buffer"))?, + ) + } else { + loader.map(|resolve| resolve(uri_value.clone())) + } + } else { + None + }; + Ok(GltfBuffer { + byte_length, + data, + name: optional_string(o.get("name"))?, + uri, + }) +} +fn parse_buffer_view(value: &Value) -> Result { + let o = object(value)?; + Ok(GltfBufferView { + buffer: i32_value(o.get("buffer"), -1)?, + byte_length: i32_value(o.get("byteLength"), 0)?, + byte_offset: i32_value(o.get("byteOffset"), 0)?, + byte_stride: i32_value(o.get("byteStride"), 0)?, + name: optional_string(o.get("name"))?, + target: i32_value(o.get("target"), -1)?, + }) +} +fn parse_accessor(value: &Value) -> Result { + let o = object(value)?; + Ok(GltfAccessor { + buffer_view: i32_value(o.get("bufferView"), -1)?, + byte_offset: i32_value(o.get("byteOffset"), 0)?, + component_type: component_type(i32_value(o.get("componentType"), 5126)?)?, + normalized: o + .get("normalized") + .and_then(Value::as_bool) + .unwrap_or(false), + count: i32_value(o.get("count"), 0)?, + type_: accessor_type(o.get("type").and_then(Value::as_str).unwrap_or("SCALAR"))?, + max: number_array(o.get("max"))?, + min: number_array(o.get("min"))?, + name: optional_string(o.get("name"))?, + }) +} +fn parse_image(value: &Value) -> Result { + let o = object(value)?; + Ok(GltfImage { + buffer_view: i32_value(o.get("bufferView"), -1)?, + mime_type: optional_string(o.get("mimeType"))?, + name: optional_string(o.get("name"))?, + uri: optional_string(o.get("uri"))?, + }) +} +fn parse_texture_ref(value: Option<&Value>) -> Result, Error> { + let Some(value) = value else { return Ok(None) }; + let o = object(value)?; + let extension = o + .get("extensions") + .and_then(Value::as_object) + .and_then(|extensions| extensions.get("KHR_texture_transform")) + .and_then(Value::as_object); + let offset = finite_vec( + extension.and_then(|value| value.get("offset")), + 2, + &[0.0, 0.0], + )?; + let scale = finite_vec( + extension.and_then(|value| value.get("scale")), + 2, + &[1.0, 1.0], + )?; + Ok(Some(GltfTextureRef { + index: i32_value(o.get("index"), -1)?, + tex_coord: i32_value( + extension + .and_then(|value| value.get("texCoord")) + .or_else(|| o.get("texCoord")), + 0, + )?, + transform: GltfTextureTransform { + offset: Vector2 { + x: offset[0], + y: offset[1], + }, + rotation: f32_value(extension.and_then(|value| value.get("rotation")), 0.0)?, + scale: Vector2 { + x: scale[0], + y: scale[1], + }, + }, + })) +} +fn parse_material(value: &Value) -> Result { + let o = object(value)?; + let pbr = o.get("pbrMetallicRoughness").and_then(Value::as_object); + let color = finite_vec(pbr.and_then(|p| p.get("baseColorFactor")), 4, &[1.0; 4])?; + let emissive = finite_vec(o.get("emissiveFactor"), 3, &[0.0; 3])?; + let normal_texture = if let Some(v) = o.get("normalTexture") { + let no = object(v)?; + Some(GltfNormalTextureRef { + inner: parse_texture_ref(Some(v))?.unwrap_or_default(), + scale: f32_value(no.get("scale"), 1.0)?, + }) + } else { + None + }; + let occlusion_texture = if let Some(v) = o.get("occlusionTexture") { + let oo = object(v)?; + Some(GltfOcclusionTextureRef { + inner: parse_texture_ref(Some(v))?.unwrap_or_default(), + strength: f32_value(oo.get("strength"), 1.0)?, + }) + } else { + None + }; + Ok(GltfDocumentMaterial { + name: optional_string(o.get("name"))?, + base_color_factor: Color4 { + r: color[0], + g: color[1], + b: color[2], + a: color[3], + }, + base_color_texture: parse_texture_ref(pbr.and_then(|p| p.get("baseColorTexture")))?, + metallic_factor: f32_value(pbr.and_then(|p| p.get("metallicFactor")), 1.0)?, + metallic_roughness_texture: parse_texture_ref( + pbr.and_then(|p| p.get("metallicRoughnessTexture")), + )?, + roughness_factor: f32_value(pbr.and_then(|p| p.get("roughnessFactor")), 1.0)?, + normal_texture, + occlusion_texture, + emissive_texture: parse_texture_ref(o.get("emissiveTexture"))?, + emissive_factor: Vector3 { + x: emissive[0], + y: emissive[1], + z: emissive[2], + }, + alpha_mode: match o + .get("alphaMode") + .and_then(Value::as_str) + .unwrap_or("OPAQUE") + { + "BLEND" => GltfAlphaMode::Blend, + "MASK" => GltfAlphaMode::Mask, + "OPAQUE" => GltfAlphaMode::Opaque, + _ => return Err(parse_error("glTF alpha mode")), + }, + alpha_cutoff: f32_value(o.get("alphaCutoff"), 0.5)?, + double_sided: o + .get("doubleSided") + .and_then(Value::as_bool) + .unwrap_or(false), + }) +} +fn parse_primitive(value: &Value) -> Result { + let o = object(value)?; + let attrs = o + .get("attributes") + .and_then(Value::as_object) + .ok_or_else(|| parse_error("glTF primitive attributes"))?; + let mut attributes = HashMap::with_capacity(attrs.len()); + for (k, v) in attrs { + attributes.insert(k.clone(), i32_value(Some(v), -1)?); + } + Ok(GltfPrimitive { + attributes, + indices: i32_value(o.get("indices"), -1)?, + material: i32_value(o.get("material"), -1)?, + mode: primitive_mode(i32_value(o.get("mode"), 4)?)?, + }) +} +fn parse_mesh(value: &Value) -> Result { + let o = object(value)?; + Ok(GltfMesh { + name: optional_string(o.get("name"))?, + primitives: parse_array(o.get("primitives"), parse_primitive)?, + weights: number_array(o.get("weights"))?, + }) +} +fn parse_node(value: &Value) -> Result { + let o = object(value)?; + let t = finite_vec(o.get("translation"), 3, &[0.0; 3])?; + let r = finite_vec(o.get("rotation"), 4, &[0.0, 0.0, 0.0, 1.0])?; + let s = finite_vec(o.get("scale"), 3, &[1.0; 3])?; + let matrix = if o.contains_key("matrix") { + Some(Some(matrix_from_gltf(finite_vec( + o.get("matrix"), + 16, + &[], + )?)?)) + } else { + None + }; + Ok(GltfNode { + children: int_array(o.get("children"))?, + matrix, + mesh: i32_value(o.get("mesh"), -1)?, + name: optional_string(o.get("name"))?, + rotation: Quaternion { + x: r[0], + y: r[1], + z: r[2], + w: r[3], + }, + scale: Vector3 { + x: s[0], + y: s[1], + z: s[2], + }, + skin: i32_value(o.get("skin"), -1)?, + translation: Vector3 { + x: t[0], + y: t[1], + z: t[2], + }, + weights: number_array(o.get("weights"))?, + }) +} +fn parse_scene(value: &Value) -> Result { + let o = object(value)?; + Ok(GltfScene { + name: optional_string(o.get("name"))?, + nodes: int_array(o.get("nodes"))?, + }) +} +fn parse_skin(value: &Value) -> Result { + let o = object(value)?; + Ok(GltfSkin { + inverse_bind_matrices: i32_value(o.get("inverseBindMatrices"), -1)?, + joints: int_array(o.get("joints"))?, + name: optional_string(o.get("name"))?, + skeleton: i32_value(o.get("skeleton"), -1)?, + }) +} +fn parse_sampler(value: &Value) -> Result { + let o = object(value)?; + Ok(GltfSampler { + mag_filter: sampler_filter(i32_value(o.get("magFilter"), 9729)?)?, + min_filter: sampler_filter(i32_value(o.get("minFilter"), 9987)?)?, + name: optional_string(o.get("name"))?, + wrap_s: sampler_wrap(i32_value(o.get("wrapS"), 10497)?)?, + wrap_t: sampler_wrap(i32_value(o.get("wrapT"), 10497)?)?, + }) +} +fn parse_texture(value: &Value) -> Result { + let o = object(value)?; + Ok(GltfTexture { + name: optional_string(o.get("name"))?, + sampler: i32_value(o.get("sampler"), -1)?, + source: i32_value(o.get("source"), -1)?, + }) +} +fn parse_animation(value: &Value) -> Result { + let o = object(value)?; + let channels = parse_array(o.get("channels"), |v| { + let c = object(v)?; + let t = object( + c.get("target") + .ok_or_else(|| parse_error("glTF animation target"))?, + )?; + Ok(GltfAnimationChannel { + sampler: i32_value(c.get("sampler"), -1)?, + target: GltfAnimationChannelTarget { + node: i32_value(t.get("node"), -1)?, + path: match t + .get("path") + .and_then(Value::as_str) + .unwrap_or("translation") + { + "translation" => GltfAnimationTargetPath::Translation, + "rotation" => GltfAnimationTargetPath::Rotation, + "scale" => GltfAnimationTargetPath::Scale, + "weights" => GltfAnimationTargetPath::Weights, + _ => return Err(parse_error("glTF animation path")), + }, + }, + }) + })?; + let samplers = parse_array(o.get("samplers"), |v| { + let s = object(v)?; + Ok(GltfAnimationSampler { + input: i32_value(s.get("input"), -1)?, + interpolation: match s + .get("interpolation") + .and_then(Value::as_str) + .unwrap_or("LINEAR") + { + "LINEAR" => GltfAnimationInterpolation::Linear, + "STEP" => GltfAnimationInterpolation::Step, + "CUBICSPLINE" => GltfAnimationInterpolation::CubicSpline, + _ => return Err(parse_error("glTF interpolation")), + }, + output: i32_value(s.get("output"), -1)?, + }) + })?; + Ok(GltfAnimation { + channels, + name: optional_string(o.get("name"))?, + samplers, + }) +} + +fn component_type(v: i32) -> Result { + match v { + 5120 => Ok(GltfComponentType::Byte), + 5121 => Ok(GltfComponentType::UnsignedByte), + 5122 => Ok(GltfComponentType::Short), + 5123 => Ok(GltfComponentType::UnsignedShort), + 5125 => Ok(GltfComponentType::UnsignedInt), + 5126 => Ok(GltfComponentType::Float), + _ => Err(parse_error("glTF component type")), + } +} +fn accessor_type(v: &str) -> Result { + match v { + "SCALAR" => Ok(GltfAccessorType::Scalar), + "VEC2" => Ok(GltfAccessorType::Vec2), + "VEC3" => Ok(GltfAccessorType::Vec3), + "VEC4" => Ok(GltfAccessorType::Vec4), + "MAT2" => Ok(GltfAccessorType::Mat2), + "MAT3" => Ok(GltfAccessorType::Mat3), + "MAT4" => Ok(GltfAccessorType::Mat4), + _ => Err(parse_error("glTF accessor type")), + } +} +fn primitive_mode(v: i32) -> Result { + match v { + 0 => Ok(GltfPrimitiveMode::Points), + 1 => Ok(GltfPrimitiveMode::Lines), + 2 => Ok(GltfPrimitiveMode::LineLoop), + 3 => Ok(GltfPrimitiveMode::LineStrip), + 4 => Ok(GltfPrimitiveMode::Triangles), + 5 => Ok(GltfPrimitiveMode::TriangleStrip), + 6 => Ok(GltfPrimitiveMode::TriangleFan), + _ => Err(parse_error("glTF primitive mode")), + } +} +fn sampler_filter(v: i32) -> Result { + match v { + 0 => Ok(GltfSamplerFilter::None), + 9728 => Ok(GltfSamplerFilter::Nearest), + 9729 => Ok(GltfSamplerFilter::Linear), + 9984 => Ok(GltfSamplerFilter::NearestMipmapNearest), + 9985 => Ok(GltfSamplerFilter::LinearMipmapNearest), + 9986 => Ok(GltfSamplerFilter::NearestMipmapLinear), + 9987 => Ok(GltfSamplerFilter::LinearMipmapLinear), + _ => Err(parse_error("glTF sampler filter")), + } +} +fn sampler_wrap(v: i32) -> Result { + match v { + 33071 => Ok(GltfSamplerWrap::ClampToEdge), + 33648 => Ok(GltfSamplerWrap::MirroredRepeat), + 10497 => Ok(GltfSamplerWrap::Repeat), + _ => Err(parse_error("glTF sampler wrap")), + } +} +fn component_size(v: GltfComponentType) -> usize { + match v { + GltfComponentType::Byte | GltfComponentType::UnsignedByte => 1, + GltfComponentType::Short | GltfComponentType::UnsignedShort => 2, + GltfComponentType::UnsignedInt | GltfComponentType::Float => 4, + } +} +fn accessor_components(v: GltfAccessorType) -> usize { + match v { + GltfAccessorType::Scalar => 1, + GltfAccessorType::Vec2 => 2, + GltfAccessorType::Vec3 => 3, + GltfAccessorType::Vec4 | GltfAccessorType::Mat2 => 4, + GltfAccessorType::Mat3 => 9, + GltfAccessorType::Mat4 => 16, + } +} +fn read_component(bytes: &[u8], kind: GltfComponentType, normalized: bool) -> Result { + let value = match kind { + GltfComponentType::Byte => { + let v = i8::from_le_bytes([bytes[0]]); + if normalized { + (f32::from(v) / 127.0).max(-1.0) + } else { + f32::from(v) + } + } + GltfComponentType::UnsignedByte => { + let v = bytes[0]; + if normalized { + f32::from(v) / 255.0 + } else { + f32::from(v) + } + } + GltfComponentType::Short => { + let v = i16::from_le_bytes( + bytes + .try_into() + .map_err(|_| parse_error("glTF component"))?, + ); + if normalized { + (f32::from(v) / 32767.0).max(-1.0) + } else { + f32::from(v) + } + } + GltfComponentType::UnsignedShort => { + let v = u16::from_le_bytes( + bytes + .try_into() + .map_err(|_| parse_error("glTF component"))?, + ); + if normalized { + f32::from(v) / 65535.0 + } else { + f32::from(v) + } + } + GltfComponentType::UnsignedInt => { + let v = u32::from_le_bytes( + bytes + .try_into() + .map_err(|_| parse_error("glTF component"))?, + ); + if normalized { + v as f32 / u32::MAX as f32 + } else { + v as f32 + } + } + GltfComponentType::Float => f32::from_le_bytes( + bytes + .try_into() + .map_err(|_| parse_error("glTF component"))?, + ), + }; + if value.is_finite() { + Ok(value) + } else { + Err(parse_error("glTF non-finite component")) + } +} +fn vectors2(rows: Vec>) -> Result, Error> { + rows.into_iter() + .map(|v| { + if v.len() == 2 { + Ok(Vector2 { x: v[0], y: v[1] }) + } else { + Err(parse_error("glTF VEC2 accessor")) + } + }) + .collect() +} +fn vectors3(rows: Vec>) -> Result, Error> { + rows.into_iter() + .map(|v| { + if v.len() == 3 { + Ok(Vector3 { + x: v[0], + y: v[1], + z: v[2], + }) + } else { + Err(parse_error("glTF VEC3 accessor")) + } + }) + .collect() +} +fn vectors4(rows: Vec>) -> Result, Error> { + rows.into_iter() + .map(|v| { + if v.len() == 4 { + Ok(Vector4 { + x: v[0], + y: v[1], + z: v[2], + w: v[3], + }) + } else { + Err(parse_error("glTF VEC4 accessor")) + } + }) + .collect() +} +fn matrix_from_gltf(v: Vec) -> Result { + if v.len() != 16 { + return Err(parse_error("glTF MAT4 accessor")); + } + Ok(Matrix4 { + m11: v[0], + m12: v[4], + m13: v[8], + m14: v[12], + m21: v[1], + m22: v[5], + m23: v[9], + m24: v[13], + m31: v[2], + m32: v[6], + m33: v[10], + m34: v[14], + m41: v[3], + m42: v[7], + m43: v[11], + m44: v[15], + }) +} + +fn insert_opt(map: &mut Map, key: &str, value: Option) { + if let Some(value) = value { + map.insert(key.into(), json!(value)); + } +} +fn insert_strings(map: &mut Map, key: &str, values: &[String]) { + if !values.is_empty() { + map.insert(key.into(), json!(values)); + } +} +fn insert_values(map: &mut Map, key: &str, values: Vec) { + if !values.is_empty() { + map.insert(key.into(), Value::Array(values)); + } +} +fn scene_json(v: &GltfScene) -> Value { + let mut o = Map::new(); + insert_opt(&mut o, "name", v.name.clone()); + if !v.nodes.is_empty() { + o.insert("nodes".into(), json!(v.nodes)); + } + Value::Object(o) +} +fn node_json(v: &GltfNode) -> Value { + let mut o = Map::new(); + insert_opt(&mut o, "name", v.name.clone()); + if !v.children.is_empty() { + o.insert("children".into(), json!(v.children)); + } + if v.mesh >= 0 { + o.insert("mesh".into(), json!(v.mesh)); + } + if v.skin >= 0 { + o.insert("skin".into(), json!(v.skin)); + } + if let Some(Some(m)) = v.matrix { + o.insert( + "matrix".into(), + json!([ + m.m11, m.m21, m.m31, m.m41, m.m12, m.m22, m.m32, m.m42, m.m13, m.m23, m.m33, m.m43, + m.m14, m.m24, m.m34, m.m44 + ]), + ); + } else { + if v.translation != Vector3::zero() { + o.insert( + "translation".into(), + json!([v.translation.x, v.translation.y, v.translation.z]), + ); + } + if v.rotation != Quaternion::identity() { + o.insert( + "rotation".into(), + json!([v.rotation.x, v.rotation.y, v.rotation.z, v.rotation.w]), + ); + } + if v.scale != Vector3::one() { + o.insert("scale".into(), json!([v.scale.x, v.scale.y, v.scale.z])); + } + } + if let Some(w) = &v.weights { + o.insert("weights".into(), json!(w)); + } + Value::Object(o) +} +fn primitive_json(v: &GltfPrimitive) -> Value { + let mut attrs = Map::new(); + let mut keys = v.attributes.keys().collect::>(); + keys.sort(); + for k in keys { + attrs.insert(k.clone(), json!(v.attributes[k])); + } + let mut o = Map::new(); + o.insert("attributes".into(), Value::Object(attrs)); + if v.indices >= 0 { + o.insert("indices".into(), json!(v.indices)); + } + if v.material >= 0 { + o.insert("material".into(), json!(v.material)); + } + if v.mode != GltfPrimitiveMode::Triangles { + o.insert("mode".into(), json!(v.mode as u8)); + } + Value::Object(o) +} +fn mesh_json(v: &GltfMesh) -> Value { + let mut o = Map::new(); + insert_opt(&mut o, "name", v.name.clone()); + o.insert( + "primitives".into(), + Value::Array(v.primitives.iter().map(primitive_json).collect()), + ); + if let Some(w) = &v.weights { + o.insert("weights".into(), json!(w)); + } + Value::Object(o) +} +fn texture_ref_json(v: &GltfTextureRef) -> Value { + let mut object = Map::new(); + object.insert("index".into(), json!(v.index)); + if v.tex_coord != 0 { + object.insert("texCoord".into(), json!(v.tex_coord)); + } + if !v.transform.is_default() { + object.insert( + "extensions".into(), + json!({"KHR_texture_transform": { + "offset": [v.transform.offset.x, v.transform.offset.y], + "rotation": v.transform.rotation, + "scale": [v.transform.scale.x, v.transform.scale.y], + "texCoord": v.tex_coord + }}), + ); + } + Value::Object(object) +} +fn material_json(v: &GltfDocumentMaterial) -> Value { + let mut o = Map::new(); + insert_opt(&mut o, "name", v.name.clone()); + let mut p = Map::new(); + p.insert( + "baseColorFactor".into(), + json!([ + v.base_color_factor.r, + v.base_color_factor.g, + v.base_color_factor.b, + v.base_color_factor.a + ]), + ); + p.insert("metallicFactor".into(), json!(v.metallic_factor)); + p.insert("roughnessFactor".into(), json!(v.roughness_factor)); + if let Some(t) = &v.base_color_texture { + p.insert("baseColorTexture".into(), texture_ref_json(t)); + } + if let Some(t) = &v.metallic_roughness_texture { + p.insert("metallicRoughnessTexture".into(), texture_ref_json(t)); + } + o.insert("pbrMetallicRoughness".into(), Value::Object(p)); + if let Some(texture) = &v.normal_texture { + let mut reference = texture_ref_json(&texture.inner) + .as_object() + .cloned() + .unwrap_or_default(); + reference.insert("scale".into(), json!(texture.scale)); + o.insert("normalTexture".into(), Value::Object(reference)); + } + if let Some(texture) = &v.occlusion_texture { + let mut reference = texture_ref_json(&texture.inner) + .as_object() + .cloned() + .unwrap_or_default(); + reference.insert("strength".into(), json!(texture.strength)); + o.insert("occlusionTexture".into(), Value::Object(reference)); + } + if let Some(texture) = &v.emissive_texture { + o.insert("emissiveTexture".into(), texture_ref_json(texture)); + } + o.insert( + "emissiveFactor".into(), + json!([ + v.emissive_factor.x, + v.emissive_factor.y, + v.emissive_factor.z + ]), + ); + o.insert( + "alphaMode".into(), + json!(match v.alpha_mode { + GltfAlphaMode::Opaque => "OPAQUE", + GltfAlphaMode::Blend => "BLEND", + GltfAlphaMode::Mask => "MASK", + }), + ); + o.insert("alphaCutoff".into(), json!(v.alpha_cutoff)); + o.insert("doubleSided".into(), json!(v.double_sided)); + Value::Object(o) +} +fn image_json(v: &GltfImage) -> Value { + let mut object = Map::new(); + insert_opt(&mut object, "name", v.name.clone()); + insert_opt(&mut object, "uri", v.uri.clone()); + insert_opt(&mut object, "mimeType", v.mime_type.clone()); + if v.buffer_view >= 0 { + object.insert("bufferView".into(), json!(v.buffer_view)); + } + Value::Object(object) +} +fn sampler_json(v: &GltfSampler) -> Value { + let mut object = Map::new(); + insert_opt(&mut object, "name", v.name.clone()); + if v.mag_filter != GltfSamplerFilter::None { + object.insert("magFilter".into(), json!(v.mag_filter as i32)); + } + if v.min_filter != GltfSamplerFilter::None { + object.insert("minFilter".into(), json!(v.min_filter as i32)); + } + object.insert("wrapS".into(), json!(v.wrap_s as i32)); + object.insert("wrapT".into(), json!(v.wrap_t as i32)); + Value::Object(object) +} +fn texture_json(v: &GltfTexture) -> Value { + let mut object = Map::new(); + insert_opt(&mut object, "name", v.name.clone()); + if v.sampler >= 0 { + object.insert("sampler".into(), json!(v.sampler)); + } + if v.source >= 0 { + object.insert("source".into(), json!(v.source)); + } + Value::Object(object) +} +fn skin_json(v: &GltfSkin) -> Value { + let mut object = Map::new(); + insert_opt(&mut object, "name", v.name.clone()); + if v.inverse_bind_matrices >= 0 { + object.insert("inverseBindMatrices".into(), json!(v.inverse_bind_matrices)); + } + if v.skeleton >= 0 { + object.insert("skeleton".into(), json!(v.skeleton)); + } + object.insert("joints".into(), json!(v.joints)); + Value::Object(object) +} +fn animation_json(v: &GltfAnimation) -> Value { + let mut object = Map::new(); + insert_opt(&mut object, "name", v.name.clone()); + object.insert( + "channels".into(), + Value::Array( + v.channels + .iter() + .map(|channel| { + json!({ + "sampler": channel.sampler, + "target": { + "node": channel.target.node, + "path": match channel.target.path { + GltfAnimationTargetPath::Translation => "translation", + GltfAnimationTargetPath::Rotation => "rotation", + GltfAnimationTargetPath::Scale => "scale", + GltfAnimationTargetPath::Weights => "weights", + } + } + }) + }) + .collect(), + ), + ); + object.insert( + "samplers".into(), + Value::Array( + v.samplers + .iter() + .map(|sampler| { + json!({ + "input": sampler.input, + "interpolation": match sampler.interpolation { + GltfAnimationInterpolation::Linear => "LINEAR", + GltfAnimationInterpolation::Step => "STEP", + GltfAnimationInterpolation::CubicSpline => "CUBICSPLINE", + }, + "output": sampler.output + }) + }) + .collect(), + ), + ); + Value::Object(object) +} +fn accessor_json(v: &GltfAccessor) -> Value { + let mut o = Map::new(); + if v.buffer_view >= 0 { + o.insert("bufferView".into(), json!(v.buffer_view)); + } + if v.byte_offset != 0 { + o.insert("byteOffset".into(), json!(v.byte_offset)); + } + o.insert("componentType".into(), json!(v.component_type as i32)); + o.insert("count".into(), json!(v.count)); + o.insert( + "type".into(), + json!(match v.type_ { + GltfAccessorType::Scalar => "SCALAR", + GltfAccessorType::Vec2 => "VEC2", + GltfAccessorType::Vec3 => "VEC3", + GltfAccessorType::Vec4 => "VEC4", + GltfAccessorType::Mat2 => "MAT2", + GltfAccessorType::Mat3 => "MAT3", + GltfAccessorType::Mat4 => "MAT4", + }), + ); + if v.normalized { + o.insert("normalized".into(), json!(true)); + } + if let Some(x) = &v.max { + o.insert("max".into(), json!(x)); + } + if let Some(x) = &v.min { + o.insert("min".into(), json!(x)); + } + insert_opt(&mut o, "name", v.name.clone()); + Value::Object(o) +} +fn buffer_view_json(v: &GltfBufferView) -> Value { + let mut o = Map::new(); + o.insert("buffer".into(), json!(v.buffer)); + o.insert("byteLength".into(), json!(v.byte_length)); + if v.byte_offset != 0 { + o.insert("byteOffset".into(), json!(v.byte_offset)); + } + if v.byte_stride != 0 { + o.insert("byteStride".into(), json!(v.byte_stride)); + } + if v.target >= 0 { + o.insert("target".into(), json!(v.target)); + } + insert_opt(&mut o, "name", v.name.clone()); + Value::Object(o) +} +fn buffer_json(v: &GltfBuffer, include_data: bool) -> Result { + let mut o = Map::new(); + o.insert("byteLength".into(), json!(v.byte_length)); + insert_opt(&mut o, "name", v.name.clone()); + if include_data { + if let Some(data) = &v.data { + o.insert( + "uri".into(), + json!(format!( + "data:application/octet-stream;base64,{}", + base64::engine::general_purpose::STANDARD.encode(data) + )), + ); + } else { + insert_opt(&mut o, "uri", v.uri.clone()); + } + } + Value::Object(o).pipe(Ok) +} + +trait Pipe: Sized { + fn pipe(self, f: impl FnOnce(Self) -> T) -> T { + f(self) + } +} +impl Pipe for T {} diff --git a/crates/libremetaverse/src/lib.rs b/crates/libremetaverse/src/lib.rs index 3f07e3d..efee475 100644 --- a/crates/libremetaverse/src/lib.rs +++ b/crates/libremetaverse/src/lib.rs @@ -13,6 +13,7 @@ mod animesh_runtime; mod animesh_skinning; mod appearance_baker; mod appearance_manager; +mod asset_archive; mod asset_cache; mod asset_manager; mod asset_material; @@ -31,6 +32,7 @@ mod estate_tools; mod event_queue; mod friends_manager; mod gesture; +mod gltf; mod group_manager; mod initial_outfit; mod inventory; @@ -47,6 +49,7 @@ mod login; mod marketplace_runtime; mod message_codec; mod message_decoder; +mod model_workflow; mod network_manager; mod object_manager; mod object_material; @@ -93,17 +96,15 @@ type Terrain = Vec>; type TerrainLoaded = Box; impl assets::OarFile { - #[allow(dead_code)] + #[allow(dead_code, clippy::needless_pass_by_value)] fn load_terrain( - _file_path: &str, - _data: Vec, - _callback: TerrainLoaded, - _bytes_read: i64, - _total_bytes: i64, + file_path: &str, + data: Vec, + callback: TerrainLoaded, + bytes_read: i64, + total_bytes: i64, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.OarFile.LoadTerrain(System.String,System.Byte[],LibreMetaverse.Assets.OarFile.TerrainLoadedCallback,System.Int64,System.Int64)", - ) + crate::asset_archive::load_terrain(file_path, &data, callback, bytes_read, total_bytes) } } diff --git a/crates/libremetaverse/src/model_workflow.rs b/crates/libremetaverse/src/model_workflow.rs new file mode 100644 index 0000000..7fee718 --- /dev/null +++ b/crates/libremetaverse/src/model_workflow.rs @@ -0,0 +1,1194 @@ +//! Offline Collada conversion, deterministic mesh assets, and opt-in uploads. + +// Owned DTO parameters and the public model shapes follow the fixed mapped API; +// format bounds make the intentional numeric narrowing safe. +#![allow(clippy::pedantic)] + +use std::collections::HashMap; +use std::fs; +use std::io::Write; +use std::path::{Component, Path, PathBuf}; + +use flate2::{Compression, write::ZlibEncoder}; +use libremetaverse_imaging::ITextureCodec; +use libremetaverse_structured_data::{OSD, OSDFormat, OSDParser}; +use libremetaverse_types::compat::{CancellationToken, Uri}; +use libremetaverse_types::{AssetType, Color4, Error, Quaternion, UUID, Vector2, Vector3}; + +use crate::GridClient; +use crate::rendering::Vertex; + +const MAX_COLLADA_BYTES: usize = 64 * 1024 * 1024; +const MAX_IMAGE_BYTES: usize = 64 * 1024 * 1024; +const MAX_VERTICES: usize = 1_000_000; +const MAX_FACES: usize = 100_000; + +fn model_error(context: &'static str) -> Error { + Error::Parse { + position: 0, + context, + } +} + +pub struct ModelMaterial { + pub diffuse_color: Color4, + pub id: String, + pub texture: String, + pub texture_data: Vec, +} + +impl Clone for ModelMaterial { + fn clone(&self) -> Self { + Self { + diffuse_color: self.diffuse_color, + id: self.id.clone(), + texture: self.texture.clone(), + texture_data: self.texture_data.clone(), + } + } +} + +impl std::fmt::Debug for ModelMaterial { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ModelMaterial") + .field("id", &self.id) + .field("texture", &self.texture) + .field("texture_bytes", &self.texture_data.len()) + .finish() + } +} + +impl ModelMaterial { + pub fn new() -> Result { + Ok(Self { + diffuse_color: Color4::white(), + id: String::new(), + texture: String::new(), + texture_data: Vec::new(), + }) + } +} + +pub struct ModelFace { + pub indices: Vec, + pub material: ModelMaterial, + pub material_id: String, + pub vertices: Vec, +} + +impl std::fmt::Debug for ModelFace { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ModelFace") + .field("indices", &self.indices) + .field("material", &self.material) + .field("material_id", &self.material_id) + .field("vertex_count", &self.vertices.len()) + .finish() + } +} + +impl ModelFace { + pub fn new() -> Result { + Ok(Self { + indices: Vec::new(), + material: ModelMaterial::new()?, + material_id: String::new(), + vertices: Vec::new(), + }) + } + + pub fn add_vertex(&mut self, vertex: Vertex) -> Result<(), Error> { + if self.indices.len() >= MAX_VERTICES * 3 { + return Err(Error::Argument); + } + let index = self + .vertices + .iter() + .position(|candidate| vertex_equal(candidate, &vertex)); + let index = match index { + Some(index) => index, + None => { + if self.vertices.len() >= MAX_VERTICES { + return Err(Error::Argument); + } + self.vertices.push(vertex); + self.vertices.len() - 1 + } + }; + self.indices + .push(u32::try_from(index).map_err(|_| Error::Argument)?); + Ok(()) + } +} + +fn vertex_equal(left: &Vertex, right: &Vertex) -> bool { + left.position.x.to_bits() == right.position.x.to_bits() + && left.position.y.to_bits() == right.position.y.to_bits() + && left.position.z.to_bits() == right.position.z.to_bits() + && left.normal.x.to_bits() == right.normal.x.to_bits() + && left.normal.y.to_bits() == right.normal.y.to_bits() + && left.normal.z.to_bits() == right.normal.z.to_bits() + && left.tex_coord.x.to_bits() == right.tex_coord.x.to_bits() + && left.tex_coord.y.to_bits() == right.tex_coord.y.to_bits() +} + +#[derive(Debug)] +pub struct ModelPrim { + pub asset: Vec, + pub bound_max: Vector3, + pub bound_min: Vector3, + pub faces: Vec, + pub id: String, + pub position: Vector3, + pub positions: Vec, + pub rotation: Quaternion, + pub scale: Vector3, +} + +impl ModelPrim { + pub fn new() -> Result { + Ok(Self { + asset: Vec::new(), + bound_max: Vector3 { + x: f32::MIN, + y: f32::MIN, + z: f32::MIN, + }, + bound_min: Vector3 { + x: f32::MAX, + y: f32::MAX, + z: f32::MAX, + }, + faces: Vec::new(), + id: String::new(), + position: Vector3::zero(), + positions: Vec::new(), + rotation: Quaternion::identity(), + scale: Vector3::one(), + }) + } + + pub fn physics_stub() -> Result { + Ok(OSD::Map(HashMap::from([ + ( + "Max".to_owned(), + OSD::from_vector3(Vector3 { + x: 0.5, + y: 0.5, + z: 0.5, + })?, + ), + ( + "Min".to_owned(), + OSD::from_vector3(Vector3 { + x: -0.5, + y: -0.5, + z: -0.5, + })?, + ), + ( + "BoundingVerts".to_owned(), + OSD::Binary(vec![ + 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 127, 0, 0, 255, 255, 255, + 127, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, 0, + 0, 255, 127, 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 255, 255, + 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, + ]), + ), + ]))) + } + + pub fn create_asset(&mut self, creator: UUID) -> Result<(), Error> { + if self.faces.len() > MAX_FACES { + return Err(Error::Argument); + } + let mut face_values = Vec::with_capacity(self.faces.len()); + for face in &self.faces { + if face.vertices.len() > u16::MAX as usize + || face.indices.iter().any(|index| *index > u16::MAX as u32) + { + return Err(Error::Argument); + } + let (uv_min, uv_max) = uv_bounds(&face.vertices); + let mut positions = Vec::with_capacity(face.vertices.len() * 6); + let mut normals = Vec::with_capacity(face.vertices.len() * 6); + let mut tex_coords = Vec::with_capacity(face.vertices.len() * 4); + for vertex in &face.vertices { + for value in [vertex.position.x, vertex.position.y, vertex.position.z] { + positions.extend_from_slice(&quantize(value, -0.5, 0.5).to_le_bytes()); + } + for value in [vertex.normal.x, vertex.normal.y, vertex.normal.z] { + normals.extend_from_slice(&quantize(value, -1.0, 1.0).to_le_bytes()); + } + tex_coords.extend_from_slice( + &quantize(vertex.tex_coord.x, uv_min.x, uv_max.x).to_le_bytes(), + ); + tex_coords.extend_from_slice( + &quantize(vertex.tex_coord.y, uv_min.y, uv_max.y).to_le_bytes(), + ); + } + let mut triangles = Vec::with_capacity(face.indices.len() * 2); + for index in &face.indices { + triangles.extend_from_slice(&(*index as u16).to_le_bytes()); + } + face_values.push(OSD::Map(HashMap::from([ + ( + "TexCoord0Domain".to_owned(), + OSD::Map(HashMap::from([ + ("Min".to_owned(), OSD::from_vector2(uv_min)?), + ("Max".to_owned(), OSD::from_vector2(uv_max)?), + ])), + ), + ( + "PositionDomain".to_owned(), + OSD::Map(HashMap::from([ + ( + "Min".to_owned(), + OSD::from_vector3(Vector3 { + x: -0.5, + y: -0.5, + z: -0.5, + })?, + ), + ( + "Max".to_owned(), + OSD::from_vector3(Vector3 { + x: 0.5, + y: 0.5, + z: 0.5, + })?, + ), + ])), + ), + ("Position".to_owned(), OSD::Binary(positions)), + ("Normal".to_owned(), OSD::Binary(normals)), + ("TexCoord0".to_owned(), OSD::Binary(tex_coords)), + ("TriangleList".to_owned(), OSD::Binary(triangles)), + ]))); + } + let mesh = compress_osd(OSD::Array(face_values))?; + let physics = compress_osd(Self::physics_stub()?)?; + let header = OSD::Map(HashMap::from([ + ("version".to_owned(), OSD::Integer(1)), + ("creator".to_owned(), OSD::UUID(creator)), + ("date".to_owned(), OSD::Date(std::time::UNIX_EPOCH)), + ( + "high_lod".to_owned(), + OSD::Map(HashMap::from([ + ("offset".to_owned(), OSD::Integer(0)), + ( + "size".to_owned(), + OSD::Integer(i32::try_from(mesh.len()).map_err(|_| Error::Argument)?), + ), + ])), + ), + ( + "physics_convex".to_owned(), + OSD::Map(HashMap::from([ + ( + "offset".to_owned(), + OSD::Integer(i32::try_from(mesh.len()).map_err(|_| Error::Argument)?), + ), + ( + "size".to_owned(), + OSD::Integer(i32::try_from(physics.len()).map_err(|_| Error::Argument)?), + ), + ])), + ), + ])); + let header = OSDParser::serialize_llsd_binary_with_osd_boolean(header, false)?; + let total = header + .len() + .checked_add(mesh.len()) + .and_then(|n| n.checked_add(physics.len())) + .ok_or(Error::Argument)?; + if total > MAX_COLLADA_BYTES { + return Err(Error::Argument); + } + self.asset = Vec::with_capacity(total); + self.asset.extend_from_slice(&header); + self.asset.extend_from_slice(&mesh); + self.asset.extend_from_slice(&physics); + Ok(()) + } +} + +fn uv_bounds(vertices: &[Vertex]) -> (Vector2, Vector2) { + if vertices.is_empty() { + return (Vector2::zero(), Vector2::zero()); + } + let mut min = Vector2 { + x: f32::MAX, + y: f32::MAX, + }; + let mut max = Vector2 { + x: f32::MIN, + y: f32::MIN, + }; + for vertex in vertices { + min.x = min.x.min(vertex.tex_coord.x); + min.y = min.y.min(vertex.tex_coord.y); + max.x = max.x.max(vertex.tex_coord.x); + max.y = max.y.max(vertex.tex_coord.y); + } + (min, max) +} +fn quantize(value: f32, min: f32, max: f32) -> u16 { + if !value.is_finite() || !min.is_finite() || !max.is_finite() || max <= min { + 0 + } else { + (((value - min) / (max - min)).clamp(0.0, 1.0) * 65535.0).round() as u16 + } +} +fn compress_osd(value: OSD) -> Result, Error> { + let bytes = OSDParser::serialize_llsd_binary_with_osd_boolean(value, false)?; + let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); + encoder + .write_all(&bytes) + .map_err(|_| Error::InvalidOperation)?; + encoder.finish().map_err(|_| Error::InvalidOperation) +} + +pub struct ColladaLoader { + texture_codec: Option>, +} +impl std::fmt::Debug for ColladaLoader { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ColladaLoader") + .field("has_texture_codec", &self.texture_codec.is_some()) + .finish() + } +} + +impl ColladaLoader { + pub fn new(texture_codec: Option>) -> Result { + Ok(Self { texture_codec }) + } + pub fn load(&self, filename: String, load_images: bool) -> Result, Error> { + let path = PathBuf::from(filename); + let metadata = fs::metadata(&path).map_err(|_| Error::InvalidOperation)?; + if metadata.len() as usize > MAX_COLLADA_BYTES { + return Err(Error::Argument); + } + let xml = fs::read_to_string(&path).map_err(|_| model_error("Collada UTF-8"))?; + let document = roxmltree::Document::parse(&xml).map_err(|_| model_error("Collada XML"))?; + if document.descendants().count() > 1_000_000 { + return Err(Error::Argument); + } + let root = path + .parent() + .unwrap_or_else(|| Path::new(".")) + .canonicalize() + .map_err(|_| Error::InvalidOperation)?; + let materials = + parse_materials(&document, &root, load_images, self.texture_codec.as_deref())?; + let material_bindings: HashMap<_, _> = document + .descendants() + .filter(|node| node.has_tag_name("instance_material")) + .filter_map(|node| { + Some(( + node.attribute("symbol")?.to_owned(), + strip_hash(node.attribute("target")?).to_owned(), + )) + }) + .collect(); + let transform = AssetTransform::from_document(&document)?; + let mut prims = Vec::new(); + for geometry in document + .descendants() + .filter(|node| node.has_tag_name("geometry")) + { + if prims.len() >= MAX_FACES { + return Err(Error::Argument); + } + let Some(mesh) = geometry.children().find(|node| node.has_tag_name("mesh")) else { + continue; + }; + let mut sources = HashMap::new(); + for source in mesh.children().filter(|node| node.has_tag_name("source")) { + if let Some(id) = source.attribute("id") { + sources.insert(id.to_owned(), parse_source(source)?); + } + } + let mut vertices_sources = HashMap::new(); + for vertices in mesh.children().filter(|node| node.has_tag_name("vertices")) { + if let Some(id) = vertices.attribute("id") + && let Some(input) = vertices.children().find(|n| { + n.has_tag_name("input") && n.attribute("semantic") == Some("POSITION") + }) + { + vertices_sources.insert( + id.to_owned(), + strip_hash(input.attribute("source").unwrap_or_default()).to_owned(), + ); + } + } + let Some(position_source) = vertices_sources + .values() + .next() + .and_then(|id| sources.get(id)) + else { + return Err(model_error("Collada position source")); + }; + let mut prim = ModelPrim::new()?; + prim.id = geometry.attribute("id").unwrap_or_default().to_owned(); + prim.positions = position_source.vectors3()?; + for position in &mut prim.positions { + *position = transform.position(*position); + } + normalize_positions(&mut prim)?; + for primitive in mesh + .children() + .filter(|n| n.has_tag_name("triangles") || n.has_tag_name("polylist")) + { + prim.faces.push(parse_faces( + primitive, + &sources, + &vertices_sources, + &prim.positions, + primitive + .attribute("material") + .and_then(|symbol| { + material_bindings + .get(symbol) + .and_then(|target| materials.get(target)) + .or_else(|| materials.get(symbol)) + }) + .cloned(), + transform, + )?); + } + if prim.faces.is_empty() { + continue; + } + prim.create_asset(UUID::zero())?; + prims.push(prim); + } + Ok(prims) + } +} + +#[derive(Clone, Copy)] +enum UpAxis { + X, + Y, + Z, +} + +#[derive(Clone, Copy)] +struct AssetTransform { + unit: f32, + up: UpAxis, +} + +impl AssetTransform { + fn from_document(document: &roxmltree::Document<'_>) -> Result { + let asset = document + .descendants() + .find(|node| node.has_tag_name("asset")); + let unit = asset + .and_then(|node| node.descendants().find(|child| child.has_tag_name("unit"))) + .and_then(|node| node.attribute("meter")) + .map(str::parse::) + .transpose() + .map_err(|_| model_error("Collada unit"))? + .unwrap_or(1.0); + if !unit.is_finite() || unit <= 0.0 || unit > 1_000_000.0 { + return Err(model_error("Collada unit bounds")); + } + let up = match asset + .and_then(|node| { + node.descendants() + .find(|child| child.has_tag_name("up_axis")) + }) + .and_then(|node| node.text()) + .map(str::trim) + .unwrap_or("Y_UP") + { + "X_UP" => UpAxis::X, + "Y_UP" => UpAxis::Y, + "Z_UP" => UpAxis::Z, + _ => return Err(model_error("Collada up axis")), + }; + Ok(Self { unit, up }) + } + + fn axis(self, value: Vector3) -> Vector3 { + match self.up { + UpAxis::X => Vector3 { + x: -value.z, + y: value.y, + z: value.x, + }, + UpAxis::Y => Vector3 { + x: value.x, + y: -value.z, + z: value.y, + }, + UpAxis::Z => value, + } + } + + fn position(self, value: Vector3) -> Vector3 { + let value = self.axis(value); + Vector3 { + x: value.x * self.unit, + y: value.y * self.unit, + z: value.z * self.unit, + } + } +} + +#[derive(Debug)] +struct Source { + values: Vec, + stride: usize, +} +impl Source { + fn vectors3(&self) -> Result, Error> { + if self.stride < 3 { + return Err(model_error("Collada VEC3 stride")); + } + self.values + .chunks(self.stride) + .map(|v| { + if v.len() >= 3 { + Ok(Vector3 { + x: v[0], + y: v[1], + z: v[2], + }) + } else { + Err(model_error("Collada VEC3 data")) + } + }) + .collect() + } +} +fn parse_source(node: roxmltree::Node<'_, '_>) -> Result { + let array = node + .descendants() + .find(|n| n.has_tag_name("float_array")) + .ok_or_else(|| model_error("Collada float array"))?; + let values = parse_floats(array.text().unwrap_or_default())?; + if values.len() > MAX_VERTICES * 4 { + return Err(Error::Argument); + } + let stride = node + .descendants() + .find(|n| n.has_tag_name("accessor")) + .and_then(|n| n.attribute("stride")) + .and_then(|v| v.parse().ok()) + .unwrap_or(1); + if stride == 0 || stride > 16 || values.len() % stride != 0 { + return Err(model_error("Collada source stride")); + } + Ok(Source { values, stride }) +} +fn parse_floats(text: &str) -> Result, Error> { + text.split_whitespace() + .map(|v| { + v.parse::() + .ok() + .filter(|n| n.is_finite()) + .ok_or_else(|| model_error("Collada finite float")) + }) + .collect() +} +fn parse_ints(text: &str) -> Result, Error> { + text.split_whitespace() + .map(|v| v.parse::().map_err(|_| model_error("Collada index"))) + .collect() +} +fn strip_hash(value: &str) -> &str { + value.strip_prefix('#').unwrap_or(value) +} + +fn normalize_positions(prim: &mut ModelPrim) -> Result<(), Error> { + if prim.positions.is_empty() || prim.positions.len() > MAX_VERTICES { + return Err(model_error("Collada positions")); + } + for p in &prim.positions { + prim.bound_min.x = prim.bound_min.x.min(p.x); + prim.bound_min.y = prim.bound_min.y.min(p.y); + prim.bound_min.z = prim.bound_min.z.min(p.z); + prim.bound_max.x = prim.bound_max.x.max(p.x); + prim.bound_max.y = prim.bound_max.y.max(p.y); + prim.bound_max.z = prim.bound_max.z.max(p.z); + } + let scale = Vector3 { + x: prim.bound_max.x - prim.bound_min.x, + y: prim.bound_max.y - prim.bound_min.y, + z: prim.bound_max.z - prim.bound_min.z, + }; + prim.scale = scale; + prim.position = Vector3 { + x: prim.bound_min.x + scale.x / 2.0, + y: prim.bound_min.y + scale.y / 2.0, + z: prim.bound_min.z + scale.z / 2.0, + }; + for p in &mut prim.positions { + p.x = if scale.x == 0.0 { + 0.0 + } else { + (p.x - prim.bound_min.x) / scale.x - 0.5 + }; + p.y = if scale.y == 0.0 { + 0.0 + } else { + (p.y - prim.bound_min.y) / scale.y - 0.5 + }; + p.z = if scale.z == 0.0 { + 0.0 + } else { + (p.z - prim.bound_min.z) / scale.z - 0.5 + }; + } + Ok(()) +} + +fn parse_faces( + node: roxmltree::Node<'_, '_>, + sources: &HashMap, + vertices_sources: &HashMap, + positions: &[Vector3], + material: Option, + transform: AssetTransform, +) -> Result { + let mut inputs = Vec::new(); + let mut stride = 0usize; + for input in node.children().filter(|n| n.has_tag_name("input")) { + let offset = input + .attribute("offset") + .unwrap_or("0") + .parse::() + .map_err(|_| model_error("Collada input offset"))?; + stride = stride.max(offset + 1); + inputs.push(( + input.attribute("semantic").unwrap_or_default(), + offset, + strip_hash(input.attribute("source").unwrap_or_default()), + )); + } + if stride == 0 || stride > 32 { + return Err(model_error("Collada primitive stride")); + } + let indices = parse_ints( + node.children() + .find(|n| n.has_tag_name("p")) + .and_then(|n| n.text()) + .unwrap_or_default(), + )?; + let counts = if node.has_tag_name("triangles") { + vec![ + 3usize; + node.attribute("count") + .and_then(|v| v.parse().ok()) + .unwrap_or(indices.len() / (stride * 3)) + ] + } else { + parse_ints( + node.children() + .find(|n| n.has_tag_name("vcount")) + .and_then(|n| n.text()) + .unwrap_or_default(), + )? + }; + let mut face = ModelFace::new()?; + face.material_id = node.attribute("material").unwrap_or_default().to_owned(); + if let Some(material) = material { + face.material = material + } + let mut cursor = 0usize; + for count in counts { + if !(3..=4).contains(&count) { + return Err(model_error("Collada supports triangles and quads")); + } + let mut polygon = Vec::with_capacity(count); + for vertex in 0..count { + let base = cursor.checked_add(vertex * stride).ok_or(Error::Argument)?; + let mut out = Vertex { + position: Vector3::zero(), + normal: Vector3::zero(), + tex_coord: Vector2::zero(), + }; + for (semantic, offset, source_id) in &inputs { + let index = *indices + .get(base + offset) + .ok_or_else(|| model_error("Collada index bounds"))?; + match *semantic { + "VERTEX" => { + let position_id = vertices_sources + .get(*source_id) + .ok_or_else(|| model_error("Collada vertices source"))?; + let _ = position_id; + out.position = *positions + .get(index) + .ok_or_else(|| model_error("Collada position index"))? + } + "NORMAL" => { + let source = sources + .get(*source_id) + .ok_or_else(|| model_error("Collada normal source"))?; + let at = index * source.stride; + if at + 2 >= source.values.len() { + return Err(model_error("Collada normal index")); + } + out.normal = transform.axis(Vector3 { + x: source.values[at], + y: source.values[at + 1], + z: source.values[at + 2], + }) + } + "TEXCOORD" => { + let source = sources + .get(*source_id) + .ok_or_else(|| model_error("Collada UV source"))?; + let at = index * source.stride; + if at + 1 >= source.values.len() { + return Err(model_error("Collada UV index")); + } + out.tex_coord = Vector2 { + x: source.values[at], + y: source.values[at + 1], + } + } + _ => {} + } + } + polygon.push(out); + } + cursor = cursor.checked_add(count * stride).ok_or(Error::Argument)?; + let order: &[usize] = if count == 3 { + &[0, 1, 2] + } else { + &[0, 1, 2, 0, 2, 3] + }; + for index in order { + let vertex = polygon + .get(*index) + .ok_or_else(|| model_error("Collada polygon index"))?; + face.add_vertex(Vertex { + position: vertex.position, + normal: vertex.normal, + tex_coord: vertex.tex_coord, + })?; + } + } + Ok(face) +} + +fn parse_materials( + document: &roxmltree::Document<'_>, + root: &Path, + load_images: bool, + codec: Option<&dyn ITextureCodec>, +) -> Result, Error> { + let mut images = HashMap::new(); + for image in document.descendants().filter(|n| n.has_tag_name("image")) { + if let (Some(id), Some(init)) = ( + image.attribute("id"), + image + .descendants() + .find(|n| n.has_tag_name("init_from")) + .and_then(|n| n.text()), + ) { + images.insert(id.to_owned(), init.to_owned()); + } + } + let mut effects = HashMap::new(); + for effect in document.descendants().filter(|n| n.has_tag_name("effect")) { + let Some(effect_id) = effect.attribute("id") else { + continue; + }; + let mut value = ModelMaterial::new()?; + value.id = effect_id.to_owned(); + let diffuse = effect + .descendants() + .find(|node| node.has_tag_name("diffuse")); + if let Some(color) = diffuse + .and_then(|node| node.descendants().find(|child| child.has_tag_name("color"))) + .and_then(|node| node.text()) + { + apply_diffuse_color(&mut value, color)?; + } + if let Some(sampler) = diffuse + .and_then(|node| { + node.descendants() + .find(|child| child.has_tag_name("texture")) + }) + .and_then(|node| node.attribute("texture")) + { + let surface = effect + .descendants() + .find(|node| { + node.has_tag_name("newparam") && node.attribute("sid") == Some(sampler) + }) + .and_then(|node| { + node.descendants() + .find(|child| child.has_tag_name("sampler2D")) + }) + .and_then(|node| { + node.descendants() + .find(|child| child.has_tag_name("source")) + }) + .and_then(|node| node.text()) + .unwrap_or(sampler); + let image_id = effect + .descendants() + .find(|node| { + node.has_tag_name("newparam") && node.attribute("sid") == Some(surface) + }) + .and_then(|node| { + node.descendants() + .find(|child| child.has_tag_name("surface")) + }) + .and_then(|node| { + node.descendants() + .find(|child| child.has_tag_name("init_from")) + }) + .and_then(|node| node.text()) + .unwrap_or(surface); + if let Some(texture) = images.get(image_id) { + apply_material_texture(&mut value, texture, root, load_images, codec)?; + } + } + effects.insert(effect_id.to_owned(), value); + } + let mut result = HashMap::new(); + for material in document + .descendants() + .filter(|n| n.has_tag_name("material")) + { + let id = material.attribute("id").unwrap_or_default().to_owned(); + let effect_id = material + .children() + .find(|node| node.has_tag_name("instance_effect")) + .and_then(|node| node.attribute("url")) + .map(strip_hash); + let mut value = effect_id + .and_then(|effect_id| effects.get(effect_id)) + .cloned() + .unwrap_or(ModelMaterial::new()?); + value.id = id.clone(); + if let Some(color) = material + .descendants() + .find(|n| n.has_tag_name("diffuse")) + .and_then(|n| n.descendants().find(|c| c.has_tag_name("color"))) + .and_then(|n| n.text()) + { + apply_diffuse_color(&mut value, color)?; + } + if let Some(texture) = material + .descendants() + .find(|n| n.has_tag_name("texture")) + .and_then(|n| n.attribute("texture")) + .and_then(|id| images.get(id)) + { + apply_material_texture(&mut value, texture, root, load_images, codec)?; + } + if let Some(name) = material.attribute("name") { + result.insert(name.to_owned(), value.clone()); + } + result.insert(id, value); + } + Ok(result) +} + +fn apply_diffuse_color(material: &mut ModelMaterial, color: &str) -> Result<(), Error> { + let values = parse_floats(color)?; + if values.len() < 3 { + return Err(model_error("Collada diffuse color")); + } + material.diffuse_color = Color4 { + r: values[0].clamp(0.0, 1.0), + g: values[1].clamp(0.0, 1.0), + b: values[2].clamp(0.0, 1.0), + a: values.get(3).copied().unwrap_or(1.0).clamp(0.0, 1.0), + }; + Ok(()) +} + +fn apply_material_texture( + material: &mut ModelMaterial, + texture: &str, + root: &Path, + load_images: bool, + codec: Option<&dyn ITextureCodec>, +) -> Result<(), Error> { + material.texture = texture.to_owned(); + if load_images { + material.texture_data = load_texture(root, texture, codec)?; + } + Ok(()) +} +fn load_texture( + root: &Path, + relative: &str, + codec: Option<&dyn ITextureCodec>, +) -> Result, Error> { + if relative.len() > 4096 || relative.contains('\\') { + return Err(model_error("Collada texture path")); + } + for component in Path::new(relative).components() { + if !matches!(component, Component::Normal(_) | Component::CurDir) { + return Err(model_error("Collada texture traversal")); + } + } + let path = root + .join(relative) + .canonicalize() + .map_err(|_| Error::InvalidOperation)?; + if !path.starts_with(root) { + return Err(model_error("Collada texture extraction root")); + } + let bytes = fs::read(&path).map_err(|_| Error::InvalidOperation)?; + if bytes.len() > MAX_IMAGE_BYTES { + return Err(Error::Argument); + } + let extension = path + .extension() + .and_then(|v| v.to_str()) + .unwrap_or_default() + .to_ascii_lowercase(); + if extension != "jp2" && extension != "j2c" { + let Some(codec) = codec else { + return Err(Error::InvalidOperation); + }; + let image = codec.decode(Box::new(std::io::Cursor::new(bytes)))?; + #[cfg(feature = "jpeg2000")] + { + return libremetaverse_imaging::J2kCodec::encode( + &image, + libremetaverse_imaging::J2kEncodeOptions::default(), + ); + } + #[cfg(not(feature = "jpeg2000"))] + { + let _ = image; + return Err(Error::InvalidOperation); + } + } + Ok(bytes) +} + +pub struct ModelUploader { + pub include_physics_stub: bool, + pub use_model_as_physics: bool, + client: GridClient, + prims: Vec, + name: String, + description: String, + live_uploads: bool, +} +impl std::fmt::Debug for ModelUploader { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ModelUploader") + .field("prim_count", &self.prims.len()) + .field("name", &self.name) + .field("live_uploads", &self.live_uploads) + .finish() + } +} +impl ModelUploader { + pub fn new( + client: GridClient, + prims: Vec, + new_inv_name: String, + new_inv_desc: String, + ) -> Result { + if prims.is_empty() + || prims.len() > 10_000 + || new_inv_name.len() > 255 + || new_inv_desc.len() > 1024 + { + return Err(Error::Argument); + } + Ok(Self { + include_physics_stub: true, + use_model_as_physics: false, + client, + prims, + name: new_inv_name, + description: new_inv_desc, + live_uploads: false, + }) + } + pub fn enable_live_uploads(&mut self) { + self.live_uploads = true; + } + pub fn asset_resources(&self, upload: bool) -> Result { + let mut images = Vec::>::new(); + let mut image_index = HashMap::::new(); + let mut meshes = Vec::new(); + let mut instances = Vec::new(); + for prim in &self.prims { + if prim.asset.is_empty() { + return Err(Error::InvalidOperation); + } + let mut faces = Vec::new(); + for face in &prim.faces { + let mut map = HashMap::from([ + ( + "diffuse_color".into(), + OSD::from_color4(face.material.diffuse_color)?, + ), + ("fullbright".into(), OSD::Boolean(false)), + ]); + if !face.material.texture_data.is_empty() { + let index = if let Some(index) = image_index.get(&face.material.texture) { + *index + } else { + let index = i32::try_from(images.len()).map_err(|_| Error::Argument)?; + image_index.insert(face.material.texture.clone(), index); + images.push(face.material.texture_data.clone()); + index + }; + map.insert("image".into(), OSD::Integer(index)); + map.insert("scales".into(), OSD::Real(1.0)); + map.insert("scalet".into(), OSD::Real(1.0)); + map.insert("offsets".into(), OSD::Real(0.0)); + map.insert("offsett".into(), OSD::Real(0.0)); + map.insert("imagerot".into(), OSD::Real(0.0)); + } + faces.push(OSD::Map(map)); + } + instances.push(OSD::Map(HashMap::from([ + ("face_list".into(), OSD::Array(faces)), + ("position".into(), OSD::from_vector3(prim.position)?), + ("rotation".into(), OSD::from_quaternion(prim.rotation)?), + ("scale".into(), OSD::from_vector3(prim.scale)?), + ("material".into(), OSD::Integer(3)), + ("physics_shape_type".into(), OSD::Integer(2)), + ( + "mesh".into(), + OSD::Integer(i32::try_from(meshes.len()).map_err(|_| Error::Argument)?), + ), + ]))); + meshes.push(OSD::Binary(prim.asset.clone())); + } + Ok(OSD::Map(HashMap::from([ + ("instance_list".into(), OSD::Array(instances)), + ("mesh_list".into(), OSD::Array(meshes)), + ( + "texture_list".into(), + OSD::Array( + images + .into_iter() + .map(|data| OSD::Binary(if upload { data } else { Vec::new() })) + .collect(), + ), + ), + ("metric".into(), OSD::String("MUT_Unspecified".into())), + ]))) + } + pub async fn prepare_upload( + &self, + cancellation_token: CancellationToken, + ) -> Result, Error> { + cancellation_token.throw_if_cancellation_requested()?; + if !self.live_uploads { + return Err(Error::InvalidOperation); + } + let network = self.client.native_network()?; + let Some(sim) = network.native_current_sim() else { + return Ok(None); + }; + let Some(caps) = sim.caps else { + return Ok(None); + }; + let Some(uri) = caps.capability_uri("NewFileAgentInventory".into())? else { + return Ok(None); + }; + let inventory = self.client.native_inventory()?; + let permission_mask = + i32::try_from(crate::PermissionMask::ALL.0).map_err(|_| Error::InvalidOperation)?; + let payload = OSD::Map(HashMap::from([ + ("name".into(), OSD::String(self.name.clone())), + ("description".into(), OSD::String(self.description.clone())), + ("asset_resources".into(), self.asset_resources(false)?), + ("asset_type".into(), OSD::String("mesh".into())), + ("inventory_type".into(), OSD::String("object".into())), + ( + "folder_id".into(), + OSD::UUID(inventory.native_find_folder_for_asset_type(AssetType::Object)), + ), + ( + "texture_folder_id".into(), + OSD::UUID(inventory.native_find_folder_for_asset_type(AssetType::Texture)), + ), + ("everyone_mask".into(), OSD::Integer(permission_mask)), + ("group_mask".into(), OSD::Integer(permission_mask)), + ("next_owner_mask".into(), OSD::Integer(permission_mask)), + ( + "expected_upload_cost".into(), + OSD::Integer(self.client.settings_ref().upload_cost()), + ), + ])); + let (_, bytes) = self + .client + .native_http_caps_client() + .post_with_uri_osd_format_osd_cancellation_token_i_progress( + uri, + OSDFormat::Xml, + payload, + cancellation_token, + None, + ) + .await?; + let response = OSDParser::deserialize_with_bytes(bytes)?; + match &response { + OSD::Map(map) + if map + .get("state") + .and_then(|state| state.as_string().ok()) + .as_deref() + == Some("upload") => + { + Ok(Some(response)) + } + _ => Ok(None), + } + } + pub async fn perform_upload( + &self, + uploader: Uri, + cancellation_token: CancellationToken, + ) -> Result, Error> { + cancellation_token.throw_if_cancellation_requested()?; + if !self.live_uploads { + return Err(Error::InvalidOperation); + } + let (_, bytes) = self + .client + .native_http_caps_client() + .post_with_uri_osd_format_osd_cancellation_token_i_progress( + uploader, + OSDFormat::Xml, + self.asset_resources(true)?, + cancellation_token, + None, + ) + .await?; + Ok(Some(OSDParser::deserialize_with_bytes(bytes)?)) + } + pub async fn upload( + &self, + cancellation_token: CancellationToken, + ) -> Result, Error> { + let Some(prepared) = self.prepare_upload(cancellation_token.clone()).await? else { + return Ok(None); + }; + let uploader = match &prepared { + OSD::Map(map) => map + .get("uploader") + .and_then(|value| value.as_uri().ok()) + .flatten(), + _ => None, + }; + let Some(uploader) = uploader else { + return Ok(None); + }; + self.perform_upload(uploader, cancellation_token).await + } +} diff --git a/docs/assets.md b/docs/assets.md index c83ad6a..6e6770c 100644 --- a/docs/assets.md +++ b/docs/assets.md @@ -31,6 +31,29 @@ task material uploads use their two-stage capability contracts and require a complete response containing the new asset UUID. Upload size, cancellation, and timeout failures remove their pending correlation state. +OAR and model workflows are native as well. The tar reader validates checksums, +ustar paths, entry kinds, offsets, counts, nesting depth, per-entry size, and +expanded size before exposing data. OAR output is sorted and uses fixed gzip and +tar metadata, so identical asset, object, terrain, parcel, and settings trees +produce identical bytes. Loading recognizes canonical `UUID_type.ext` asset +names, bounded RAW32 terrain, and typed region-settings XML. Archive paths never +escape their selected root, and symbolic or hard links are rejected. + +`GltfDocument` accepts bounded JSON glTF 2.0 and GLB 2.0 documents, resolves data +URIs or caller-provided external buffers, validates buffer-view/accessor ranges, +and decodes normalized scalar, vector, matrix, skin, and animation data. JSON and +GLB output retain scenes, nodes, surfaces, materials, textures, skins, and +animations in stable order; multi-buffer GLB output aligns and rebases views. +Collada conversion applies declared units and up-axis orientation, resolves +profile-COMMON effects and material bindings, triangulates triangles/quads, and +confines referenced textures to the document directory. Mesh assets use stable +quantization and LLSD section ordering. + +Model upload is deliberately opt-in. Offline resource/pricing payload generation +does no network I/O; live preparation discovers the completed capability and +inventory layers, includes real destination folders, permission masks, and the +configured cost, then honors cancellation through both pricing and upload calls. + The disk cache uses UUID-only filenames by default and confines custom filename callbacks to the configured cache directory. Reads reject empty, oversized, or non-regular files. Writes use uniquely named same-directory files, flush them, diff --git a/tests/api-compile/Cargo.lock b/tests/api-compile/Cargo.lock index aa7c60b..a3f27b9 100644 --- a/tests/api-compile/Cargo.lock +++ b/tests/api-compile/Cargo.lock @@ -305,6 +305,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + [[package]] name = "find-msvc-tools" version = "0.1.10" @@ -734,6 +744,7 @@ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" name = "libremetaverse" version = "0.0.1" dependencies = [ + "base64", "bcdec_rs", "flate2", "futures-channel", @@ -746,6 +757,7 @@ dependencies = [ "reqwest", "roxmltree", "serde_json", + "tar", "tokio", "vorbis_rs", ] @@ -865,6 +877,12 @@ dependencies = [ "libremetaverse-types", ] +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "litemap" version = "0.8.2" @@ -1390,6 +1408,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rustls" version = "0.23.43" @@ -1686,6 +1717,17 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -2174,6 +2216,16 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + [[package]] name = "yoke" version = "0.8.3" diff --git a/tests/compat/tests/archive_model_semantics.rs b/tests/compat/tests/archive_model_semantics.rs new file mode 100644 index 0000000..08ee214 --- /dev/null +++ b/tests/compat/tests/archive_model_semantics.rs @@ -0,0 +1,288 @@ +use std::fs; +use std::path::PathBuf; +use std::sync::{Arc, Mutex}; + +use libremetaverse::GridClient; +use libremetaverse::assets::{ + OarFile, OarFileAssetLoadedCallback, OarFileSceneObjectLoadedCallback, + OarFileSettingsLoadedCallback, OarFileTerrainLoadedCallback, RegionSettings, +}; +use libremetaverse::import_export::{ColladaLoader, ModelFace, ModelPrim, ModelUploader}; +use libremetaverse::rendering::Vertex; +use libremetaverse_structured_data::OSD; +use libremetaverse_types::{Quaternion, UUID, Vector2, Vector3}; + +fn temporary_directory(label: &str) -> PathBuf { + let id = UUID::random().expect("temporary UUID").to_string(); + let path = std::env::temp_dir().join(format!("metacrate-{label}-{id}")); + fs::create_dir_all(&path).expect("create temporary directory"); + path +} + +#[test] +fn oar_packaging_is_deterministic_and_sorted() { + let root = temporary_directory("oar"); + for directory in ["assets", "objects", "terrains", "landdata", "settings"] { + fs::create_dir_all(root.join(directory)).expect("create OAR directory"); + } + fs::write(root.join("assets/z.texture"), b"z").expect("write z asset"); + fs::write(root.join("assets/a.texture"), b"a").expect("write a asset"); + fs::write(root.join("terrains/region.r32"), 1.0_f32.to_le_bytes()).expect("write terrain"); + let first = root.join("first.oar"); + let second = root.join("second.oar"); + OarFile::package_archive( + root.to_string_lossy().into_owned(), + first.to_string_lossy().into_owned(), + ) + .expect("first package"); + OarFile::package_archive( + root.to_string_lossy().into_owned(), + second.to_string_lossy().into_owned(), + ) + .expect("second package"); + assert_eq!( + fs::read(first).expect("first bytes"), + fs::read(second).expect("second bytes") + ); + fs::remove_dir_all(root).expect("remove temporary OAR directory"); +} + +#[test] +fn oar_canonical_asset_and_settings_names_round_trip() { + let root = temporary_directory("oar-roundtrip"); + for directory in ["assets", "objects", "terrains", "landdata", "settings"] { + fs::create_dir_all(root.join(directory)).expect("create OAR directory"); + } + let asset_id = UUID::random().expect("asset UUID"); + let asset_bytes = b"native-j2c-payload".to_vec(); + fs::write( + root.join(format!("assets/{asset_id}_texture.jp2")), + &asset_bytes, + ) + .expect("write canonical asset"); + let mut settings = RegionSettings::new().expect("region settings"); + settings.allow_damage = true; + settings.water_height = 23.5; + settings + .to_xml( + root.join("settings/Native Region.xml") + .to_string_lossy() + .into_owned(), + ) + .expect("write settings"); + let archive = root.join("roundtrip.oar"); + OarFile::package_archive( + root.to_string_lossy().into_owned(), + archive.to_string_lossy().into_owned(), + ) + .expect("package round trip"); + + let loaded_asset = Arc::new(Mutex::new(None)); + let loaded_asset_out = Arc::clone(&loaded_asset); + let loaded_settings = Arc::new(Mutex::new(None)); + let loaded_settings_out = Arc::clone(&loaded_settings); + OarFile::unpackage_archive( + archive.to_string_lossy().into_owned(), + OarFileAssetLoadedCallback::from_callback(move |asset, _, _| { + *loaded_asset_out.lock().expect("asset lock") = + Some((asset.asset_id(), asset.asset_type(), asset.asset_data)); + }), + OarFileTerrainLoadedCallback::from_callback(|_, _, _| {}), + OarFileSceneObjectLoadedCallback::from_callback(|_, _, _| {}), + OarFileSettingsLoadedCallback::from_callback(move |name, settings| { + *loaded_settings_out.lock().expect("settings lock") = + Some((name, settings.allow_damage, settings.water_height)); + }), + ) + .expect("unpackage round trip"); + + let asset = loaded_asset.lock().expect("loaded asset lock").clone(); + assert_eq!( + asset, + Some(( + asset_id, + libremetaverse_types::AssetType::Texture, + asset_bytes + )) + ); + assert_eq!( + loaded_settings + .lock() + .expect("loaded settings lock") + .clone(), + Some(("Native Region".to_owned(), true, 23.5)) + ); + fs::remove_dir_all(root).expect("remove round-trip directory"); +} + +#[test] +fn model_face_deduplicates_vertices_and_asset_is_deterministic() { + let vertex = || Vertex { + position: Vector3 { + x: -0.5, + y: 0.25, + z: 0.5, + }, + normal: Vector3 { + x: 0.0, + y: 0.0, + z: 1.0, + }, + tex_coord: Vector2 { x: 0.0, y: 1.0 }, + }; + let mut face = ModelFace::new().expect("model face"); + face.add_vertex(vertex()).expect("first vertex"); + face.add_vertex(vertex()).expect("duplicate vertex"); + assert_eq!(face.vertices.len(), 1); + assert_eq!(face.indices, [0, 0]); + + let mut prim = ModelPrim::new().expect("model prim"); + prim.faces.push(face); + prim.create_asset(UUID::zero()).expect("first model asset"); + let first = prim.asset.clone(); + prim.create_asset(UUID::zero()).expect("second model asset"); + assert!(!first.is_empty()); + assert_eq!(prim.asset, first); +} + +#[test] +fn model_upload_resources_are_deterministic_and_offline() { + let mut face = ModelFace::new().expect("model face"); + face.material.texture = "surface.jp2".into(); + face.material.texture_data = vec![1, 2, 3, 4]; + for position in [ + Vector3::zero(), + Vector3 { + x: 1.0, + y: 0.0, + z: 0.0, + }, + Vector3 { + x: 0.0, + y: 1.0, + z: 0.0, + }, + ] { + face.add_vertex(Vertex { + position, + normal: Vector3 { + x: 0.0, + y: 0.0, + z: 1.0, + }, + tex_coord: Vector2::zero(), + }) + .expect("upload vertex"); + } + let mut prim = ModelPrim::new().expect("model prim"); + prim.faces.push(face); + prim.create_asset(UUID::zero()).expect("mesh asset"); + let uploader = ModelUploader::new( + GridClient::new().expect("offline client"), + vec![prim], + "Offline mesh".into(), + "No network required".into(), + ) + .expect("model uploader"); + let first = uploader.asset_resources(false).expect("offline resources"); + let second = uploader.asset_resources(false).expect("repeat resources"); + assert_eq!(first, second); + let OSD::Map(map) = first else { + panic!("resource map"); + }; + let OSD::Array(textures) = map.get("texture_list").expect("texture list") else { + panic!("texture array"); + }; + assert_eq!(textures, &[OSD::Binary(Vec::new())]); + let OSD::Map(upload) = uploader.asset_resources(true).expect("upload resources") else { + panic!("upload map"); + }; + assert_eq!( + upload.get("texture_list"), + Some(&OSD::Array(vec![OSD::Binary(vec![1, 2, 3, 4])])) + ); +} + +#[test] +fn collada_triangle_converts_to_native_model_asset() { + let root = temporary_directory("collada"); + let file = root.join("triangle.dae"); + fs::write( + &file, + r##" + + + 0 0 0 1 0 0 0 1 0 + + +

0 1 2

+
+
"##, + ) + .expect("write Collada fixture"); + let prims = ColladaLoader::new(None) + .expect("Collada loader") + .load(file.to_string_lossy().into_owned(), false) + .expect("load Collada"); + assert_eq!(prims.len(), 1); + assert_eq!(prims[0].faces.len(), 1); + assert_eq!(prims[0].faces[0].indices, [0, 1, 2]); + assert!(!prims[0].asset.is_empty()); + assert_eq!(prims[0].rotation, Quaternion::identity()); + fs::remove_dir_all(root).expect("remove temporary Collada directory"); +} + +#[test] +fn collada_applies_units_effects_and_instance_material_bindings() { + let root = temporary_directory("collada-material"); + let file = root.join("material.dae"); + fs::write( + &file, + r##" + Z_UP + + 0.1 0.8 0.2 1 + + + + 0 0 0 1 0 0 0 1 0 + + +

0 1 2

+
+ + + +
"##, + ) + .expect("write material Collada fixture"); + let prims = ColladaLoader::new(None) + .expect("Collada loader") + .load(file.to_string_lossy().into_owned(), false) + .expect("load material Collada"); + assert_eq!(prims.len(), 1); + assert_eq!(prims[0].scale.x, 2.0); + assert_eq!(prims[0].scale.y, 2.0); + assert_eq!(prims[0].faces[0].material.id, "surface"); + assert_eq!(prims[0].faces[0].material.diffuse_color.g, 0.8); + fs::remove_dir_all(root).expect("remove material Collada directory"); +} + +#[test] +fn collada_texture_path_cannot_escape_document_root() { + let root = temporary_directory("collada-traversal"); + let file = root.join("traversal.dae"); + fs::write( + &file, + r##"../secret.jp2 +"##, + ) + .expect("write traversal fixture"); + assert!( + ColladaLoader::new(None) + .expect("Collada loader") + .load(file.to_string_lossy().into_owned(), true) + .is_err() + ); + fs::remove_dir_all(root).expect("remove traversal directory"); +} diff --git a/tests/compat/tests/asset_document_semantics.rs b/tests/compat/tests/asset_document_semantics.rs index 2338f09..7346e05 100644 --- a/tests/compat/tests/asset_document_semantics.rs +++ b/tests/compat/tests/asset_document_semantics.rs @@ -515,3 +515,67 @@ fn gltf_node_explicit_matrix() { assert_close(matrix.m24, 6.0, 1e-5); assert_close(matrix.m34, 7.0, 1e-5); } + +#[test] +fn gltf_rejects_buffer_view_outside_declared_buffer() { + let json = r#"{ + "asset":{"version":"2.0"}, + "buffers":[{"byteLength":4,"uri":"data:application/octet-stream;base64,AAAAAA=="}], + "bufferViews":[{"buffer":0,"byteOffset":3,"byteLength":4}] + }"#; + assert!(GltfDocument::load_gltf(json.into(), None).is_err()); +} + +#[test] +fn gltf_rejects_cyclic_node_graphs() { + let json = r#"{ + "asset":{"version":"2.0"}, + "nodes":[{"children":[1]},{"children":[0]}], + "scenes":[{"nodes":[0]}], + "scene":0 + }"#; + assert!(GltfDocument::load_gltf(json.into(), None).is_err()); +} + +#[test] +fn gltf_surface_skin_and_animation_round_trip() { + let json = r#"{ + "asset":{"version":"2.0"}, + "accessors":[ + {"componentType":5126,"count":0,"type":"SCALAR"}, + {"componentType":5126,"count":0,"type":"VEC3"} + ], + "images":[{"uri":"data:image/png;base64,iVBORw0KGgo=","mimeType":"image/png","name":"image"}], + "samplers":[{"magFilter":9729,"minFilter":9987,"wrapS":10497,"wrapT":33071,"name":"sampler"}], + "textures":[{"sampler":0,"source":0,"name":"texture"}], + "materials":[{ + "name":"surface", + "pbrMetallicRoughness":{"baseColorTexture":{"index":0},"metallicRoughnessTexture":{"index":0}}, + "normalTexture":{"index":0,"scale":0.5}, + "occlusionTexture":{"index":0,"strength":0.75}, + "emissiveTexture":{"index":0}, + "emissiveFactor":[0.1,0.2,0.3] + }], + "nodes":[{"skin":0}], + "skins":[{"joints":[0],"skeleton":0,"name":"skin"}], + "animations":[{ + "name":"move", + "samplers":[{"input":0,"output":1,"interpolation":"LINEAR"}], + "channels":[{"sampler":0,"target":{"node":0,"path":"translation"}}] + }] + }"#; + let document = GltfDocument::load_gltf(json.into(), None).expect("load complete surface"); + let serialized = document.to_json(None).expect("serialize complete surface"); + let round_trip = GltfDocument::load_gltf(serialized, None).expect("reload complete surface"); + assert_eq!(round_trip.images().len(), 1); + assert_eq!(round_trip.samplers().len(), 1); + assert_eq!(round_trip.textures().len(), 1); + assert_eq!(round_trip.skins()[0].joints(), [0]); + assert_eq!(round_trip.animations()[0].channels().len(), 1); + let material = &round_trip.materials()[0]; + assert!(material.base_color_texture().is_some()); + assert!(material.metallic_roughness_texture().is_some()); + assert!(material.normal_texture().is_some()); + assert!(material.occlusion_texture().is_some()); + assert!(material.emissive_texture().is_some()); +} diff --git a/tests/compat/tests/wire_shims.rs b/tests/compat/tests/wire_shims.rs index 4da1fe7..2ee7b4f 100644 --- a/tests/compat/tests/wire_shims.rs +++ b/tests/compat/tests/wire_shims.rs @@ -6,16 +6,8 @@ use libremetaverse::messages::linden::AgentDropGroupMessage; use libremetaverse::packets::UseCircuitCodePacket; use libremetaverse::{BitPack, PacketFrequency, PermissionMask, Permissions, Primitive}; -fn member_id(result: Result) -> &'static str { - result - .err() - .expect("failure-only shim unexpectedly returned a value") - .csharp_member() - .expect("failure-only shim error") -} - #[test] -fn each_wire_data_domain_is_callable_and_failure_only() { +fn each_wire_data_domain_is_callable_at_its_native_boundary() { let packet = UseCircuitCodePacket::new_with_constructor() .expect("generated packet constructor is native"); assert_eq!(packet.circuit_code.code, 0); @@ -26,20 +18,26 @@ fn each_wire_data_domain_is_callable_and_failure_only() { .is_empty() ); assert_eq!( - member_id(AssetTexture::new_with_constructor()), - "M:LibreMetaverse.Assets.AssetTexture.#ctor" + AssetTexture::new_with_constructor() + .expect("native asset texture constructor") + .components, + 0 ); assert_eq!( - member_id(GltfDocument::new()), - "M:LibreMetaverse.Assets.Gltf.GltfDocument.#ctor" + GltfDocument::new() + .expect("native glTF constructor") + .version(), + "2.0" + ); + assert!( + ModelPrim::new() + .expect("native model constructor") + .asset + .is_empty() ); assert_eq!( - member_id(ModelPrim::new()), - "M:LibreMetaverse.ImportExport.ModelPrim.#ctor" - ); - assert_eq!( - member_id(OarFile::package_archive(String::new(), String::new())), - "M:LibreMetaverse.Assets.OarFile.PackageArchive(System.String,System.String)" + OarFile::package_archive(String::new(), String::new()), + Err(libremetaverse::Error::InvalidOperation) ); let bit_pack = BitPack::new(Vec::new(), 0).expect("native bit pack constructor"); assert_eq!(bit_pack.byte_pos(), 0); diff --git a/tools/generate_api_shims.py b/tools/generate_api_shims.py index cab011f..2758889 100644 --- a/tools/generate_api_shims.py +++ b/tools/generate_api_shims.py @@ -140,6 +140,38 @@ NATIVE_TYPES = { "T:LibreMetaverse.Assets.AssetTexture": "crate::asset_models::AssetTexture", "T:LibreMetaverse.Assets.AssetWearable": "crate::asset_models::AssetWearable", "T:LibreMetaverse.Assets.GltfTextureTransform": "crate::asset_material::GltfTextureTransform", + "T:LibreMetaverse.Assets.Gltf.GltfAccessor": "crate::gltf::GltfAccessor", + "T:LibreMetaverse.Assets.Gltf.GltfAnimation": "crate::gltf::GltfAnimation", + "T:LibreMetaverse.Assets.Gltf.GltfAnimationChannel": "crate::gltf::GltfAnimationChannel", + "T:LibreMetaverse.Assets.Gltf.GltfAnimationChannelTarget": "crate::gltf::GltfAnimationChannelTarget", + "T:LibreMetaverse.Assets.Gltf.GltfAnimationSampler": "crate::gltf::GltfAnimationSampler", + "T:LibreMetaverse.Assets.Gltf.GltfBuffer": "crate::gltf::GltfBuffer", + "T:LibreMetaverse.Assets.Gltf.GltfBufferView": "crate::gltf::GltfBufferView", + "T:LibreMetaverse.Assets.Gltf.GltfDocument": "crate::gltf::GltfDocument", + "T:LibreMetaverse.Assets.Gltf.GltfDocumentMaterial": "crate::gltf::GltfDocumentMaterial", + "T:LibreMetaverse.Assets.Gltf.GltfImage": "crate::gltf::GltfImage", + "T:LibreMetaverse.Assets.Gltf.GltfMesh": "crate::gltf::GltfMesh", + "T:LibreMetaverse.Assets.Gltf.GltfNode": "crate::gltf::GltfNode", + "T:LibreMetaverse.Assets.Gltf.GltfNormalTextureRef": "crate::gltf::GltfNormalTextureRef", + "T:LibreMetaverse.Assets.Gltf.GltfOcclusionTextureRef": "crate::gltf::GltfOcclusionTextureRef", + "T:LibreMetaverse.Assets.Gltf.GltfPrimitive": "crate::gltf::GltfPrimitive", + "T:LibreMetaverse.Assets.Gltf.GltfSampler": "crate::gltf::GltfSampler", + "T:LibreMetaverse.Assets.Gltf.GltfScene": "crate::gltf::GltfScene", + "T:LibreMetaverse.Assets.Gltf.GltfSkin": "crate::gltf::GltfSkin", + "T:LibreMetaverse.Assets.Gltf.GltfTexture": "crate::gltf::GltfTexture", + "T:LibreMetaverse.Assets.Gltf.GltfTextureRef": "crate::gltf::GltfTextureRef", + "T:LibreMetaverse.Assets.TarArchiveReader": "crate::asset_archive::TarArchiveReader", + "T:LibreMetaverse.Assets.TarArchiveWriter": "crate::asset_archive::TarArchiveWriter", + "T:LibreMetaverse.Assets.OarFile.AssetLoadedCallback": "crate::asset_archive::OarFileAssetLoadedCallback", + "T:LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback": "crate::asset_archive::OarFileSceneObjectLoadedCallback", + "T:LibreMetaverse.Assets.OarFile.SettingsLoadedCallback": "crate::asset_archive::OarFileSettingsLoadedCallback", + "T:LibreMetaverse.Assets.OarFile.TerrainLoadedCallback": "crate::asset_archive::OarFileTerrainLoadedCallback", + "T:LibreMetaverse.Assets.RegionSettings": "crate::asset_archive::RegionSettings", + "T:LibreMetaverse.ImportExport.ColladaLoader": "crate::model_workflow::ColladaLoader", + "T:LibreMetaverse.ImportExport.ModelFace": "crate::model_workflow::ModelFace", + "T:LibreMetaverse.ImportExport.ModelMaterial": "crate::model_workflow::ModelMaterial", + "T:LibreMetaverse.ImportExport.ModelPrim": "crate::model_workflow::ModelPrim", + "T:LibreMetaverse.ImportExport.ModelUploader": "crate::model_workflow::ModelUploader", "T:LibreMetaverse.GLTFMaterialUpdate": "crate::object_material::GLTFMaterialUpdate", "T:LibreMetaverse.Assets.GestureStep": "crate::gesture::GestureStep", "T:LibreMetaverse.Assets.GestureStepAnimation": "crate::gesture::GestureStepAnimation", @@ -474,6 +506,25 @@ NATIVE_DECLARATIONS = { } NATIVE_MEMBER_BODIES = { + "F:LibreMetaverse.Assets.ArchiveConstants.ASSET_TYPE_TO_EXTENSION": + "crate::asset_archive::asset_type_to_extension()", + "F:LibreMetaverse.Assets.ArchiveConstants.EXTENSION_TO_ASSET_TYPE": + "crate::asset_archive::extension_to_asset_type()", + "M:LibreMetaverse.Assets.ArchiveConstants.#ctor": "Ok(Self)", + "M:LibreMetaverse.Assets.OarFile.ClearAssetFolder(System.String)": + "crate::asset_archive::clear_asset_folder(assets_path)", + "M:LibreMetaverse.Assets.OarFile.PackageArchive(System.String,System.String)": + "crate::asset_archive::package_archive(directory_name, filename)", + "M:LibreMetaverse.Assets.OarFile.SaveAssetsAsync(LibreMetaverse.AssetManager,LibreMetaverse.AssetType,System.Collections.Generic.IList{LibreMetaverse.UUID},System.String)": + "crate::asset_archive::save_assets(asset_manager, asset_type, assets, assets_path).await", + "M:LibreMetaverse.Assets.OarFile.SaveRegionSettings(LibreMetaverse.Simulator,System.String)": + "crate::asset_archive::save_region_settings(sim, settings_path)", + "M:LibreMetaverse.Assets.OarFile.SaveSimAssetsAsync(LibreMetaverse.AssetManager,LibreMetaverse.AssetType,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID,System.String)": + "crate::asset_archive::save_sim_asset(asset_manager, asset_type, asset_id, item_id, prim_id, assets_path).await", + "M:LibreMetaverse.Assets.OarFile.SaveTerrain(LibreMetaverse.Simulator,System.String)": + "crate::asset_archive::save_terrain(sim, terrain_path)", + "M:LibreMetaverse.Assets.OarFile.UnpackageArchive(System.String,LibreMetaverse.Assets.OarFile.AssetLoadedCallback,LibreMetaverse.Assets.OarFile.TerrainLoadedCallback,LibreMetaverse.Assets.OarFile.SceneObjectLoadedCallback,LibreMetaverse.Assets.OarFile.SettingsLoadedCallback)": + "crate::asset_archive::unpackage_archive(filename, asset_callback, terrain_callback, object_callback, settings_callback)", "P:LibreMetaverse.GridClient.Estate": "self.native_estate().unwrap_or_else(|_| panic!(\"failed to construct EstateTools\"))", "P:LibreMetaverse.GridClient.Estate#set": "self.native_set_estate(value)", "P:LibreMetaverse.GridClient.Environment":