Implement Color3 roblox datatype

This commit is contained in:
Filip Tibell 2023-03-15 14:01:38 +01:00
parent b80d5e4bab
commit 2a6b88e6a7
No known key found for this signature in database
4 changed files with 220 additions and 0 deletions

View file

@ -136,6 +136,9 @@ impl<'lua> RbxVariantToLua<'lua> for LuaAnyUserData<'lua> {
Rbx::BrickColor(value) => lua.create_userdata(BrickColor::from(value))?, Rbx::BrickColor(value) => lua.create_userdata(BrickColor::from(value))?,
Rbx::Color3(value) => lua.create_userdata(Color3::from(value))?,
Rbx::Color3uint8(value) => lua.create_userdata(Color3::from(value))?,
Rbx::UDim(value) => lua.create_userdata(UDim::from(value))?, Rbx::UDim(value) => lua.create_userdata(UDim::from(value))?,
Rbx::UDim2(value) => lua.create_userdata(UDim2::from(value))?, Rbx::UDim2(value) => lua.create_userdata(UDim2::from(value))?,
@ -168,6 +171,9 @@ impl<'lua> LuaToRbxVariant<'lua> for LuaAnyUserData<'lua> {
let f = match variant_type { let f = match variant_type {
RbxVariantType::BrickColor => convert::<BrickColor, rbx::BrickColor>, RbxVariantType::BrickColor => convert::<BrickColor, rbx::BrickColor>,
RbxVariantType::Color3 => convert::<Color3, rbx::Color3>,
RbxVariantType::Color3uint8 => convert::<Color3, rbx::Color3uint8>,
RbxVariantType::UDim => convert::<UDim, rbx::UDim>, RbxVariantType::UDim => convert::<UDim, rbx::UDim>,
RbxVariantType::UDim2 => convert::<UDim2, rbx::UDim2>, RbxVariantType::UDim2 => convert::<UDim2, rbx::UDim2>,

View file

@ -0,0 +1,211 @@
use core::fmt;
use glam::Vec3;
use mlua::prelude::*;
use rbx_dom_weak::types::{Color3 as RbxColor3, Color3uint8 as RbxColor3uint8};
use super::super::*;
/**
An implementation of the [Color3](https://create.roblox.com/docs/reference/engine/datatypes/Color3) Roblox datatype.
This implements all documented properties, methods & constructors of the Color3 class as of March 2023.
*/
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Color3 {
pub(crate) r: f32,
pub(crate) g: f32,
pub(crate) b: f32,
}
impl Color3 {
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
datatype_table.set(
"new",
lua.create_function(|_, (r, g, b): (Option<f32>, Option<f32>, Option<f32>)| {
Ok(Color3 {
r: r.unwrap_or_default(),
g: g.unwrap_or_default(),
b: b.unwrap_or_default(),
})
})?,
)?;
datatype_table.set(
"fromRGB",
lua.create_function(|_, (r, g, b): (Option<u8>, Option<u8>, Option<u8>)| {
Ok(Color3 {
r: (r.unwrap_or_default() as f32) / 255f32,
g: (g.unwrap_or_default() as f32) / 255f32,
b: (b.unwrap_or_default() as f32) / 255f32,
})
})?,
)?;
datatype_table.set(
"fromHSV",
lua.create_function(|_, (h, s, v): (f32, f32, f32)| {
// https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
let i = (h * 6.0).floor();
let f = h * 6.0 - i;
let p = v * (1.0 - s);
let q = v * (1.0 - f * s);
let t = v * (1.0 - (1.0 - f) * s);
let (r, g, b) = match (i % 6.0) as u8 {
0 => (v, t, p),
1 => (q, v, p),
2 => (p, v, t),
3 => (p, q, v),
4 => (t, p, v),
5 => (v, p, q),
_ => unreachable!(),
};
Ok(Color3 { r, g, b })
})?,
)?;
datatype_table.set(
"fromHex",
lua.create_function(|_, hex: String| {
let trimmed = hex.trim_start_matches('#').to_ascii_uppercase();
let chars = if trimmed.len() == 3 {
(
u8::from_str_radix(&trimmed[..1].repeat(2), 16),
u8::from_str_radix(&trimmed[1..2].repeat(2), 16),
u8::from_str_radix(&trimmed[2..3].repeat(2), 16),
)
} else if trimmed.len() == 6 {
(
u8::from_str_radix(&trimmed[..2], 16),
u8::from_str_radix(&trimmed[2..4], 16),
u8::from_str_radix(&trimmed[4..6], 16),
)
} else {
return Err(LuaError::RuntimeError(format!(
"Hex color string must be 3 or 6 characters long, got {} character{}",
trimmed.len(),
if trimmed.len() == 1 { "" } else { "s" }
)));
};
match chars {
(Ok(r), Ok(g), Ok(b)) => Ok(Color3 {
r: (r as f32) / 255f32,
g: (g as f32) / 255f32,
b: (b as f32) / 255f32,
}),
_ => Err(LuaError::RuntimeError(format!(
"Hex color string '{}' contains invalid character",
trimmed
))),
}
})?,
)?;
Ok(())
}
}
impl LuaUserData for Color3 {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("R", |_, this| Ok(this.r));
fields.add_field_method_get("G", |_, this| Ok(this.g));
fields.add_field_method_get("B", |_, this| Ok(this.b));
}
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
// Methods
methods.add_method("Lerp", |_, this, (rhs, alpha): (Color3, f32)| {
let v3_this = Vec3::new(this.r, this.g, this.b);
let v3_rhs = Vec3::new(rhs.r, rhs.g, rhs.b);
let v3 = v3_this.lerp(v3_rhs, alpha);
Ok(Color3 {
r: v3.x,
g: v3.y,
b: v3.z,
})
});
methods.add_method("ToHSV", |_, this, ()| {
// https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
let (r, g, b) = (this.r, this.g, this.b);
let min = r.min(g).min(b);
let max = r.max(g).max(b);
let diff = max - min;
let hue = match max {
max if max == min => 0.0,
max if max == this.r => (g - b) + diff * (if g < b { 6.0 } else { 0.0 }),
max if max == this.g => (b - r) + diff * 2.0,
max if max == this.b => (r - g) + diff * 4.0,
_ => unreachable!(),
};
let hue = hue / 6.0 * diff;
let sat = if max == 0.0 { 0.0 } else { diff / max };
let sat = sat.clamp(0.0, 1.0);
Ok((hue, sat, max))
});
methods.add_method("ToHex", |_, this, ()| {
Ok(format!(
"{:02X}{:02X}{:02X}",
this.r.clamp(u8::MIN as f32, u8::MAX as f32) as u8,
this.g.clamp(u8::MIN as f32, u8::MAX as f32) as u8,
this.b.clamp(u8::MIN as f32, u8::MAX as f32) as u8,
))
});
// Metamethods
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
}
}
impl Default for Color3 {
fn default() -> Self {
Self {
r: 0f32,
g: 0f32,
b: 0f32,
}
}
}
impl fmt::Display for Color3 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}, {}, {}", self.r, self.g, self.b)
}
}
impl From<RbxColor3> for Color3 {
fn from(v: RbxColor3) -> Self {
Self {
r: v.r,
g: v.g,
b: v.b,
}
}
}
impl From<Color3> for RbxColor3 {
fn from(v: Color3) -> Self {
Self {
r: v.r,
g: v.g,
b: v.b,
}
}
}
impl From<RbxColor3uint8> for Color3 {
fn from(v: RbxColor3uint8) -> Self {
Self {
r: (v.r as f32) / 255f32,
g: (v.g as f32) / 255f32,
b: (v.b as f32) / 255f32,
}
}
}
impl From<Color3> for RbxColor3uint8 {
fn from(v: Color3) -> Self {
Self {
r: v.r.clamp(u8::MIN as f32, u8::MAX as f32) as u8,
g: v.g.clamp(u8::MIN as f32, u8::MAX as f32) as u8,
b: v.b.clamp(u8::MIN as f32, u8::MAX as f32) as u8,
}
}
}

View file

@ -1,4 +1,5 @@
mod brick_color; mod brick_color;
mod color3;
mod udim; mod udim;
mod udim2; mod udim2;
mod vector2; mod vector2;
@ -7,6 +8,7 @@ mod vector3;
mod vector3int16; mod vector3int16;
pub use brick_color::BrickColor; pub use brick_color::BrickColor;
pub use color3::Color3;
pub use udim::UDim; pub use udim::UDim;
pub use udim2::UDim2; pub use udim2::UDim2;
pub use vector2::Vector2; pub use vector2::Vector2;

View file

@ -22,6 +22,7 @@ fn make_all_datatypes(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaTable)>> {
use datatypes::types::*; use datatypes::types::*;
Ok(vec![ Ok(vec![
("BrickColor", make_dt(lua, BrickColor::make_table)?), ("BrickColor", make_dt(lua, BrickColor::make_table)?),
("Color3", make_dt(lua, Color3::make_table)?),
("UDim", make_dt(lua, UDim::make_table)?), ("UDim", make_dt(lua, UDim::make_table)?),
("UDim2", make_dt(lua, UDim2::make_table)?), ("UDim2", make_dt(lua, UDim2::make_table)?),
("Vector2", make_dt(lua, Vector2::make_table)?), ("Vector2", make_dt(lua, Vector2::make_table)?),