diff --git a/Cargo.lock b/Cargo.lock index b9456a7..ac2b8b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -213,6 +213,7 @@ version = "0.0.1" dependencies = [ "getrandom", "md-5", + "stats_alloc", "uuid", ] @@ -301,6 +302,12 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "stats_alloc" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0e04424e733e69714ca1bbb9204c1a57f09f5493439520f9f68c132ad25eec" + [[package]] name = "syn" version = "2.0.119" diff --git a/README.md b/README.md index 8c9660b..386818f 100644 --- a/README.md +++ b/README.md @@ -54,10 +54,15 @@ Running `cargo test --workspace` is intentionally red during the shim stage. Milestone 04 implementation has begun. `libremetaverse-types` now provides native UUID, incremental CRC-32, little-endian floating-point byte helpers, Vector2/Vector3/Vector3d/Vector4 arithmetic and serialization, Color4 color -conversion, and Ray storage. UUID protocol bytes use explicit network order -while `compat::Guid` conversions preserve the mixed-endian .NET byte-array -layout. Vector and color byte encodings are explicitly little-endian and do not -depend on host architecture. The remaining Types surfaces stay visibly -failure-only until their owning milestone issues are completed. +conversion, Ray storage, Quaternion rotation/interpolation/serialization, and +Matrix4 construction, transforms, inversion, and decomposition. UUID protocol +bytes use explicit network order while `compat::Guid` conversions preserve the +mixed-endian .NET byte-array layout. Vector, color, and normalized quaternion +byte encodings are explicitly little-endian and do not depend on host +architecture. Matrix4 preserves LibreMetaverse's row-major storage, row-vector +transform direction, and translation in the fourth row; quaternion components +are stored as `(x, y, z, w)` and products use the reference Hamilton-product +direction. The remaining Types surfaces stay visibly failure-only until their +owning milestone issues are completed. The controlled audit aggregates every expected failure by standardized C# member ID and rejects unrelated fixture, assertion, compile, or symbol errors. diff --git a/api/RUST-MAPPING.tsv b/api/RUST-MAPPING.tsv index 6b483ed..556bad6 100644 --- a/api/RUST-MAPPING.tsv +++ b/api/RUST-MAPPING.tsv @@ -24290,7 +24290,7 @@ M:LibreMetaverse.Quaternion.Normalize(LibreMetaverse.Quaternion) method LibreMet M:LibreMetaverse.Quaternion.Parse(System.String) method LibreMetaverse.Quaternion LibreMetaverse.Quaternion.Parse(System.String val) libremetaverse-types libremetaverse_types::Quaternion::parse pub fn parse(val: String) -> Result owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.Quaternion.Slerp(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion,System.Single) method LibreMetaverse.Quaternion LibreMetaverse.Quaternion.Slerp(LibreMetaverse.Quaternion q1, LibreMetaverse.Quaternion q2, System.Single amount) libremetaverse-types libremetaverse_types::Quaternion::slerp pub fn slerp(q1: libremetaverse_types::Quaternion, q2: libremetaverse_types::Quaternion, amount: f32) -> Result owned,owned,owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.Quaternion.Subtract(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion) method LibreMetaverse.Quaternion LibreMetaverse.Quaternion.Subtract(LibreMetaverse.Quaternion q1, LibreMetaverse.Quaternion q2) libremetaverse-types libremetaverse_types::Quaternion::subtract pub fn subtract(q1: libremetaverse_types::Quaternion, q2: libremetaverse_types::Quaternion) -> Result owned,owned sync Result<_, crate::Error> direct_snake_case method mapped -M:LibreMetaverse.Quaternion.ToBytes(System.Byte[],System.Int32) method System.Void LibreMetaverse.Quaternion.ToBytes(System.Byte[] dest, System.Int32 pos) libremetaverse-types libremetaverse_types::Quaternion::to_bytes pub fn to_bytes(&self, dest: Vec, pos: i32) -> Result<(), crate::Error> shared_self,owned,owned sync Result<_, crate::Error> direct_snake_case method mapped +M:LibreMetaverse.Quaternion.ToBytes(System.Byte[],System.Int32) method System.Void LibreMetaverse.Quaternion.ToBytes(System.Byte[] dest, System.Int32 pos) libremetaverse-types libremetaverse_types::Quaternion::to_bytes pub fn to_bytes(&self, dest: &mut [u8], pos: i32) -> Result<(), crate::Error> shared_self,mutable_borrow,owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.Quaternion.ToEulerVector method LibreMetaverse.Vector3 LibreMetaverse.Quaternion.ToEulerVector() libremetaverse-types libremetaverse_types::Quaternion::to_euler_vector pub fn to_euler_vector(&self) -> Result shared_self sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.Quaternion.ToRawString method System.String LibreMetaverse.Quaternion.ToRawString() libremetaverse-types libremetaverse_types::Quaternion::to_raw_string pub fn to_raw_string(&self) -> Result shared_self sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.Quaternion.ToString method System.String LibreMetaverse.Quaternion.ToString() libremetaverse-types libremetaverse_types::Quaternion::to_string pub fn to_string(&self) -> String shared_self sync infallible_placeholder direct_snake_case method mapped diff --git a/api/SHIM-COVERAGE.md b/api/SHIM-COVERAGE.md index 74bc1e2..8212751 100644 --- a/api/SHIM-COVERAGE.md +++ b/api/SHIM-COVERAGE.md @@ -13,7 +13,7 @@ Generated by `python3 tools/generate_api_shims.py`; do not edit by hand. | `LibreMetaverse.Rendering.MeshFoundry` | 1 | 14 | callable failure-only shim | | `LibreMetaverse.Rendering.Simple` | 1 | 6 | callable failure-only shim | | `LibreMetaverse.StructuredData` | 16 | 295 | callable failure-only shim | -| `LibreMetaverse.Types` | 45 | 942 | native implementation: 8 types / 300 members; remaining surface is callable failure-only shims | +| `LibreMetaverse.Types` | 45 | 942 | native implementation: 10 types / 419 members; remaining surface is callable failure-only shims | | `LibreMetaverse.Utilities` | 3 | 13 | callable failure-only shim | | `LibreMetaverse.Voice.Vivox` | 64 | 531 | callable failure-only shim | | `LibreMetaverse.Voice.WebRTC` | 12 | 210 | callable failure-only shim | diff --git a/crates/libremetaverse-types/Cargo.toml b/crates/libremetaverse-types/Cargo.toml index 763811e..48379dc 100644 --- a/crates/libremetaverse-types/Cargo.toml +++ b/crates/libremetaverse-types/Cargo.toml @@ -14,3 +14,10 @@ workspace = true getrandom = "0.4" md-5 = "0.10" uuid = { version = "1.18", features = ["v4"] } + +[dev-dependencies] +stats_alloc = "0.1.10" + +[[bench]] +name = "geometry" +harness = false diff --git a/crates/libremetaverse-types/benches/geometry.rs b/crates/libremetaverse-types/benches/geometry.rs new file mode 100644 index 0000000..07eeaa4 --- /dev/null +++ b/crates/libremetaverse-types/benches/geometry.rs @@ -0,0 +1,65 @@ +use std::alloc::System; +use std::hint::black_box; +use std::time::Instant; + +use libremetaverse_types::{Matrix4, Quaternion, Vector3}; +use stats_alloc::{INSTRUMENTED_SYSTEM, Region, StatsAlloc}; + +#[global_allocator] +static ALLOCATOR: &StatsAlloc = &INSTRUMENTED_SYSTEM; + +fn main() { + const ITERATIONS: usize = 250_000; + + let rotation = Quaternion::create_from_eulers_with_single_single_single(0.25, 0.5, 0.75) + .expect("valid benchmark rotation"); + let matrix = Matrix4::mul_with_matrix4_matrix4( + Matrix4::create_scale(Vector3 { + x: 2.0, + y: 3.0, + z: 4.0, + }) + .expect("valid benchmark scale"), + Matrix4::create_translation(Vector3 { + x: 10.0, + y: 20.0, + z: 30.0, + }) + .expect("valid benchmark translation"), + ); + + // Warm up runtime and formatting paths before entering the measured region. + black_box(Matrix4::transform(matrix, rotation).expect("matrix transform")); + black_box( + Quaternion::slerp(Quaternion::identity(), rotation, 0.5).expect("quaternion interpolation"), + ); + + let allocation_region = Region::new(ALLOCATOR); + let started = Instant::now(); + let mut transformed = matrix; + let mut interpolated = Quaternion::identity(); + for index in 0..ITERATIONS { + let amount = f32::from(u8::try_from(index & 255).expect("masked benchmark index")) / 255.0; + transformed = Matrix4::transform(black_box(transformed), black_box(rotation)) + .expect("matrix transform"); + interpolated = Quaternion::slerp( + black_box(interpolated), + black_box(rotation), + black_box(amount), + ) + .expect("quaternion interpolation"); + } + let elapsed = started.elapsed(); + let allocation_stats = allocation_region.change(); + + black_box((transformed, interpolated)); + println!( + "geometry hot paths: {ITERATIONS} iterations in {elapsed:?}, {} allocations, {} reallocations", + allocation_stats.allocations, allocation_stats.reallocations + ); + assert_eq!( + (allocation_stats.allocations, allocation_stats.reallocations), + (0, 0), + "matrix transforms and quaternion interpolation must remain allocation-free" + ); +} diff --git a/crates/libremetaverse-types/src/compat.rs b/crates/libremetaverse-types/src/compat.rs index 41eab29..97a857c 100644 --- a/crates/libremetaverse-types/src/compat.rs +++ b/crates/libremetaverse-types/src/compat.rs @@ -23,6 +23,8 @@ impl ReadWrite for T {} #[derive(Clone, Debug)] pub enum Object { Color4(crate::Color4), + Matrix4(crate::Matrix4), + Quaternion(crate::Quaternion), String(String), UUID(crate::UUID), Vector2(crate::Vector2), @@ -35,6 +37,8 @@ impl PartialEq for Object { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Color4(lhs), Self::Color4(rhs)) => crate::Color4::eq(*lhs, *rhs), + (Self::Matrix4(lhs), Self::Matrix4(rhs)) => crate::Matrix4::eq(*lhs, *rhs), + (Self::Quaternion(lhs), Self::Quaternion(rhs)) => crate::Quaternion::eq(*lhs, *rhs), (Self::String(lhs), Self::String(rhs)) => lhs == rhs, (Self::UUID(lhs), Self::UUID(rhs)) => lhs == rhs, (Self::Vector2(lhs), Self::Vector2(rhs)) => crate::Vector2::eq(*lhs, *rhs), @@ -53,6 +57,8 @@ impl std::hash::Hash for Object { std::mem::discriminant(self).hash(state); match self { Self::Color4(value) => value.get_hash_code().hash(state), + Self::Matrix4(value) => value.get_hash_code().hash(state), + Self::Quaternion(value) => value.get_hash_code().hash(state), Self::String(value) => value.hash(state), Self::UUID(value) => value.hash(state), Self::Vector2(value) => value.get_hash_code().hash(state), @@ -80,6 +86,16 @@ impl From for Object { Self::Color4(value) } } +impl From for Object { + fn from(value: crate::Matrix4) -> Self { + Self::Matrix4(value) + } +} +impl From for Object { + fn from(value: crate::Quaternion) -> Self { + Self::Quaternion(value) + } +} impl From for Object { fn from(value: crate::Vector2) -> Self { Self::Vector2(value) diff --git a/crates/libremetaverse-types/src/generated.rs b/crates/libremetaverse-types/src/generated.rs index 6ec252c..6ebb310 100644 --- a/crates/libremetaverse-types/src/generated.rs +++ b/crates/libremetaverse-types/src/generated.rs @@ -1026,452 +1026,77 @@ pub enum Material { } /// C# type: `T:LibreMetaverse.Matrix4`. -#[derive(Clone, Copy, Debug)] -pub struct Matrix4 { - /// C# member: `F:LibreMetaverse.Matrix4.M11`. - pub m11: f32, - /// C# member: `F:LibreMetaverse.Matrix4.M12`. - pub m12: f32, - /// C# member: `F:LibreMetaverse.Matrix4.M13`. - pub m13: f32, - /// C# member: `F:LibreMetaverse.Matrix4.M14`. - pub m14: f32, - /// C# member: `F:LibreMetaverse.Matrix4.M21`. - pub m21: f32, - /// C# member: `F:LibreMetaverse.Matrix4.M22`. - pub m22: f32, - /// C# member: `F:LibreMetaverse.Matrix4.M23`. - pub m23: f32, - /// C# member: `F:LibreMetaverse.Matrix4.M24`. - pub m24: f32, - /// C# member: `F:LibreMetaverse.Matrix4.M31`. - pub m31: f32, - /// C# member: `F:LibreMetaverse.Matrix4.M32`. - pub m32: f32, - /// C# member: `F:LibreMetaverse.Matrix4.M33`. - pub m33: f32, - /// C# member: `F:LibreMetaverse.Matrix4.M34`. - pub m34: f32, - /// C# member: `F:LibreMetaverse.Matrix4.M41`. - pub m41: f32, - /// C# member: `F:LibreMetaverse.Matrix4.M42`. - pub m42: f32, - /// C# member: `F:LibreMetaverse.Matrix4.M43`. - pub m43: f32, - /// C# member: `F:LibreMetaverse.Matrix4.M44`. - pub m44: f32, -} -impl Matrix4 { - /// C# member: `F:LibreMetaverse.Matrix4.Identity`. - pub fn identity() -> libremetaverse_types::Matrix4 { - libremetaverse_types::unimplemented_api!("F:LibreMetaverse.Matrix4.Identity") - } - /// C# member: `F:LibreMetaverse.Matrix4.Zero`. - pub fn zero() -> libremetaverse_types::Matrix4 { - libremetaverse_types::unimplemented_api!("F:LibreMetaverse.Matrix4.Zero") - } - /// C# member: `M:LibreMetaverse.Matrix4.#ctor(LibreMetaverse.Matrix4)`. - pub fn new_with_matrix4(m: libremetaverse_types::Matrix4) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.#ctor(LibreMetaverse.Matrix4)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.#ctor(System.Single,System.Single,System.Single)`. - pub fn new_with_single_single_single( - roll: f32, - pitch: f32, - yaw: f32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.#ctor(System.Single,System.Single,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.#ctor(System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single)`. - pub fn new_with_single_single_single_single_single_single_single_single_single_single_single_single_single_single_single_single( - m11: f32, - m12: f32, - m13: f32, - m14: f32, - m21: f32, - m22: f32, - m23: f32, - m24: f32, - m31: f32, - m32: f32, - m33: f32, - m34: f32, - m41: f32, - m42: f32, - m43: f32, - m44: f32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.#ctor(System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.Add(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. - pub fn add_with_matrix4_matrix4( - matrix1: libremetaverse_types::Matrix4, - matrix2: libremetaverse_types::Matrix4, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.Add(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.Adjoint(LibreMetaverse.Matrix4)`. - pub fn adjoint( - matrix: libremetaverse_types::Matrix4, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.Adjoint(LibreMetaverse.Matrix4)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.Adjoint3x3(LibreMetaverse.Matrix4)`. - pub fn adjoint3x3( - matrix: libremetaverse_types::Matrix4, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.Adjoint3x3(LibreMetaverse.Matrix4)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.CreateFromAxisAngle(LibreMetaverse.Vector3,System.Single)`. - pub fn create_from_axis_angle( - axis: libremetaverse_types::Vector3, - angle: f32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.CreateFromAxisAngle(LibreMetaverse.Vector3,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.CreateFromEulers(System.Single,System.Single,System.Single)`. - pub fn create_from_eulers( - roll: f32, - pitch: f32, - yaw: f32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.CreateFromEulers(System.Single,System.Single,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.CreateFromQuaternion(LibreMetaverse.Quaternion)`. - pub fn create_from_quaternion( - quaternion: libremetaverse_types::Quaternion, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.CreateFromQuaternion(LibreMetaverse.Quaternion)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.CreateLookAt(LibreMetaverse.Vector3,LibreMetaverse.Vector3,LibreMetaverse.Vector3)`. - pub fn create_look_at( - camera_position: libremetaverse_types::Vector3, - camera_target: libremetaverse_types::Vector3, - camera_up_vector: libremetaverse_types::Vector3, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.CreateLookAt(LibreMetaverse.Vector3,LibreMetaverse.Vector3,LibreMetaverse.Vector3)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.CreateRotationX(System.Single)`. - pub fn create_rotation_x(radians: f32) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.CreateRotationX(System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.CreateRotationY(System.Single)`. - pub fn create_rotation_y(radians: f32) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.CreateRotationY(System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.CreateRotationZ(System.Single)`. - pub fn create_rotation_z(radians: f32) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.CreateRotationZ(System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.CreateScale(LibreMetaverse.Vector3)`. - pub fn create_scale( - scale: libremetaverse_types::Vector3, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.CreateScale(LibreMetaverse.Vector3)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.CreateTranslation(LibreMetaverse.Vector3)`. - pub fn create_translation( - position: libremetaverse_types::Vector3, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.CreateTranslation(LibreMetaverse.Vector3)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.CreateWorld(LibreMetaverse.Vector3,LibreMetaverse.Vector3,LibreMetaverse.Vector3)`. - pub fn create_world( - position: libremetaverse_types::Vector3, - forward: libremetaverse_types::Vector3, - up: libremetaverse_types::Vector3, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.CreateWorld(LibreMetaverse.Vector3,LibreMetaverse.Vector3,LibreMetaverse.Vector3)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.Decompose(LibreMetaverse.Vector3@,LibreMetaverse.Quaternion@,LibreMetaverse.Vector3@)`. - pub fn decompose( - &mut self, - scale: &mut libremetaverse_types::Vector3, - rotation: &mut libremetaverse_types::Quaternion, - translation: &mut libremetaverse_types::Vector3, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.Decompose(LibreMetaverse.Vector3@,LibreMetaverse.Quaternion@,LibreMetaverse.Vector3@)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.Determinant`. - pub fn determinant(&mut self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Matrix4.Determinant") - } - /// C# member: `M:LibreMetaverse.Matrix4.Determinant3x3`. - pub fn determinant3x3(&mut self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Matrix4.Determinant3x3") - } - /// C# member: `M:LibreMetaverse.Matrix4.Divide(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. - pub fn divide_with_matrix4_matrix4( - matrix1: libremetaverse_types::Matrix4, - matrix2: libremetaverse_types::Matrix4, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.Divide(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.Divide(LibreMetaverse.Matrix4,System.Single)`. - pub fn divide_with_matrix4_single( - matrix1: libremetaverse_types::Matrix4, - divider: f32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.Divide(LibreMetaverse.Matrix4,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.Equals(LibreMetaverse.Matrix4)`. - pub fn equals_with_matrix4(&self, other: libremetaverse_types::Matrix4) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Matrix4.Equals(LibreMetaverse.Matrix4)" - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.Equals(System.Object)`. - pub fn equals_with_object(&self, obj: Option) -> bool { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.Matrix4.Equals(System.Object)") - } - /// C# member: `M:LibreMetaverse.Matrix4.GetEulerAngles(System.Single@,System.Single@,System.Single@)`. - pub fn get_euler_angles( - &self, - roll: &mut f32, - pitch: &mut f32, - yaw: &mut f32, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.GetEulerAngles(System.Single@,System.Single@,System.Single@)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.GetHashCode`. - pub fn get_hash_code(&self) -> i32 { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.Matrix4.GetHashCode") - } - /// C# member: `M:LibreMetaverse.Matrix4.GetQuaternion`. - pub fn get_quaternion(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Matrix4.GetQuaternion") - } - /// C# member: `M:LibreMetaverse.Matrix4.Inverse(LibreMetaverse.Matrix4)`. - pub fn inverse( - matrix: libremetaverse_types::Matrix4, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.Inverse(LibreMetaverse.Matrix4)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.Inverse3x3(LibreMetaverse.Matrix4)`. - pub fn inverse3x3( - matrix: libremetaverse_types::Matrix4, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.Inverse3x3(LibreMetaverse.Matrix4)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.Lerp(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4,System.Single)`. - pub fn lerp( - matrix1: libremetaverse_types::Matrix4, - matrix2: libremetaverse_types::Matrix4, - amount: f32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.Lerp(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.Minor(LibreMetaverse.Matrix4,System.Int32,System.Int32)`. - pub fn minor( - matrix: libremetaverse_types::Matrix4, - row: i32, - col: i32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.Minor(LibreMetaverse.Matrix4,System.Int32,System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.Multiply(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. - pub fn multiply_with_matrix4_matrix4( - matrix1: libremetaverse_types::Matrix4, - matrix2: libremetaverse_types::Matrix4, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.Multiply(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.Multiply(LibreMetaverse.Matrix4,System.Single)`. - pub fn multiply_with_matrix4_single( - matrix1: libremetaverse_types::Matrix4, - scale_factor: f32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.Multiply(LibreMetaverse.Matrix4,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.Negate(LibreMetaverse.Matrix4)`. - pub fn negate( - matrix: libremetaverse_types::Matrix4, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.Negate(LibreMetaverse.Matrix4)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.Subtract(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. - pub fn subtract( - matrix1: libremetaverse_types::Matrix4, - matrix2: libremetaverse_types::Matrix4, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.Subtract(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.Matrix4.ToString") - } - /// C# member: `M:LibreMetaverse.Matrix4.Trace`. - pub fn trace(&mut self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Matrix4.Trace") - } - /// C# member: `M:LibreMetaverse.Matrix4.Transform(LibreMetaverse.Matrix4,LibreMetaverse.Quaternion)`. - pub fn transform( - value: libremetaverse_types::Matrix4, - rotation: libremetaverse_types::Quaternion, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.Transform(LibreMetaverse.Matrix4,LibreMetaverse.Quaternion)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.Transpose(LibreMetaverse.Matrix4)`. - pub fn transpose( - matrix: libremetaverse_types::Matrix4, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Matrix4.Transpose(LibreMetaverse.Matrix4)", - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.op_Addition(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. - pub fn add_with_matrix4_matrix4_31d5e46d( - left: libremetaverse_types::Matrix4, - right: libremetaverse_types::Matrix4, - ) -> libremetaverse_types::Matrix4 { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Matrix4.op_Addition(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)" - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.op_Division(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. - pub fn div_with_matrix4_matrix4( - left: libremetaverse_types::Matrix4, - right: libremetaverse_types::Matrix4, - ) -> libremetaverse_types::Matrix4 { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Matrix4.op_Division(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)" - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.op_Division(LibreMetaverse.Matrix4,System.Single)`. - pub fn div_with_matrix4_single( - matrix: libremetaverse_types::Matrix4, - divider: f32, - ) -> libremetaverse_types::Matrix4 { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Matrix4.op_Division(LibreMetaverse.Matrix4,System.Single)" - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.op_Equality(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. - pub fn eq(left: libremetaverse_types::Matrix4, right: libremetaverse_types::Matrix4) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Matrix4.op_Equality(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)" - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.op_Inequality(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. - pub fn ne(left: libremetaverse_types::Matrix4, right: libremetaverse_types::Matrix4) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Matrix4.op_Inequality(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)" - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.op_Multiply(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. - pub fn mul_with_matrix4_matrix4( - left: libremetaverse_types::Matrix4, - right: libremetaverse_types::Matrix4, - ) -> libremetaverse_types::Matrix4 { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Matrix4.op_Multiply(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)" - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.op_Multiply(LibreMetaverse.Matrix4,System.Single)`. - pub fn mul_with_matrix4_single( - left: libremetaverse_types::Matrix4, - scalar: f32, - ) -> libremetaverse_types::Matrix4 { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Matrix4.op_Multiply(LibreMetaverse.Matrix4,System.Single)" - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.op_Subtraction(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. - pub fn sub( - left: libremetaverse_types::Matrix4, - right: libremetaverse_types::Matrix4, - ) -> libremetaverse_types::Matrix4 { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Matrix4.op_Subtraction(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)" - ) - } - /// C# member: `M:LibreMetaverse.Matrix4.op_UnaryNegation(LibreMetaverse.Matrix4)`. - pub fn neg(matrix: libremetaverse_types::Matrix4) -> libremetaverse_types::Matrix4 { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Matrix4.op_UnaryNegation(LibreMetaverse.Matrix4)" - ) - } - /// C# member: `P:LibreMetaverse.Matrix4.AtAxis`. - pub fn at_axis(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Matrix4.AtAxis") - } - /// C# member: `P:LibreMetaverse.Matrix4.Item(System.Int32)`. - pub fn item_with_int32(&self, row: i32) -> libremetaverse_types::Vector4 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Matrix4.Item(System.Int32)") - } - /// C# member: `P:LibreMetaverse.Matrix4.Item(System.Int32,System.Int32)`. - pub fn item_with_int32_int32(&self, row: i32, column: i32) -> f32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Matrix4.Item(System.Int32,System.Int32)" - ) - } - /// C# member: `P:LibreMetaverse.Matrix4.LeftAxis`. - pub fn left_axis(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Matrix4.LeftAxis") - } - /// C# member: `P:LibreMetaverse.Matrix4.UpAxis`. - pub fn up_axis(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Matrix4.UpAxis") - } -} +/// C# member: `F:LibreMetaverse.Matrix4.Identity`. +/// C# member: `F:LibreMetaverse.Matrix4.M11`. +/// C# member: `F:LibreMetaverse.Matrix4.M12`. +/// C# member: `F:LibreMetaverse.Matrix4.M13`. +/// C# member: `F:LibreMetaverse.Matrix4.M14`. +/// C# member: `F:LibreMetaverse.Matrix4.M21`. +/// C# member: `F:LibreMetaverse.Matrix4.M22`. +/// C# member: `F:LibreMetaverse.Matrix4.M23`. +/// C# member: `F:LibreMetaverse.Matrix4.M24`. +/// C# member: `F:LibreMetaverse.Matrix4.M31`. +/// C# member: `F:LibreMetaverse.Matrix4.M32`. +/// C# member: `F:LibreMetaverse.Matrix4.M33`. +/// C# member: `F:LibreMetaverse.Matrix4.M34`. +/// C# member: `F:LibreMetaverse.Matrix4.M41`. +/// C# member: `F:LibreMetaverse.Matrix4.M42`. +/// C# member: `F:LibreMetaverse.Matrix4.M43`. +/// C# member: `F:LibreMetaverse.Matrix4.M44`. +/// C# member: `F:LibreMetaverse.Matrix4.Zero`. +/// C# member: `M:LibreMetaverse.Matrix4.#ctor(LibreMetaverse.Matrix4)`. +/// C# member: `M:LibreMetaverse.Matrix4.#ctor(System.Single,System.Single,System.Single)`. +/// C# member: `M:LibreMetaverse.Matrix4.#ctor(System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single,System.Single)`. +/// C# member: `M:LibreMetaverse.Matrix4.Add(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. +/// C# member: `M:LibreMetaverse.Matrix4.Adjoint(LibreMetaverse.Matrix4)`. +/// C# member: `M:LibreMetaverse.Matrix4.Adjoint3x3(LibreMetaverse.Matrix4)`. +/// C# member: `M:LibreMetaverse.Matrix4.CreateFromAxisAngle(LibreMetaverse.Vector3,System.Single)`. +/// C# member: `M:LibreMetaverse.Matrix4.CreateFromEulers(System.Single,System.Single,System.Single)`. +/// C# member: `M:LibreMetaverse.Matrix4.CreateFromQuaternion(LibreMetaverse.Quaternion)`. +/// C# member: `M:LibreMetaverse.Matrix4.CreateLookAt(LibreMetaverse.Vector3,LibreMetaverse.Vector3,LibreMetaverse.Vector3)`. +/// C# member: `M:LibreMetaverse.Matrix4.CreateRotationX(System.Single)`. +/// C# member: `M:LibreMetaverse.Matrix4.CreateRotationY(System.Single)`. +/// C# member: `M:LibreMetaverse.Matrix4.CreateRotationZ(System.Single)`. +/// C# member: `M:LibreMetaverse.Matrix4.CreateScale(LibreMetaverse.Vector3)`. +/// C# member: `M:LibreMetaverse.Matrix4.CreateTranslation(LibreMetaverse.Vector3)`. +/// C# member: `M:LibreMetaverse.Matrix4.CreateWorld(LibreMetaverse.Vector3,LibreMetaverse.Vector3,LibreMetaverse.Vector3)`. +/// C# member: `M:LibreMetaverse.Matrix4.Decompose(LibreMetaverse.Vector3@,LibreMetaverse.Quaternion@,LibreMetaverse.Vector3@)`. +/// C# member: `M:LibreMetaverse.Matrix4.Determinant`. +/// C# member: `M:LibreMetaverse.Matrix4.Determinant3x3`. +/// C# member: `M:LibreMetaverse.Matrix4.Divide(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. +/// C# member: `M:LibreMetaverse.Matrix4.Divide(LibreMetaverse.Matrix4,System.Single)`. +/// C# member: `M:LibreMetaverse.Matrix4.Equals(LibreMetaverse.Matrix4)`. +/// C# member: `M:LibreMetaverse.Matrix4.Equals(System.Object)`. +/// C# member: `M:LibreMetaverse.Matrix4.GetEulerAngles(System.Single@,System.Single@,System.Single@)`. +/// C# member: `M:LibreMetaverse.Matrix4.GetHashCode`. +/// C# member: `M:LibreMetaverse.Matrix4.GetQuaternion`. +/// C# member: `M:LibreMetaverse.Matrix4.Inverse(LibreMetaverse.Matrix4)`. +/// C# member: `M:LibreMetaverse.Matrix4.Inverse3x3(LibreMetaverse.Matrix4)`. +/// C# member: `M:LibreMetaverse.Matrix4.Lerp(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4,System.Single)`. +/// C# member: `M:LibreMetaverse.Matrix4.Minor(LibreMetaverse.Matrix4,System.Int32,System.Int32)`. +/// C# member: `M:LibreMetaverse.Matrix4.Multiply(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. +/// C# member: `M:LibreMetaverse.Matrix4.Multiply(LibreMetaverse.Matrix4,System.Single)`. +/// C# member: `M:LibreMetaverse.Matrix4.Negate(LibreMetaverse.Matrix4)`. +/// C# member: `M:LibreMetaverse.Matrix4.Subtract(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. +/// C# member: `M:LibreMetaverse.Matrix4.ToString`. +/// C# member: `M:LibreMetaverse.Matrix4.Trace`. +/// C# member: `M:LibreMetaverse.Matrix4.Transform(LibreMetaverse.Matrix4,LibreMetaverse.Quaternion)`. +/// C# member: `M:LibreMetaverse.Matrix4.Transpose(LibreMetaverse.Matrix4)`. +/// C# member: `M:LibreMetaverse.Matrix4.op_Addition(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. +/// C# member: `M:LibreMetaverse.Matrix4.op_Division(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. +/// C# member: `M:LibreMetaverse.Matrix4.op_Division(LibreMetaverse.Matrix4,System.Single)`. +/// C# member: `M:LibreMetaverse.Matrix4.op_Equality(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. +/// C# member: `M:LibreMetaverse.Matrix4.op_Inequality(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. +/// C# member: `M:LibreMetaverse.Matrix4.op_Multiply(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. +/// C# member: `M:LibreMetaverse.Matrix4.op_Multiply(LibreMetaverse.Matrix4,System.Single)`. +/// C# member: `M:LibreMetaverse.Matrix4.op_Subtraction(LibreMetaverse.Matrix4,LibreMetaverse.Matrix4)`. +/// C# member: `M:LibreMetaverse.Matrix4.op_UnaryNegation(LibreMetaverse.Matrix4)`. +/// C# member: `P:LibreMetaverse.Matrix4.AtAxis`. +/// C# member: `P:LibreMetaverse.Matrix4.Item(System.Int32)`. +/// C# member: `P:LibreMetaverse.Matrix4.Item(System.Int32,System.Int32)`. +/// C# member: `P:LibreMetaverse.Matrix4.LeftAxis`. +/// C# member: `P:LibreMetaverse.Matrix4.UpAxis`. +pub use crate::geometry::Matrix4; /// C# type: `T:LibreMetaverse.MruRemovalStrategy`1`. pub struct MruRemovalStrategy { @@ -1974,361 +1599,56 @@ pub enum ProfileCurve { } /// C# type: `T:LibreMetaverse.Quaternion`. -#[derive(Clone, Copy, Debug)] -pub struct Quaternion { - /// C# member: `F:LibreMetaverse.Quaternion.W`. - pub w: f32, - /// C# member: `F:LibreMetaverse.Quaternion.X`. - pub x: f32, - /// C# member: `F:LibreMetaverse.Quaternion.Y`. - pub y: f32, - /// C# member: `F:LibreMetaverse.Quaternion.Z`. - pub z: f32, -} -impl Quaternion { - /// C# member: `F:LibreMetaverse.Quaternion.Identity`. - pub fn identity() -> libremetaverse_types::Quaternion { - libremetaverse_types::unimplemented_api!("F:LibreMetaverse.Quaternion.Identity") - } - /// C# member: `M:LibreMetaverse.Quaternion.#ctor(LibreMetaverse.Vector3,System.Single)`. - pub fn new_with_vector3_single( - vector_part: libremetaverse_types::Vector3, - scalar_part: f32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.#ctor(LibreMetaverse.Vector3,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.#ctor(System.Byte[],System.Int32,System.Boolean)`. - pub fn new_with_bytes_int32_boolean( - byte_array: Vec, - pos: i32, - normalized: bool, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.#ctor(System.Byte[],System.Int32,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.#ctor(System.Single,System.Single,System.Single)`. - pub fn new_with_single_single_single(x: f32, y: f32, z: f32) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.#ctor(System.Single,System.Single,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.#ctor(System.Single,System.Single,System.Single,System.Single)`. - pub fn new_with_single_single_single_single( - x: f32, - y: f32, - z: f32, - w: f32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.#ctor(System.Single,System.Single,System.Single,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.Add(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. - pub fn add_with_quaternion_quaternion( - q1: libremetaverse_types::Quaternion, - q2: libremetaverse_types::Quaternion, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.Add(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.ApproxEquals(LibreMetaverse.Quaternion,System.Single)`. - pub fn approx_equals( - &mut self, - quat: libremetaverse_types::Quaternion, - tolerance: f32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.ApproxEquals(LibreMetaverse.Quaternion,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.Conjugate(LibreMetaverse.Quaternion)`. - pub fn conjugate( - quaternion: libremetaverse_types::Quaternion, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.Conjugate(LibreMetaverse.Quaternion)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.CreateFromAxisAngle(LibreMetaverse.Vector3,System.Single)`. - pub fn create_from_axis_angle_with_vector3_single( - axis: libremetaverse_types::Vector3, - angle: f32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.CreateFromAxisAngle(LibreMetaverse.Vector3,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.CreateFromAxisAngle(System.Single,System.Single,System.Single,System.Single)`. - pub fn create_from_axis_angle_with_single_single_single_single( - axis_x: f32, - axis_y: f32, - axis_z: f32, - angle: f32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.CreateFromAxisAngle(System.Single,System.Single,System.Single,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.CreateFromEulers(LibreMetaverse.Vector3)`. - pub fn create_from_eulers_with_vector3( - eulers: libremetaverse_types::Vector3, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.CreateFromEulers(LibreMetaverse.Vector3)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.CreateFromEulers(System.Single,System.Single,System.Single)`. - pub fn create_from_eulers_with_single_single_single( - roll: f32, - pitch: f32, - yaw: f32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.CreateFromEulers(System.Single,System.Single,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.CreateFromRotationMatrix(LibreMetaverse.Matrix4)`. - pub fn create_from_rotation_matrix( - matrix: libremetaverse_types::Matrix4, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.CreateFromRotationMatrix(LibreMetaverse.Matrix4)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.Divide(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. - pub fn divide( - q1: libremetaverse_types::Quaternion, - q2: libremetaverse_types::Quaternion, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.Divide(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.Dot(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. - pub fn dot( - q1: libremetaverse_types::Quaternion, - q2: libremetaverse_types::Quaternion, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.Dot(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.Equals(LibreMetaverse.Quaternion)`. - pub fn equals_with_quaternion(&self, other: libremetaverse_types::Quaternion) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Quaternion.Equals(LibreMetaverse.Quaternion)" - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.Equals(System.Object)`. - pub fn equals_with_object(&self, obj: Option) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Quaternion.Equals(System.Object)" - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.FromBytes(System.Byte[],System.Int32,System.Boolean)`. - pub fn from_bytes( - byte_array: Vec, - pos: i32, - normalized: bool, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.FromBytes(System.Byte[],System.Int32,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.GetAxisAngle(LibreMetaverse.Vector3@,System.Single@)`. - pub fn get_axis_angle( - &self, - axis: &mut libremetaverse_types::Vector3, - angle: &mut f32, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.GetAxisAngle(LibreMetaverse.Vector3@,System.Single@)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.GetBytes`. - pub fn get_bytes(&self) -> Result, crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.Quaternion.GetBytes") - } - /// C# member: `M:LibreMetaverse.Quaternion.GetEulerAngles(System.Single@,System.Single@,System.Single@)`. - pub fn get_euler_angles( - &self, - roll: &mut f32, - pitch: &mut f32, - yaw: &mut f32, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.GetEulerAngles(System.Single@,System.Single@,System.Single@)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.GetHashCode`. - pub fn get_hash_code(&self) -> i32 { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.Quaternion.GetHashCode") - } - /// C# member: `M:LibreMetaverse.Quaternion.Inverse(LibreMetaverse.Quaternion)`. - pub fn inverse( - quaternion: libremetaverse_types::Quaternion, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.Inverse(LibreMetaverse.Quaternion)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.Length`. - pub fn length(&mut self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Quaternion.Length") - } - /// C# member: `M:LibreMetaverse.Quaternion.LengthSquared`. - pub fn length_squared(&mut self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Quaternion.LengthSquared") - } - /// C# member: `M:LibreMetaverse.Quaternion.Multiply(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. - pub fn multiply_with_quaternion_quaternion( - a: libremetaverse_types::Quaternion, - b: libremetaverse_types::Quaternion, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.Multiply(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.Multiply(LibreMetaverse.Quaternion,System.Single)`. - pub fn multiply_with_quaternion_single( - quaternion: libremetaverse_types::Quaternion, - scale_factor: f32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.Multiply(LibreMetaverse.Quaternion,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.Negate(LibreMetaverse.Quaternion)`. - pub fn negate( - quaternion: libremetaverse_types::Quaternion, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.Negate(LibreMetaverse.Quaternion)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.Normalize(LibreMetaverse.Quaternion)`. - pub fn normalize( - q: libremetaverse_types::Quaternion, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.Normalize(LibreMetaverse.Quaternion)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.Parse(System.String)`. - pub fn parse(val: String) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Quaternion.Parse(System.String)") - } - /// C# member: `M:LibreMetaverse.Quaternion.Slerp(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion,System.Single)`. - pub fn slerp( - q1: libremetaverse_types::Quaternion, - q2: libremetaverse_types::Quaternion, - amount: f32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.Slerp(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.Subtract(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. - pub fn subtract( - q1: libremetaverse_types::Quaternion, - q2: libremetaverse_types::Quaternion, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.Subtract(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.ToBytes(System.Byte[],System.Int32)`. - pub fn to_bytes(&self, dest: Vec, pos: i32) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Quaternion.ToBytes(System.Byte[],System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.ToEulerVector`. - pub fn to_euler_vector(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Quaternion.ToEulerVector") - } - /// C# member: `M:LibreMetaverse.Quaternion.ToRawString`. - pub fn to_raw_string(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Quaternion.ToRawString") - } - /// C# member: `M:LibreMetaverse.Quaternion.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.Quaternion.ToString") - } - /// C# member: `M:LibreMetaverse.Quaternion.TryParse(System.String,LibreMetaverse.Quaternion@)`. - pub fn try_parse(val: String, result: &mut libremetaverse_types::Quaternion) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Quaternion.TryParse(System.String,LibreMetaverse.Quaternion@)" - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.op_Addition(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. - pub fn add_with_quaternion_quaternion_b0243903( - q1: libremetaverse_types::Quaternion, - q2: libremetaverse_types::Quaternion, - ) -> libremetaverse_types::Quaternion { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Quaternion.op_Addition(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)" - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.op_Division(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. - pub fn div( - q1: libremetaverse_types::Quaternion, - q2: libremetaverse_types::Quaternion, - ) -> libremetaverse_types::Quaternion { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Quaternion.op_Division(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)" - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.op_Equality(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. - pub fn eq(q1: libremetaverse_types::Quaternion, q2: libremetaverse_types::Quaternion) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Quaternion.op_Equality(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)" - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.op_Inequality(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. - pub fn ne(q1: libremetaverse_types::Quaternion, q2: libremetaverse_types::Quaternion) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Quaternion.op_Inequality(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)" - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.op_Multiply(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. - pub fn mul_with_quaternion_quaternion( - a: libremetaverse_types::Quaternion, - b: libremetaverse_types::Quaternion, - ) -> libremetaverse_types::Quaternion { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Quaternion.op_Multiply(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)" - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.op_Multiply(LibreMetaverse.Quaternion,System.Single)`. - pub fn mul_with_quaternion_single( - q: libremetaverse_types::Quaternion, - scale_factor: f32, - ) -> libremetaverse_types::Quaternion { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Quaternion.op_Multiply(LibreMetaverse.Quaternion,System.Single)" - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.op_Subtraction(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. - pub fn sub( - q1: libremetaverse_types::Quaternion, - q2: libremetaverse_types::Quaternion, - ) -> libremetaverse_types::Quaternion { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Quaternion.op_Subtraction(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)" - ) - } - /// C# member: `M:LibreMetaverse.Quaternion.op_UnaryNegation(LibreMetaverse.Quaternion)`. - pub fn neg(q: libremetaverse_types::Quaternion) -> libremetaverse_types::Quaternion { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Quaternion.op_UnaryNegation(LibreMetaverse.Quaternion)" - ) - } -} +/// C# member: `F:LibreMetaverse.Quaternion.Identity`. +/// C# member: `F:LibreMetaverse.Quaternion.W`. +/// C# member: `F:LibreMetaverse.Quaternion.X`. +/// C# member: `F:LibreMetaverse.Quaternion.Y`. +/// C# member: `F:LibreMetaverse.Quaternion.Z`. +/// C# member: `M:LibreMetaverse.Quaternion.#ctor(LibreMetaverse.Vector3,System.Single)`. +/// C# member: `M:LibreMetaverse.Quaternion.#ctor(System.Byte[],System.Int32,System.Boolean)`. +/// C# member: `M:LibreMetaverse.Quaternion.#ctor(System.Single,System.Single,System.Single)`. +/// C# member: `M:LibreMetaverse.Quaternion.#ctor(System.Single,System.Single,System.Single,System.Single)`. +/// C# member: `M:LibreMetaverse.Quaternion.Add(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. +/// C# member: `M:LibreMetaverse.Quaternion.ApproxEquals(LibreMetaverse.Quaternion,System.Single)`. +/// C# member: `M:LibreMetaverse.Quaternion.Conjugate(LibreMetaverse.Quaternion)`. +/// C# member: `M:LibreMetaverse.Quaternion.CreateFromAxisAngle(LibreMetaverse.Vector3,System.Single)`. +/// C# member: `M:LibreMetaverse.Quaternion.CreateFromAxisAngle(System.Single,System.Single,System.Single,System.Single)`. +/// C# member: `M:LibreMetaverse.Quaternion.CreateFromEulers(LibreMetaverse.Vector3)`. +/// C# member: `M:LibreMetaverse.Quaternion.CreateFromEulers(System.Single,System.Single,System.Single)`. +/// C# member: `M:LibreMetaverse.Quaternion.CreateFromRotationMatrix(LibreMetaverse.Matrix4)`. +/// C# member: `M:LibreMetaverse.Quaternion.Divide(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. +/// C# member: `M:LibreMetaverse.Quaternion.Dot(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. +/// C# member: `M:LibreMetaverse.Quaternion.Equals(LibreMetaverse.Quaternion)`. +/// C# member: `M:LibreMetaverse.Quaternion.Equals(System.Object)`. +/// C# member: `M:LibreMetaverse.Quaternion.FromBytes(System.Byte[],System.Int32,System.Boolean)`. +/// C# member: `M:LibreMetaverse.Quaternion.GetAxisAngle(LibreMetaverse.Vector3@,System.Single@)`. +/// C# member: `M:LibreMetaverse.Quaternion.GetBytes`. +/// C# member: `M:LibreMetaverse.Quaternion.GetEulerAngles(System.Single@,System.Single@,System.Single@)`. +/// C# member: `M:LibreMetaverse.Quaternion.GetHashCode`. +/// C# member: `M:LibreMetaverse.Quaternion.Inverse(LibreMetaverse.Quaternion)`. +/// C# member: `M:LibreMetaverse.Quaternion.Length`. +/// C# member: `M:LibreMetaverse.Quaternion.LengthSquared`. +/// C# member: `M:LibreMetaverse.Quaternion.Multiply(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. +/// C# member: `M:LibreMetaverse.Quaternion.Multiply(LibreMetaverse.Quaternion,System.Single)`. +/// C# member: `M:LibreMetaverse.Quaternion.Negate(LibreMetaverse.Quaternion)`. +/// C# member: `M:LibreMetaverse.Quaternion.Normalize(LibreMetaverse.Quaternion)`. +/// C# member: `M:LibreMetaverse.Quaternion.Parse(System.String)`. +/// C# member: `M:LibreMetaverse.Quaternion.Slerp(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion,System.Single)`. +/// C# member: `M:LibreMetaverse.Quaternion.Subtract(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. +/// C# member: `M:LibreMetaverse.Quaternion.ToBytes(System.Byte[],System.Int32)`. +/// C# member: `M:LibreMetaverse.Quaternion.ToEulerVector`. +/// C# member: `M:LibreMetaverse.Quaternion.ToRawString`. +/// C# member: `M:LibreMetaverse.Quaternion.ToString`. +/// C# member: `M:LibreMetaverse.Quaternion.TryParse(System.String,LibreMetaverse.Quaternion@)`. +/// C# member: `M:LibreMetaverse.Quaternion.op_Addition(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. +/// C# member: `M:LibreMetaverse.Quaternion.op_Division(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. +/// C# member: `M:LibreMetaverse.Quaternion.op_Equality(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. +/// C# member: `M:LibreMetaverse.Quaternion.op_Inequality(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. +/// C# member: `M:LibreMetaverse.Quaternion.op_Multiply(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. +/// C# member: `M:LibreMetaverse.Quaternion.op_Multiply(LibreMetaverse.Quaternion,System.Single)`. +/// C# member: `M:LibreMetaverse.Quaternion.op_Subtraction(LibreMetaverse.Quaternion,LibreMetaverse.Quaternion)`. +/// C# member: `M:LibreMetaverse.Quaternion.op_UnaryNegation(LibreMetaverse.Quaternion)`. +pub use crate::geometry::Quaternion; /// C# type: `T:LibreMetaverse.Ray`. /// C# member: `F:LibreMetaverse.Ray.Direction`. diff --git a/crates/libremetaverse-types/src/geometry.rs b/crates/libremetaverse-types/src/geometry.rs new file mode 100644 index 0000000..4b3723a --- /dev/null +++ b/crates/libremetaverse-types/src/geometry.rs @@ -0,0 +1,1593 @@ +//! Native quaternion and 4x4 matrix geometry compatible with `LibreMetaverse`. + +#![allow(clippy::cast_possible_truncation)] // C# geometry explicitly narrows transcendental results. +#![allow(clippy::float_cmp)] // Exact zero and component comparisons are reference behavior. +#![allow(clippy::inherent_to_string)] // The mapped C# methods are named ToString. +#![allow(clippy::many_single_char_names)] // Geometry formulas conventionally use compact operands. +#![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the public compatibility map. +#![allow(clippy::must_use_candidate)] // Attributes are not part of the mapped C# surface. +#![allow(clippy::needless_pass_by_value)] // Owned arguments mirror mapped C# value parameters. +#![allow(clippy::should_implement_trait)] // Operator entry points have fixed generated names. +#![allow(clippy::single_match_else)] // TryParse mirrors the reference success/failure branches. +#![allow(clippy::too_many_arguments)] // The 16-scalar constructor is the mapped public API. + +use crate::byte_order::{read_single_little_endian, write_single_little_endian}; +use crate::compat::Object; +use crate::math_compat::{clamp_f32, format_f32, hash_f32, parse_f32}; +use crate::{Error, Vector3, Vector4}; + +fn sqrt_single(value: f32) -> f32 { + f64::from(value).sqrt() as f32 +} +fn sin_single(value: f32) -> f32 { + f64::from(value).sin() as f32 +} +fn cos_single(value: f32) -> f32 { + f64::from(value).cos() as f32 +} +fn acos_single(value: f32) -> f32 { + f64::from(value).acos() as f32 +} +fn atan2_single(y: f32, x: f32) -> f32 { + f64::from(y).atan2(f64::from(x)) as f32 +} + +/// A quaternion stored as `(x, y, z, w)`. +/// +/// Products use the same Hamilton-product direction as the C# reference. +/// Serialized quaternions contain normalized `x`, `y`, and `z` in explicit +/// little-endian order; the non-negative `w` component is reconstructed. +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct Quaternion { + pub x: f32, + pub y: f32, + pub z: f32, + pub w: f32, +} + +impl Quaternion { + #[must_use] + pub const fn identity() -> Self { + Self { + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + } + } + + pub const fn new_with_single_single_single_single( + x: f32, + y: f32, + z: f32, + w: f32, + ) -> Result { + Ok(Self { x, y, z, w }) + } + pub const fn new_with_vector3_single( + vector_part: Vector3, + scalar_part: f32, + ) -> Result { + Ok(Self { + x: vector_part.x, + y: vector_part.y, + z: vector_part.z, + w: scalar_part, + }) + } + pub fn new_with_single_single_single(x: f32, y: f32, z: f32) -> Result { + let xyz_sum = 1.0 - x * x - y * y - z * z; + Ok(Self { + x, + y, + z, + w: if xyz_sum > 0.0 { + sqrt_single(xyz_sum) + } else { + 0.0 + }, + }) + } + pub fn new_with_bytes_int32_boolean( + byte_array: Vec, + pos: i32, + normalized: bool, + ) -> Result { + Self::from_bytes(byte_array, pos, normalized) + } + pub fn from_bytes(byte_array: Vec, pos: i32, normalized: bool) -> Result { + let x = read_single_little_endian(&byte_array, pos)?; + let y = read_single_little_endian( + &byte_array, + pos.checked_add(4).ok_or(Error::IndexOutOfRange)?, + )?; + let z = read_single_little_endian( + &byte_array, + pos.checked_add(8).ok_or(Error::IndexOutOfRange)?, + )?; + if normalized { + Self::new_with_single_single_single(x, y, z) + } else { + let w = read_single_little_endian( + &byte_array, + pos.checked_add(12).ok_or(Error::IndexOutOfRange)?, + )?; + Ok(Self { x, y, z, w }) + } + } + + fn length_squared_value(self) -> f32 { + self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w + } + fn length_value(self) -> f32 { + sqrt_single(self.length_squared_value()) + } + pub fn length(&mut self) -> Result { + Ok(self.length_value()) + } + pub fn length_squared(&mut self) -> Result { + Ok(self.length_squared_value()) + } + pub fn approx_equals(&mut self, quat: Self, tolerance: f32) -> Result { + Ok(Self::sub_value(*self, quat).length_squared_value() <= tolerance * tolerance) + } + pub fn get_bytes(&self) -> Result, Error> { + let mut bytes = vec![0; 12]; + self.to_bytes(&mut bytes, 0)?; + Ok(bytes) + } + pub fn to_bytes(&self, dest: &mut [u8], pos: i32) -> Result<(), Error> { + let mut norm = self.length_value(); + if norm == 0.0 { + return Err(Error::InvalidOperation); + } + norm = 1.0 / norm; + let (x, y, z) = if self.w >= 0.0 { + (self.x, self.y, self.z) + } else { + (-self.x, -self.y, -self.z) + }; + write_single_little_endian(dest, pos, norm * x)?; + write_single_little_endian( + dest, + pos.checked_add(4).ok_or(Error::IndexOutOfRange)?, + norm * y, + )?; + write_single_little_endian( + dest, + pos.checked_add(8).ok_or(Error::IndexOutOfRange)?, + norm * z, + ) + } + pub fn get_euler_angles( + &self, + roll: &mut f32, + pitch: &mut f32, + yaw: &mut f32, + ) -> Result<(), Error> { + *roll = 0.0; + *pitch = 0.0; + *yaw = 0.0; + let squared = Self { + x: self.x * self.x, + y: self.y * self.y, + z: self.z * self.z, + w: self.w * self.w, + }; + let m = squared.x + squared.y + squared.z + squared.w; + if f64::from(m).abs() < 0.001 { + return Ok(()); + } + let n = 2.0 * (self.y * self.w + self.x * self.z); + let p = m * m - n * n; + if p > 0.0 { + *roll = atan2_single( + 2.0 * (self.x * self.w - self.y * self.z), + -squared.x - squared.y + squared.z + squared.w, + ); + *pitch = atan2_single(n, sqrt_single(p)); + *yaw = atan2_single( + 2.0 * (self.z * self.w - self.x * self.y), + squared.x - squared.y - squared.z + squared.w, + ); + } else if n > 0.0 { + *pitch = std::f32::consts::FRAC_PI_2; + *yaw = atan2_single( + self.z * self.w + self.x * self.y, + 0.5 - squared.x - squared.y, + ); + } else { + *pitch = -std::f32::consts::FRAC_PI_2; + *yaw = atan2_single( + self.z * self.w + self.x * self.y, + 0.5 - squared.x - squared.z, + ); + } + Ok(()) + } + pub fn to_euler_vector(&self) -> Result { + let (mut roll, mut pitch, mut yaw) = (0.0, 0.0, 0.0); + self.get_euler_angles(&mut roll, &mut pitch, &mut yaw)?; + Ok(Vector3 { + x: roll, + y: pitch, + z: yaw, + }) + } + pub fn get_axis_angle(&self, axis: &mut Vector3, angle: &mut f32) -> Result<(), Error> { + let q = Self::normalize_value(*self); + let sine = sqrt_single(1.0 - q.w * q.w); + if sine >= 0.001 { + let mut inverse_sine = 1.0 / sine; + if q.w < 0.0 { + inverse_sine = -inverse_sine; + } + *axis = Vector3::mul_with_vector3_single( + Vector3 { + x: q.x, + y: q.y, + z: q.z, + }, + inverse_sine, + ); + *angle = 2.0 * acos_single(q.w); + if *angle > std::f32::consts::PI { + *angle = 2.0 * std::f32::consts::PI - *angle; + } + } else { + *axis = Vector3::unit_x(); + *angle = 0.0; + } + Ok(()) + } + + fn add_value(a: Self, b: Self) -> Self { + Self { + x: a.x + b.x, + y: a.y + b.y, + z: a.z + b.z, + w: a.w + b.w, + } + } + fn sub_value(a: Self, b: Self) -> Self { + Self { + x: a.x - b.x, + y: a.y - b.y, + z: a.z - b.z, + w: a.w - b.w, + } + } + fn mul_value(a: Self, b: Self) -> Self { + Self { + x: a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y, + y: a.w * b.y + a.y * b.w + a.z * b.x - a.x * b.z, + z: a.w * b.z + a.z * b.w + a.x * b.y - a.y * b.x, + w: a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z, + } + } + fn mul_scalar(q: Self, scale: f32) -> Self { + Self { + x: q.x * scale, + y: q.y * scale, + z: q.z * scale, + w: q.w * scale, + } + } + fn neg_value(q: Self) -> Self { + Self { + x: -q.x, + y: -q.y, + z: -q.z, + w: -q.w, + } + } + fn conjugate_value(q: Self) -> Self { + Self { + x: -q.x, + y: -q.y, + z: -q.z, + w: q.w, + } + } + fn normalize_value(q: Self) -> Self { + let magnitude = q.length_value(); + if magnitude > 0.000_000_1 { + Self::mul_scalar(q, 1.0 / magnitude) + } else { + Self::identity() + } + } + + pub fn add_with_quaternion_quaternion(a: Self, b: Self) -> Result { + Ok(Self::add_value(a, b)) + } + pub fn conjugate(q: Self) -> Result { + Ok(Self::conjugate_value(q)) + } + pub fn create_from_axis_angle_with_single_single_single_single( + x: f32, + y: f32, + z: f32, + angle: f32, + ) -> Result { + Self::create_from_axis_angle_with_vector3_single(Vector3 { x, y, z }, angle) + } + pub fn create_from_axis_angle_with_vector3_single( + axis: Vector3, + mut angle: f32, + ) -> Result { + let axis = Vector3::normalize(axis)?; + angle *= 0.5; + let sine = sin_single(angle); + Ok(Self::normalize_value(Self { + x: axis.x * sine, + y: axis.y * sine, + z: axis.z * sine, + w: cos_single(angle), + })) + } + pub fn create_from_eulers_with_vector3(eulers: Vector3) -> Result { + Self::create_from_eulers_with_single_single_single(eulers.x, eulers.y, eulers.z) + } + pub fn create_from_eulers_with_single_single_single( + roll: f32, + pitch: f32, + yaw: f32, + ) -> Result { + if roll > std::f32::consts::TAU + || pitch > std::f32::consts::TAU + || yaw > std::f32::consts::TAU + { + return Err(Error::Argument); + } + let at_cos = f64::from(roll / 2.0).cos(); + let at_sin = f64::from(roll / 2.0).sin(); + let left_cos = f64::from(pitch / 2.0).cos(); + let left_sin = f64::from(pitch / 2.0).sin(); + let up_cos = f64::from(yaw / 2.0).cos(); + let up_sin = f64::from(yaw / 2.0).sin(); + let at_left_cos = at_cos * left_cos; + let at_left_sin = at_sin * left_sin; + Ok(Self { + x: (at_sin * left_cos * up_cos + at_cos * left_sin * up_sin) as f32, + y: (at_cos * left_sin * up_cos - at_sin * left_cos * up_sin) as f32, + z: (at_left_cos * up_sin + at_left_sin * up_cos) as f32, + w: (at_left_cos * up_cos - at_left_sin * up_sin) as f32, + }) + } + pub fn create_from_rotation_matrix(matrix: Matrix4) -> Result { + Ok(Self::from_rotation_matrix_value(matrix)) + } + fn from_rotation_matrix_value(matrix: Matrix4) -> Self { + let trace = matrix.m11 + matrix.m22 + matrix.m33; + if trace > 0.0 { + let mut root = sqrt_single(trace + 1.0); + let w = root * 0.5; + root = 0.5 / root; + return Self { + x: (matrix.m23 - matrix.m32) * root, + y: (matrix.m31 - matrix.m13) * root, + z: (matrix.m12 - matrix.m21) * root, + w, + }; + } + if matrix.m11 >= matrix.m22 && matrix.m11 >= matrix.m33 { + let root = sqrt_single((1.0 + matrix.m11) - matrix.m22 - matrix.m33); + let inverse = 0.5 / root; + return Self { + x: 0.5 * root, + y: (matrix.m12 + matrix.m21) * inverse, + z: (matrix.m13 + matrix.m31) * inverse, + w: (matrix.m23 - matrix.m32) * inverse, + }; + } + if matrix.m22 > matrix.m33 { + let root = sqrt_single((1.0 + matrix.m22) - matrix.m11 - matrix.m33); + let inverse = 0.5 / root; + return Self { + x: (matrix.m21 + matrix.m12) * inverse, + y: 0.5 * root, + z: (matrix.m32 + matrix.m23) * inverse, + w: (matrix.m31 - matrix.m13) * inverse, + }; + } + let root = sqrt_single((1.0 + matrix.m33) - matrix.m11 - matrix.m22); + let inverse = 0.5 / root; + Self { + x: (matrix.m31 + matrix.m13) * inverse, + y: (matrix.m32 + matrix.m23) * inverse, + z: 0.5 * root, + w: (matrix.m12 - matrix.m21) * inverse, + } + } + pub fn divide(q1: Self, q2: Self) -> Result { + Ok(Self::mul_value(Self::inverse_value(q1), q2)) + } + pub fn dot(q1: Self, q2: Self) -> Result { + Ok(q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w) + } + fn inverse_value(q: Self) -> Self { + let norm = q.length_squared_value(); + if norm == 0.0 { + Self::default() + } else { + Self::mul_scalar(Self::conjugate_value(q), 1.0 / norm) + } + } + pub fn inverse(q: Self) -> Result { + Ok(Self::inverse_value(q)) + } + pub fn slerp(mut q1: Self, mut q2: Self, amount: f32) -> Result { + let mut angle = Self::dot(q1, q2)?; + if angle < 0.0 { + q1 = Self::mul_scalar(q1, -1.0); + angle *= -1.0; + } + let (scale, inverse_scale) = if angle + 1.0 > 0.05 { + if 1.0 - angle >= 0.05 { + let theta = acos_single(angle); + let inverse_sine = 1.0 / sin_single(theta); + ( + sin_single(theta * (1.0 - amount)) * inverse_sine, + sin_single(theta * amount) * inverse_sine, + ) + } else { + (1.0 - amount, amount) + } + } else { + q2 = Self { + x: -q1.y, + y: q1.x, + z: -q1.w, + w: q1.z, + }; + ( + sin_single(std::f32::consts::PI * (0.5 - amount)), + sin_single(std::f32::consts::PI * amount), + ) + }; + Ok(Self::add_value( + Self::mul_scalar(q1, scale), + Self::mul_scalar(q2, inverse_scale), + )) + } + pub fn subtract(q1: Self, q2: Self) -> Result { + Ok(Self::sub_value(q1, q2)) + } + pub fn multiply_with_quaternion_quaternion(a: Self, b: Self) -> Result { + Ok(Self::mul_value(a, b)) + } + pub fn multiply_with_quaternion_single(q: Self, scale: f32) -> Result { + Ok(Self::mul_scalar(q, scale)) + } + pub fn negate(q: Self) -> Result { + Ok(Self::neg_value(q)) + } + pub fn normalize(q: Self) -> Result { + Ok(Self::normalize_value(q)) + } + pub fn parse(val: String) -> Result { + let trimmed = val.trim(); + let trimmed = if trimmed.len() >= 2 && trimmed.starts_with('<') && trimmed.ends_with('>') { + &trimmed[1..trimmed.len() - 1] + } else { + trimmed + }; + let split: Vec<_> = trimmed.split(',').collect(); + match split.len() { + 3 => Self::new_with_single_single_single( + parse_f32(split[0])?, + parse_f32(split[1])?, + parse_f32(split[2])?, + ), + 4 => Ok(Self { + x: parse_f32(split[0])?, + y: parse_f32(split[1])?, + z: parse_f32(split[2])?, + w: parse_f32(split[3])?, + }), + _ => Err(Error::Argument), + } + } + pub fn try_parse(val: String, result: &mut Self) -> bool { + match Self::parse(val) { + Ok(value) => { + *result = value; + true + } + Err(_) => { + *result = Self::default(); + false + } + } + } + pub fn equals_with_quaternion(&self, other: Self) -> bool { + Self::eq(*self, other) + } + pub fn equals_with_object(&self, obj: Option) -> bool { + matches!(obj, Some(Object::Quaternion(value)) if Self::eq(*self, value)) + } + pub fn get_hash_code(&self) -> i32 { + hash_f32(self.x) ^ hash_f32(self.y) ^ hash_f32(self.z) ^ hash_f32(self.w) + } + pub fn to_string(&self) -> String { + format!( + "<{}, {}, {}, {}>", + format_f32(self.x), + format_f32(self.y), + format_f32(self.z), + format_f32(self.w) + ) + } + pub fn to_raw_string(&self) -> Result { + Ok(format!( + "{:.3} {:.3} {:.3} {:.3}", + self.x, self.y, self.z, self.w + )) + } + pub fn eq(a: Self, b: Self) -> bool { + a.w == b.w && a.x == b.x && a.y == b.y && a.z == b.z + } + pub fn ne(a: Self, b: Self) -> bool { + !Self::eq(a, b) + } + pub fn add_with_quaternion_quaternion_b0243903(a: Self, b: Self) -> Self { + Self::add_value(a, b) + } + pub fn sub(a: Self, b: Self) -> Self { + Self::sub_value(a, b) + } + pub fn mul_with_quaternion_quaternion(a: Self, b: Self) -> Self { + Self::mul_value(a, b) + } + pub fn mul_with_quaternion_single(q: Self, scale: f32) -> Self { + Self::mul_scalar(q, scale) + } + pub fn div(q1: Self, q2: Self) -> Self { + Self::mul_value(Self::inverse_value(q1), q2) + } + pub fn neg(q: Self) -> Self { + Self::neg_value(q) + } +} + +/// A row-major 4x4 matrix used with row vectors and translation in row four. +/// +/// Matrix products, vector transforms, and axis accessors preserve the exact +/// handedness and row/column conventions of the C# reference. +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct Matrix4 { + pub m11: f32, + pub m12: f32, + pub m13: f32, + pub m14: f32, + pub m21: f32, + pub m22: f32, + pub m23: f32, + pub m24: f32, + pub m31: f32, + pub m32: f32, + pub m33: f32, + pub m34: f32, + pub m41: f32, + pub m42: f32, + pub m43: f32, + pub m44: f32, +} + +impl Matrix4 { + #[must_use] + pub const fn zero() -> Self { + Self::from_values([0.0; 16]) + } + #[must_use] + pub const fn identity() -> Self { + Self::from_values([ + 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, + ]) + } + const fn from_values(v: [f32; 16]) -> Self { + Self { + m11: v[0], + m12: v[1], + m13: v[2], + m14: v[3], + m21: v[4], + m22: v[5], + m23: v[6], + m24: v[7], + m31: v[8], + m32: v[9], + m33: v[10], + m34: v[11], + m41: v[12], + m42: v[13], + m43: v[14], + m44: v[15], + } + } + fn values(self) -> [f32; 16] { + [ + self.m11, self.m12, self.m13, self.m14, self.m21, self.m22, self.m23, self.m24, + self.m31, self.m32, self.m33, self.m34, self.m41, self.m42, self.m43, self.m44, + ] + } + pub const fn new_with_matrix4(matrix: Self) -> Result { + Ok(matrix) + } + pub fn new_with_single_single_single(roll: f32, pitch: f32, yaw: f32) -> Result { + Self::create_from_eulers(roll, pitch, yaw) + } + pub const fn new_with_single_single_single_single_single_single_single_single_single_single_single_single_single_single_single_single( + m11: f32, + m12: f32, + m13: f32, + m14: f32, + m21: f32, + m22: f32, + m23: f32, + m24: f32, + m31: f32, + m32: f32, + m33: f32, + m34: f32, + m41: f32, + m42: f32, + m43: f32, + m44: f32, + ) -> Result { + Ok(Self { + m11, + m12, + m13, + m14, + m21, + m22, + m23, + m24, + m31, + m32, + m33, + m34, + m41, + m42, + m43, + m44, + }) + } + pub fn at_axis(&self) -> Vector3 { + Vector3 { + x: self.m11, + y: self.m21, + z: self.m31, + } + } + pub fn left_axis(&self) -> Vector3 { + Vector3 { + x: self.m12, + y: self.m22, + z: self.m32, + } + } + pub fn up_axis(&self) -> Vector3 { + Vector3 { + x: self.m13, + y: self.m23, + z: self.m33, + } + } + pub fn determinant(&mut self) -> Result { + Ok(self.determinant_value()) + } + fn determinant_value(self) -> f32 { + self.m14 * self.m23 * self.m32 * self.m41 + - self.m13 * self.m24 * self.m32 * self.m41 + - self.m14 * self.m22 * self.m33 * self.m41 + + self.m12 * self.m24 * self.m33 * self.m41 + + self.m13 * self.m22 * self.m34 * self.m41 + - self.m12 * self.m23 * self.m34 * self.m41 + - self.m14 * self.m23 * self.m31 * self.m42 + + self.m13 * self.m24 * self.m31 * self.m42 + + self.m14 * self.m21 * self.m33 * self.m42 + - self.m11 * self.m24 * self.m33 * self.m42 + - self.m13 * self.m21 * self.m34 * self.m42 + + self.m11 * self.m23 * self.m34 * self.m42 + + self.m14 * self.m22 * self.m31 * self.m43 + - self.m12 * self.m24 * self.m31 * self.m43 + - self.m14 * self.m21 * self.m32 * self.m43 + + self.m11 * self.m24 * self.m32 * self.m43 + + self.m12 * self.m21 * self.m34 * self.m43 + - self.m11 * self.m22 * self.m34 * self.m43 + - self.m13 * self.m22 * self.m31 * self.m44 + + self.m12 * self.m23 * self.m31 * self.m44 + + self.m13 * self.m21 * self.m32 * self.m44 + - self.m11 * self.m23 * self.m32 * self.m44 + - self.m12 * self.m21 * self.m33 * self.m44 + + self.m11 * self.m22 * self.m33 * self.m44 + } + pub fn determinant3x3(&mut self) -> Result { + Ok(self.determinant3_value()) + } + fn determinant3_value(self) -> f32 { + let d1 = self.m11 * self.m22 * self.m33; + let d2 = self.m12 * self.m23 * self.m31; + let d3 = self.m13 * self.m21 * self.m32; + let d4 = self.m31 * self.m22 * self.m13; + let d5 = self.m32 * self.m23 * self.m11; + let d6 = self.m33 * self.m21 * self.m12; + d1 + d2 + d3 - (d4 + d5 + d6) + } + pub fn trace(&mut self) -> Result { + Ok(self.m11 + self.m22 + self.m33 + self.m44) + } + pub fn get_euler_angles( + &self, + roll: &mut f32, + pitch: &mut f32, + yaw: &mut f32, + ) -> Result<(), Error> { + let mut angle_y = f64::from(clamp_f32(self.m13, -1.0, 1.0)).asin(); + let cosine_y = angle_y.cos(); + let (mut angle_x, mut angle_z); + if cosine_y.abs() > 0.005 { + angle_x = f64::from(self.m33) / cosine_y; + let sine_x = f64::from(-self.m23) / cosine_y; + angle_x = sine_x.atan2(angle_x); + let cosine_z = f64::from(self.m11) / cosine_y; + let sine_z = f64::from(-self.m12) / cosine_y; + angle_z = sine_z.atan2(cosine_z); + } else { + angle_x = 0.0; + angle_z = f64::from(self.m21).atan2(f64::from(self.m22)); + } + if angle_x < 0.0 { + angle_x += 2.0 * std::f64::consts::PI; + } + if angle_y < 0.0 { + angle_y += 2.0 * std::f64::consts::PI; + } + if angle_z < 0.0 { + angle_z += 2.0 * std::f64::consts::PI; + } + *roll = angle_x as f32; + *pitch = angle_y as f32; + *yaw = angle_z as f32; + Ok(()) + } + pub fn get_quaternion(&self) -> Result { + let trace = self.m11 + self.m22 + self.m33 + self.m44; + if trace > f32::from_bits(1) { + let s = 0.5 / sqrt_single(trace); + return Ok(Quaternion { + x: (self.m32 - self.m23) * s, + y: (self.m13 - self.m31) * s, + z: (self.m21 - self.m12) * s, + w: 0.25 / s, + }); + } + if self.m11 > self.m22 && self.m11 > self.m33 { + let s = 2.0 * sqrt_single(1.0 + self.m11 - self.m22 - self.m33); + return Ok(Quaternion { + x: 0.25 * s, + y: (self.m12 + self.m21) / s, + z: (self.m13 + self.m31) / s, + w: (self.m23 - self.m32) / s, + }); + } + if self.m22 > self.m33 { + let s = 2.0 * sqrt_single(1.0 + self.m22 - self.m11 - self.m33); + Ok(Quaternion { + x: (self.m12 + self.m21) / s, + y: 0.25 * s, + z: (self.m23 + self.m32) / s, + w: (self.m13 - self.m31) / s, + }) + } else { + let s = 2.0 * sqrt_single(1.0 + self.m33 - self.m11 - self.m22); + Ok(Quaternion { + x: (self.m13 + self.m31) / s, + y: (self.m23 + self.m32) / s, + z: 0.25 * s, + w: (self.m12 - self.m21) / s, + }) + } + } + pub fn decompose( + &mut self, + scale: &mut Vector3, + rotation: &mut Quaternion, + translation: &mut Vector3, + ) -> Result { + *translation = Vector3 { + x: self.m41, + y: self.m42, + z: self.m43, + }; + let products = [ + self.m11 * self.m12 * self.m13 * self.m14, + self.m21 * self.m22 * self.m23 * self.m24, + self.m31 * self.m32 * self.m33 * self.m34, + ]; + if products.iter().any(|v| v.is_nan()) { + return Err(Error::Argument); + } + let signs = products.map(|v| if v < 0.0 { -1.0 } else { 1.0 }); + let sx = + signs[0] * sqrt_single(self.m11 * self.m11 + self.m12 * self.m12 + self.m13 * self.m13); + let sy = + signs[1] * sqrt_single(self.m21 * self.m21 + self.m22 * self.m22 + self.m23 * self.m23); + let sz = + signs[2] * sqrt_single(self.m31 * self.m31 + self.m32 * self.m32 + self.m33 * self.m33); + *scale = Vector3 { + x: sx, + y: sy, + z: sz, + }; + if sx == 0.0 || sy == 0.0 || sz == 0.0 { + *rotation = Quaternion::identity(); + return Ok(false); + } + let normalized = Self::from_values([ + self.m11 / sx, + self.m12 / sx, + self.m13 / sx, + 0.0, + self.m21 / sy, + self.m22 / sy, + self.m23 / sy, + 0.0, + self.m31 / sz, + self.m32 / sz, + self.m33 / sz, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + ]); + *rotation = Quaternion::from_rotation_matrix_value(normalized); + Ok(true) + } + + fn component_map(a: Self, b: Self, op: impl Fn(f32, f32) -> f32) -> Self { + let left = a.values(); + let right = b.values(); + let mut output = [0.0; 16]; + for index in 0..16 { + output[index] = op(left[index], right[index]); + } + Self::from_values(output) + } + fn scalar_map(value: Self, scalar: f32, op: impl Fn(f32, f32) -> f32) -> Self { + let input = value.values(); + let mut output = [0.0; 16]; + for index in 0..16 { + output[index] = op(input[index], scalar); + } + Self::from_values(output) + } + pub fn add_with_matrix4_matrix4(a: Self, b: Self) -> Result { + Ok(Self::component_map(a, b, |x, y| x + y)) + } + pub fn create_from_axis_angle(axis: Vector3, angle: f32) -> Result { + let x = axis.x; + let y = axis.y; + let z = axis.z; + let sine = sin_single(angle); + let cosine = cos_single(angle); + let xx = x * x; + let yy = y * y; + let zz = z * z; + let xy = x * y; + let xz = x * z; + let yz = y * z; + Ok(Self::from_values([ + xx + cosine * (1.0 - xx), + (xy - cosine * xy) + sine * z, + (xz - cosine * xz) - sine * y, + 0.0, + (xy - cosine * xy) - sine * z, + yy + cosine * (1.0 - yy), + (yz - cosine * yz) + sine * x, + 0.0, + (xz - cosine * xz) + sine * y, + (yz - cosine * yz) - sine * x, + zz + cosine * (1.0 - zz), + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + ])) + } + pub fn create_from_eulers(roll: f32, pitch: f32, yaw: f32) -> Result { + let a = cos_single(roll); + let b = sin_single(roll); + let c = cos_single(pitch); + let d = sin_single(pitch); + let e = cos_single(yaw); + let f = sin_single(yaw); + let ad = a * d; + let bd = b * d; + Ok(Self::from_values([ + c * e, + -c * f, + d, + 0.0, + bd * e + a * f, + -bd * f + a * e, + -b * c, + 0.0, + -ad * e + b * f, + ad * f + b * e, + a * c, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + ])) + } + pub fn create_from_quaternion(q: Quaternion) -> Result { + let xx = q.x * q.x; + let yy = q.y * q.y; + let zz = q.z * q.z; + let xy = q.x * q.y; + let zw = q.z * q.w; + let zx = q.z * q.x; + let yw = q.y * q.w; + let yz = q.y * q.z; + let xw = q.x * q.w; + Ok(Self::from_values([ + 1.0 - 2.0 * (yy + zz), + 2.0 * (xy + zw), + 2.0 * (zx - yw), + 0.0, + 2.0 * (xy - zw), + 1.0 - 2.0 * (zz + xx), + 2.0 * (yz + xw), + 0.0, + 2.0 * (zx + yw), + 2.0 * (yz - xw), + 1.0 - 2.0 * (yy + xx), + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + ])) + } + pub fn create_look_at(position: Vector3, target: Vector3, up: Vector3) -> Result { + let z = Vector3::normalize(Vector3::sub(position, target))?; + let x = Vector3::normalize(Vector3::cross(up, z)?)?; + let y = Vector3::cross(z, x)?; + Ok(Self::from_values([ + x.x, + y.x, + z.x, + 0.0, + x.y, + y.y, + z.y, + 0.0, + x.z, + y.z, + z.z, + 0.0, + -Vector3::dot(x, position)?, + -Vector3::dot(y, position)?, + -Vector3::dot(z, position)?, + 1.0, + ])) + } + pub fn create_rotation_x(radians: f32) -> Result { + let cosine = cos_single(radians); + let sine = sin_single(radians); + Ok(Self::from_values([ + 1.0, 0.0, 0.0, 0.0, 0.0, cosine, sine, 0.0, 0.0, -sine, cosine, 0.0, 0.0, 0.0, 0.0, 1.0, + ])) + } + pub fn create_rotation_y(radians: f32) -> Result { + let cosine = cos_single(radians); + let sine = sin_single(radians); + Ok(Self::from_values([ + cosine, 0.0, -sine, 0.0, 0.0, 1.0, 0.0, 0.0, sine, 0.0, cosine, 0.0, 0.0, 0.0, 0.0, 1.0, + ])) + } + pub fn create_rotation_z(radians: f32) -> Result { + let cosine = cos_single(radians); + let sine = sin_single(radians); + Ok(Self::from_values([ + cosine, sine, 0.0, 0.0, -sine, cosine, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, + ])) + } + pub fn create_scale(scale: Vector3) -> Result { + Ok(Self::from_values([ + scale.x, 0.0, 0.0, 0.0, 0.0, scale.y, 0.0, 0.0, 0.0, 0.0, scale.z, 0.0, 0.0, 0.0, 0.0, + 1.0, + ])) + } + pub fn create_translation(position: Vector3) -> Result { + Ok(Self::from_values([ + 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, position.x, position.y, + position.z, 1.0, + ])) + } + pub fn create_world(position: Vector3, forward: Vector3, up: Vector3) -> Result { + let forward = Vector3::normalize(forward)?; + let right = Vector3::normalize(Vector3::cross(forward, up)?)?; + let up = Vector3::normalize(Vector3::cross(right, forward)?)?; + Ok(Self::from_values([ + right.x, right.y, right.z, 0.0, up.x, up.y, up.z, 0.0, -forward.x, -forward.y, + -forward.z, 0.0, position.x, position.y, position.z, 1.0, + ])) + } + pub fn divide_with_matrix4_matrix4(a: Self, b: Self) -> Result { + Ok(Self::component_map(a, b, |x, y| x / y)) + } + pub fn divide_with_matrix4_single(a: Self, divider: f32) -> Result { + Ok(Self::scalar_map(a, 1.0 / divider, |x, y| x * y)) + } + pub fn lerp(a: Self, b: Self, amount: f32) -> Result { + Ok(Self::component_map(a, b, |x, y| x + (y - x) * amount)) + } + fn multiply_value(a: Self, b: Self) -> Self { + let left = a.values(); + let right = b.values(); + let mut output = [0.0; 16]; + for row in 0..4 { + for column in 0..4 { + output[row * 4 + column] = left[row * 4] * right[column] + + left[row * 4 + 1] * right[4 + column] + + left[row * 4 + 2] * right[8 + column] + + left[row * 4 + 3] * right[12 + column]; + } + } + Self::from_values(output) + } + pub fn multiply_with_matrix4_matrix4(a: Self, b: Self) -> Result { + Ok(Self::multiply_value(a, b)) + } + pub fn multiply_with_matrix4_single(a: Self, scale: f32) -> Result { + Ok(Self::scalar_map(a, scale, |x, y| x * y)) + } + pub fn negate(matrix: Self) -> Result { + Ok(Self::scalar_map(matrix, -1.0, |x, y| x * y)) + } + pub fn subtract(a: Self, b: Self) -> Result { + Ok(Self::component_map(a, b, |x, y| x - y)) + } + pub fn transform(value: Self, rotation: Quaternion) -> Result { + let x2 = rotation.x + rotation.x; + let y2 = rotation.y + rotation.y; + let z2 = rotation.z + rotation.z; + let a = (1.0 - rotation.y * y2) - rotation.z * z2; + let b = rotation.x * y2 - rotation.w * z2; + let c = rotation.x * z2 + rotation.w * y2; + let d = rotation.x * y2 + rotation.w * z2; + let e = (1.0 - rotation.x * x2) - rotation.z * z2; + let f = rotation.y * z2 - rotation.w * x2; + let g = rotation.x * z2 - rotation.w * y2; + let h = rotation.y * z2 + rotation.w * x2; + let i = (1.0 - rotation.x * x2) - rotation.y * y2; + Ok(Self::from_values([ + (value.m11 * a + value.m12 * b) + value.m13 * c, + (value.m11 * d + value.m12 * e) + value.m13 * f, + (value.m11 * g + value.m12 * h) + value.m13 * i, + value.m14, + (value.m21 * a + value.m22 * b) + value.m23 * c, + (value.m21 * d + value.m22 * e) + value.m23 * f, + (value.m21 * g + value.m22 * h) + value.m23 * i, + value.m24, + (value.m31 * a + value.m32 * b) + value.m33 * c, + (value.m31 * d + value.m32 * e) + value.m33 * f, + (value.m31 * g + value.m32 * h) + value.m33 * i, + value.m34, + (value.m41 * a + value.m42 * b) + value.m43 * c, + (value.m41 * d + value.m42 * e) + value.m43 * f, + (value.m41 * g + value.m42 * h) + value.m43 * i, + value.m44, + ])) + } + pub fn transpose(matrix: Self) -> Result { + Ok(Self::from_values([ + matrix.m11, matrix.m21, matrix.m31, matrix.m41, matrix.m12, matrix.m22, matrix.m32, + matrix.m42, matrix.m13, matrix.m23, matrix.m33, matrix.m43, matrix.m14, matrix.m24, + matrix.m34, matrix.m44, + ])) + } + fn minor_value(matrix: Self, row: i32, column: i32) -> Self { + let values = matrix.values(); + let mut minor = [0.0; 16]; + let mut target_row = 0; + for (source_row, source_row_i32) in [0_i32, 1, 2, 3].into_iter().enumerate() { + if source_row_i32 == row { + continue; + } + let mut target_column = 0; + for (source_column, source_column_i32) in [0_i32, 1, 2, 3].into_iter().enumerate() { + if source_column_i32 == column { + continue; + } + minor[target_row * 4 + target_column] = values[source_row * 4 + source_column]; + target_column += 1; + } + target_row += 1; + } + Self::from_values([ + minor[0], minor[1], minor[2], minor[3], minor[4], minor[5], minor[6], minor[7], + minor[8], minor[9], minor[10], minor[11], 0.0, 0.0, 0.0, 0.0, + ]) + } + pub fn minor(matrix: Self, row: i32, col: i32) -> Result { + Ok(Self::minor_value(matrix, row, col)) + } + fn adjoint_value(matrix: Self) -> Self { + let mut cofactors = [0.0; 16]; + for (row, row_i32) in [0_i32, 1, 2, 3].into_iter().enumerate() { + for (column, column_i32) in [0_i32, 1, 2, 3].into_iter().enumerate() { + let sign = if (row + column) % 2 == 0 { 1.0 } else { -1.0 }; + cofactors[row * 4 + column] = + sign * Self::minor_value(matrix, row_i32, column_i32).determinant3_value(); + } + } + let cofactor_matrix = Self::from_values(cofactors); + Self::from_values([ + cofactor_matrix.m11, + cofactor_matrix.m21, + cofactor_matrix.m31, + cofactor_matrix.m41, + cofactor_matrix.m12, + cofactor_matrix.m22, + cofactor_matrix.m32, + cofactor_matrix.m42, + cofactor_matrix.m13, + cofactor_matrix.m23, + cofactor_matrix.m33, + cofactor_matrix.m43, + cofactor_matrix.m14, + cofactor_matrix.m24, + cofactor_matrix.m34, + cofactor_matrix.m44, + ]) + } + pub fn adjoint(matrix: Self) -> Result { + Ok(Self::adjoint_value(matrix)) + } + pub fn adjoint3x3(matrix: Self) -> Result { + Ok(Self::adjoint_value(matrix)) + } + pub fn inverse(matrix: Self) -> Result { + let determinant = matrix.determinant_value(); + if determinant == 0.0 { + return Err(Error::Argument); + } + Ok(Self::scalar_map( + Self::adjoint_value(matrix), + 1.0 / determinant, + |x, y| x * y, + )) + } + pub fn inverse3x3(matrix: Self) -> Result { + let determinant = matrix.determinant3_value(); + if determinant == 0.0 { + return Err(Error::Argument); + } + Ok(Self::scalar_map( + Self::adjoint_value(matrix), + 1.0 / determinant, + |x, y| x * y, + )) + } + pub fn equals_with_matrix4(&self, other: Self) -> bool { + Self::eq(*self, other) + } + pub fn equals_with_object(&self, obj: Option) -> bool { + matches!(obj,Some(Object::Matrix4(value))if Self::eq(*self,value)) + } + pub fn get_hash_code(&self) -> i32 { + self.values() + .into_iter() + .fold(0, |hash, value| hash ^ hash_f32(value)) + } + pub fn to_string(&self) -> String { + let v = self.values().map(format_f32); + format!( + "|{}, {}, {}, {}|\n|{}, {}, {}, {}|\n|{}, {}, {}, {}|\n|{}, {}, {}, {}|", + v[0], + v[1], + v[2], + v[3], + v[4], + v[5], + v[6], + v[7], + v[8], + v[9], + v[10], + v[11], + v[12], + v[13], + v[14], + v[15] + ) + } + /// Returns the requested row in storage order. + /// + /// # Panics + /// + /// Panics when `row` is outside the reference indexer range `0..=3`. + pub fn item_with_int32(&self, row: i32) -> Vector4 { + match row { + 0 => Vector4 { + x: self.m11, + y: self.m12, + z: self.m13, + w: self.m14, + }, + 1 => Vector4 { + x: self.m21, + y: self.m22, + z: self.m23, + w: self.m24, + }, + 2 => Vector4 { + x: self.m31, + y: self.m32, + z: self.m33, + w: self.m34, + }, + 3 => Vector4 { + x: self.m41, + y: self.m42, + z: self.m43, + w: self.m44, + }, + _ => panic!("Matrix4 row index must be from 0-3"), + } + } + /// Returns the component at a zero-based row and column. + /// + /// # Panics + /// + /// Panics when either coordinate is outside the reference range `0..=3`. + pub fn item_with_int32_int32(&self, row: i32, column: i32) -> f32 { + match (row, column) { + (0, 0) => self.m11, + (0, 1) => self.m12, + (0, 2) => self.m13, + (0, 3) => self.m14, + (1, 0) => self.m21, + (1, 1) => self.m22, + (1, 2) => self.m23, + (1, 3) => self.m24, + (2, 0) => self.m31, + (2, 1) => self.m32, + (2, 2) => self.m33, + (2, 3) => self.m34, + (3, 0) => self.m41, + (3, 1) => self.m42, + (3, 2) => self.m43, + (3, 3) => self.m44, + _ => panic!("Matrix4 row and column values must be from 0-3"), + } + } + pub fn eq(left: Self, right: Self) -> bool { + left.values() + .into_iter() + .zip(right.values()) + .all(|(a, b)| a == b) + } + pub fn ne(left: Self, right: Self) -> bool { + !Self::eq(left, right) + } + pub fn add_with_matrix4_matrix4_31d5e46d(left: Self, right: Self) -> Self { + Self::component_map(left, right, |a, b| a + b) + } + pub fn sub(left: Self, right: Self) -> Self { + Self::component_map(left, right, |a, b| a - b) + } + pub fn mul_with_matrix4_matrix4(left: Self, right: Self) -> Self { + Self::multiply_value(left, right) + } + pub fn mul_with_matrix4_single(left: Self, scalar: f32) -> Self { + Self::scalar_map(left, scalar, |a, b| a * b) + } + pub fn div_with_matrix4_matrix4(left: Self, right: Self) -> Self { + Self::component_map(left, right, |a, b| a / b) + } + pub fn div_with_matrix4_single(matrix: Self, divider: f32) -> Self { + Self::scalar_map(matrix, 1.0 / divider, |a, b| a * b) + } + pub fn neg(matrix: Self) -> Self { + Self::scalar_map(matrix, -1.0, |a, b| a * b) + } +} + +#[cfg(test)] +mod tests { + use super::{Matrix4, Quaternion, sqrt_single}; + use crate::{Error, Vector3, Vector4}; + + fn assert_close(actual: f32, expected: f32) { + assert!( + (actual - expected).abs() <= 0.000_01, + "expected {expected}, got {actual}" + ); + } + + fn assert_vector_close(actual: Vector3, expected: Vector3) { + assert_close(actual.x, expected.x); + assert_close(actual.y, expected.y); + assert_close(actual.z, expected.z); + } + + fn assert_matrix_close(actual: Matrix4, expected: Matrix4) { + for (actual, expected) in actual.values().into_iter().zip(expected.values()) { + assert_close(actual, expected); + } + } + + #[test] + fn quaternion_bytes_preserve_reference_normalization_and_write_order() { + let value = Quaternion { + x: 2.0, + y: -3.0, + z: 4.0, + w: -5.0, + }; + let bytes = value.get_bytes().unwrap(); + assert_eq!(bytes.len(), 12); + let decoded = Quaternion::from_bytes(bytes, 0, true).unwrap(); + let norm = sqrt_single(54.0); + assert_close(decoded.x, -2.0 / norm); + assert_close(decoded.y, 3.0 / norm); + assert_close(decoded.z, -4.0 / norm); + assert!(decoded.w >= 0.0); + + let raw = [1.0_f32, 2.0, 3.0, 4.0] + .into_iter() + .flat_map(f32::to_le_bytes) + .collect(); + assert_eq!( + Quaternion::from_bytes(raw, 0, false).unwrap(), + Quaternion { + x: 1.0, + y: 2.0, + z: 3.0, + w: 4.0 + } + ); + + let mut short = [0_u8; 8]; + assert_eq!(value.to_bytes(&mut short, 0), Err(Error::IndexOutOfRange)); + assert_ne!(short, [0; 8]); + assert_eq!( + Quaternion::default().to_bytes(&mut [0; 12], 0), + Err(Error::InvalidOperation) + ); + } + + #[test] + fn quaternion_rotation_multiplication_and_inverse_match_reference_direction() { + let quarter_turn = Quaternion::create_from_axis_angle_with_vector3_single( + Vector3::unit_z(), + std::f32::consts::FRAC_PI_2, + ) + .unwrap(); + let rotated = Vector3::mul_with_vector3_quaternion(Vector3::unit_x(), quarter_turn); + assert_vector_close(rotated, Vector3::unit_y()); + + let matrix = Matrix4::create_from_quaternion(quarter_turn).unwrap(); + assert_vector_close( + Vector3::transform_normal(Vector3::unit_x(), matrix).unwrap(), + Vector3::unit_y(), + ); + let recovered = Quaternion::create_from_rotation_matrix(matrix).unwrap(); + assert_close(Quaternion::dot(quarter_turn, recovered).unwrap().abs(), 1.0); + + assert_matrix_close( + Matrix4::mul_with_matrix4_matrix4( + matrix, + Matrix4::create_rotation_z(-std::f32::consts::FRAC_PI_2).unwrap(), + ), + Matrix4::identity(), + ); + assert_eq!( + Quaternion::mul_with_quaternion_quaternion( + quarter_turn, + Quaternion::inverse(quarter_turn).unwrap(), + ), + Quaternion::identity() + ); + } + + #[test] + fn quaternion_euler_axis_slerp_and_parsing_cover_edge_behaviors() { + let eulers = Vector3 { + x: 0.25, + y: 0.5, + z: 0.75, + }; + let value = Quaternion::create_from_eulers_with_vector3(eulers).unwrap(); + let matrix = Matrix4::create_from_eulers(eulers.x, eulers.y, eulers.z).unwrap(); + assert_close( + Quaternion::dot(value, matrix.get_quaternion().unwrap()) + .unwrap() + .abs(), + 1.0, + ); + + let mut axis = Vector3::zero(); + let mut angle = 0.0; + value.get_axis_angle(&mut axis, &mut angle).unwrap(); + let from_axis = + Quaternion::create_from_axis_angle_with_vector3_single(axis, angle).unwrap(); + assert_close(Quaternion::dot(value, from_axis).unwrap().abs(), 1.0); + + let halfway = Quaternion::slerp(Quaternion::identity(), value, 0.5).unwrap(); + assert_close(halfway.length_value(), 1.0); + assert_eq!( + Quaternion::create_from_eulers_with_single_single_single( + std::f32::consts::TAU + 0.1, + 0.0, + 0.0 + ), + Err(Error::Argument) + ); + + let parsed = Quaternion::parse(" <1, 2, 3, 4> ".to_owned()).unwrap(); + assert_eq!(parsed.to_string(), "<1, 2, 3, 4>"); + assert_eq!(parsed.to_raw_string().unwrap(), "1.000 2.000 3.000 4.000"); + let mut output = Quaternion::identity(); + assert!(!Quaternion::try_parse("1, 2".to_owned(), &mut output)); + assert_eq!(output, Quaternion::default()); + } + + #[test] + fn matrix_transform_chain_inverse_and_conventions_are_compatible() { + let scale = Matrix4::create_scale(Vector3 { + x: 2.0, + y: 3.0, + z: 4.0, + }) + .unwrap(); + let rotation = Matrix4::create_rotation_z(std::f32::consts::FRAC_PI_2).unwrap(); + let translation = Matrix4::create_translation(Vector3 { + x: 10.0, + y: 20.0, + z: 30.0, + }) + .unwrap(); + let transform = Matrix4::mul_with_matrix4_matrix4( + Matrix4::mul_with_matrix4_matrix4(scale, rotation), + translation, + ); + assert_vector_close( + Vector3::transform(Vector3::unit_x(), transform).unwrap(), + Vector3 { + x: 10.0, + y: 22.0, + z: 30.0, + }, + ); + assert_eq!( + transform.item_with_int32(3), + Vector4 { + x: 10.0, + y: 20.0, + z: 30.0, + w: 1.0 + } + ); + assert_eq!(transform.item_with_int32_int32(3, 1), 20.0); + + let inverse = Matrix4::inverse(transform).unwrap(); + assert_matrix_close( + Matrix4::multiply_with_matrix4_matrix4(transform, inverse).unwrap(), + Matrix4::identity(), + ); + assert_eq!(Matrix4::inverse(Matrix4::zero()), Err(Error::Argument)); + } + + #[test] + fn matrix_decomposition_and_rotation_transform_round_trip() { + let expected_scale = Vector3 { + x: 2.0, + y: 3.0, + z: 4.0, + }; + let expected_rotation = + Quaternion::create_from_eulers_with_single_single_single(0.25, 0.5, 0.75).unwrap(); + let expected_translation = Vector3 { + x: 7.0, + y: 8.0, + z: 9.0, + }; + let mut value = Matrix4::mul_with_matrix4_matrix4( + Matrix4::mul_with_matrix4_matrix4( + Matrix4::create_scale(expected_scale).unwrap(), + Matrix4::create_from_quaternion(expected_rotation).unwrap(), + ), + Matrix4::create_translation(expected_translation).unwrap(), + ); + let (mut scale, mut rotation, mut translation) = + (Vector3::zero(), Quaternion::default(), Vector3::zero()); + assert!( + value + .decompose(&mut scale, &mut rotation, &mut translation) + .unwrap() + ); + assert_vector_close(scale, expected_scale); + assert_vector_close(translation, expected_translation); + assert_close( + Quaternion::dot(rotation, expected_rotation).unwrap().abs(), + 1.0, + ); + + let rotated = Matrix4::transform(Matrix4::identity(), expected_rotation).unwrap(); + assert_matrix_close( + rotated, + Matrix4::create_from_quaternion(expected_rotation).unwrap(), + ); + + let mut singular = Matrix4::zero(); + assert!( + !singular + .decompose(&mut scale, &mut rotation, &mut translation) + .unwrap() + ); + assert_eq!(rotation, Quaternion::identity()); + } + + #[test] + fn matrix_axes_minors_format_and_hash_follow_component_storage() { + let matrix = Matrix4::from_values([ + 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, + ]); + assert_eq!( + matrix.at_axis(), + Vector3 { + x: 1.0, + y: 5.0, + z: 9.0 + } + ); + assert_eq!( + matrix.left_axis(), + Vector3 { + x: 2.0, + y: 6.0, + z: 10.0 + } + ); + assert_eq!( + matrix.up_axis(), + Vector3 { + x: 3.0, + y: 7.0, + z: 11.0 + } + ); + assert_eq!( + Matrix4::minor(matrix, 0, 0).unwrap().item_with_int32(0), + Vector4 { + x: 6.0, + y: 7.0, + z: 8.0, + w: 0.0 + } + ); + assert!(matrix.to_string().starts_with("|1, 2, 3, 4|\n|5, 6, 7, 8|")); + assert_eq!( + matrix.get_hash_code(), + Matrix4::new_with_matrix4(matrix).unwrap().get_hash_code() + ); + } + + #[test] + #[should_panic(expected = "Matrix4 row and column values must be from 0-3")] + fn matrix_indexer_rejects_invalid_coordinates_like_reference() { + Matrix4::identity().item_with_int32_int32(4, 0); + } +} diff --git a/crates/libremetaverse-types/src/lib.rs b/crates/libremetaverse-types/src/lib.rs index 79327de..25d8849 100644 --- a/crates/libremetaverse-types/src/lib.rs +++ b/crates/libremetaverse-types/src/lib.rs @@ -7,6 +7,7 @@ mod color4; pub mod compat; mod crc32; mod generated; +mod geometry; mod math_compat; pub mod shim; mod uuid; diff --git a/tests/PARITY.md b/tests/PARITY.md index 2cc1f87..c396992 100644 --- a/tests/PARITY.md +++ b/tests/PARITY.md @@ -103,19 +103,19 @@ Reviewed Rust tests live outside `generated_parity.rs` and carry a `parity-case` | `LibreMetaverse.Tests/AppearanceTests.cs::AppearanceTests.AvatarAppearanceEventArgs_DecodeVisualParams_EmptyList_ReturnsEmptyDictionary::test` | `AppearanceTests.AvatarAppearanceEventArgs_DecodeVisualParams_EmptyList_ReturnsEmptyDictionary` | `` | `LibreMetaverse.Tests/AppearanceTests.cs:168` | `Appearance` | `` | `tests/compat/tests/appearance_visual_semantics.rs:159 (`appearance_event_args_decode_visual_params_empty_list_returns_empty_dictionary`)` | `translated` | `af99909259d531024895ce7ea325b49927a12daa886a35eab3d52e3f8fc76921` | | `LibreMetaverse.Tests/AppearanceTests.cs::AppearanceTests.AvatarAppearanceEventArgs_DecodeVisualParams_RoundTrip_DefaultValues::test` | `AppearanceTests.AvatarAppearanceEventArgs_DecodeVisualParams_RoundTrip_DefaultValues` | `` | `LibreMetaverse.Tests/AppearanceTests.cs:176` | `Appearance` | `` | `tests/compat/tests/appearance_visual_semantics.rs:166 (`appearance_event_args_decode_visual_params_round_trip_default_values`)` | `translated` | `0aec57fab35764525195e6c0e08a63ff10a467be684295381731c04e836539b5` | | `LibreMetaverse.Tests/AppearanceTests.cs::AppearanceTests.AvatarAppearanceEventArgs_DecodeVisualParams_MatchesAvatarDecodeVisualParams::test` | `AppearanceTests.AvatarAppearanceEventArgs_DecodeVisualParams_MatchesAvatarDecodeVisualParams` | `` | `LibreMetaverse.Tests/AppearanceTests.cs:199` | `Appearance` | `` | `tests/compat/tests/appearance_visual_semantics.rs:188 (`appearance_event_args_decode_visual_params_matches_avatar_decode_visual_params`)` | `translated` | `ff87dd3a506027ad248006b3b55d85a18b0810bc5b4e3a72eb81d31ebbe4184a` | -| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.SetBaseColorFactor_OverrideToDefault_IsNudgedOffDefault::test` | `AssetMaterialOverrideTests.SetBaseColorFactor_OverrideToDefault_IsNudgedOffDefault` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:43` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:117 (`base_color_override_to_default_is_nudged`)` | `translated` | `df553fd4df83e48d4dd74cdd8aa15a737ea3a416bcad802f7110bbfe79928a31` | -| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.SetBaseColorFactor_NotOverride_LeavesExactDefaultUnchanged::test` | `AssetMaterialOverrideTests.SetBaseColorFactor_NotOverride_LeavesExactDefaultUnchanged` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:52` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:131 (`base_color_non_override_keeps_default`)` | `translated` | `fec94fcfac0f36cc5813ed087a30e04fcc9815bc02072ea3f5dd282b8698a89c` | -| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.SetMetallicFactor_OverrideToOne_ClampsBelowDefault::test` | `AssetMaterialOverrideTests.SetMetallicFactor_OverrideToOne_ClampsBelowDefault` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:61` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:145 (`metallic_override_to_one_clamps_below_default`)` | `translated` | `7fcee795160428082f6e579881867c7b6c83f31549077fc28a46d18c75c485c9` | -| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.SetAlphaMode_OverrideToDefaultOpaque_SetsOverrideFlag::test` | `AssetMaterialOverrideTests.SetAlphaMode_OverrideToDefaultOpaque_SetsOverrideFlag` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:71` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:159 (`alpha_mode_override_sets_flag`)` | `translated` | `8af09d12a58083657e27e285802bb3a6a1d1c136397cea53865d964006343715` | -| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.SetTextureId_OverrideToNull_UsesSentinelUuid::test` | `AssetMaterialOverrideTests.SetTextureId_OverrideToNull_UsesSentinelUuid` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:80` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:169 (`null_texture_override_uses_sentinel`)` | `translated` | `d9ebf410d2a55c3619b99524a571396fb268e5edd41de955cb86e4ec8cc55c78` | -| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ApplyOverride_SentinelTexture_ClearsBaseTexture::test` | `AssetMaterialOverrideTests.ApplyOverride_SentinelTexture_ClearsBaseTexture` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:89` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:182 (`sentinel_override_clears_base_texture`)` | `translated` | `17fd8cdb12ed5d1d0aa1b0a05dbcc357f75aa350d5079b066c957da459d9f628` | -| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ApplyOverride_UntouchedTexture_LeavesBaseTextureAlone::test` | `AssetMaterialOverrideTests.ApplyOverride_UntouchedTexture_LeavesBaseTextureAlone` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:103` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:203 (`untouched_override_keeps_base_texture`)` | `translated` | `80ec5e369e3682d92ec6996841b15bda38a3ef6edcd36a2fd9893d8c15459348` | -| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ApplyOverride_ChangedScalar_ReplacesBaseValue::test` | `AssetMaterialOverrideTests.ApplyOverride_ChangedScalar_ReplacesBaseValue` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:116` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:220 (`changed_scalar_override_replaces_base`)` | `translated` | `4c305c54c4e451932fbd4a4719957f92f38a7f7d28259559b43cb95fcf9250d1` | -| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.SetBaseMaterial_ResetsFieldsButKeepsTransforms::test` | `AssetMaterialOverrideTests.SetBaseMaterial_ResetsFieldsButKeepsTransforms` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:128` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:232 (`set_base_material_keeps_texture_transforms`)` | `translated` | `39312602e736fd8607a41d9e6b6348bfaf6e659cf3f63100ee15edd20b672306` | -| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ToOverrideOsd_RoundTrip_PreservesOverriddenFields::test` | `AssetMaterialOverrideTests.ToOverrideOsd_RoundTrip_PreservesOverriddenFields` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:142` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:257 (`override_osd_round_trip_preserves_fields`)` | `translated` | `8d542528a14e3b4dcb74ea8a940a44673aada51f98baf8abcdd59a06f36e3cb0` | -| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ToOverrideOsd_UntouchedMaterial_ProducesEmptyMap::test` | `AssetMaterialOverrideTests.ToOverrideOsd_UntouchedMaterial_ProducesEmptyMap` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:161` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:290 (`untouched_material_override_osd_is_empty`)` | `translated` | `c9e29f7ec0e6b28579e0499e3ec89841e01a313a9386ba60257e5e701129b1c2` | -| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ToOverrideOsd_OverrideToDefaultRoughness_StillEncodesField::test` | `AssetMaterialOverrideTests.ToOverrideOsd_OverrideToDefaultRoughness_StillEncodesField` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:170` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:298 (`default_roughness_override_is_encoded`)` | `translated` | `4ea9920c6709bd29858a8da66313fcee5ebebd646743c5d822dd6c9e477bf3b4` | -| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ToJson_RoundTrip_PreservesNameAndColor::test` | `AssetMaterialOverrideTests.ToJson_RoundTrip_PreservesNameAndColor` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:184` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:317 (`material_json_round_trip_preserves_name_and_color`)` | `translated` | `d7ae4270d82671a970fc1b6457292860d24481dd84e9fb919ae60fded5f1914e` | +| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.SetBaseColorFactor_OverrideToDefault_IsNudgedOffDefault::test` | `AssetMaterialOverrideTests.SetBaseColorFactor_OverrideToDefault_IsNudgedOffDefault` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:43` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:118 (`base_color_override_to_default_is_nudged`)` | `translated` | `df553fd4df83e48d4dd74cdd8aa15a737ea3a416bcad802f7110bbfe79928a31` | +| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.SetBaseColorFactor_NotOverride_LeavesExactDefaultUnchanged::test` | `AssetMaterialOverrideTests.SetBaseColorFactor_NotOverride_LeavesExactDefaultUnchanged` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:52` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:132 (`base_color_non_override_keeps_default`)` | `translated` | `fec94fcfac0f36cc5813ed087a30e04fcc9815bc02072ea3f5dd282b8698a89c` | +| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.SetMetallicFactor_OverrideToOne_ClampsBelowDefault::test` | `AssetMaterialOverrideTests.SetMetallicFactor_OverrideToOne_ClampsBelowDefault` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:61` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:146 (`metallic_override_to_one_clamps_below_default`)` | `translated` | `7fcee795160428082f6e579881867c7b6c83f31549077fc28a46d18c75c485c9` | +| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.SetAlphaMode_OverrideToDefaultOpaque_SetsOverrideFlag::test` | `AssetMaterialOverrideTests.SetAlphaMode_OverrideToDefaultOpaque_SetsOverrideFlag` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:71` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:160 (`alpha_mode_override_sets_flag`)` | `translated` | `8af09d12a58083657e27e285802bb3a6a1d1c136397cea53865d964006343715` | +| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.SetTextureId_OverrideToNull_UsesSentinelUuid::test` | `AssetMaterialOverrideTests.SetTextureId_OverrideToNull_UsesSentinelUuid` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:80` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:170 (`null_texture_override_uses_sentinel`)` | `translated` | `d9ebf410d2a55c3619b99524a571396fb268e5edd41de955cb86e4ec8cc55c78` | +| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ApplyOverride_SentinelTexture_ClearsBaseTexture::test` | `AssetMaterialOverrideTests.ApplyOverride_SentinelTexture_ClearsBaseTexture` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:89` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:183 (`sentinel_override_clears_base_texture`)` | `translated` | `17fd8cdb12ed5d1d0aa1b0a05dbcc357f75aa350d5079b066c957da459d9f628` | +| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ApplyOverride_UntouchedTexture_LeavesBaseTextureAlone::test` | `AssetMaterialOverrideTests.ApplyOverride_UntouchedTexture_LeavesBaseTextureAlone` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:103` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:204 (`untouched_override_keeps_base_texture`)` | `translated` | `80ec5e369e3682d92ec6996841b15bda38a3ef6edcd36a2fd9893d8c15459348` | +| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ApplyOverride_ChangedScalar_ReplacesBaseValue::test` | `AssetMaterialOverrideTests.ApplyOverride_ChangedScalar_ReplacesBaseValue` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:116` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:221 (`changed_scalar_override_replaces_base`)` | `translated` | `4c305c54c4e451932fbd4a4719957f92f38a7f7d28259559b43cb95fcf9250d1` | +| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.SetBaseMaterial_ResetsFieldsButKeepsTransforms::test` | `AssetMaterialOverrideTests.SetBaseMaterial_ResetsFieldsButKeepsTransforms` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:128` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:233 (`set_base_material_keeps_texture_transforms`)` | `translated` | `39312602e736fd8607a41d9e6b6348bfaf6e659cf3f63100ee15edd20b672306` | +| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ToOverrideOsd_RoundTrip_PreservesOverriddenFields::test` | `AssetMaterialOverrideTests.ToOverrideOsd_RoundTrip_PreservesOverriddenFields` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:142` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:258 (`override_osd_round_trip_preserves_fields`)` | `translated` | `8d542528a14e3b4dcb74ea8a940a44673aada51f98baf8abcdd59a06f36e3cb0` | +| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ToOverrideOsd_UntouchedMaterial_ProducesEmptyMap::test` | `AssetMaterialOverrideTests.ToOverrideOsd_UntouchedMaterial_ProducesEmptyMap` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:161` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:291 (`untouched_material_override_osd_is_empty`)` | `translated` | `c9e29f7ec0e6b28579e0499e3ec89841e01a313a9386ba60257e5e701129b1c2` | +| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ToOverrideOsd_OverrideToDefaultRoughness_StillEncodesField::test` | `AssetMaterialOverrideTests.ToOverrideOsd_OverrideToDefaultRoughness_StillEncodesField` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:170` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:299 (`default_roughness_override_is_encoded`)` | `translated` | `4ea9920c6709bd29858a8da66313fcee5ebebd646743c5d822dd6c9e477bf3b4` | +| `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs::AssetMaterialOverrideTests.ToJson_RoundTrip_PreservesNameAndColor::test` | `AssetMaterialOverrideTests.ToJson_RoundTrip_PreservesNameAndColor` | `` | `LibreMetaverse.Tests/AssetMaterialOverrideTests.cs:184` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:318 (`material_json_round_trip_preserves_name_and_color`)` | `translated` | `d7ae4270d82671a970fc1b6457292860d24481dd84e9fb919ae60fded5f1914e` | | `LibreMetaverse.Tests/AvatarManagerTests.cs::AvatarManagerTests.RequestOwnAvatarTextures_WhenNotConnected_DoesNotThrow::test` | `AvatarManagerTests.RequestOwnAvatarTextures_WhenNotConnected_DoesNotThrow` | `` | `LibreMetaverse.Tests/AvatarManagerTests.cs:42` | `Appearance` | `` | `crates/libremetaverse/src/avatar_animesh_semantics.rs:587 (`avatar_manager_request_own_avatar_textures_when_not_connected_does_not_throw`)` | `translated` | `bf3539f68a0adc58c2166289ca1c75f6ff03906f71ffe2fb749f6990404b541a` | | `LibreMetaverse.Tests/AvatarManagerTests.cs::AvatarManagerTests.UpdateLastReceivedCOFVersion_AcceptsHigherVersion::test` | `AvatarManagerTests.UpdateLastReceivedCOFVersion_AcceptsHigherVersion` | `` | `LibreMetaverse.Tests/AvatarManagerTests.cs:55` | `Appearance` | `` | `crates/libremetaverse/src/avatar_animesh_semantics.rs:597 (`appearance_manager_update_last_received_cof_version_accepts_higher_version`)` | `translated` | `3656364491dcd7a05c868b11ea0d8be48609d10c6054b9696f54cbac10cfe6df` | | `LibreMetaverse.Tests/AvatarManagerTests.cs::AvatarManagerTests.UpdateLastReceivedCOFVersion_RejectsLowerVersion::test` | `AvatarManagerTests.UpdateLastReceivedCOFVersion_RejectsLowerVersion` | `` | `LibreMetaverse.Tests/AvatarManagerTests.cs:68` | `Appearance` | `` | `crates/libremetaverse/src/avatar_animesh_semantics.rs:612 (`appearance_manager_update_last_received_cof_version_rejects_lower_version`)` | `translated` | `f4a827fcace9d50306a8c375183e056140996d2a39b02e4dd12fc6cc79e5d0f0` | @@ -246,18 +246,18 @@ Reviewed Rust tests live outside `generated_parity.rs` and carry a `parity-case` | `LibreMetaverse.Tests/GLTFMaterialUploadTests.cs::GLTFMaterialUploadTests.RequestUpdateMaterialTaskInventoryAsync_HappyPath_PostsItemIdAndTaskId::test` | `GLTFMaterialUploadTests.RequestUpdateMaterialTaskInventoryAsync_HappyPath_PostsItemIdAndTaskId` | `` | `LibreMetaverse.Tests/GLTFMaterialUploadTests.cs:92` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `tests/compat/tests/asset_capability_semantics.rs:356 (`material_task_upload_posts_item_and_task_ids`)` | `translated` | `7ac0e7d210b1aefa795e479f4da0d2dd4caae0b3bbe84ca124707c3ae005bbbd` | | `LibreMetaverse.Tests/GLTFMaterialUploadTests.cs::GLTFMaterialUploadTests.RequestUpdateMaterialAgentInventoryAsync_NoCapability_Throws::test` | `GLTFMaterialUploadTests.RequestUpdateMaterialAgentInventoryAsync_NoCapability_Throws` | `` | `LibreMetaverse.Tests/GLTFMaterialUploadTests.cs:115` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `tests/compat/tests/asset_capability_semantics.rs:384 (`material_agent_upload_without_capability_is_invalid_operation`)` | `translated` | `325269807d778264e571d1518ca9a2ca69a27ba67ad689e580f2636ab595f335` | | `LibreMetaverse.Tests/GLTFMaterialUploadTests.cs::GLTFMaterialUploadTests.RequestUpdateMaterialAgentInventoryAsync_UploadedJsonRoundTripsMaterial::test` | `GLTFMaterialUploadTests.RequestUpdateMaterialAgentInventoryAsync_UploadedJsonRoundTripsMaterial` | `` | `LibreMetaverse.Tests/GLTFMaterialUploadTests.cs:131` | `` | `LibreMetaverse.Tests/TestHelpers/FakeGridClient.cs` | `tests/compat/tests/asset_capability_semantics.rs:400 (`uploaded_material_json_round_trips`)` | `translated` | `fd059dfc8c9299c3b6583b3b47ba18ae9c92e3ad923f88634019b0ab9f34d401` | -| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.LoadGltf_ParsesSceneGraph::test` | `GltfDocumentTests.LoadGltf_ParsesSceneGraph` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:122` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:336 (`gltf_parses_scene_graph`)` | `translated` | `e20583d8de31f793d1dc49d1dfde89e1eb8c00186359e7df4184e862ec9ed649` | -| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.LoadGltf_ParsesMeshAndPrimitive::test` | `GltfDocumentTests.LoadGltf_ParsesMeshAndPrimitive` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:136` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:349 (`gltf_parses_mesh_and_primitive`)` | `translated` | `ef6a76308db1966423c7746f5414f3d0553587efc388bbedb962f38cc122ecaf` | -| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.LoadGltf_ParsesMaterial::test` | `GltfDocumentTests.LoadGltf_ParsesMaterial` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:152` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:363 (`gltf_parses_material`)` | `translated` | `0147b4d7102c5223f247f69fa4cf3daeb0aa6300ad46ef34d6a17163b694c13e` | -| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.GetPositions_ReturnsCorrectTriangle::test` | `GltfDocumentTests.GetPositions_ReturnsCorrectTriangle` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:169` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:377 (`gltf_positions_are_correct_triangle`)` | `translated` | `c3d444122ab73794a011d5a5aafb0c2aa030975393f967e871dc88db200f5e96` | -| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.GetIndices_ReturnsCorrectValues::test` | `GltfDocumentTests.GetIndices_ReturnsCorrectValues` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:184` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:395 (`gltf_indices_are_correct`)` | `translated` | `fa39e87a3dfee7258d9a95907a97cbb107f5add7d6a6f9d9800249bb67e2fc58` | -| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.GetPositions_EmptyWhenNoAttribute::test` | `GltfDocumentTests.GetPositions_EmptyWhenNoAttribute` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:197` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:405 (`gltf_positions_empty_without_attribute`)` | `translated` | `06c691911ee6933f1884f6540d69afafa1f272acfed827c628580438204d9b4b` | -| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.GetNormals_EmptyWhenNoAttribute::test` | `GltfDocumentTests.GetNormals_EmptyWhenNoAttribute` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:205` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:419 (`gltf_normals_empty_without_attribute`)` | `translated` | `e04480b0b9e23cb561cc55e744ebd85203c759f98ad69febeeb291919f0d91af` | -| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.ToJson_RoundTrip_PreservesSceneGraph::test` | `GltfDocumentTests.ToJson_RoundTrip_PreservesSceneGraph` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:215` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:432 (`gltf_json_round_trip_preserves_scene_graph`)` | `translated` | `41c385b7bde3f8e4f4ad98d4f1f737fc6ccc241c2613526fd745eb35430386b2` | -| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.ToGlb_RoundTrip_PreservesGeometry::test` | `GltfDocumentTests.ToGlb_RoundTrip_PreservesGeometry` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:230` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:449 (`glb_round_trip_preserves_geometry`)` | `translated` | `1e97fe2d1492ac195f6a4386329c76ad131ab99a41b8e03216b1bab8b48fae36` | -| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.Load_AutoDetectsGlbVsJson::test` | `GltfDocumentTests.Load_AutoDetectsGlbVsJson` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:252` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:463 (`gltf_load_auto_detects_glb_and_json`)` | `translated` | `6d07930be2302e504ef9ece3fef10f47b855d33ae6c8bff152e82df0beb2a474` | -| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.ParseNode_TrsDefaults::test` | `GltfDocumentTests.ParseNode_TrsDefaults` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:268` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:475 (`gltf_node_trs_defaults`)` | `translated` | `0f0fbe116d20276c2aee917b940bded9de277ed0b3e3b659844c5b3532755259` | -| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.ParseNode_ExplicitMatrix::test` | `GltfDocumentTests.ParseNode_ExplicitMatrix` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:280` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:495 (`gltf_node_explicit_matrix`)` | `translated` | `197ba2ee3a66cbb7d927acea13ce9dc84e62af8a500d939957749039118755c8` | +| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.LoadGltf_ParsesSceneGraph::test` | `GltfDocumentTests.LoadGltf_ParsesSceneGraph` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:122` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:337 (`gltf_parses_scene_graph`)` | `translated` | `e20583d8de31f793d1dc49d1dfde89e1eb8c00186359e7df4184e862ec9ed649` | +| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.LoadGltf_ParsesMeshAndPrimitive::test` | `GltfDocumentTests.LoadGltf_ParsesMeshAndPrimitive` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:136` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:350 (`gltf_parses_mesh_and_primitive`)` | `translated` | `ef6a76308db1966423c7746f5414f3d0553587efc388bbedb962f38cc122ecaf` | +| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.LoadGltf_ParsesMaterial::test` | `GltfDocumentTests.LoadGltf_ParsesMaterial` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:152` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:364 (`gltf_parses_material`)` | `translated` | `0147b4d7102c5223f247f69fa4cf3daeb0aa6300ad46ef34d6a17163b694c13e` | +| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.GetPositions_ReturnsCorrectTriangle::test` | `GltfDocumentTests.GetPositions_ReturnsCorrectTriangle` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:169` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:378 (`gltf_positions_are_correct_triangle`)` | `translated` | `c3d444122ab73794a011d5a5aafb0c2aa030975393f967e871dc88db200f5e96` | +| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.GetIndices_ReturnsCorrectValues::test` | `GltfDocumentTests.GetIndices_ReturnsCorrectValues` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:184` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:396 (`gltf_indices_are_correct`)` | `translated` | `fa39e87a3dfee7258d9a95907a97cbb107f5add7d6a6f9d9800249bb67e2fc58` | +| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.GetPositions_EmptyWhenNoAttribute::test` | `GltfDocumentTests.GetPositions_EmptyWhenNoAttribute` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:197` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:406 (`gltf_positions_empty_without_attribute`)` | `translated` | `06c691911ee6933f1884f6540d69afafa1f272acfed827c628580438204d9b4b` | +| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.GetNormals_EmptyWhenNoAttribute::test` | `GltfDocumentTests.GetNormals_EmptyWhenNoAttribute` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:205` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:420 (`gltf_normals_empty_without_attribute`)` | `translated` | `e04480b0b9e23cb561cc55e744ebd85203c759f98ad69febeeb291919f0d91af` | +| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.ToJson_RoundTrip_PreservesSceneGraph::test` | `GltfDocumentTests.ToJson_RoundTrip_PreservesSceneGraph` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:215` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:433 (`gltf_json_round_trip_preserves_scene_graph`)` | `translated` | `41c385b7bde3f8e4f4ad98d4f1f737fc6ccc241c2613526fd745eb35430386b2` | +| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.ToGlb_RoundTrip_PreservesGeometry::test` | `GltfDocumentTests.ToGlb_RoundTrip_PreservesGeometry` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:230` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:450 (`glb_round_trip_preserves_geometry`)` | `translated` | `1e97fe2d1492ac195f6a4386329c76ad131ab99a41b8e03216b1bab8b48fae36` | +| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.Load_AutoDetectsGlbVsJson::test` | `GltfDocumentTests.Load_AutoDetectsGlbVsJson` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:252` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:464 (`gltf_load_auto_detects_glb_and_json`)` | `translated` | `6d07930be2302e504ef9ece3fef10f47b855d33ae6c8bff152e82df0beb2a474` | +| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.ParseNode_TrsDefaults::test` | `GltfDocumentTests.ParseNode_TrsDefaults` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:268` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:476 (`gltf_node_trs_defaults`)` | `translated` | `0f0fbe116d20276c2aee917b940bded9de277ed0b3e3b659844c5b3532755259` | +| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.ParseNode_ExplicitMatrix::test` | `GltfDocumentTests.ParseNode_ExplicitMatrix` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:280` | `` | `` | `tests/compat/tests/asset_document_semantics.rs:496 (`gltf_node_explicit_matrix`)` | `translated` | `197ba2ee3a66cbb7d927acea13ce9dc84e62af8a500d939957749039118755c8` | | `LibreMetaverse.Tests/GridClientTests.cs::GridClientTests.GetGridRegion::test` | `GridClientTests.GetGridRegion` | `` | `LibreMetaverse.Tests/GridClientTests.cs:90` | `GridClient, RequiresLiveServer` | `` | `tests/compat/tests/network_semantics.rs:345 (`get_grid_region_live`)` | `ignored-live` | `b0b72fc63fe829984167a479b763784c7daae8691399f3f3d2feb824090ad59f` | | `LibreMetaverse.Tests/HelpersZeroCodingTests.cs::HelpersZeroCodingTests.ZeroDecode_ValidInput_DecodesCorrectly::test` | `HelpersZeroCodingTests.ZeroDecode_ValidInput_DecodesCorrectly` | `` | `LibreMetaverse.Tests/HelpersZeroCodingTests.cs:36` | `Helpers` | `` | `tests/compat/tests/wire_semantics.rs:79 (`zero_decode_valid_input_decodes_correctly`)` | `translated` | `62353da2b1afb7bfdf5142706d15d22086a5e1e8c55f39af3e64686dda4ea071` | | `LibreMetaverse.Tests/HelpersZeroCodingTests.cs::HelpersZeroCodingTests.ZeroDecode_NullSource_ThrowsArgumentNullException::test` | `HelpersZeroCodingTests.ZeroDecode_NullSource_ThrowsArgumentNullException` | `` | `LibreMetaverse.Tests/HelpersZeroCodingTests.cs:64` | `Helpers` | `` | `tests/compat/tests/wire_semantics.rs:97 (`zero_decode_null_source_throws_argument_null`)` | `translated` | `50ebbd8ea35b87df18cccdea9a26534d5c142228e5b1698cf191b9fd2e9a9957` | diff --git a/tests/red-suite-baseline.json b/tests/red-suite-baseline.json index de73c4b..2756c09 100644 --- a/tests/red-suite-baseline.json +++ b/tests/red-suite-baseline.json @@ -13,9 +13,11 @@ "allowed_parity_passes": [ "LibreMetaverse.Tests/AppearanceManagerTests.cs::AppearanceManagerTests.BAKED_TEXTURE_COUNT_Is11::test", "LibreMetaverse.Tests/MarketplaceFolderClassifierTests.cs::MarketplaceFolderClassifierTests.ValidateListing_ValidFlags_AreBitmaskComposable::test", + "LibreMetaverse.Tests/TypeTests.cs::TypeTests.Quaternions::test", + "LibreMetaverse.Tests/TypeTests.cs::TypeTests.TestMatrix::test", "LibreMetaverse.Tests/TypeTests.cs::TypeTests.UUIDs::test", "LibreMetaverse.Tests/TypeTests.cs::TypeTests.Vector3ApproxEquals::test", "LibreMetaverse.Tests/TypeTests.cs::TypeTests.VectorCasting::test" ], - "support_passes": 42 + "support_passes": 49 } diff --git a/tests/upstream-tests.json b/tests/upstream-tests.json index fee2244..5af05cb 100644 --- a/tests/upstream-tests.json +++ b/tests/upstream-tests.json @@ -1814,7 +1814,7 @@ "rust_body_sha256": "c039310618f45189cfbbbad45e51b18db16171f4e5d7aba14eee6137b96201d0", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 117, + "rust_line": 118, "rust_test": "base_color_override_to_default_is_nudged", "semantic_review": "reviewed" }, @@ -1833,7 +1833,7 @@ "rust_body_sha256": "dadd13a673274f9c46c4732756401c1343443f31ff0616056e381ed10b6373dc", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 131, + "rust_line": 132, "rust_test": "base_color_non_override_keeps_default", "semantic_review": "reviewed" }, @@ -1852,7 +1852,7 @@ "rust_body_sha256": "b9ad9050bbffbaa6fd60b3c45d141c6b0191bcba4287667e4028d47881abcfe2", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 145, + "rust_line": 146, "rust_test": "metallic_override_to_one_clamps_below_default", "semantic_review": "reviewed" }, @@ -1871,7 +1871,7 @@ "rust_body_sha256": "fb03f033cb63519fd7793810bee920e639c19e00bc3532e08a27ebad4e8fa231", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 159, + "rust_line": 160, "rust_test": "alpha_mode_override_sets_flag", "semantic_review": "reviewed" }, @@ -1890,7 +1890,7 @@ "rust_body_sha256": "6a9ef4111be381b3341cb8e53be5a2a6adabd621875d6c65d1967a8016b26487", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 169, + "rust_line": 170, "rust_test": "null_texture_override_uses_sentinel", "semantic_review": "reviewed" }, @@ -1909,7 +1909,7 @@ "rust_body_sha256": "80df9c92a01fc69c56d092ef960aa4541f3da61553dd7596ddaea075193ba01f", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 182, + "rust_line": 183, "rust_test": "sentinel_override_clears_base_texture", "semantic_review": "reviewed" }, @@ -1928,7 +1928,7 @@ "rust_body_sha256": "ff9dcb6f7b8b10dc102d348cf57914a4cb55e8be010018249f20cc45879abe67", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 203, + "rust_line": 204, "rust_test": "untouched_override_keeps_base_texture", "semantic_review": "reviewed" }, @@ -1947,7 +1947,7 @@ "rust_body_sha256": "80e2719ba1bcf4a5926d56ab3a39f89b92eaf36d0308af99a85de900b77c1f5e", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 220, + "rust_line": 221, "rust_test": "changed_scalar_override_replaces_base", "semantic_review": "reviewed" }, @@ -1966,7 +1966,7 @@ "rust_body_sha256": "45356599a1d9581365323a5d6f8822ae69082200322dd4357b256e5141c055f1", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 232, + "rust_line": 233, "rust_test": "set_base_material_keeps_texture_transforms", "semantic_review": "reviewed" }, @@ -1985,7 +1985,7 @@ "rust_body_sha256": "de5047cdff7424adf0f762d00f77dfa6c94cd6ef3f099e123807b1bdbbd34341", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 257, + "rust_line": 258, "rust_test": "override_osd_round_trip_preserves_fields", "semantic_review": "reviewed" }, @@ -2004,7 +2004,7 @@ "rust_body_sha256": "30aa45a8fff68f73082836c06bd0802eec3d162bba408e6c0dccb9741de92a66", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 290, + "rust_line": 291, "rust_test": "untouched_material_override_osd_is_empty", "semantic_review": "reviewed" }, @@ -2023,7 +2023,7 @@ "rust_body_sha256": "7aa100c1787a91289101ff37c598691ad785e209204aaf0121b29441cbdcda59", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 298, + "rust_line": 299, "rust_test": "default_roughness_override_is_encoded", "semantic_review": "reviewed" }, @@ -2042,7 +2042,7 @@ "rust_body_sha256": "60a805d63479f0e804adad6b9a7d9a8598a61732631d35be4295a732421d9504", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 317, + "rust_line": 318, "rust_test": "material_json_round_trip_preserves_name_and_color", "semantic_review": "reviewed" }, @@ -4636,7 +4636,7 @@ "rust_body_sha256": "2e0a979b27ac05993976138f0ac08be4572fd50cf43d5dd034c41a9f678c0ae2", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 336, + "rust_line": 337, "rust_test": "gltf_parses_scene_graph", "semantic_review": "reviewed" }, @@ -4655,7 +4655,7 @@ "rust_body_sha256": "9d33cc4646ab6692d0d4fd8b3a86c532246103eb531555b9d84bd9ef4b18534a", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 349, + "rust_line": 350, "rust_test": "gltf_parses_mesh_and_primitive", "semantic_review": "reviewed" }, @@ -4674,7 +4674,7 @@ "rust_body_sha256": "8a12d86a3042d1c1f2a365c81825e33b32d3370f556f585c357b3cfab77525b7", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 363, + "rust_line": 364, "rust_test": "gltf_parses_material", "semantic_review": "reviewed" }, @@ -4693,7 +4693,7 @@ "rust_body_sha256": "2fe6ac879e16e27db934670ff1b02ebeab2e4f2c566284f46a8d8ca353369348", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 377, + "rust_line": 378, "rust_test": "gltf_positions_are_correct_triangle", "semantic_review": "reviewed" }, @@ -4712,7 +4712,7 @@ "rust_body_sha256": "e59afeb85c77aabfe07bf62efc6e6ea28158d71f7fad09c3d6bd4c16af8206ef", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 395, + "rust_line": 396, "rust_test": "gltf_indices_are_correct", "semantic_review": "reviewed" }, @@ -4731,7 +4731,7 @@ "rust_body_sha256": "0d6612fd86c2dfee7a1cde2b781cd73e62415092bb34063817dbeca85bea0dda", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 405, + "rust_line": 406, "rust_test": "gltf_positions_empty_without_attribute", "semantic_review": "reviewed" }, @@ -4750,7 +4750,7 @@ "rust_body_sha256": "8c07115698eb8ebed83f68c7f45c203dde1ff58b92cd4a1da10f555b3397424b", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 419, + "rust_line": 420, "rust_test": "gltf_normals_empty_without_attribute", "semantic_review": "reviewed" }, @@ -4769,7 +4769,7 @@ "rust_body_sha256": "4869d0570b83d05570c0c3f626795a1bebb497eedb00a566d0bf63e6a164ea02", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 432, + "rust_line": 433, "rust_test": "gltf_json_round_trip_preserves_scene_graph", "semantic_review": "reviewed" }, @@ -4788,7 +4788,7 @@ "rust_body_sha256": "657d90a0b70805fb9b1bb7fd8b821a2fa64cf68e25793c2fef5369bb7b323d93", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 449, + "rust_line": 450, "rust_test": "glb_round_trip_preserves_geometry", "semantic_review": "reviewed" }, @@ -4807,7 +4807,7 @@ "rust_body_sha256": "0b3b6f016d3b32515045aaa6fc3bf170376bad397a5d705dc2ffc8deb369a0e7", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 463, + "rust_line": 464, "rust_test": "gltf_load_auto_detects_glb_and_json", "semantic_review": "reviewed" }, @@ -4826,7 +4826,7 @@ "rust_body_sha256": "de38ffd7d24d30e90cd78fd8f1f8022aa2ab215f53c44f5cd7f6d346bfcb966c", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 475, + "rust_line": 476, "rust_test": "gltf_node_trs_defaults", "semantic_review": "reviewed" }, @@ -4845,7 +4845,7 @@ "rust_body_sha256": "367ea10e55c9b61d9f536890cbf95f975adf74bf23524cb9ddb78bb6131e1433", "status": "translated", "rust_file": "tests/compat/tests/asset_document_semantics.rs", - "rust_line": 495, + "rust_line": 496, "rust_test": "gltf_node_explicit_matrix", "semantic_review": "reviewed" }, diff --git a/tools/generate_api_shims.py b/tools/generate_api_shims.py index 31a5bfd..bcf86a3 100644 --- a/tools/generate_api_shims.py +++ b/tools/generate_api_shims.py @@ -40,6 +40,8 @@ TARGETS = { NATIVE_TYPES = { "T:LibreMetaverse.Color4": "crate::color4::Color4", "T:LibreMetaverse.CRC32": "crate::crc32::CRC32", + "T:LibreMetaverse.Matrix4": "crate::geometry::Matrix4", + "T:LibreMetaverse.Quaternion": "crate::geometry::Quaternion", "T:LibreMetaverse.Ray": "crate::vectors::Ray", "T:LibreMetaverse.UUID": "crate::uuid::UUID", "T:LibreMetaverse.Vector2": "crate::vectors::Vector2", diff --git a/tools/generate_rust_mapping.py b/tools/generate_rust_mapping.py index 0cedc57..3b9b9f3 100644 --- a/tools/generate_rust_mapping.py +++ b/tools/generate_rust_mapping.py @@ -221,6 +221,10 @@ MEMBER_SIGNATURE_OVERRIDES = { "pub fn to_float_bytes(&self, dest: &mut [u8], pos: i32) -> Result<(), crate::Error>", "shared_self,mutable_borrow,owned", ), + "M:LibreMetaverse.Quaternion.ToBytes(System.Byte[],System.Int32)": ( + "pub fn to_bytes(&self, dest: &mut [u8], pos: i32) -> Result<(), crate::Error>", + "shared_self,mutable_borrow,owned", + ), "M:LibreMetaverse.Vector2.ToBytes(System.Byte[],System.Int32)": ( "pub fn to_bytes(&self, dest: &mut [u8], pos: i32) -> Result<(), crate::Error>", "shared_self,mutable_borrow,owned",