From e5599e1d275aab13c3a4279d43ec7bd470a48933 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 13 Jan 2024 11:52:20 -0500 Subject: [PATCH] scripting, ecs: expose Transform to lua, update component ticks on changes from lua --- examples/testbed/scripts/test.lua | 17 +- examples/testbed/src/main.rs | 32 +-- lyra-ecs/src/archetype.rs | 24 +- lyra-ecs/src/world.rs | 10 + lyra-scripting/Cargo.toml | 1 + .../lyra-scripting-derive/src/lib.rs | 27 ++- lyra-scripting/src/lua/dynamic_iter.rs | 58 ++++- lyra-scripting/src/lua/providers/math.rs | 4 +- lyra-scripting/src/lua/world.rs | 38 ++- lyra-scripting/src/lua/wrappers/mod.rs | 221 +++++++++++++++++- 10 files changed, 374 insertions(+), 58 deletions(-) diff --git a/examples/testbed/scripts/test.lua b/examples/testbed/scripts/test.lua index de8a543..bd2bd06 100644 --- a/examples/testbed/scripts/test.lua +++ b/examples/testbed/scripts/test.lua @@ -1,6 +1,6 @@ print("Hello World") -function on_init() +--[[ function on_init() print("Lua script was initialized!") end @@ -10,16 +10,23 @@ end function on_pre_update() print("Lua's pre-update function was called") -end +end ]] function on_update() - print("Lua's update function was called") + --print("Lua's update function was called") + + world:view(function (t) + print("Found entity at " .. tostring(t)) + t.translation = t.translation + Vec3.new(0, 0.0008, 0) + + return t + end, Transform) end -function on_post_update() +--[[ function on_post_update() print("Lua's post-update function was called") end function on_last() print("Lua's last function was called") -end \ No newline at end of file +end ]] \ No newline at end of file diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index a06463f..66ecc00 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -1,6 +1,6 @@ use std::ptr::NonNull; -use lyra_engine::{math::{self, Vec3}, math::Transform, input::{KeyCode, ActionHandler, Action, ActionKind, LayoutId, ActionMapping, ActionSource, ActionMappingId, InputActionPlugin, MouseInput, MouseAxis, CommonActionLabel}, game::Game, render::{window::{CursorGrabMode, WindowOptions}, light::{PointLight, directional::DirectionalLight, SpotLight}}, change_tracker::Ct, ecs::{system::{Criteria, CriteriaSchedule, BatchedSystem, IntoSystem}, world::World, Component}, DeltaTime, scene::{TransformComponent, ModelComponent, CameraComponent}, lua::{LuaScriptingPlugin, LuaScript}, Script, ScriptList}; +use lyra_engine::{math::{self, Vec3}, math::Transform, input::{KeyCode, ActionHandler, Action, ActionKind, LayoutId, ActionMapping, ActionSource, ActionMappingId, InputActionPlugin, MouseInput, MouseAxis, CommonActionLabel}, game::Game, render::{window::{CursorGrabMode, WindowOptions}, light::{PointLight, directional::DirectionalLight, SpotLight}}, change_tracker::Ct, ecs::{system::{Criteria, CriteriaSchedule, BatchedSystem, IntoSystem}, world::World, Component}, DeltaTime, scene::{ModelComponent, CameraComponent}, lua::{LuaScriptingPlugin, LuaScript}, Script, ScriptList}; use lyra_engine::assets::{ResourceManager, Model}; mod free_fly_camera; @@ -77,9 +77,9 @@ struct CubeFlag; async fn main() { let setup_sys = |world: &mut World| -> anyhow::Result<()> { { - let mut window_options = world.get_resource_mut::>(); + /* let mut window_options = world.get_resource_mut::>(); window_options.cursor_grab = CursorGrabMode::Confined; - window_options.cursor_visible = false; + window_options.cursor_visible = false; */ } let mut resman = world.get_resource_mut::(); @@ -92,14 +92,14 @@ async fn main() { world.spawn(( ModelComponent(antique_camera_model), - TransformComponent::from(Transform::from_xyz(0.0, -5.0, -10.0)), + Transform::from_xyz(0.0, -5.0, -10.0), )); { let cube_tran = Transform::from_xyz(-3.5, 0.0, -8.0); //cube_tran.rotate_y(math::Angle::Degrees(180.0)); world.spawn(( - TransformComponent::from(cube_tran), + cube_tran, ModelComponent(crate_model.clone()), CubeFlag, )); @@ -117,7 +117,7 @@ async fn main() { diffuse: 1.0, specular: 1.3, }, - TransformComponent::from(light_tran), + light_tran, ModelComponent(cube_model.clone()), )); } @@ -139,7 +139,7 @@ async fn main() { diffuse: 7.0, specular: 1.0, }, - TransformComponent::from(light_tran), + Transform::from(light_tran), ModelComponent(cube_model.clone()), )); } @@ -183,7 +183,7 @@ async fn main() { diffuse: 1.0, specular: 1.3, }, - TransformComponent::from(light_tran), + Transform::from(light_tran), ModelComponent(cube_model), )); } @@ -214,13 +214,13 @@ async fn main() { const SPEED: f32 = 4.0; let delta_time = **world.get_resource::(); - for (mut transform, _) in world.view_iter::<(&mut TransformComponent, &CubeFlag)>() { - let t = &mut transform.transform; + for (mut transform, _) in world.view_iter::<(&mut Transform, &CubeFlag)>() { + let t = &mut transform; t.rotate_y(math::Angle::Degrees(SPEED * delta_time)); } - for (mut transform, _s) in world.view_iter::<(&mut TransformComponent, &mut SpotLight)>() { - let t = &mut transform.transform; + for (mut transform, _s) in world.view_iter::<(&mut Transform, &mut SpotLight)>() { + let t = &mut transform; t.rotate_x(math::Angle::Degrees(SPEED * delta_time)); } @@ -235,8 +235,8 @@ async fn main() { sys.with_system(spin_system.into_system()); //sys.with_system(fps_system); - game.with_system("fixed", sys, &[]); - fps_plugin(game); + //game.with_system("fixed", sys, &[]); + //fps_plugin(game); }; let action_handler_plugin = |game: &mut Game| { @@ -293,7 +293,9 @@ async fn main() { Ok(()) }; */ - game.world().add_resource(action_handler); + let world = game.world(); + world.add_resource(action_handler); + world.spawn((Vec3::new(0.5, 0.1, 3.0),)); game.with_plugin(InputActionPlugin); //game.with_system("input_test", test_system, &[]); }; diff --git a/lyra-ecs/src/archetype.rs b/lyra-ecs/src/archetype.rs index 52e1760..f91f512 100644 --- a/lyra-ecs/src/archetype.rs +++ b/lyra-ecs/src/archetype.rs @@ -210,6 +210,8 @@ impl ArchetypeId { pub struct Archetype { pub id: ArchetypeId, pub(crate) entities: HashMap, + /// map an Archetype entity id to an entity + pub(crate) ids_to_entity: HashMap, pub(crate) columns: Vec, pub capacity: usize, } @@ -226,6 +228,7 @@ impl Archetype { Archetype { id: new_id, entities: HashMap::new(), + ids_to_entity: HashMap::new(), columns, capacity: DEFAULT_CAPACITY, } @@ -246,16 +249,17 @@ impl Archetype { self.capacity = new_cap; } - let entity_index = self.entities.len(); - self.entities.insert(entity, ArchetypeEntityId(entity_index as u64)); + let entity_index = ArchetypeEntityId(self.entities.len() as u64); + self.entities.insert(entity, entity_index); + self.ids_to_entity.insert(entity_index, entity); bundle.take(|data, type_id, _size| { let col = self.get_column_mut(type_id).unwrap(); - unsafe { col.set_at(entity_index, data, *tick); } + unsafe { col.set_at(entity_index.0 as usize, data, *tick); } col.len += 1; }); - ArchetypeEntityId(entity_index as u64) + entity_index } /// Removes an entity from the Archetype and frees its components. Returns the entity record that took its place in the component column. @@ -286,6 +290,7 @@ impl Archetype { // safe from the .expect at the start of this method. self.entities.remove(&entity).unwrap(); + self.ids_to_entity.remove(&entity_index).unwrap(); // now change the ArchetypeEntityId to be the index that the moved entity was moved into. removed_entity.map(|(e, _a)| (e, entity_index)) @@ -351,14 +356,15 @@ impl Archetype { self.capacity = new_cap; } - let entity_index = self.entities.len(); - self.entities.insert(entity, ArchetypeEntityId(entity_index as u64)); + let entity_index = ArchetypeEntityId(self.entities.len() as u64); + self.entities.insert(entity, entity_index); + self.ids_to_entity.insert(entity_index, entity); for col in self.columns.iter_mut() { col.len += 1; } - ArchetypeEntityId(entity_index as u64) + entity_index } /// Moves the entity from this archetype into another one. @@ -401,6 +407,10 @@ impl Archetype { pub fn entities(&self) -> &HashMap { &self.entities } + + pub fn entity_of_index(&self, id: ArchetypeEntityId) -> Option { + self.ids_to_entity.get(&id).cloned() + } } #[cfg(test)] diff --git a/lyra-ecs/src/world.rs b/lyra-ecs/src/world.rs index 6d2cb98..f6a3274 100644 --- a/lyra-ecs/src/world.rs +++ b/lyra-ecs/src/world.rs @@ -209,6 +209,16 @@ impl World { current_arch.remove_entity(entity, &tick); } + pub fn entity_archetype(&self, entity: Entity) -> Option<&Archetype> { + self.entity_index.get(&entity.id) + .and_then(|record| self.archetypes.get(&record.id)) + } + + pub fn entity_archetype_mut(&mut self, entity: Entity) -> Option<&mut Archetype> { + self.entity_index.get_mut(&entity.id) + .and_then(|record| self.archetypes.get_mut(&record.id)) + } + /// View into the world for a set of entities that satisfy the queries. pub fn view_iter(&self) -> ViewIter { let archetypes = self.archetypes.values().collect(); diff --git a/lyra-scripting/Cargo.toml b/lyra-scripting/Cargo.toml index 46539a6..320ac06 100644 --- a/lyra-scripting/Cargo.toml +++ b/lyra-scripting/Cargo.toml @@ -21,6 +21,7 @@ tracing = "0.1.37" # enabled with lua feature mlua = { version = "0.9.2", features = ["lua54"], optional = true } # luajit maybe? +itertools = "0.12.0" [dev-dependencies] diff --git a/lyra-scripting/lyra-scripting-derive/src/lib.rs b/lyra-scripting/lyra-scripting-derive/src/lib.rs index a0f40c1..ed6e7b1 100644 --- a/lyra-scripting/lyra-scripting-derive/src/lib.rs +++ b/lyra-scripting/lyra-scripting-derive/src/lib.rs @@ -220,6 +220,9 @@ pub(crate) struct WrapUsage { pub matrix: Option, pub vec: Option, + + pub custom_methods: Option, + pub custom_fields: Option, } impl syn::parse::Parse for WrapUsage { @@ -234,6 +237,8 @@ impl syn::parse::Parse for WrapUsage { meta_method_idents: Punctuated::default(), matrix: None, vec: None, + custom_methods: None, + custom_fields: None, }; /* let mut derive_idents = None; let mut field_idents = None; @@ -298,6 +303,14 @@ impl syn::parse::Parse for WrapUsage { s.meta_method_idents = meta_methods; } }, + "custom_methods" => { + let methods_block = input.parse()?; + s.custom_methods = Some(methods_block); + }, + "custom_fields" => { + let block = input.parse()?; + s.custom_fields = Some(block); + } _ => { return Err(syn::Error::new_spanned(ident, "unknown wrapper command")); } @@ -335,6 +348,8 @@ pub fn wrap_math_vec_copy(input: proc_macro::TokenStream) -> proc_macro::TokenSt vec.to_method_tokens(&path, &wrapper_typename)); let derive_idents_iter = input.derive_idents.iter(); + let custom_methods = input.custom_methods; + let custom_fields = input.custom_fields; let field_get_set_pairs = input.field_idents.iter().map(|i| { let is = i.to_string(); @@ -566,7 +581,7 @@ pub fn wrap_math_vec_copy(input: proc_macro::TokenStream) -> proc_macro::TokenSt fn from_lua(value: mlua::Value<'lua>, _lua: &'lua mlua::Lua) -> mlua::Result { match value { mlua::Value::UserData(ud) => Ok(*ud.borrow::()?), - _ => unreachable!(), + _ => panic!("Attempt to get {} from a {} value", stringify!(#wrapper_typename), value.type_name()), } } } @@ -577,6 +592,8 @@ pub fn wrap_math_vec_copy(input: proc_macro::TokenStream) -> proc_macro::TokenSt #matrix_wrapper_fields #vec_wrapper_fields + + #custom_fields } fn add_methods<'lua, M: mlua::prelude::LuaUserDataMethods<'lua, Self>>(methods: &mut M) { @@ -591,15 +608,19 @@ pub fn wrap_math_vec_copy(input: proc_macro::TokenStream) -> proc_macro::TokenSt }); #meta_method_idents - + #matrix_wrapper_methods #vec_wrapper_methods + + #custom_methods } } impl lyra_scripting::lua::LuaWrapper for #wrapper_typename { fn wrapped_type_id() -> std::any::TypeId { - std::any::TypeId::of::<#path>() + let t = std::any::TypeId::of::<#path>(); + println!("Got id of {}, it is {:?}", stringify!(#path), t); + t } } }) diff --git a/lyra-scripting/src/lua/dynamic_iter.rs b/lyra-scripting/src/lua/dynamic_iter.rs index 4e6e2cc..ad833b6 100644 --- a/lyra-scripting/src/lua/dynamic_iter.rs +++ b/lyra-scripting/src/lua/dynamic_iter.rs @@ -1,6 +1,6 @@ use std::{ptr::NonNull, ops::{Range, Deref}}; -use lyra_ecs::{ComponentColumn, ComponentInfo, Archetype, ArchetypeId, ArchetypeEntityId, query::dynamic::{DynamicType, QueryDynamicType}, query::Fetch}; +use lyra_ecs::{ComponentColumn, ComponentInfo, Archetype, ArchetypeId, ArchetypeEntityId, query::dynamic::{DynamicType, QueryDynamicType}, query::Fetch, Entity}; use lyra_reflect::TypeRegistry; #[cfg(feature = "lua")] @@ -45,6 +45,11 @@ impl<'a> From> for FetchDynamicTy } } +pub struct DynamicViewRow { + entity: Entity, + item: Vec, +} + #[derive(Clone)] pub struct DynamicViewIter { world_ptr: ScriptWorldPtr, @@ -69,15 +74,15 @@ impl<'a> From> for DynamicViewIter } impl Iterator for DynamicViewIter { - type Item = Vec; + type Item = DynamicViewRow; fn next(&mut self) -> Option { loop { if let Some(entity_index) = self.component_indices.next() { let mut fetch_res = vec![]; + let entity_index = ArchetypeEntityId(entity_index); for fetcher in self.fetchers.iter_mut() { - let entity_index = ArchetypeEntityId(entity_index); if !fetcher.can_visit_item(entity_index) { break; } else { @@ -90,7 +95,14 @@ impl Iterator for DynamicViewIter { continue; } - return Some(fetch_res); + let arch = unsafe { self.archetypes.get_unchecked(self.next_archetype - 1).as_ref() }; + let entity = arch.entity_of_index(entity_index).unwrap(); + let row = DynamicViewRow { + entity, + item: fetch_res, + }; + + return Some(row); } else { if self.next_archetype >= self.archetypes.len() { return None; // ran out of archetypes to go through @@ -121,6 +133,19 @@ impl Iterator for DynamicViewIter { } } +#[cfg(feature = "lua")] +pub struct ReflectedItem<'a> { + pub proxy: &'a ReflectLuaProxy, + pub comp_ptr: NonNull, + pub comp_ud: mlua::AnyUserData<'a>, +} + +#[cfg(feature = "lua")] +pub struct ReflectedRow<'a> { + pub entity: Entity, + pub row: Vec>, +} + pub struct ReflectedIterator { pub world: ScriptWorldPtr, pub dyn_view: DynamicViewIter, @@ -131,7 +156,7 @@ impl ReflectedIterator { #[cfg(feature = "lua")] - pub fn next_lua<'a>(&mut self, lua: &'a mlua::Lua) -> Option), mlua::AnyUserData<'a>)>> { + pub fn next_lua<'a>(&mut self, lua: &'a mlua::Lua) -> Option> { let n = self.dyn_view.next(); @@ -142,8 +167,13 @@ impl ReflectedIterator { .map(|r| NonNull::from(r.deref())); } - let mut dynamic_row = Vec::new(); - for d in row.iter() { + /* let mut row = ReflectedRow { + entity: row.entity, + row: row.item, + }; */ + + let mut dynamic_row = vec![]; + for d in row.item.iter() { let id = d.info.type_id.as_rust(); let reflected_components = unsafe { self.reflected_components.as_ref().unwrap().as_ref() }; @@ -155,10 +185,20 @@ impl ReflectedIterator { let userdata = (proxy.fn_as_uservalue)(lua, d.ptr).unwrap(); - dynamic_row.push(( (proxy, d.ptr), userdata)); + dynamic_row.push(ReflectedItem { + proxy, + comp_ptr: d.ptr, + comp_ud: userdata + }); + //dynamic_row.push(( (proxy, d.ptr), userdata)); } - Some(dynamic_row) + let row = ReflectedRow { + entity: row.entity, + row: dynamic_row + }; + + Some(row) } else { None } diff --git a/lyra-scripting/src/lua/providers/math.rs b/lyra-scripting/src/lua/providers/math.rs index a4be794..d7d37cd 100644 --- a/lyra-scripting/src/lua/providers/math.rs +++ b/lyra-scripting/src/lua/providers/math.rs @@ -1,7 +1,7 @@ use lyra_ecs::World; use lyra_game::math; use crate::lua::RegisterLuaType; -use crate::lua::wrappers::LuaVec3; +use crate::lua::wrappers::{LuaVec3, LuaTransform}; use crate::{ScriptApiProvider, lua::LuaContext}; @@ -13,6 +13,7 @@ impl ScriptApiProvider for LyraMathApiProvider { fn prepare_world(&mut self, world: &mut World) { world.register_lua_wrapper::(); + world.register_lua_wrapper::(); } fn expose_api(&mut self, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { @@ -29,6 +30,7 @@ impl ScriptApiProvider for LyraMathApiProvider { let globals = ctx.globals(); globals.set("Vec3", ctx.create_proxy::()?)?; + globals.set("Transform", ctx.create_proxy::()?)?; //globals.set("Vec3", LuaVec3(math::Vec3::ZERO).into_lua(&ctx)?)?; Ok(()) diff --git a/lyra-scripting/src/lua/world.rs b/lyra-scripting/src/lua/world.rs index 7011d21..6c1cfbb 100644 --- a/lyra-scripting/src/lua/world.rs +++ b/lyra-scripting/src/lua/world.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{sync::Arc, ptr::NonNull}; use lyra_ecs::query::dynamic::QueryDynamicType; use lyra_reflect::TypeRegistry; @@ -62,7 +62,7 @@ impl mlua::UserData for ScriptWorldPtr { let f = lua.create_function_mut(move |lua, ()| { if let Some(row) = reflected_iter.next_lua(lua) { - let row = row.into_iter().map(|(_, ud)| ud.into_lua(lua)) + let row = row.row.into_iter().map(|i| i.comp_ud.into_lua(lua)) .collect::>>()?; Ok(mlua::MultiValue::from_vec(row)) } else { @@ -73,7 +73,7 @@ impl mlua::UserData for ScriptWorldPtr { Ok(f) }); - methods.add_method("view", |lua, this, (system, queries): (mlua::Function, mlua::Variadic)| { + methods.add_method_mut("view", |lua, this, (system, queries): (mlua::Function, mlua::Variadic)| { if queries.is_empty() { return Err(mlua::Error::BadArgument { to: Some("world:view".to_string()), pos: 2, name: Some("...".to_string()), cause: Arc::new(mlua::Error::external(WorldError::LuaInvalidUsage("no component types provided".to_string()))) @@ -99,29 +99,51 @@ impl mlua::UserData for ScriptWorldPtr { reflected_components: None, }; - let reg = this.as_ref().get_resource::(); + let mut current = world.current_tick(); + let mut has_ticked = false; while let Some(row) = reflected_iter.next_lua(lua) { - let (reflects, values): (Vec<(_, _)>, Vec<_>) = row.into_iter().unzip(); + let r = row.row.into_iter() + .map(|r| (r.proxy, r.comp_ud, r.comp_ptr)) + .collect::>(); + let (reflects, values, ptrs): + (Vec<&ReflectLuaProxy>, Vec, Vec>) + = itertools::multiunzip(r); let value_row: Vec<_> = values.into_iter().map(|ud| ud.into_lua(lua)).collect::>>()?; let mult_val = mlua::MultiValue::from_vec(value_row); let res: mlua::MultiValue = system.call(mult_val)?; // if values were returned, find the type in the type registry, and apply the new values if res.len() <= reflects.len() { + // we only want to tick one time per system + if !has_ticked { + current = world.tick(); + has_ticked = true; + } + for (i, comp) in res.into_iter().enumerate() { - let (_proxy, ptr) = reflects[i]; + let ptr = ptrs[i]; match comp.as_userdata() { Some(ud) => { let lua_comp = reflect_user_data(ud); let refl_comp = lua_comp.reflect_branch.as_component_unchecked(); let lua_typeid = refl_comp.info.type_id.as_rust(); - let reg_type = reg.get_type(lua_typeid).unwrap(); + // update the component tick + let world = unsafe { this.inner.as_mut() }; + let arch = world.entity_archetype_mut(row.entity).unwrap(); + let idx = arch.entities().get(&row.entity).unwrap().clone(); + let c = arch.get_column_mut(refl_comp.type_id.into()).unwrap(); + c.entity_ticks[idx.0 as usize] = current; + + // apply the new component data + let reg = this.as_ref().get_resource::(); + let reg_type = reg.get_type(lua_typeid).unwrap(); + let proxy = reg_type.get_data::().unwrap(); - (proxy.fn_apply)(lua, ptr, ud)? + (proxy.fn_apply)(lua, ptr, ud)?; } None => { panic!("A userdata value was not returned!"); diff --git a/lyra-scripting/src/lua/wrappers/mod.rs b/lyra-scripting/src/lua/wrappers/mod.rs index 1ff5e93..22a2317 100644 --- a/lyra-scripting/src/lua/wrappers/mod.rs +++ b/lyra-scripting/src/lua/wrappers/mod.rs @@ -5,7 +5,7 @@ use crate::lyra_engine; use crate as lyra_scripting; // f32 types -/* wrap_math_vec_copy!( +wrap_math_vec_copy!( math::Vec2, derives(PartialEq), fields(x, y), @@ -17,23 +17,27 @@ use crate as lyra_scripting; Mod(LuaVec2, f32), Eq, Unm ) -); */ +); wrap_math_vec_copy!( math::Vec3, derives(PartialEq), fields(x, y, z), metamethods( - ToString, - Eq, Unm - ) - /* metamethods( - Add(LuaVec3, f32), + Add(LuaVec3), Sub(LuaVec3, f32), Div(LuaVec3, f32), Mul(LuaVec3, f32), Mod(LuaVec3, f32), - Eq, Unm - ) */ + Eq, Unm, ToString, + ), + custom_methods { + /* methods.add_meta_method(mlua::MetaMethod::Add, |_, this, (trans,): (LuaTransform,)| { + println!("Adding"); + let mut t = *trans; + t.translation += **this; + Ok(LuaTransform(t)) + }); */ + } ); /* wrap_math_vec_copy!( math::Vec3A, @@ -362,4 +366,201 @@ wrap_math_vec_copy!( Mul(LuaMat2, f32), Unm ) -); */ \ No newline at end of file +); */ + +/* wrap_math_vec_copy!( + math::Mat4, + derives(PartialEq), + no_new, + matrix { + col_type = LuaVec4 + }, + metamethods( + Eq, + Add, + Sub, + Mul(LuaMat4, f32), + Unm + ) +); */ + +wrap_math_vec_copy!( + math::Quat, + derives(PartialEq), + no_new, + metamethods( + Eq, + // __mul for LuaVec3 is manually implemented below since it doesn't return Self + Mul(LuaQuat, f32), + Add, + Sub, + Div(f32), + ), + custom_methods { + methods.add_function("new", |_, (x, y, z, w)| { + Ok(Self(math::Quat::from_xyzw(x, y, z, w))) + }); + + methods.add_function("from_rotation_x", |_, (rad,)| { + let q = math::Quat::from_rotation_x(rad); + Ok(Self(q)) + }); + + methods.add_function("from_rotation_y", |_, (rad,)| { + let q = math::Quat::from_rotation_y(rad); + Ok(Self(q)) + }); + + methods.add_function("from_rotation_z", |_, (rad,)| { + let q = math::Quat::from_rotation_z(rad); + Ok(Self(q)) + }); + + methods.add_method("dot", |_, this, (rhs,): (Self,)| { + Ok(this.dot(rhs.0)) + }); + + methods.add_method("length", |_, this, ()| { + Ok(this.length()) + }); + + methods.add_method("length_squared", |_, this, ()| { + Ok(this.length_squared()) + }); + + methods.add_method("normalize", |_, this, ()| { + Ok(Self(this.normalize())) + }); + + methods.add_method("mult_quat", |_, this, (rhs,): (Self,)| { + Ok(Self(this.0 * rhs.0)) + }); + + methods.add_method("mult_vec3", |_, this, (rhs,): (LuaVec3,)| { + Ok(LuaVec3(this.0 * rhs.0)) + }); + + // manually implemented here since it doesn't return `Self` + methods.add_meta_method(mlua::MetaMethod::Mul, |_, this, (rhs,): (LuaVec3,)| { + Ok(LuaVec3(this.0 * rhs.0)) + }); + + methods.add_method("lerp", |_, this, (rhs, alpha): (Self, f32)| { + Ok(Self(this.lerp(*rhs, alpha))) + }); + } +); + +wrap_math_vec_copy!( + math::Transform, + //derives(PartialEq), + no_new, + metamethods(ToString, Eq), + custom_fields { + fields.add_field_method_get("translation", |_, this| { + Ok(LuaVec3(this.translation)) + }); + fields.add_field_method_set("translation", |_, this, v: LuaVec3| { + this.translation = *v; + Ok(()) + }); + + fields.add_field_method_get("rotation", |_, this| { + Ok(LuaQuat(this.rotation)) + }); + fields.add_field_method_set("rotation", |_, this, v: LuaQuat| { + this.rotation = *v; + Ok(()) + }); + + fields.add_field_method_get("scale", |_, this| { + Ok(LuaVec3(this.scale)) + }); + fields.add_field_method_set("scale", |_, this, v: LuaVec3| { + this.scale = *v; + Ok(()) + }); + }, + custom_methods { + methods.add_function("default", |_, ()| { + Ok(Self(math::Transform::default())) + }); + + methods.add_function("new", |_, (pos, rot, scale): (LuaVec3, LuaQuat, LuaVec3)| { + Ok(Self(math::Transform::new(*pos, *rot, *scale))) + }); + + methods.add_function("from_translation", |_, (pos,): (LuaVec3,)| { + Ok(Self(math::Transform::from_translation(*pos))) + }); + + methods.add_function("from_xyz", |_, (x, y, z)| { + Ok(Self(math::Transform::from_xyz(x, y, z))) + }); + + methods.add_method("forward", |_, this, ()| { + Ok(LuaVec3(this.forward())) + }); + + methods.add_method("left", |_, this, ()| { + Ok(LuaVec3(this.left())) + }); + + methods.add_method("up", |_, this, ()| { + Ok(LuaVec3(this.up())) + }); + + methods.add_method_mut("rotate", |_, this, (quat,): (LuaQuat,)| { + this.rotate(*quat); + Ok(()) + }); + + methods.add_method_mut("rotate_x", |_, this, (deg,): (f32,)| { + this.rotate_x(math::Angle::Degrees(deg)); + Ok(()) + }); + + methods.add_method_mut("rotate_y", |_, this, (deg,): (f32,)| { + this.rotate_y(math::Angle::Degrees(deg)); + Ok(()) + }); + + methods.add_method_mut("rotate_z", |_, this, (deg,): (f32,)| { + this.rotate_z(math::Angle::Degrees(deg)); + Ok(()) + }); + + methods.add_method_mut("rotate_x_rad", |_, this, (rad,): (f32,)| { + this.rotate_x(math::Angle::Radians(rad)); + Ok(()) + }); + + methods.add_method_mut("rotate_y_rad", |_, this, (rad,): (f32,)| { + this.rotate_y(math::Angle::Radians(rad)); + Ok(()) + }); + + methods.add_method_mut("rotate_z_rad", |_, this, (rad,): (f32,)| { + this.rotate_z(math::Angle::Radians(rad)); + Ok(()) + }); + + methods.add_method("lerp", |_, this, (rhs, alpha): (Self, f32)| { + Ok(Self(this.lerp(*rhs, alpha))) + }); + + // rotate a transform + methods.add_meta_method(mlua::MetaMethod::Mul, |_, this, (quat,): (LuaQuat,)| { + let mut t = *this; + t.rotation *= *quat; + Ok(t) + }); + + // move a transform + methods.add_meta_method(mlua::MetaMethod::Add, |_, this, (pos,): (LuaVec3,)| { + let mut t = *this; + t.translation += *pos; + Ok(t) + }); + } +); \ No newline at end of file