Implement vector color and ray compatibility
This commit is contained in:
429
crates/libremetaverse-types/src/color4.rs
Normal file
429
crates/libremetaverse-types/src/color4.rs
Normal file
@@ -0,0 +1,429 @@
|
||||
//! Native four-channel color compatible with `LibreMetaverse`'s `Color4`.
|
||||
|
||||
#![allow(clippy::cast_possible_truncation)] // Explicit C# numeric conversions are part of the API.
|
||||
#![allow(clippy::cast_sign_loss)] // FloatToByte clamps before the reference-compatible cast.
|
||||
#![allow(clippy::float_cmp)] // Exact component equality is required by the reference type.
|
||||
#![allow(clippy::inherent_to_string)] // The mapped C# method is named ToString.
|
||||
#![allow(clippy::items_after_statements)] // Constants stay next to their compatibility formulas.
|
||||
#![allow(clippy::many_single_char_names)] // Color/HSV formulas use the reference channel names.
|
||||
#![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.
|
||||
|
||||
use crate::Error;
|
||||
use crate::compat::Object;
|
||||
use crate::math_compat::{
|
||||
checked_range, clamp_f32, compare_f32, format_f32, hash_f32, lerp_f32, max_f32, min_f32,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Color4 {
|
||||
pub r: f32,
|
||||
pub g: f32,
|
||||
pub b: f32,
|
||||
pub a: f32,
|
||||
}
|
||||
|
||||
impl Color4 {
|
||||
#[must_use]
|
||||
pub const fn black() -> Self {
|
||||
Self {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 1.0,
|
||||
}
|
||||
}
|
||||
#[must_use]
|
||||
pub const fn white() -> Self {
|
||||
Self {
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_byte_byte_byte_byte(r: u8, g: u8, b: u8, a: u8) -> Result<Self, Error> {
|
||||
const QUANTA: f32 = 1.0 / 255.0;
|
||||
Ok(Self {
|
||||
r: f32::from(r) * QUANTA,
|
||||
g: f32::from(g) * QUANTA,
|
||||
b: f32::from(b) * QUANTA,
|
||||
a: f32::from(a) * QUANTA,
|
||||
})
|
||||
}
|
||||
pub fn new_with_single_single_single_single(
|
||||
r: f32,
|
||||
g: f32,
|
||||
b: f32,
|
||||
a: f32,
|
||||
) -> Result<Self, Error> {
|
||||
if r > 1.0 || g > 1.0 || b > 1.0 || a > 1.0 {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
Ok(Self {
|
||||
r: clamp_f32(r, 0.0, 1.0),
|
||||
g: clamp_f32(g, 0.0, 1.0),
|
||||
b: clamp_f32(b, 0.0, 1.0),
|
||||
a: clamp_f32(a, 0.0, 1.0),
|
||||
})
|
||||
}
|
||||
pub fn new_with_bytes_int32_boolean(
|
||||
bytes: Vec<u8>,
|
||||
pos: i32,
|
||||
inverted: bool,
|
||||
) -> Result<Self, Error> {
|
||||
Self::from_bytes_slice(&bytes, pos, inverted, false)
|
||||
}
|
||||
pub fn new_with_bytes_int32_boolean_boolean(
|
||||
bytes: Vec<u8>,
|
||||
pos: i32,
|
||||
inverted: bool,
|
||||
alpha_inverted: bool,
|
||||
) -> Result<Self, Error> {
|
||||
Self::from_bytes_slice(&bytes, pos, inverted, alpha_inverted)
|
||||
}
|
||||
fn from_bytes_slice(
|
||||
bytes: &[u8],
|
||||
pos: i32,
|
||||
inverted: bool,
|
||||
alpha_inverted: bool,
|
||||
) -> Result<Self, Error> {
|
||||
let range = checked_range(pos, 4, bytes.len())?;
|
||||
let values = &bytes[range];
|
||||
const QUANTA: f32 = 1.0 / 255.0;
|
||||
let channel = |value: u8| f32::from(if inverted { 255 - value } else { value }) * QUANTA;
|
||||
let mut result = Self {
|
||||
r: channel(values[0]),
|
||||
g: channel(values[1]),
|
||||
b: channel(values[2]),
|
||||
a: channel(values[3]),
|
||||
};
|
||||
if alpha_inverted {
|
||||
result.a = 1.0 - result.a;
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
pub fn from_bytes_with_bytes_int32_boolean(
|
||||
bytes: Vec<u8>,
|
||||
pos: i32,
|
||||
inverted: bool,
|
||||
) -> Result<Self, Error> {
|
||||
Self::new_with_bytes_int32_boolean(bytes, pos, inverted)
|
||||
}
|
||||
pub fn from_bytes_with_bytes_int32_boolean_boolean(
|
||||
bytes: Vec<u8>,
|
||||
pos: i32,
|
||||
inverted: bool,
|
||||
alpha_inverted: bool,
|
||||
) -> Result<Self, Error> {
|
||||
Self::new_with_bytes_int32_boolean_boolean(bytes, pos, inverted, alpha_inverted)
|
||||
}
|
||||
|
||||
pub fn compare_to(&self, color: Self) -> Result<i32, Error> {
|
||||
let this_hue = self.get_hue()?;
|
||||
let that_hue = color.get_hue()?;
|
||||
Ok(if this_hue < 0.0 && that_hue < 0.0 {
|
||||
if self.r == color.r {
|
||||
compare_f32(self.a, color.a)
|
||||
} else {
|
||||
compare_f32(self.r, color.r)
|
||||
}
|
||||
} else if this_hue == that_hue {
|
||||
compare_f32(self.a, color.a)
|
||||
} else {
|
||||
compare_f32(this_hue, that_hue)
|
||||
})
|
||||
}
|
||||
pub fn get_bytes_with_method(&self) -> Result<Vec<u8>, Error> {
|
||||
self.get_bytes_with_boolean(false)
|
||||
}
|
||||
pub fn get_bytes_with_boolean(&self, inverted: bool) -> Result<Vec<u8>, Error> {
|
||||
let mut bytes = vec![0; 4];
|
||||
self.to_bytes_with_bytes_int32_boolean(&mut bytes, 0, inverted)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
pub fn get_float_bytes(&self) -> Result<Vec<u8>, Error> {
|
||||
let mut bytes = vec![0; 16];
|
||||
self.to_float_bytes(&mut bytes, 0)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
pub fn to_bytes_with_bytes_int32(&self, dest: &mut [u8], pos: i32) -> Result<(), Error> {
|
||||
self.to_bytes_with_bytes_int32_boolean(dest, pos, false)
|
||||
}
|
||||
pub fn to_bytes_with_bytes_int32_boolean(
|
||||
&self,
|
||||
dest: &mut [u8],
|
||||
pos: i32,
|
||||
inverted: bool,
|
||||
) -> Result<(), Error> {
|
||||
for (offset, value) in [(0, self.r), (1, self.g), (2, self.b), (3, self.a)] {
|
||||
let index = checked_range(
|
||||
pos.checked_add(offset).ok_or(Error::IndexOutOfRange)?,
|
||||
1,
|
||||
dest.len(),
|
||||
)?
|
||||
.start;
|
||||
let byte = float_to_byte(value);
|
||||
dest[index] = if inverted { 255 - byte } else { byte };
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn to_float_bytes(&self, dest: &mut [u8], pos: i32) -> Result<(), Error> {
|
||||
for (offset, value) in [(0, self.r), (4, self.g), (8, self.b), (12, self.a)] {
|
||||
crate::byte_order::write_single_little_endian(
|
||||
dest,
|
||||
pos.checked_add(offset).ok_or(Error::IndexOutOfRange)?,
|
||||
value,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn get_hue(&self) -> Result<f32, Error> {
|
||||
const HUE_MAX: f32 = 360.0;
|
||||
let max = max_f32(max_f32(self.r, self.g), self.b);
|
||||
let min = min_f32(min_f32(self.r, self.g), self.b);
|
||||
let tolerance = (f64::from(max) * 0.000_01).abs();
|
||||
if f64::from((max - min).abs()) < tolerance {
|
||||
return Ok(-1.0);
|
||||
}
|
||||
if f64::from((self.r - max).abs()) < tolerance {
|
||||
let b_delta = ((max - self.b) * (HUE_MAX / 6.0) + (max - min) / 2.0) / (max - min);
|
||||
let g_delta = ((max - self.g) * (HUE_MAX / 6.0) + (max - min) / 2.0) / (max - min);
|
||||
Ok(b_delta - g_delta)
|
||||
} else if f64::from((self.g - max).abs()) < tolerance {
|
||||
let r_delta = ((max - self.r) * (HUE_MAX / 6.0) + (max - min) / 2.0) / (max - min);
|
||||
let b_delta = ((max - self.b) * (HUE_MAX / 6.0) + (max - min) / 2.0) / (max - min);
|
||||
Ok(HUE_MAX / 3.0 + r_delta - b_delta)
|
||||
} else {
|
||||
let g_delta = ((max - self.g) * (HUE_MAX / 6.0) + (max - min) / 2.0) / (max - min);
|
||||
let r_delta = ((max - self.r) * (HUE_MAX / 6.0) + (max - min) / 2.0) / (max - min);
|
||||
Ok(2.0 * HUE_MAX / 3.0 + g_delta - r_delta)
|
||||
}
|
||||
}
|
||||
pub fn from_hsv(hue: f64, saturation: f64, value: f64) -> Result<Self, Error> {
|
||||
let (mut r, mut g, mut b) = (0.0, 0.0, 0.0);
|
||||
if saturation == 0.0 {
|
||||
r = value;
|
||||
g = value;
|
||||
b = value;
|
||||
} else {
|
||||
let sector_pos = hue / 60.0;
|
||||
let sector_number = sector_pos.floor() as i32;
|
||||
let fractional = sector_pos - f64::from(sector_number);
|
||||
let p = value * (1.0 - saturation);
|
||||
let q = value * (1.0 - saturation * fractional);
|
||||
let t = value * (1.0 - saturation * (1.0 - fractional));
|
||||
match sector_number {
|
||||
0 => {
|
||||
r = value;
|
||||
g = t;
|
||||
b = p;
|
||||
}
|
||||
1 => {
|
||||
r = q;
|
||||
g = value;
|
||||
b = p;
|
||||
}
|
||||
2 => {
|
||||
r = p;
|
||||
g = value;
|
||||
b = t;
|
||||
}
|
||||
3 => {
|
||||
r = p;
|
||||
g = q;
|
||||
b = value;
|
||||
}
|
||||
4 => {
|
||||
r = t;
|
||||
g = p;
|
||||
b = value;
|
||||
}
|
||||
5 => {
|
||||
r = value;
|
||||
g = p;
|
||||
b = q;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Self::new_with_single_single_single_single(r as f32, g as f32, b as f32, 1.0)
|
||||
}
|
||||
pub fn lerp(value1: Self, value2: Self, amount: f32) -> Result<Self, Error> {
|
||||
Self::new_with_single_single_single_single(
|
||||
clamp_f32(lerp_f32(value1.r, value2.r, amount), 0.0, 1.0),
|
||||
clamp_f32(lerp_f32(value1.g, value2.g, amount), 0.0, 1.0),
|
||||
clamp_f32(lerp_f32(value1.b, value2.b, amount), 0.0, 1.0),
|
||||
clamp_f32(lerp_f32(value1.a, value2.a, amount), 0.0, 1.0),
|
||||
)
|
||||
}
|
||||
pub fn to_string(&self) -> String {
|
||||
format!(
|
||||
"<{}, {}, {}, {}>",
|
||||
format_f32(self.r),
|
||||
format_f32(self.g),
|
||||
format_f32(self.b),
|
||||
format_f32(self.a)
|
||||
)
|
||||
}
|
||||
pub fn to_rgb_string(&self) -> Result<String, Error> {
|
||||
Ok(format!(
|
||||
"<{}, {}, {}>",
|
||||
format_f32(self.r),
|
||||
format_f32(self.g),
|
||||
format_f32(self.b)
|
||||
))
|
||||
}
|
||||
pub fn equals_with_object(&self, obj: Option<Object>) -> bool {
|
||||
matches!(obj, Some(Object::Color4(value)) if Self::eq(*self, value))
|
||||
}
|
||||
pub fn equals_with_color4(&self, other: Self) -> bool {
|
||||
Self::eq(*self, other)
|
||||
}
|
||||
pub fn get_hash_code(&self) -> i32 {
|
||||
hash_f32(self.r) ^ hash_f32(self.g) ^ hash_f32(self.b) ^ hash_f32(self.a)
|
||||
}
|
||||
pub fn eq(lhs: Self, rhs: Self) -> bool {
|
||||
lhs.r == rhs.r && lhs.g == rhs.g && lhs.b == rhs.b && lhs.a == rhs.a
|
||||
}
|
||||
pub fn ne(lhs: Self, rhs: Self) -> bool {
|
||||
!Self::eq(lhs, rhs)
|
||||
}
|
||||
pub fn add(lhs: Self, rhs: Self) -> Self {
|
||||
Self::channel_op(lhs, rhs, |a, b| a + b)
|
||||
}
|
||||
pub fn sub(lhs: Self, rhs: Self) -> Self {
|
||||
Self::channel_op(lhs, rhs, |a, b| a - b)
|
||||
}
|
||||
pub fn mul(lhs: Self, rhs: Self) -> Self {
|
||||
Self::channel_op(lhs, rhs, |a, b| a * b)
|
||||
}
|
||||
fn channel_op(lhs: Self, rhs: Self, op: impl Fn(f32, f32) -> f32) -> Self {
|
||||
Self {
|
||||
r: clamp_f32(op(lhs.r, rhs.r), 0.0, 1.0),
|
||||
g: clamp_f32(op(lhs.g, rhs.g), 0.0, 1.0),
|
||||
b: clamp_f32(op(lhs.b, rhs.b), 0.0, 1.0),
|
||||
a: clamp_f32(op(lhs.a, rhs.a), 0.0, 1.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn float_to_byte(value: f32) -> u8 {
|
||||
(clamp_f32(value, 0.0, 1.0) * 255.0).floor() as u8
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn byte_and_float_serialization_match_golden_values() {
|
||||
let color = Color4::new_with_single_single_single_single(1.0, 0.5, 0.25, 0.0).unwrap();
|
||||
assert_eq!(color.get_bytes_with_method().unwrap(), [255, 127, 63, 0]);
|
||||
assert_eq!(
|
||||
color.get_bytes_with_boolean(true).unwrap(),
|
||||
[0, 128, 192, 255]
|
||||
);
|
||||
let decoded =
|
||||
Color4::new_with_bytes_int32_boolean_boolean(vec![0, 128, 192, 255], 0, true, true)
|
||||
.unwrap();
|
||||
assert_eq!(decoded.r, 1.0);
|
||||
assert_eq!(decoded.a, 1.0);
|
||||
let float_bytes = color.get_float_bytes().unwrap();
|
||||
assert_eq!(&float_bytes[0..4], &1.0_f32.to_le_bytes());
|
||||
assert_eq!(&float_bytes[4..8], &0.5_f32.to_le_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_preserves_partial_mutation_before_bad_index() {
|
||||
let mut output = [0xaa; 2];
|
||||
assert_eq!(
|
||||
Color4::white().to_bytes_with_bytes_int32(&mut output, 0),
|
||||
Err(Error::IndexOutOfRange)
|
||||
);
|
||||
assert_eq!(output, [255, 255]);
|
||||
let mut floats = [0xaa; 6];
|
||||
assert_eq!(
|
||||
Color4::white().to_float_bytes(&mut floats, 0),
|
||||
Err(Error::IndexOutOfRange)
|
||||
);
|
||||
assert_eq!(&floats[..4], &1.0_f32.to_le_bytes());
|
||||
assert_eq!(&floats[4..], &[0xaa; 2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constructor_clamping_hsv_and_channel_operators_match_reference() {
|
||||
assert_eq!(
|
||||
Color4::new_with_single_single_single_single(-1.0, 0.5, 0.0, 1.0).unwrap(),
|
||||
Color4 {
|
||||
r: 0.0,
|
||||
g: 0.5,
|
||||
b: 0.0,
|
||||
a: 1.0
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
Color4::new_with_single_single_single_single(1.0001, 0.0, 0.0, 0.0),
|
||||
Err(Error::Argument)
|
||||
);
|
||||
assert_eq!(
|
||||
Color4::from_hsv(0.0, 1.0, 1.0).unwrap(),
|
||||
Color4 {
|
||||
r: 1.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 1.0
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
Color4::add(
|
||||
Color4 {
|
||||
r: 0.75,
|
||||
g: 0.25,
|
||||
b: 0.0,
|
||||
a: 0.5
|
||||
},
|
||||
Color4 {
|
||||
r: 0.5,
|
||||
g: 0.5,
|
||||
b: 0.5,
|
||||
a: 0.75
|
||||
}
|
||||
),
|
||||
Color4 {
|
||||
r: 1.0,
|
||||
g: 0.75,
|
||||
b: 0.5,
|
||||
a: 1.0
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hue_comparison_strings_and_hashes_match_reference_rules() {
|
||||
let red = Color4 {
|
||||
r: 1.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 1.0,
|
||||
};
|
||||
let green = Color4 {
|
||||
r: 0.0,
|
||||
g: 1.0,
|
||||
b: 0.0,
|
||||
a: 1.0,
|
||||
};
|
||||
assert_eq!(red.get_hue().unwrap(), 0.0);
|
||||
assert_eq!(green.get_hue().unwrap(), 120.0);
|
||||
assert_eq!(red.compare_to(green).unwrap(), -1);
|
||||
assert_eq!(red.to_string(), "<1, 0, 0, 1>");
|
||||
assert_eq!(red.to_rgb_string().unwrap(), "<1, 0, 0>");
|
||||
let negative_zero = Color4 { r: -0.0, ..red };
|
||||
let positive_zero = Color4 { r: 0.0, ..red };
|
||||
assert_eq!(negative_zero.get_hash_code(), positive_zero.get_hash_code());
|
||||
assert!(red.equals_with_object(Some(Object::from(red))));
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::future::Future;
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
use std::sync::{
|
||||
@@ -19,10 +20,47 @@ pub trait ReadWrite: std::io::Read + std::io::Write {}
|
||||
|
||||
impl<T: std::io::Read + std::io::Write> ReadWrite for T {}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Object {
|
||||
Color4(crate::Color4),
|
||||
String(String),
|
||||
UUID(crate::UUID),
|
||||
Vector2(crate::Vector2),
|
||||
Vector3(crate::Vector3),
|
||||
Vector3d(crate::Vector3d),
|
||||
Vector4(crate::Vector4),
|
||||
}
|
||||
|
||||
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::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),
|
||||
(Self::Vector3(lhs), Self::Vector3(rhs)) => crate::Vector3::eq(*lhs, *rhs),
|
||||
(Self::Vector3d(lhs), Self::Vector3d(rhs)) => crate::Vector3d::eq(*lhs, *rhs),
|
||||
(Self::Vector4(lhs), Self::Vector4(rhs)) => crate::Vector4::eq(*lhs, *rhs),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Object {}
|
||||
|
||||
impl std::hash::Hash for Object {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
std::mem::discriminant(self).hash(state);
|
||||
match self {
|
||||
Self::Color4(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),
|
||||
Self::Vector3(value) => value.get_hash_code().hash(state),
|
||||
Self::Vector3d(value) => value.get_hash_code().hash(state),
|
||||
Self::Vector4(value) => value.get_hash_code().hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Object {
|
||||
@@ -37,6 +75,32 @@ impl From<&str> for Object {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::Color4> for Object {
|
||||
fn from(value: crate::Color4) -> Self {
|
||||
Self::Color4(value)
|
||||
}
|
||||
}
|
||||
impl From<crate::Vector2> for Object {
|
||||
fn from(value: crate::Vector2) -> Self {
|
||||
Self::Vector2(value)
|
||||
}
|
||||
}
|
||||
impl From<crate::Vector3> for Object {
|
||||
fn from(value: crate::Vector3) -> Self {
|
||||
Self::Vector3(value)
|
||||
}
|
||||
}
|
||||
impl From<crate::Vector3d> for Object {
|
||||
fn from(value: crate::Vector3d) -> Self {
|
||||
Self::Vector3d(value)
|
||||
}
|
||||
}
|
||||
impl From<crate::Vector4> for Object {
|
||||
fn from(value: crate::Vector4) -> Self {
|
||||
Self::Vector4(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct Utf16CodeUnit(pub u16);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,11 +3,14 @@
|
||||
extern crate self as libremetaverse_types;
|
||||
|
||||
mod byte_order;
|
||||
mod color4;
|
||||
pub mod compat;
|
||||
mod crc32;
|
||||
mod generated;
|
||||
mod math_compat;
|
||||
pub mod shim;
|
||||
mod uuid;
|
||||
mod vectors;
|
||||
|
||||
pub use generated::*;
|
||||
pub use shim::{Error, NotImplemented, not_implemented};
|
||||
|
||||
189
crates/libremetaverse-types/src/math_compat.rs
Normal file
189
crates/libremetaverse-types/src/math_compat.rs
Normal file
@@ -0,0 +1,189 @@
|
||||
//! Small helpers that preserve the scalar behavior used by the C# value types.
|
||||
|
||||
#![allow(clippy::cast_possible_truncation)] // Hashes intentionally fold raw IEEE bit patterns.
|
||||
#![allow(clippy::cast_possible_wrap)] // .NET hash codes reinterpret unsigned IEEE bits as i32.
|
||||
#![allow(clippy::float_cmp)] // Signed-zero and exact scalar compatibility require exact comparisons.
|
||||
|
||||
use crate::Error;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
pub(crate) fn checked_range(
|
||||
pos: i32,
|
||||
width: usize,
|
||||
len: usize,
|
||||
) -> Result<std::ops::Range<usize>, Error> {
|
||||
let start = usize::try_from(pos).map_err(|_| Error::IndexOutOfRange)?;
|
||||
let end = start.checked_add(width).ok_or(Error::IndexOutOfRange)?;
|
||||
if end > len {
|
||||
return Err(Error::IndexOutOfRange);
|
||||
}
|
||||
Ok(start..end)
|
||||
}
|
||||
|
||||
pub(crate) fn clamp_f32(value: f32, min: f32, max: f32) -> f32 {
|
||||
if value > max {
|
||||
max
|
||||
} else if value < min {
|
||||
min
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clamp_f64(value: f64, min: f64, max: f64) -> f64 {
|
||||
if value > max {
|
||||
max
|
||||
} else if value < min {
|
||||
min
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn lerp_f32(value1: f32, value2: f32, amount: f32) -> f32 {
|
||||
value1 + (value2 - value1) * amount
|
||||
}
|
||||
|
||||
pub(crate) fn lerp_f64(value1: f64, value2: f64, amount: f64) -> f64 {
|
||||
value1 + (value2 - value1) * amount
|
||||
}
|
||||
|
||||
pub(crate) fn smooth_step_f32(value1: f32, value2: f32, amount: f32) -> f32 {
|
||||
let amount = clamp_f32(amount, 0.0, 1.0);
|
||||
lerp_f32(value1, value2, amount * amount * (3.0 - 2.0 * amount))
|
||||
}
|
||||
|
||||
pub(crate) fn smooth_step_f64(value1: f64, value2: f64, amount: f64) -> f64 {
|
||||
let amount = clamp_f64(amount, 0.0, 1.0);
|
||||
lerp_f64(value1, value2, amount * amount * (3.0 - 2.0 * amount))
|
||||
}
|
||||
|
||||
pub(crate) fn compare_f32(lhs: f32, rhs: f32) -> i32 {
|
||||
match (lhs.is_nan(), rhs.is_nan()) {
|
||||
(true, true) => 0,
|
||||
(true, false) => -1,
|
||||
(false, true) => 1,
|
||||
(false, false) => match lhs.partial_cmp(&rhs).expect("non-NaN floats are ordered") {
|
||||
Ordering::Less => -1,
|
||||
Ordering::Equal => 0,
|
||||
Ordering::Greater => 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn compare_f64(lhs: f64, rhs: f64) -> i32 {
|
||||
match (lhs.is_nan(), rhs.is_nan()) {
|
||||
(true, true) => 0,
|
||||
(true, false) => -1,
|
||||
(false, true) => 1,
|
||||
(false, false) => match lhs.partial_cmp(&rhs).expect("non-NaN floats are ordered") {
|
||||
Ordering::Less => -1,
|
||||
Ordering::Equal => 0,
|
||||
Ordering::Greater => 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn hash_f32(value: f32) -> i32 {
|
||||
let bits = if value == 0.0 {
|
||||
0
|
||||
} else if value.is_nan() {
|
||||
0x7fc0_0000
|
||||
} else {
|
||||
value.to_bits()
|
||||
};
|
||||
bits as i32
|
||||
}
|
||||
|
||||
pub(crate) fn hash_f64(value: f64) -> i32 {
|
||||
let bits = if value == 0.0 {
|
||||
0
|
||||
} else if value.is_nan() {
|
||||
0x7ff8_0000_0000_0000
|
||||
} else {
|
||||
value.to_bits()
|
||||
};
|
||||
((bits as u32) ^ ((bits >> 32) as u32)) as i32
|
||||
}
|
||||
|
||||
pub(crate) fn max_f32(lhs: f32, rhs: f32) -> f32 {
|
||||
if lhs.is_nan() {
|
||||
lhs
|
||||
} else if rhs.is_nan() {
|
||||
rhs
|
||||
} else if lhs == rhs {
|
||||
if lhs == 0.0 && lhs.is_sign_negative() {
|
||||
rhs
|
||||
} else {
|
||||
lhs
|
||||
}
|
||||
} else if lhs > rhs {
|
||||
lhs
|
||||
} else {
|
||||
rhs
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn min_f32(lhs: f32, rhs: f32) -> f32 {
|
||||
if lhs.is_nan() {
|
||||
lhs
|
||||
} else if rhs.is_nan() {
|
||||
rhs
|
||||
} else if lhs == rhs {
|
||||
if lhs == 0.0 && lhs.is_sign_negative() {
|
||||
lhs
|
||||
} else {
|
||||
rhs
|
||||
}
|
||||
} else if lhs < rhs {
|
||||
lhs
|
||||
} else {
|
||||
rhs
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn max_f64(lhs: f64, rhs: f64) -> f64 {
|
||||
if lhs.is_nan() {
|
||||
lhs
|
||||
} else if rhs.is_nan() {
|
||||
rhs
|
||||
} else {
|
||||
lhs.max(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn min_f64(lhs: f64, rhs: f64) -> f64 {
|
||||
if lhs.is_nan() {
|
||||
lhs
|
||||
} else if rhs.is_nan() {
|
||||
rhs
|
||||
} else {
|
||||
lhs.min(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_f32(value: &str) -> Result<f32, Error> {
|
||||
value.trim().parse().map_err(|_| Error::Argument)
|
||||
}
|
||||
|
||||
pub(crate) fn parse_f64(value: &str) -> Result<f64, Error> {
|
||||
value.trim().parse().map_err(|_| Error::Argument)
|
||||
}
|
||||
|
||||
pub(crate) fn format_f32(value: f32) -> String {
|
||||
match value {
|
||||
value if value.is_nan() => "NaN".to_owned(),
|
||||
value if value == f32::INFINITY => "Infinity".to_owned(),
|
||||
value if value == f32::NEG_INFINITY => "-Infinity".to_owned(),
|
||||
value => value.to_string().replace('e', "E"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn format_f64(value: f64) -> String {
|
||||
match value {
|
||||
value if value.is_nan() => "NaN".to_owned(),
|
||||
value if value == f64::INFINITY => "Infinity".to_owned(),
|
||||
value if value == f64::NEG_INFINITY => "-Infinity".to_owned(),
|
||||
value => value.to_string().replace('e', "E"),
|
||||
}
|
||||
}
|
||||
1711
crates/libremetaverse-types/src/vectors.rs
Normal file
1711
crates/libremetaverse-types/src/vectors.rs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user