Implement PhysicalProperties roblox datatype

This commit is contained in:
Filip Tibell 2023-03-17 13:23:52 +01:00
parent e57abbe5d9
commit e57316341b
No known key found for this signature in database
5 changed files with 290 additions and 14 deletions

View file

@ -113,23 +113,28 @@ impl<'lua> RbxVariantToLua<'lua> for LuaAnyUserData<'lua> {
fn rbx_variant_to_lua(lua: &'lua Lua, variant: &RbxVariant) -> DatatypeConversionResult<Self> {
use super::types::*;
use RbxVariant as Rbx;
use rbx_dom_weak::types as rbx;
// NOTE: Enum is intentionally left out here, it has a custom
// conversion going from instance property > datatype instead,
// check `EnumItem::from_instance_property` for specifics
/*
NOTES:
1. Enum is intentionally left out here, it has a custom
conversion going from instance property > datatype instead,
check `EnumItem::from_instance_property` for specifics
2. PhysicalProperties can only be converted if they are custom
physical properties, since default physical properties values
depend on what other related properties an instance might have
*/
Ok(match variant.clone() {
// Not yet implemented datatypes
// Rbx::Font(_) => todo!(),
// Rbx::PhysicalProperties(_) => todo!(),
Rbx::Axes(value) => lua.create_userdata(Axes::from(value))?,
Rbx::Faces(value) => lua.create_userdata(Faces::from(value))?,
Rbx::CFrame(value) => lua.create_userdata(CFrame::from(value))?,
Rbx::OptionalCFrame(value) => match value {
Some(value) => lua.create_userdata(CFrame::from(value))?,
None => lua.create_userdata(CFrame::IDENTITY)?
},
Rbx::BrickColor(value) => lua.create_userdata(BrickColor::from(value))?,
Rbx::Color3(value) => lua.create_userdata(Color3::from(value))?,
@ -152,6 +157,15 @@ impl<'lua> RbxVariantToLua<'lua> for LuaAnyUserData<'lua> {
Rbx::Vector3(value) => lua.create_userdata(Vector3::from(value))?,
Rbx::Vector3int16(value) => lua.create_userdata(Vector3int16::from(value))?,
Rbx::OptionalCFrame(value) => match value {
Some(value) => lua.create_userdata(CFrame::from(value))?,
None => lua.create_userdata(CFrame::IDENTITY)?
},
Rbx::PhysicalProperties(rbx::PhysicalProperties::Custom(value)) => {
lua.create_userdata(PhysicalProperties::from(value))?
},
v => {
return Err(DatatypeConversionError::FromRbxVariant {
from: v.variant_name(),
@ -178,10 +192,6 @@ impl<'lua> LuaToRbxVariant<'lua> for LuaAnyUserData<'lua> {
RbxVariantType::Faces => convert::<Faces, rbx::Faces>,
RbxVariantType::CFrame => convert::<CFrame, rbx::CFrame>,
RbxVariantType::OptionalCFrame => return match self.borrow::<CFrame>() {
Ok(value) => Ok(RbxVariant::OptionalCFrame(Some(rbx::CFrame::from(*value)))),
Err(e) => Err(lua_userdata_error_to_conversion_error(variant_type, e)),
},
RbxVariantType::BrickColor => convert::<BrickColor, rbx::BrickColor>,
RbxVariantType::Color3 => convert::<Color3, rbx::Color3>,
@ -206,6 +216,20 @@ impl<'lua> LuaToRbxVariant<'lua> for LuaAnyUserData<'lua> {
RbxVariantType::Vector3 => convert::<Vector3, rbx::Vector3>,
RbxVariantType::Vector3int16 => convert::<Vector3int16, rbx::Vector3int16>,
RbxVariantType::OptionalCFrame => return match self.borrow::<CFrame>() {
Ok(value) => Ok(RbxVariant::OptionalCFrame(Some(rbx::CFrame::from(*value)))),
Err(e) => Err(lua_userdata_error_to_conversion_error(variant_type, e)),
},
RbxVariantType::PhysicalProperties => return match self.borrow::<PhysicalProperties>() {
Ok(value) => {
let props = rbx::CustomPhysicalProperties::from(*value);
let custom = rbx::PhysicalProperties::Custom(props);
Ok(RbxVariant::PhysicalProperties(custom))
},
Err(e) => Err(lua_userdata_error_to_conversion_error(variant_type, e)),
},
_ => return Err(DatatypeConversionError::ToRbxVariant {
to: variant_type.variant_name(),
from: "userdata",

View file

@ -11,6 +11,7 @@ mod faces;
mod number_range;
mod number_sequence;
mod number_sequence_keypoint;
mod physical_properties;
mod ray;
mod rect;
mod region3;
@ -32,6 +33,7 @@ pub use faces::Faces;
pub use number_range::NumberRange;
pub use number_sequence::NumberSequence;
pub use number_sequence_keypoint::NumberSequenceKeypoint;
pub use physical_properties::PhysicalProperties;
pub use r#enum::Enum;
pub use r#enum_item::EnumItem;
pub use r#enums::Enums;
@ -99,6 +101,7 @@ mod tests {
number_range: "datatypes/NumberRange",
number_sequence: "datatypes/NumberSequence",
number_sequence_keypoint: "datatypes/NumberSequenceKeypoint",
physical_properties: "datatypes/PhysicalProperties",
ray: "datatypes/Ray",
rect: "datatypes/Rect",
udim: "datatypes/UDim",

View file

@ -0,0 +1,183 @@
use core::fmt;
use mlua::prelude::*;
use rbx_dom_weak::types::CustomPhysicalProperties as RbxCustomPhysicalProperties;
use super::{super::*, EnumItem};
/**
An implementation of the [PhysicalProperties](https://create.roblox.com/docs/reference/engine/datatypes/PhysicalProperties) Roblox datatype.
This implements all documented properties, methods & constructors of the PhysicalProperties class as of March 2023.
*/
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PhysicalProperties {
pub(crate) density: f32,
pub(crate) friction: f32,
pub(crate) friction_weight: f32,
pub(crate) elasticity: f32,
pub(crate) elasticity_weight: f32,
}
impl PhysicalProperties {
pub(crate) fn from_material(material_enum_item: &EnumItem) -> Option<PhysicalProperties> {
MATERIAL_ENUM_MAP
.iter()
.find(|props| props.0 == material_enum_item.name)
.map(|props| PhysicalProperties {
density: props.1,
friction: props.2,
elasticity: props.3,
friction_weight: props.4,
elasticity_weight: props.5,
})
}
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
type ArgsMaterial = EnumItem;
type ArgsNumbers = (f32, f32, f32, Option<f32>, Option<f32>);
datatype_table.set(
"new",
lua.create_function(|lua, args: LuaMultiValue| {
if let Ok(value) = ArgsMaterial::from_lua_multi(args.clone(), lua) {
if value.parent.desc.name == "Material" {
match PhysicalProperties::from_material(&value) {
Some(props) => Ok(props),
None => Err(LuaError::RuntimeError(format!(
"Found unknown Material '{}'",
value.name
))),
}
} else {
Err(LuaError::RuntimeError(format!(
"Expected argument #1 to be a Material, got {}",
value.parent.desc.name
)))
}
} else if let Ok((
density,
friction,
elasticity,
friction_weight,
elasticity_weight,
)) = ArgsNumbers::from_lua_multi(args, lua)
{
Ok(PhysicalProperties {
density,
friction,
friction_weight: friction_weight.unwrap_or(1.0),
elasticity,
elasticity_weight: elasticity_weight.unwrap_or(1.0),
})
} else {
// FUTURE: Better error message here using given arg types
Err(LuaError::RuntimeError(
"Invalid arguments to constructor".to_string(),
))
}
})?,
)
}
}
impl LuaUserData for PhysicalProperties {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("Density", |_, this| Ok(this.density));
fields.add_field_method_get("Friction", |_, this| Ok(this.friction));
fields.add_field_method_get("FrictionWeight", |_, this| Ok(this.friction_weight));
fields.add_field_method_get("Elasticity", |_, this| Ok(this.elasticity));
fields.add_field_method_get("ElasticityWeight", |_, this| Ok(this.elasticity_weight));
}
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
}
}
impl fmt::Display for PhysicalProperties {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}, {}, {}, {}, {}",
self.density,
self.friction,
self.elasticity,
self.friction_weight,
self.elasticity_weight
)
}
}
impl From<RbxCustomPhysicalProperties> for PhysicalProperties {
fn from(v: RbxCustomPhysicalProperties) -> Self {
Self {
density: v.density,
friction: v.friction,
friction_weight: v.friction_weight,
elasticity: v.elasticity,
elasticity_weight: v.elasticity_weight,
}
}
}
impl From<PhysicalProperties> for RbxCustomPhysicalProperties {
fn from(v: PhysicalProperties) -> Self {
RbxCustomPhysicalProperties {
density: v.density,
friction: v.friction,
friction_weight: v.friction_weight,
elasticity: v.elasticity,
elasticity_weight: v.elasticity_weight,
}
}
}
/*
NOTE: The material definitions below are generated using the
physical_properties_enum_map script in the scripts dir next
to src, which can be ran in the Roblox Studio command bar
*/
#[rustfmt::skip]
const MATERIAL_ENUM_MAP: &[(&str, f32, f32, f32, f32, f32)] = &[
("Plastic", 0.70, 0.30, 0.50, 1.00, 1.00),
("Wood", 0.35, 0.48, 0.20, 1.00, 1.00),
("Slate", 2.69, 0.40, 0.20, 1.00, 1.00),
("Concrete", 2.40, 0.70, 0.20, 0.30, 1.00),
("CorrodedMetal", 7.85, 0.70, 0.20, 1.00, 1.00),
("DiamondPlate", 7.85, 0.35, 0.25, 1.00, 1.00),
("Foil", 2.70, 0.40, 0.25, 1.00, 1.00),
("Grass", 0.90, 0.40, 0.10, 1.00, 1.50),
("Ice", 0.92, 0.02, 0.15, 3.00, 1.00),
("Marble", 2.56, 0.20, 0.17, 1.00, 1.00),
("Granite", 2.69, 0.40, 0.20, 1.00, 1.00),
("Brick", 1.92, 0.80, 0.15, 0.30, 1.00),
("Pebble", 2.40, 0.40, 0.17, 1.00, 1.50),
("Sand", 1.60, 0.50, 0.05, 5.00, 2.50),
("Fabric", 0.70, 0.35, 0.05, 1.00, 1.00),
("SmoothPlastic", 0.70, 0.20, 0.50, 1.00, 1.00),
("Metal", 7.85, 0.40, 0.25, 1.00, 1.00),
("WoodPlanks", 0.35, 0.48, 0.20, 1.00, 1.00),
("Cobblestone", 2.69, 0.50, 0.17, 1.00, 1.00),
("Air", 0.01, 0.01, 0.01, 1.00, 1.00),
("Water", 1.00, 0.00, 0.01, 1.00, 1.00),
("Rock", 2.69, 0.50, 0.17, 1.00, 1.00),
("Glacier", 0.92, 0.05, 0.15, 2.00, 1.00),
("Snow", 0.90, 0.30, 0.03, 3.00, 4.00),
("Sandstone", 2.69, 0.50, 0.15, 5.00, 1.00),
("Mud", 0.90, 0.30, 0.07, 3.00, 4.00),
("Basalt", 2.69, 0.70, 0.15, 0.30, 1.00),
("Ground", 0.90, 0.45, 0.10, 1.00, 1.00),
("CrackedLava", 2.69, 0.65, 0.15, 1.00, 1.00),
("Neon", 0.70, 0.30, 0.20, 1.00, 1.00),
("Glass", 2.40, 0.25, 0.20, 1.00, 1.00),
("Asphalt", 2.36, 0.80, 0.20, 0.30, 1.00),
("LeafyGrass", 0.90, 0.40, 0.10, 2.00, 2.00),
("Salt", 2.16, 0.50, 0.05, 1.00, 1.00),
("Limestone", 2.69, 0.50, 0.15, 1.00, 1.00),
("Pavement", 2.69, 0.50, 0.17, 0.30, 1.00),
("ForceField", 2.40, 0.25, 0.20, 1.00, 1.00),
];

View file

@ -29,6 +29,7 @@ fn make_all_datatypes(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaValue)>> {
("NumberRange", make_dt(lua, NumberRange::make_table)?),
("NumberSequence", make_dt(lua, NumberSequence::make_table)?),
("NumberSequenceKeypoint", make_dt(lua, NumberSequenceKeypoint::make_table)?),
("PhysicalProperties", make_dt(lua, PhysicalProperties::make_table)?),
("Ray", make_dt(lua, Ray::make_table)?),
("Rect", make_dt(lua, Rect::make_table)?),
("UDim", make_dt(lua, UDim::make_table)?),

View file

@ -0,0 +1,65 @@
-- HACK: Make luau happy, with the mlua rust
-- crate all globals are also present in _G
local PhysicalProperties = _G.PhysicalProperties
local Enum = _G.Enum
-- Constructors & properties
PhysicalProperties.new(Enum.Material.SmoothPlastic)
PhysicalProperties.new(0, 0, 0)
PhysicalProperties.new(0, 0, 0, 0, 0)
assert(not pcall(function()
return PhysicalProperties.new()
end))
assert(not pcall(function()
return PhysicalProperties.new(false)
end))
assert(not pcall(function()
return PhysicalProperties.new({})
end))
assert(not pcall(function()
return PhysicalProperties.new(newproxy(true))
end))
assert(not pcall(function()
return PhysicalProperties.new(Enum.Axis.X)
end))
assert(PhysicalProperties.new(1, 2, 3).FrictionWeight == 1)
assert(PhysicalProperties.new(1, 2, 3).ElasticityWeight == 1)
assert(PhysicalProperties.new(1, 2, 3, 4, 5).Density == 1)
assert(PhysicalProperties.new(1, 2, 3, 4, 5).Friction == 2)
assert(PhysicalProperties.new(1, 2, 3, 4, 5).Elasticity == 3)
assert(PhysicalProperties.new(1, 2, 3, 4, 5).FrictionWeight == 4)
assert(PhysicalProperties.new(1, 2, 3, 4, 5).ElasticityWeight == 5)
local function fuzzyEq(n0: number, n1: number)
return math.abs(n1 - n0) <= 0.0001
end
local plastic = PhysicalProperties.new(Enum.Material.Plastic)
assert(fuzzyEq(plastic.Density, 0.7))
assert(fuzzyEq(plastic.Friction, 0.3))
assert(fuzzyEq(plastic.Elasticity, 0.5))
local splastic = PhysicalProperties.new(Enum.Material.SmoothPlastic)
assert(fuzzyEq(splastic.Density, 0.7))
assert(fuzzyEq(splastic.Friction, 0.2))
assert(fuzzyEq(splastic.Elasticity, 0.5))
local sand = PhysicalProperties.new(Enum.Material.Sand)
assert(fuzzyEq(sand.Density, 1.6))
assert(fuzzyEq(sand.Friction, 0.5))
assert(fuzzyEq(sand.Elasticity, 0.05))
assert(fuzzyEq(sand.FrictionWeight, 5))
assert(fuzzyEq(sand.ElasticityWeight, 2.5))
-- Ops
assert(not pcall(function()
return PhysicalProperties.new() + PhysicalProperties.new()
end))
assert(not pcall(function()
return PhysicalProperties.new() / PhysicalProperties.new()
end))