kitgit

tirbofish/dropbear · commit

160b23ff5cec41fd4777aee71023784eb0b3d625

wip: got asset working (sorta), everything else is broken

Unverified

Thribhu K <4tkbytes@pm.me> · 2026-02-14 15:14

view full diff

diff --git a/crates/dropbear-engine/src/asset.rs b/crates/dropbear-engine/src/asset.rs
index fc1eb9f..acdc1f1 100644
--- a/crates/dropbear-engine/src/asset.rs
+++ b/crates/dropbear-engine/src/asset.rs
@@ -12,12 +12,23 @@ use crate::model::Model;
 pub static ASSET_REGISTRY: LazyLock<Arc<RwLock<AssetRegistry>>> = LazyLock::new(|| Arc::new(RwLock::new(AssetRegistry::new())));
 
 /// A handle with type [`T`] that provides an index to the [AssetRegistry] contents.
-#[derive(Hash, Eq, PartialEq, Debug)]
+#[derive(Hash, Eq, Debug)]
 pub struct Handle<T> {
     pub id: u64,
     _phantom: PhantomData<T>
 }
 
+impl<T> PartialEq for Handle<T> {
+    fn eq(&self, other: &Self) -> bool {
+        if self.is_null() && other.is_null() {
+            return false;
+        }
+        self.id == other.id
+    }
+
+
+}
+
 impl<T> Copy for Handle<T> {}
 
 impl<T> Clone for Handle<T> {
@@ -30,7 +41,7 @@ impl<T> Handle<T> {
     /// Creates a null handle, for when there is no way to uniquely identify a hash (such as a viewport texture).
     ///
     /// # Safety
-    /// You will want to watch out, as adding this onto the asset_old registry with a type
+    /// You will want to watch out, as adding this onto the asset registry with a type
     /// where there already is a null handle item, it will be overwritten and data
     /// will not be saved. It is the reason why you will want to consider using the [Self::is_null]
     /// function to verify if the storage of the type has gone through correctly.
@@ -54,6 +65,14 @@ pub struct AssetRegistry {
     models: HashMap<u64, Model>,
     model_labels: HashMap<String, Handle<Model>>,
 }
+
+#[derive(Debug, Clone)]
+#[dropbear_macro::repr_c_enum]
+pub enum AssetKind {
+    Texture,
+    Model,
+}
+
 /// Common
 impl AssetRegistry {
     pub fn new() -> Self {
@@ -79,14 +98,14 @@ impl AssetRegistry {
         hasher.finish()
     }
 
-    /// Checks if the asset_old registry contains a handle with the given hash.
+    /// Checks if the asset registry contains a handle with the given hash.
     ///
     /// It will check all different types, so it does not point out specifically where.
     pub fn contains_hash(&self, hash: u64) -> bool {
         self.textures.contains_key(&hash) || self.models.contains_key(&hash)
     }
 
-    /// Checks if the asset_old registry contains a handle with the given string label.
+    /// Checks if the asset registry contains a handle with the given string label.
     ///
     /// It will check all different types, so it does not point out specifically where.
     pub fn contains_label(&self, label: &str) -> bool {
@@ -126,7 +145,7 @@ impl AssetRegistry {
         self.texture_labels.remove(label);
     }
 
-    /// Updates the asset_old server by inserting the texture provided at the location of the handle,
+    /// Updates the asset server by inserting the texture provided at the location of the handle,
     /// and removing the old texture (by returning it back to you).
     pub fn update_texture(&mut self, handle: Handle<Texture>, texture: Texture) -> Option<Texture> {
         self.textures.insert(handle.id, texture)
@@ -225,6 +244,10 @@ impl AssetRegistry {
     pub fn model_handle_by_hash(&self, hash: u64) -> Option<Handle<Model>> {
         self.models.contains_key(&hash).then(|| Handle::new(hash))
     }
+
+    pub fn get_label_from_model_handle(&self, handle: Handle<Model>) -> Option<String> {
+        self.model_labels.iter().find_map(|(label, h)| if *h == handle { Some(label.clone()) } else { None })
+    }
 }
 
 impl Default for AssetRegistry {
diff --git a/crates/dropbear-engine/src/texture.rs b/crates/dropbear-engine/src/texture.rs
index 4b1dd30..76551f9 100644
--- a/crates/dropbear-engine/src/texture.rs
+++ b/crates/dropbear-engine/src/texture.rs
@@ -68,7 +68,7 @@ pub struct Texture {
     pub sampler: wgpu::Sampler,
     pub size: wgpu::Extent3d,
     pub view: wgpu::TextureView,
-    pub(crate) hash: Option<u64>,
+    pub hash: Option<u64>,
 }
 
 impl Texture {
diff --git a/crates/dropbear-engine/src/utils.rs b/crates/dropbear-engine/src/utils.rs
index 95f8531..8a5acb9 100644
--- a/crates/dropbear-engine/src/utils.rs
+++ b/crates/dropbear-engine/src/utils.rs
@@ -181,7 +181,7 @@ impl ResourceReference {
         })
     }
 
-    /// Returns the canonical euca URI for this reference if it points to a file asset_old.
+    /// Returns the canonical euca URI for this reference if it points to a file asset.
     pub fn as_uri(&self) -> Option<&str> {
         match &self.ref_type {
             ResourceReferenceType::File(reference) => Some(reference.as_str()),
diff --git a/crates/dropbear-macro/src/lib.rs b/crates/dropbear-macro/src/lib.rs
index fba3a17..3c3c5c6 100644
--- a/crates/dropbear-macro/src/lib.rs
+++ b/crates/dropbear-macro/src/lib.rs
@@ -4,7 +4,7 @@ use syn::{
     parse::{Parse, ParseStream},
     parse_macro_input, parse_quote,
     spanned::Spanned,
-    DeriveInput, FnArg, GenericArgument, Ident, Item, ItemFn, ItemMod, LitStr, PathArguments,
+    DeriveInput, FnArg, GenericArgument, Ident, Item, ItemFn, ItemMod, ItemEnum, LitStr, PathArguments,
     ReturnType, Token, Type,
 };
 
@@ -270,6 +270,210 @@ pub fn export(attr: TokenStream, item: TokenStream) -> TokenStream {
     expanded.into()
 }
 
+#[proc_macro_attribute]
+pub fn repr_c_enum(_attr: TokenStream, item: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(item as ItemEnum);
+    let enum_ident = input.ident.clone();
+    let enum_name = enum_ident.to_string();
+    let mod_ident = Ident::new(&format!("{}_ffi", to_snake_case(&enum_name)), enum_ident.span());
+    let tag_ident = Ident::new(&format!("{}Tag", enum_name), enum_ident.span());
+    let data_ident = Ident::new(&format!("{}Data", enum_name), enum_ident.span());
+    let ffi_ident = Ident::new(&format!("{}Ffi", enum_name), enum_ident.span());
+
+    let mut tag_variants = Vec::new();
+    let mut variant_structs = Vec::new();
+    let mut data_union_fields = Vec::new();
+    let mut match_arms = Vec::new();
+    let mut array_structs = std::collections::BTreeMap::new();
+
+    for (index, variant) in input.variants.iter().enumerate() {
+        let variant_ident = &variant.ident;
+        let variant_name = variant_ident.to_string();
+        let variant_struct_ident = Ident::new(&format!("{}{}", enum_name, variant_name), variant_ident.span());
+
+        let index = index as u32;
+        tag_variants.push(quote! { #variant_ident = #index });
+        data_union_fields.push(quote! {
+            pub #variant_ident: ::std::mem::ManuallyDrop<#variant_struct_ident>
+        });
+
+        let (fields, field_inits, match_pattern) = build_variant_fields(
+            &enum_ident,
+            variant,
+            &mut array_structs,
+        );
+
+        variant_structs.push(quote! {
+            #[repr(C)]
+            #[derive(Clone, Debug)]
+            pub struct #variant_struct_ident {
+                #(#fields)*
+            }
+        });
+
+        match_arms.push(quote! {
+            #match_pattern => {
+                let data = #data_ident {
+                    #variant_ident: ::std::mem::ManuallyDrop::new(#variant_struct_ident { #(#field_inits)* })
+                };
+                #ffi_ident { tag: #tag_ident::#variant_ident, data }
+            }
+        });
+    }
+
+    let array_struct_defs = array_structs.values().cloned().collect::<Vec<_>>();
+
+    let expanded = quote! {
+        #input
+
+        #[allow(non_snake_case)]
+        pub mod #mod_ident {
+            use super::*;
+
+            #(#array_struct_defs)*
+
+            #[repr(u32)]
+            #[derive(Clone, Copy, Debug, PartialEq, Eq)]
+            pub enum #tag_ident {
+                #(#tag_variants,)*
+            }
+
+            #(#variant_structs)*
+
+            #[repr(C)]
+            #[allow(non_snake_case)]
+            pub union #data_ident {
+                #(#data_union_fields,)*
+            }
+
+            #[repr(C)]
+            pub struct #ffi_ident {
+                pub tag: #tag_ident,
+                pub data: #data_ident,
+            }
+
+            impl From<&#enum_ident> for #ffi_ident {
+                fn from(value: &#enum_ident) -> Self {
+                    match value {
+                        #(#match_arms)*
+                    }
+                }
+            }
+        }
+    };
+
+    expanded.into()
+}
+
+fn build_variant_fields(
+    enum_ident: &Ident,
+    variant: &syn::Variant,
+    array_structs: &mut std::collections::BTreeMap<String, proc_macro2::TokenStream>,
+) -> (Vec<proc_macro2::TokenStream>, Vec<proc_macro2::TokenStream>, proc_macro2::TokenStream) {
+    let mut fields = Vec::new();
+    let mut field_inits = Vec::new();
+
+    match &variant.fields {
+        syn::Fields::Named(named) => {
+            let mut pat_fields = Vec::new();
+            for field in &named.named {
+                let field_ident = field.ident.as_ref().expect("named field");
+                let (field_ty, init_expr) = map_field_type(enum_ident, &field.ty, field_ident, array_structs);
+                fields.push(quote! { pub #field_ident: #field_ty, });
+                field_inits.push(quote! { #field_ident: #init_expr, });
+                pat_fields.push(quote! { #field_ident });
+            }
+            let variant_ident = &variant.ident;
+            let match_pattern = quote! { #enum_ident::#variant_ident { #(#pat_fields),* } };
+            (fields, field_inits, match_pattern)
+        }
+        syn::Fields::Unnamed(unnamed) => {
+            let mut pat_fields = Vec::new();
+            for (idx, field) in unnamed.unnamed.iter().enumerate() {
+                let field_ident = Ident::new(&format!("_{}", idx), field.span());
+                let (field_ty, init_expr) = map_field_type(enum_ident, &field.ty, &field_ident, array_structs);
+                fields.push(quote! { pub #field_ident: #field_ty, });
+                field_inits.push(quote! { #field_ident: #init_expr, });
+                pat_fields.push(quote! { #field_ident });
+            }
+            let variant_ident = &variant.ident;
+            let match_pattern = quote! { #enum_ident::#variant_ident( #(#pat_fields),* ) };
+            (fields, field_inits, match_pattern)
+        }
+        syn::Fields::Unit => {
+            let variant_ident = &variant.ident;
+            let match_pattern = quote! { #enum_ident::#variant_ident };
+            (fields, field_inits, match_pattern)
+        }
+    }
+}
+
+fn map_field_type(
+    _enum_ident: &Ident,
+    ty: &Type,
+    field_ident: &Ident,
+    array_structs: &mut std::collections::BTreeMap<String, proc_macro2::TokenStream>,
+) -> (Type, proc_macro2::TokenStream) {
+    if let Some(inner) = vec_inner_type(ty) {
+        let inner_ident = type_ident_name(&inner).unwrap_or_else(|| "Unknown".to_string());
+        let array_ident = Ident::new(&format!("{}ArrayFfi", inner_ident), field_ident.span());
+        let array_struct = quote! {
+            #[repr(C)]
+            #[derive(Clone, Debug)]
+            pub struct #array_ident {
+                pub ptr: *const #inner,
+                pub len: usize,
+            }
+        };
+        array_structs.entry(array_ident.to_string()).or_insert(array_struct);
+        let array_ty: Type = parse_quote!(#array_ident);
+        let init_expr = quote! { #array_ident { ptr: #field_ident.as_ptr(), len: #field_ident.len() } };
+        return (array_ty, init_expr);
+    }
+
+    let init_expr = quote! { #field_ident.clone() };
+    (ty.clone(), init_expr)
+}
+
+fn vec_inner_type(ty: &Type) -> Option<Type> {
+    if let Type::Path(path) = ty {
+        let last = path.path.segments.last()?;
+        if last.ident != "Vec" {
+            return None;
+        }
+        if let PathArguments::AngleBracketed(args) = &last.arguments {
+            if let Some(GenericArgument::Type(inner)) = args.args.first() {
+                return Some(inner.clone());
+            }
+        }
+    }
+    None
+}
+
+fn type_ident_name(ty: &Type) -> Option<String> {
+    if let Type::Path(path) = ty {
+        return path.path.segments.last().map(|s| s.ident.to_string());
+    }
+    None
+}
+
+fn to_snake_case(input: &str) -> String {
+    let mut out = String::new();
+    for (i, ch) in input.chars().enumerate() {
+        if ch.is_uppercase() {
+            if i != 0 {
+                out.push('_');
+            }
+            for lc in ch.to_lowercase() {
+                out.push(lc);
+            }
+        } else {
+            out.push(ch);
+        }
+    }
+    out
+}
+
 struct ArgSpec {
     name: Ident,
     ty: Type,
@@ -409,6 +613,36 @@ fn build_c_wrapper(
                 .to_compile_error();
         }
 
+        if !is_primitive_type(&arg.ty) {
+            let (target_ty, is_mut_ref) = match &arg.ty {
+                Type::Reference(reference) => (&*reference.elem, reference.mutability.is_some()),
+                _ => {
+                    return syn::Error::new(arg.ty.span(), "Object inputs must be references")
+                        .to_compile_error();
+                }
+            };
+
+            let ptr_ty = if is_mut_ref {
+                quote! { *mut #target_ty }
+            } else {
+                quote! { *const #target_ty }
+            };
+
+            wrapper_inputs.push(quote! { #name: #ptr_ty });
+            conversions.push(quote! {
+                if #name.is_null() {
+                    return crate::scripting::native::DropbearNativeError::NullPointer.code();
+                }
+            });
+            if is_mut_ref {
+                conversions.push(quote! { let #name = unsafe { &mut *#name }; });
+            } else {
+                conversions.push(quote! { let #name = unsafe { &*#name }; });
+            }
+            call_args.push(quote! { #name });
+            continue;
+        }
+
         let ty = &arg.ty;
         wrapper_inputs.push(quote! { #name: #ty });
         call_args.push(quote! { #name });
@@ -577,8 +811,35 @@ fn build_kotlin_wrapper(
         }
 
         if !is_primitive_type(&arg.ty) {
-            return syn::Error::new(arg.ty.span(), "JNI export only supports primitive arguments, String, entities, or define(...) pointers")
-                .to_compile_error();
+            wrapper_inputs.push(quote! { #name: #jni_path::objects::JObject });
+            let (target_ty, is_mut_ref) = match &arg.ty {
+                Type::Reference(reference) => (&*reference.elem, reference.mutability.is_some()),
+                _ => (&arg.ty, false),
+            };
+
+            let value_name = Ident::new(&format!("{}_value", name), name.span());
+            conversions.push(quote! {
+                let #value_name = match crate::scripting::jni::utils::FromJObject::from_jobject(&mut env, &#name) {
+                    Ok(v) => v,
+                    Err(e) => {
+                        let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to convert object: {:?}", e));
+                        return crate::ffi_error_return!();
+                    }
+                };
+            });
+
+            if matches!(&arg.ty, Type::Reference(_)) {
+                if is_mut_ref {
+                    conversions.push(quote! { let #name = &mut #value_name; });
+                } else {
+                    conversions.push(quote! { let #name = &#value_name; });
+                }
+                call_args.push(quote! { #name });
+            } else {
+                conversions.push(quote! { let #name: #target_ty = #value_name; });
+                call_args.push(quote! { #name });
+            }
+            continue;
         }
 
         let jni_ty = jni_param_type(&arg.ty, &jni_path);
diff --git a/crates/eucalyptus-core/build.rs b/crates/eucalyptus-core/build.rs
index 1c464f4..6bf41b4 100644
--- a/crates/eucalyptus-core/build.rs
+++ b/crates/eucalyptus-core/build.rs
@@ -29,9 +29,10 @@ fn generate_c_header() -> anyhow::Result<()> {
     let src_dir = manifest_dir.join("src");
     let mut functions = Vec::new();
     let mut structs = std::collections::HashMap::new();
-    collect_exported_functions(&src_dir, &mut functions, &mut structs)?;
+    let mut enums = Vec::new();
+    collect_exported_functions(&src_dir, &mut functions, &mut structs, &mut enums)?;
 
-    let header = render_header(&functions, &structs);
+    let header = render_header(&functions, &structs, &enums);
     std::fs::write(&output_path, header)?;
 
     Ok(())
@@ -54,18 +55,20 @@ fn collect_exported_functions(
     dir: &std::path::Path,
     out: &mut Vec<ExportedFunction>,
     structs: &mut std::collections::HashMap<String, StructDef>,
+    enums: &mut Vec<EnumDef>,
 ) -> anyhow::Result<()> {
     if dir.is_dir() {
         for entry in std::fs::read_dir(dir)? {
             let entry = entry?;
             let path = entry.path();
             if path.is_dir() {
-                collect_exported_functions(&path, out, structs)?;
+                collect_exported_functions(&path, out, structs, enums)?;
             } else if path.extension().and_then(|s| s.to_str()) == Some("rs") {
                 let content = std::fs::read_to_string(&path)?;
                 let file = syn::parse_file(&content)?;
                 extract_exports_from_file(&file, out, structs)?;
                 extract_structs_from_file(&file, structs)?;
+                extract_repr_c_enums_from_file(&file, enums)?;
             }
         }
     }
@@ -102,6 +105,8 @@ fn extract_exports_from_file(
                             "uint64_t".to_string()
                         } else if let Some(define_ty) = define_ty {
                             type_to_c(&define_ty, true)
+                        } else if is_object_input(&pat_ty.ty) {
+                            object_input_to_c(&pat_ty.ty)
                         } else {
                             type_to_c(&pat_ty.ty, false)
                         };
@@ -136,6 +141,59 @@ struct StructDef {
     is_repr_c: bool,
 }
 
+#[derive(Debug)]
+struct EnumDef {
+    name: String,
+    variants: Vec<EnumVariantDef>,
+}
+
+#[derive(Debug)]
+struct EnumVariantDef {
+    name: String,
+    fields: Vec<ExportParam>,
+}
+
+fn extract_repr_c_enums_from_file(
+    file: &syn::File,
+    enums: &mut Vec<EnumDef>,
+) -> anyhow::Result<()> {
+    for item in &file.items {
+        if let syn::Item::Enum(enm) = item {
+            if !has_repr_c_enum_attr(&enm.attrs) {
+                continue;
+            }
+            let mut variants = Vec::new();
+            for variant in &enm.variants {
+                let mut fields = Vec::new();
+                match &variant.fields {
+                    syn::Fields::Named(named) => {
+                        for field in &named.named {
+                            let name = field
+                                .ident
+                                .as_ref()
+                                .map(|i| i.to_string())
+                                .unwrap_or_else(|| "field".to_string());
+                            let ty = type_to_c(&field.ty, false);
+                            fields.push(ExportParam { name, ty });
+                        }
+                    }
+                    syn::Fields::Unnamed(unnamed) => {
+                        for (idx, field) in unnamed.unnamed.iter().enumerate() {
+                            let name = format!("_{}", idx);
+                            let ty = type_to_c(&field.ty, false);
+                            fields.push(ExportParam { name, ty });
+                        }
+                    }
+                    syn::Fields::Unit => {}
+                }
+                variants.push(EnumVariantDef { name: variant.ident.to_string(), fields });
+            }
+            enums.push(EnumDef { name: enm.ident.to_string(), variants });
+        }
+    }
+    Ok(())
+}
+
 fn extract_structs_from_file(
     file: &syn::File,
     structs: &mut std::collections::HashMap<String, StructDef>,
@@ -338,6 +396,10 @@ fn type_to_c(ty: &syn::Type, for_output: bool) -> String {
         let mutability = if reference.mutability.is_some() { "" } else { "const " };
         return format!("{}{}*", mutability, inner);
     }
+    if let Some(inner) = vec_inner_type(ty) {
+        return array_struct_name_from_type(&inner);
+    }
+
     if let syn::Type::Path(path) = ty {
         let ident = path.path.segments.last().map(|s| s.ident.to_string()).unwrap_or_else(|| "void".to_string());
         return match ident.as_str() {
@@ -350,7 +412,7 @@ fn type_to_c(ty: &syn::Type, for_output: bool) -> String {
             "i64" => "int64_t".to_string(),
             "u64" => "uint64_t".to_string(),
             "isize" => "intptr_t".to_string(),
-            "usize" => "uintptr_t".to_string(),
+            "usize" => "size_t".to_string(),
             "f32" => "float".to_string(),
             "f64" => "double".to_string(),
             "bool" => "bool".to_string(),
@@ -368,21 +430,74 @@ fn type_to_c(ty: &syn::Type, for_output: bool) -> String {
     "void".to_string()
 }
 
+fn vec_inner_type(ty: &syn::Type) -> Option<syn::Type> {
+    if let syn::Type::Path(path) = ty {
+        let last = path.path.segments.last()?;
+        if last.ident != "Vec" {
+            return None;
+        }
+        if let syn::PathArguments::AngleBracketed(args) = &last.arguments {
+            if let Some(syn::GenericArgument::Type(inner)) = args.args.first() {
+                return Some(inner.clone());
+            }
+        }
+    }
+    None
+}
+
+fn type_name_from_type(ty: &syn::Type) -> Option<String> {
+    if let syn::Type::Path(path) = ty {
+        return path.path.segments.last().map(|s| s.ident.to_string());
+    }
+    None
+}
+
+fn array_struct_name_from_type(ty: &syn::Type) -> String {
+    if let Some(inner) = vec_inner_type(ty) {
+        let inner_name = array_struct_name_from_type(&inner);
+        return format!("{}ArrayFfi", inner_name);
+    }
+
+    let name = type_name_from_type(ty).unwrap_or_else(|| "Unknown".to_string());
+    format!("{}ArrayFfi", name)
+}
+
 fn render_header(
     funcs: &[ExportedFunction],
     structs: &std::collections::HashMap<String, StructDef>,
+    enums: &[EnumDef],
 ) -> String {
     let mut out = String::new();
     out.push_str("#ifndef DROPBEAR_H\n");
     out.push_str("#define DROPBEAR_H\n\n");
     out.push_str("#include <stdbool.h>\n");
     out.push_str("#include <stdint.h>\n\n");
+    out.push_str("#include <stddef.h>\n\n");
+
+    let mut emitted_arrays = std::collections::HashSet::new();
+    for enm in enums {
+        emit_repr_c_enum(enm, &mut emitted_arrays, &mut out);
+    }
 
     let mut needed = std::collections::HashSet::new();
     for func in funcs {
         if let Some(out_ty) = &func.out_type {
             if is_custom_type(out_ty) {
                 needed.insert(out_ty.clone());
+            } else if is_opaque_ptr_name(out_ty) {
+                needed.insert(out_ty.clone());
+            }
+        }
+        for param in &func.params {
+            if is_opaque_ptr_name(&param.ty) {
+                needed.insert(param.ty.clone());
+            }
+            if let Some(base) = base_type_name(&param.ty) {
+                if is_custom_type(&base) {
+                    needed.insert(base);
+                } else if is_opaque_ptr_name(&base) {
+                    needed.insert(base);
+                }
             }
         }
     }
@@ -408,6 +523,66 @@ fn render_header(
     out
 }
 
+fn emit_repr_c_enum(
+    enm: &EnumDef,
+    emitted_arrays: &mut std::collections::HashSet<String>,
+    out: &mut String,
+) {
+    let tag_name = format!("{}Tag", enm.name);
+    let data_name = format!("{}Data", enm.name);
+    let ffi_name = format!("{}Ffi", enm.name);
+
+    for var in &enm.variants {
+        for field in &var.fields {
+            if is_array_type(&field.ty) {
+                emit_array_struct(&field.ty, &std::collections::HashMap::new(), emitted_arrays, out);
+            }
+        }
+    }
+
+    out.push_str(&format!("typedef enum {} {{\n", tag_name));
+    for (idx, var) in enm.variants.iter().enumerate() {
+        out.push_str(&format!("    {}_{} = {},\n", tag_name, var.name, idx));
+    }
+    out.push_str(&format!("}} {};\n\n", tag_name));
+
+    for var in &enm.variants {
+        let struct_name = format!("{}{}", enm.name, var.name);
+        out.push_str(&format!("typedef struct {} {{\n", struct_name));
+        for field in &var.fields {
+            out.push_str(&format!("    {} {};\n", field.ty, field.name));
+        }
+        out.push_str(&format!("}} {};\n\n", struct_name));
+    }
+
+    out.push_str(&format!("typedef union {} {{\n", data_name));
+    for var in &enm.variants {
+        let struct_name = format!("{}{}", enm.name, var.name);
+        out.push_str(&format!("    {} {};\n", struct_name, var.name));
+    }
+    out.push_str(&format!("}} {};\n\n", data_name));
+
+    out.push_str(&format!("typedef struct {} {{\n", ffi_name));
+    out.push_str(&format!("    {} tag;\n", tag_name));
+    out.push_str(&format!("    {} data;\n", data_name));
+    out.push_str(&format!("}} {};\n\n", ffi_name));
+}
+
+fn has_repr_c_enum_attr(attrs: &[syn::Attribute]) -> bool {
+    for attr in attrs {
+        let path = attr.path();
+        if path.segments.last().map(|s| s.ident == "repr_c_enum").unwrap_or(false) {
+            return true;
+        }
+        if path.segments.iter().any(|s| s.ident == "dropbear_macro")
+            && path.segments.last().map(|s| s.ident == "repr_c_enum").unwrap_or(false)
+        {
+            return true;
+        }
+    }
+    false
+}
+
 fn has_repr_c(attrs: &[syn::Attribute]) -> bool {
     for attr in attrs {
         if !attr.path().is_ident("repr") {
@@ -427,18 +602,83 @@ fn is_custom_type(ty: &str) -> bool {
     if ty.ends_with('*') {
         return false;
     }
+    if is_opaque_ptr_name(ty) {
+        return false;
+    }
     !is_builtin_c_type(ty)
 }
 
+fn base_type_name(ty: &str) -> Option<String> {
+    let mut t = ty.trim().to_string();
+    if t.starts_with("const ") {
+        t = t.trim_start_matches("const ").trim().to_string();
+    }
+    if t.ends_with('*') {
+        t = t.trim_end_matches('*').trim().to_string();
+        return Some(t);
+    }
+    None
+}
+
+fn is_object_input(ty: &syn::Type) -> bool {
+    let inner = peel_reference(ty);
+    if is_string_type(inner) || is_primitive_type(inner) {
+        return false;
+    }
+    !is_define_or_entity_like(inner)
+}
+
+fn object_input_to_c(ty: &syn::Type) -> String {
+    match ty {
+        syn::Type::Reference(reference) => {
+            let inner = type_to_c(&reference.elem, false);
+            let mutability = if reference.mutability.is_some() { "" } else { "const " };
+            format!("{}{}*", mutability, inner)
+        }
+        _ => type_to_c(ty, false),
+    }
+}
+
+fn is_define_or_entity_like(_ty: &syn::Type) -> bool {
+    false
+}
+
+fn is_string_type(ty: &syn::Type) -> bool {
+    matches!(ty, syn::Type::Path(path) if path.path.segments.last().map(|s| s.ident == "String").unwrap_or(false))
+}
+
+fn is_primitive_type(ty: &syn::Type) -> bool {
+    matches!(ty, syn::Type::Path(path) if {
+        let ident = path.path.segments.last().map(|s| s.ident.to_string()).unwrap_or_default();
+        matches!(
+            ident.as_str(),
+            "i8" | "u8" | "i16" | "u16" | "i32" | "u32" | "i64" | "u64" |
+            "isize" | "usize" | "f32" | "f64" | "bool"
+        )
+    })
+}
+
+fn peel_reference<'a>(ty: &'a syn::Type) -> &'a syn::Type {
+    if let syn::Type::Reference(reference) = ty {
+        &reference.elem
+    } else {
+        ty
+    }
+}
+
 fn is_builtin_c_type(ty: &str) -> bool {
     matches!(
         ty,
         "int8_t" | "uint8_t" | "int16_t" | "uint16_t" | "int32_t" | "uint32_t" |
         "int64_t" | "uint64_t" | "intptr_t" | "uintptr_t" | "bool" | "float" |
-        "double" | "char" | "char*" | "const char*"
+        "double" | "char" | "char*" | "const char*" | "void*"
     )
 }
 
+fn is_opaque_ptr_name(name: &str) -> bool {
+    name.ends_with("Ptr")
+}
+
 fn emit_structs_recursive(
     name: &str,
     structs: &std::collections::HashMap<String, StructDef>,
@@ -449,8 +689,23 @@ fn emit_structs_recursive(
         return;
     }
 
+    if is_array_type(name) {
+        emit_array_struct(name, structs, emitted, out);
+        return;
+    }
+
+    if is_opaque_ptr_name(name) {
+        out.push_str(&format!("typedef void* {};\n\n", name));
+        emitted.insert(name.to_string());
+        return;
+    }
+
     if let Some(def) = structs.get(name) {
         for field in &def.fields {
+            if is_array_type(&field.ty) {
+                emit_array_struct(&field.ty, structs, emitted, out);
+                continue;
+            }
             if is_custom_type(&field.ty) {
                 emit_structs_recursive(&field.ty, structs, emitted, out);
             }
@@ -471,3 +726,78 @@ fn emit_structs_recursive(
         emitted.insert(name.to_string());
     }
 }
+
+fn is_array_type(ty: &str) -> bool {
+    ty.ends_with("ArrayFfi")
+}
+
+fn emit_array_struct(
+    name: &str,
+    structs: &std::collections::HashMap<String, StructDef>,
+    emitted: &mut std::collections::HashSet<String>,
+    out: &mut String,
+) {
+    if emitted.contains(name) {
+        return;
+    }
+    let elem = name.trim_end_matches("ArrayFfi");
+
+    if is_array_type(elem) {
+        emit_array_struct(elem, structs, emitted, out);
+    }
+
+    if is_builtin_rust_primitive(elem) {
+        let c_elem = map_primitive_name(elem);
+        if !emitted.contains(elem) {
+            emitted.insert(elem.to_string());
+        }
+        out.push_str(&format!("typedef struct {} {{\n", name));
+        out.push_str(&format!("    const {}* ptr;\n", c_elem));
+        out.push_str("    size_t len;\n");
+        out.push_str(&format!("}} {};\n\n", name));
+        emitted.insert(name.to_string());
+        return;
+    }
+
+    if !emitted.contains(elem) {
+        if structs.contains_key(elem) {
+            emit_structs_recursive(elem, structs, emitted, out);
+        } else {
+            out.push_str(&format!("typedef struct {} {};// opaque\n\n", elem, elem));
+            emitted.insert(elem.to_string());
+        }
+    }
+
+    out.push_str(&format!("typedef struct {} {{\n", name));
+    out.push_str(&format!("    const {}* ptr;\n", elem));
+    out.push_str("    size_t len;\n");
+    out.push_str(&format!("}} {};\n\n", name));
+    emitted.insert(name.to_string());
+}
+
+fn is_builtin_rust_primitive(name: &str) -> bool {
+    matches!(
+        name,
+        "i8" | "u8" | "i16" | "u16" | "i32" | "u32" | "i64" | "u64" |
+        "isize" | "usize" | "f32" | "f64" | "bool"
+    )
+}
+
+fn map_primitive_name(name: &str) -> &'static str {
+    match name {
+        "i8" => "int8_t",
+        "u8" => "uint8_t",
+        "i16" => "int16_t",
+        "u16" => "uint16_t",
+        "i32" => "int32_t",
+        "u32" => "uint32_t",
+        "i64" => "int64_t",
+        "u64" => "uint64_t",
+        "isize" => "intptr_t",
+        "usize" => "size_t",
+        "f32" => "float",
+        "f64" => "double",
+        "bool" => "bool",
+        _ => "void",
+    }
+}
diff --git a/crates/eucalyptus-core/src/asset.rs b/crates/eucalyptus-core/src/asset.rs
deleted file mode 100644
index 19f37a7..0000000
--- a/crates/eucalyptus-core/src/asset.rs
+++ /dev/null
@@ -1,91 +0,0 @@
-pub mod shared {
-    use dropbear_engine::asset::{AssetHandle, AssetRegistry};
-    use crate::scripting::result::DropbearNativeResult;
-
-    pub fn is_model_handle(
-        registry: &AssetRegistry,
-        handle: u64,
-    ) -> DropbearNativeResult<bool> {
-        let handle = AssetHandle::new(handle);
-        let result = registry.is_model(handle);
-        Ok(result)
-    }
-
-    pub fn is_texture_handle(
-        registry: &AssetRegistry,
-        handle: u64,
-    ) -> DropbearNativeResult<bool> {
-        let handle = AssetHandle::new(handle);
-        let result = registry.is_material(handle);
-        Ok(result)
-    }
-}
-
-pub mod jni {
-    #![allow(non_snake_case)]
-    use jni::sys::{jboolean, jlong};
-    use jni::objects::JClass;
-    use dropbear_engine::asset::AssetRegistry;
-    use jni::JNIEnv;
-    use crate::asset::shared::{is_model_handle, is_texture_handle};
-    use crate::convert_ptr;
-
-    #[unsafe(no_mangle)]
-    pub extern "system" fn Java_com_dropbear_asset_AssetHandleNative_isModelHandle(
-        _env: JNIEnv,
-        _class: JClass,
-        asset_registry_ptr: jlong,
-        handle: jlong,
-    ) -> jboolean {
-        let asset = convert_ptr!(asset_registry_ptr => AssetRegistry);
-        let result = is_model_handle(asset, handle as u64);
-        match result {
-            Ok(val) => val as jboolean,
-            Err(e) => {
-                crate::ffi_error_return!("[ERROR] {}", e)
-            }
-        }
-    }
-
-    #[unsafe(no_mangle)]
-    pub extern "system" fn Java_com_dropbear_asset_AssetHandleNative_isTextureHandle(
-        _env: JNIEnv,
-        _class: JClass,
-        asset_registry_ptr: jlong,
-        handle: jlong,
-    ) -> jboolean {
-        let asset = convert_ptr!(asset_registry_ptr => AssetRegistry);
-        let result = is_texture_handle(asset, handle as u64);
-        match result {
-            Ok(val) => val as jboolean,
-            Err(e) => {
-                crate::ffi_error_return!("[ERROR] {}", e)
-            }
-        }
-    }
-}
-
-#[dropbear_macro::impl_c_api]
-pub mod native {
-    use dropbear_engine::asset::AssetRegistry;
-    use crate::asset::shared::{is_model_handle, is_texture_handle};
-    use crate::convert_ptr;
-    use crate::ptr::AssetRegistryPtr;
-    use crate::scripting::result::DropbearNativeResult;
-
-    pub fn dropbear_is_model_handle(
-        asset_registry_ptr: AssetRegistryPtr,
-        handle: u64,
-    ) -> DropbearNativeResult<bool> {
-        let asset = convert_ptr!(asset_registry_ptr => AssetRegistry);
-        is_model_handle(asset, handle)
-    }
-
-    pub fn dropbear_is_texture_handle(
-        asset_registry_ptr: AssetRegistryPtr,
-        handle: u64,
-    ) -> DropbearNativeResult<bool> {
-        let asset = convert_ptr!(asset_registry_ptr => AssetRegistry);
-        is_texture_handle(asset, handle)
-    }
-}
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/asset/mod.rs b/crates/eucalyptus-core/src/asset/mod.rs
new file mode 100644
index 0000000..f244e30
--- /dev/null
+++ b/crates/eucalyptus-core/src/asset/mod.rs
@@ -0,0 +1,37 @@
+pub mod texture;
+pub mod model;
+
+use dropbear_engine::asset::AssetKind;
+use crate::ptr::{AssetRegistryPtr, AssetRegistryUnwrapped};
+use crate::scripting::result::DropbearNativeResult;
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.DropbearEngineNative", func = "getAsset"),
+    c(name = "dropbear_engine_get_asset")
+)]
+fn dropbear_asset_get_asset(
+    #[dropbear_macro::define(AssetRegistryPtr)]
+    asset: &AssetRegistryUnwrapped,
+    label: String,
+    kind: &AssetKind,
+) -> DropbearNativeResult<Option<u64>> {
+    let reader = asset.read();
+    match kind {
+        AssetKind::Texture => {
+            let result = reader.get_texture_handle_from_label(&label);
+            if let Some(handle) = result {
+                Ok(Some(handle.id))
+            } else {
+                Ok(None)
+            }
+        }
+        AssetKind::Model => {
+            let result = reader.get_model_handle_from_label(&label);
+            if let Some(handle) = result {
+                Ok(Some(handle.id))
+            } else {
+                Ok(None)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/asset/model.rs b/crates/eucalyptus-core/src/asset/model.rs
new file mode 100644
index 0000000..0a1a527
--- /dev/null
+++ b/crates/eucalyptus-core/src/asset/model.rs
@@ -0,0 +1,351 @@
+use crate::scripting::native::DropbearNativeError;
+use crate::scripting::result::DropbearNativeResult;
+use crate::types::{NQuaternion, NVector2, NVector3, NVector4};
+use dropbear_engine::asset::Handle;
+use dropbear_engine::model::{Animation, AnimationChannel, AnimationInterpolation, ChannelValues, Material, Mesh, ModelVertex, Node, NodeTransform, Skin};
+use dropbear_engine::texture::Texture;
+use crate::ptr::{AssetRegistryPtr, AssetRegistryUnwrapped};
+
+#[repr(C)]
+#[derive(Clone, Debug)]
+pub struct NModelVertex {
+    pub position: NVector3,
+    pub normal: NVector3,
+    pub tangent: NVector4,
+    pub tex_coords0: NVector2,
+    pub tex_coords1: NVector2,
+    pub colour0: NVector4,
+    pub joints0: Vec<i32>,
+    pub weights0: NVector4,
+}
+
+#[repr(C)]
+#[derive(Clone, Debug)]
+pub struct NMesh {
+    pub name: String,
+    pub num_elements: i32,
+    pub material_index: i32,
+    pub vertices: Vec<NModelVertex>,
+}
+
+#[repr(C)]
+#[derive(Clone, Debug)]
+pub struct NMaterial {
+    pub name: String,
+    pub diffuse_texture: u64,
+    pub normal_texture: u64,
+    pub tint: NVector4,
+    pub emissive_factor: NVector3,
+    pub metallic_factor: f32,
+    pub roughness_factor: f32,
+    pub alpha_cutoff: Option<f32>,
+    pub double_sided: bool,
+    pub occlusion_strength: f32,
+    pub normal_scale: f32,
+    pub uv_tiling: NVector2,
+    pub emissive_texture: Option<u64>,
+    pub metallic_roughness_texture: Option<u64>,
+    pub occlusion_texture: Option<u64>,
+}
+
+#[repr(C)]
+#[derive(Clone, Debug)]
+pub struct NNodeTransform {
+    pub translation: NVector3,
+    pub rotation: NQuaternion,
+    pub scale: NVector3,
+}
+
+#[repr(C)]
+#[derive(Clone, Debug)]
+pub struct NNode {
+    pub name: String,
+    pub parent: Option<i32>,
+    pub children: Vec<i32>,
+    pub transform: NNodeTransform,
+}
+
+#[repr(C)]
+#[derive(Clone, Debug)]
+pub struct NSkin {
+    pub name: String,
+    pub joints: Vec<i32>,
+    pub inverse_bind_matrices: Vec<Vec<f64>>,
+    pub skeleton_root: Option<i32>,
+}
+
+#[repr(C)]
+#[derive(Clone, Debug)]
+pub struct NAnimation {
+    pub name: String,
+    pub channels: Vec<NAnimationChannel>,
+    pub duration: f32,
+}
+
+#[repr(C)]
+#[derive(Clone, Debug)]
+pub struct NAnimationChannel {
+    pub target_node: i32,
+    pub times: Vec<f64>,
+    pub values: NChannelValues,
+    pub interpolation: NAnimationInterpolation,
+}
+
+#[repr(C)]
+#[derive(Clone, Debug)]
+pub enum NAnimationInterpolation {
+    Linear,
+    Step,
+    CubicSpline,
+}
+
+#[repr(C)]
+#[derive(Clone, Debug)]
+pub enum NChannelValues {
+    Translations { values: Vec<NVector3> },
+    Rotations { values: Vec<NQuaternion> },
+    Scales { values: Vec<NVector3> },
+}
+
+fn texture_handle_id(
+    registry: &dropbear_engine::asset::AssetRegistry,
+    texture: &Texture,
+) -> u64 {
+    texture
+        .hash
+        .and_then(|hash| registry.texture_handle_by_hash(hash).map(|h| h.id))
+        .unwrap_or(0)
+}
+
+fn map_vertex(vertex: &ModelVertex) -> NModelVertex {
+    NModelVertex {
+        position: NVector3::from(vertex.position),
+        normal: NVector3::from(vertex.normal),
+        tangent: NVector4::from(vertex.tangent),
+        tex_coords0: NVector2::from(vertex.tex_coords0),
+        tex_coords1: NVector2::from(vertex.tex_coords1),
+        colour0: NVector4::from(vertex.colour0),
+        joints0: vertex.joints0.iter().map(|v| *v as i32).collect(),
+        weights0: NVector4::from(vertex.weights0),
+    }
+}
+
+fn map_mesh(mesh: &Mesh) -> NMesh {
+    NMesh {
+        name: mesh.name.clone(),
+        num_elements: mesh.num_elements as i32,
+        material_index: mesh.material as i32,
+        vertices: mesh.vertices.iter().map(map_vertex).collect(),
+    }
+}
+
+fn map_material(
+    registry: &dropbear_engine::asset::AssetRegistry,
+    material: &Material,
+) -> NMaterial {
+    NMaterial {
+        name: material.name.clone(),
+        diffuse_texture: texture_handle_id(registry, &material.diffuse_texture),
+        normal_texture: texture_handle_id(registry, &material.normal_texture),
+        tint: NVector4::from(material.tint),
+        emissive_factor: NVector3::from(material.emissive_factor),
+        metallic_factor: material.metallic_factor,
+        roughness_factor: material.roughness_factor,
+        alpha_cutoff: material.alpha_cutoff,
+        double_sided: material.double_sided,
+        occlusion_strength: material.occlusion_strength,
+        normal_scale: material.normal_scale,
+        uv_tiling: NVector2::from(material.uv_tiling),
+        emissive_texture: material
+            .emissive_texture
+            .as_ref()
+            .map(|tex| texture_handle_id(registry, tex))
+            .filter(|id| *id != 0),
+        metallic_roughness_texture: material
+            .metallic_roughness_texture
+            .as_ref()
+            .map(|tex| texture_handle_id(registry, tex))
+            .filter(|id| *id != 0),
+        occlusion_texture: material
+            .occlusion_texture
+            .as_ref()
+            .map(|tex| texture_handle_id(registry, tex))
+            .filter(|id| *id != 0),
+    }
+}
+
+fn map_node_transform(transform: &NodeTransform) -> NNodeTransform {
+    NNodeTransform {
+        translation: NVector3::from(transform.translation),
+        rotation: NQuaternion::from(transform.rotation),
+        scale: NVector3::from(transform.scale),
+    }
+}
+
+fn map_node(node: &Node) -> NNode {
+    NNode {
+        name: node.name.clone(),
+        parent: node.parent.map(|v| v as i32),
+        children: node.children.iter().map(|v| *v as i32).collect(),
+        transform: map_node_transform(&node.transform),
+    }
+}
+
+fn map_skin(skin: &Skin) -> NSkin {
+    let inverse_bind_matrices = skin
+        .inverse_bind_matrices
+        .iter()
+        .map(|matrix| matrix.to_cols_array().iter().map(|v| *v as f64).collect())
+        .collect();
+
+    NSkin {
+        name: skin.name.clone(),
+        joints: skin.joints.iter().map(|v| *v as i32).collect(),
+        inverse_bind_matrices,
+        skeleton_root: skin.skeleton_root.map(|v| v as i32),
+    }
+}
+
+fn map_interpolation(value: &AnimationInterpolation) -> NAnimationInterpolation {
+    match value {
+        AnimationInterpolation::Linear => NAnimationInterpolation::Linear,
+        AnimationInterpolation::Step => NAnimationInterpolation::Step,
+        AnimationInterpolation::CubicSpline => NAnimationInterpolation::CubicSpline,
+    }
+}
+
+fn map_channel_values(values: &ChannelValues) -> NChannelValues {
+    match values {
+        ChannelValues::Translations(list) => NChannelValues::Translations {
+            values: list.iter().map(|v| NVector3::from(*v)).collect(),
+        },
+        ChannelValues::Rotations(list) => NChannelValues::Rotations {
+            values: list.iter().map(|v| NQuaternion::from(*v)).collect(),
+        },
+        ChannelValues::Scales(list) => NChannelValues::Scales {
+            values: list.iter().map(|v| NVector3::from(*v)).collect(),
+        },
+    }
+}
+
+fn map_animation_channel(channel: &AnimationChannel) -> NAnimationChannel {
+    NAnimationChannel {
+        target_node: channel.target_node as i32,
+        times: channel.times.iter().map(|v| *v as f64).collect(),
+        values: map_channel_values(&channel.values),
+        interpolation: map_interpolation(&channel.interpolation),
+    }
+}
+
+fn map_animation(animation: &Animation) -> NAnimation {
+    NAnimation {
+        name: animation.name.clone(),
+        channels: animation.channels.iter().map(map_animation_channel).collect(),
+        duration: animation.duration,
+    }
+}
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.asset.ModelNative", func = "getLabel"),
+    c(name = "dropbear_asset_model_get_label")
+)]
+fn dropbear_asset_model_get_label(
+    #[dropbear_macro::define(AssetRegistryPtr)]
+    asset: &AssetRegistryUnwrapped,
+    model_handle: u64,
+) -> DropbearNativeResult<String> {
+    let label = asset
+        .read()
+        .get_label_from_model_handle(Handle::new(model_handle))
+        .ok_or_else(|| DropbearNativeError::InvalidHandle)?;
+    Ok(label)
+}
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.asset.ModelNative", func = "getMeshes"),
+    c(name = "dropbear_asset_model_get_meshes")
+)]
+fn dropbear_asset_model_get_meshes(
+    #[dropbear_macro::define(AssetRegistryPtr)]
+    asset: &AssetRegistryUnwrapped,
+    model_handle: u64,
+) -> DropbearNativeResult<Vec<NMesh>> {
+    let reader = asset.read();
+    let model = reader
+        .get_model(Handle::new(model_handle))
+        .ok_or(DropbearNativeError::InvalidHandle)?;
+
+    Ok(model.meshes.iter().map(map_mesh).collect())
+}
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.asset.ModelNative", func = "getMaterials"),
+    c(name = "dropbear_asset_model_get_materials")
+)]
+fn dropbear_asset_model_get_materials(
+    #[dropbear_macro::define(AssetRegistryPtr)]
+    asset: &AssetRegistryUnwrapped,
+    model_handle: u64,
+) -> DropbearNativeResult<Vec<NMaterial>> {
+    let reader = asset.read();
+    let model = reader
+        .get_model(Handle::new(model_handle))
+        .ok_or(DropbearNativeError::InvalidHandle)?;
+
+    Ok(model
+        .materials
+        .iter()
+        .map(|mat| map_material(&reader, mat))
+        .collect())
+}
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.asset.ModelNative", func = "getSkins"),
+    c(name = "dropbear_asset_model_get_skins")
+)]
+pub fn dropbear_asset_model_get_skins(
+    #[dropbear_macro::define(AssetRegistryPtr)]
+    asset: &AssetRegistryUnwrapped,
+    model_handle: u64,
+) -> DropbearNativeResult<Vec<NSkin>> {
+    let reader = asset.read();
+    let model = reader
+        .get_model(Handle::new(model_handle))
+        .ok_or(DropbearNativeError::InvalidHandle)?;
+
+    Ok(model.skins.iter().map(map_skin).collect())
+}
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.asset.ModelNative", func = "getAnimations"),
+    c(name = "dropbear_asset_model_get_animations")
+)]
+pub fn dropbear_asset_model_get_animations(
+    #[dropbear_macro::define(AssetRegistryPtr)]
+    asset: &AssetRegistryUnwrapped,
+    model_handle: u64,
+) -> DropbearNativeResult<Vec<NAnimation>> {
+    let reader = asset.read();
+    let model = reader
+        .get_model(Handle::new(model_handle))
+        .ok_or(DropbearNativeError::InvalidHandle)?;
+
+    Ok(model.animations.iter().map(map_animation).collect())
+}
+
+#[dropbear_macro::export(
+    kotlin(class = "com.dropbear.asset.ModelNative", func = "getNodes"),
+    c(name = "dropbear_asset_model_get_nodes")
+)]
+pub fn dropbear_asset_model_get_nodes(
+    #[dropbear_macro::define(AssetRegistryPtr)]
+    asset: &AssetRegistryUnwrapped,
+    model_handle: u64,
+) -> DropbearNativeResult<Vec<NNode>> {
+    let reader = asset.read();
+    let model = reader
+        .get_model(Handle::new(model_handle))
+        .ok_or(DropbearNativeError::InvalidHandle)?;
+
+    Ok(model.nodes.iter().map(map_node).collect())
+}
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/asset/texture.rs b/crates/eucalyptus-core/src/asset/texture.rs
new file mode 100644
index 0000000..0acc2e9
--- /dev/null
+++ b/crates/eucalyptus-core/src/asset/texture.rs
@@ -0,0 +1,83 @@
+pub mod shared {
+    use dropbear_engine::asset::{AssetHandle, AssetRegistry};
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+
+    pub fn get_texture_name(
+        asset_registry: &AssetRegistry,
+        handle: u64,
+    ) -> DropbearNativeResult<String> {
+        let texture = asset_registry
+            .get_material(AssetHandle::new(handle))
+            .ok_or_else(|| DropbearNativeError::NoSuchHandle)?;
+
+        Ok(texture.name.clone())
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+    use jni::JNIEnv;
+    use jni::sys::{jlong, jstring};
+    use dropbear_engine::asset::AssetRegistry;
+    use jni::objects::JClass;
+    use crate::asset::texture::shared::get_texture_name;
+    use crate::scripting::native::DropbearNativeError;
+
+    #[unsafe(no_mangle)]
+    pub extern "system" fn Java_com_dropbear_asset_TextureHandleNative_getTextureName(
+        env: JNIEnv,
+        _class: JClass,
+        asset_registry_ptr: jlong,
+        handle: jlong,
+    ) -> jstring {
+        let asset_registry = crate::convert_ptr!(asset_registry_ptr => AssetRegistry);
+        let result = get_texture_name(asset_registry, handle as u64);
+        match result {
+            Ok(name) => {
+                let output = env.new_string(name).map_err(|_| DropbearNativeError::JNIFailedToCreateObject);
+                match output {
+                    Ok(jstr) => jstr.into_raw(),
+                    Err(e) => {
+                        crate::ffi_error_return!("[ERROR] Failed to create Java string: {}", e)
+                    }
+                }
+            }
+            Err(e) => {
+                crate::ffi_error_return!("[ERROR] {}", e)
+            }
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use std::ffi::CString;
+    use crate::ptr::AssetRegistryPtr;
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+    use std::ffi::c_char;
+    use dropbear_engine::asset::AssetRegistry;
+    use crate::asset::texture::shared::get_texture_name;
+
+    pub fn dropbear_get_texture_name(
+        asset_registry_ptr: AssetRegistryPtr,
+        handle: u64,
+    ) -> DropbearNativeResult<*mut c_char> {
+        let asset_registry = crate::convert_ptr!(asset_registry_ptr => AssetRegistry);
+
+        let result = get_texture_name(asset_registry, handle)
+            .map(|name| {
+                match CString::new(name) {
+                    Ok(c_str) => {
+                        Ok(c_str.into_raw())
+                    },
+                    Err(_) => {
+                        Err(DropbearNativeError::CStringError)
+                    }
+                }
+            })?;
+        
+        result
+    }
+}
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/asset_old/mod.rs b/crates/eucalyptus-core/src/asset_old/mod.rs
deleted file mode 100644
index b664ce1..0000000
--- a/crates/eucalyptus-core/src/asset_old/mod.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-pub mod texture;
-pub mod model;
-
-use dropbear_engine::asset::AssetKind;
-use crate::pointer_convert;
-use crate::ptr::AssetRegistryUnwrapped;
-use crate::scripting::result::DropbearNativeResult;
-
-/**
- * Fetches the asset_old information from the internal AssetRegistry (located in
- * `dropbear_engine::asset_old::AssetRegistry`) from the provided label.
- */
-pub fn dropbear_asset_get_asset(
-    asset_ptr: u64,
-    label: String,
-    kind: AssetKind,
-) -> DropbearNativeResult<Option<u64>> {
-    let asset = pointer_convert!(asset_ptr => AssetRegistryUnwrapped);
-    let reader = asset.read();
-    match kind {
-        AssetKind::Texture => {
-            let result = reader.get_texture_handle_from_label(&label);
-            if let Some(handle) = result {
-                Ok(Some(handle.id))
-            } else {
-                Ok(None)
-            }
-        }
-        AssetKind::Model => {
-            let result = reader.get_model_handle_from_label(&label);
-            if let Some(handle) = result {
-                Ok(Some(handle.id))
-            } else {
-                Ok(None)
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/asset_old/model.rs b/crates/eucalyptus-core/src/asset_old/model.rs
deleted file mode 100644
index 6d764f3..0000000
--- a/crates/eucalyptus-core/src/asset_old/model.rs
+++ /dev/null
@@ -1,345 +0,0 @@
-use crate::pointer_convert;
-use crate::ptr::AssetRegistryUnwrapped;
-use crate::scripting::native::DropbearNativeError;
-use crate::scripting::result::DropbearNativeResult;
-use crate::types::{NQuaternion, NVector2, NVector3, NVector4};
-use dropbear_engine::asset::Handle;
-use dropbear_engine::model::{Animation, AnimationChannel, AnimationInterpolation, ChannelValues, Material, Mesh, ModelVertex, Node, NodeTransform, Skin};
-use dropbear_engine::texture::Texture;
-
-#[derive(Clone, Debug, uniffi::Record)]
-pub struct NModelVertex {
-    pub position: NVector3,
-    pub normal: NVector3,
-    pub tangent: NVector4,
-    pub tex_coords0: NVector2,
-    pub tex_coords1: NVector2,
-    pub colour0: NVector4,
-    pub joints0: Vec<i32>,
-    pub weights0: NVector4,
-}
-
-#[derive(Clone, Debug, uniffi::Record)]
-pub struct NMesh {
-    pub name: String,
-    pub num_elements: i32,
-    pub material_index: i32,
-    pub vertices: Vec<NModelVertex>,
-}
-
-#[derive(Clone, Debug, uniffi::Record)]
-pub struct NMaterial {
-    pub name: String,
-    pub diffuse_texture: u64,
-    pub normal_texture: u64,
-    pub tint: NVector4,
-    pub emissive_factor: NVector3,
-    pub metallic_factor: f32,
-    pub roughness_factor: f32,
-    pub alpha_cutoff: Option<f32>,
-    pub double_sided: bool,
-    pub occlusion_strength: f32,
-    pub normal_scale: f32,
-    pub uv_tiling: NVector2,
-    pub emissive_texture: Option<u64>,
-    pub metallic_roughness_texture: Option<u64>,
-    pub occlusion_texture: Option<u64>,
-}
-
-#[derive(Clone, Debug, uniffi::Record)]
-pub struct NNodeTransform {
-    pub translation: NVector3,
-    pub rotation: NQuaternion,
-    pub scale: NVector3,
-}
-
-#[derive(Clone, Debug, uniffi::Record)]
-pub struct NNode {
-    pub name: String,
-    pub parent: Option<i32>,
-    pub children: Vec<i32>,
-    pub transform: NNodeTransform,
-}
-
-#[derive(Clone, Debug, uniffi::Record)]
-pub struct NSkin {
-    pub name: String,
-    pub joints: Vec<i32>,
-    pub inverse_bind_matrices: Vec<Vec<f64>>,
-    pub skeleton_root: Option<i32>,
-}
-
-#[derive(Clone, Debug, uniffi::Record)]
-pub struct NAnimation {
-    pub name: String,
-    pub channels: Vec<NAnimationChannel>,
-    pub duration: f32,
-}
-
-#[derive(Clone, Debug, uniffi::Record)]
-pub struct NAnimationChannel {
-    pub target_node: i32,
-    pub times: Vec<f64>,
-    pub values: NChannelValues,
-    pub interpolation: NAnimationInterpolation,
-}
-
-#[derive(Clone, Debug, uniffi::Enum)]
-pub enum NAnimationInterpolation {
-    Linear,
-    Step,
-    CubicSpline,
-}
-
-#[derive(Clone, Debug, uniffi::Enum)]
-pub enum NChannelValues {
-    Translations { values: Vec<NVector3> },
-    Rotations { values: Vec<NQuaternion> },
-    Scales { values: Vec<NVector3> },
-}
-
-fn texture_handle_id(
-    registry: &dropbear_engine::asset::AssetRegistry,
-    texture: &Texture,
-) -> u64 {
-    texture
-        .hash()
-        .and_then(|hash| registry.texture_handle_by_hash(hash).map(|h| h.id))
-        .unwrap_or(0)
-}
-
-fn to_nvector3(v: glam::Vec3) -> NVector3 {
-    NVector3::from([v.x, v.y, v.z])
-}
-
-fn to_nvector2(v: [f32; 2]) -> NVector2 {
-    NVector2::from(v)
-}
-
-fn to_nvector4(v: [f32; 4]) -> NVector4 {
-    NVector4::from(v)
-}
-
-fn to_nquaternion(q: glam::Quat) -> NQuaternion {
-    NQuaternion {
-        x: q.x as f64,
-        y: q.y as f64,
-        z: q.z as f64,
-        w: q.w as f64,
-    }
-}
-
-fn map_vertex(vertex: &ModelVertex) -> NModelVertex {
-    NModelVertex {
-        position: NVector3::from(vertex.position),
-        normal: NVector3::from(vertex.normal),
-        tangent: to_nvector4(vertex.tangent),
-        tex_coords0: to_nvector2(vertex.tex_coords0),
-        tex_coords1: to_nvector2(vertex.tex_coords1),
-        colour0: to_nvector4(vertex.colour0),
-        joints0: vertex.joints0.iter().map(|v| *v as i32).collect(),
-        weights0: to_nvector4(vertex.weights0),
-    }
-}
-
-fn map_mesh(mesh: &Mesh) -> NMesh {
-    NMesh {
-        name: mesh.name.clone(),
-        num_elements: mesh.num_elements as i32,
-        material_index: mesh.material as i32,
-        vertices: mesh.vertices.iter().map(map_vertex).collect(),
-    }
-}
-
-fn map_material(
-    registry: &dropbear_engine::asset::AssetRegistry,
-    material: &Material,
-) -> NMaterial {
-    NMaterial {
-        name: material.name.clone(),
-        diffuse_texture: texture_handle_id(registry, &material.diffuse_texture),
-        normal_texture: texture_handle_id(registry, &material.normal_texture),
-        tint: to_nvector4(material.tint),
-        emissive_factor: NVector3::from(material.emissive_factor),
-        metallic_factor: material.metallic_factor,
-        roughness_factor: material.roughness_factor,
-        alpha_cutoff: material.alpha_cutoff,
-        double_sided: material.double_sided,
-        occlusion_strength: material.occlusion_strength,
-        normal_scale: material.normal_scale,
-        uv_tiling: to_nvector2(material.uv_tiling),
-        emissive_texture: material
-            .emissive_texture
-            .as_ref()
-            .map(|tex| texture_handle_id(registry, tex))
-            .filter(|id| *id != 0),
-        metallic_roughness_texture: material
-            .metallic_roughness_texture
-            .as_ref()
-            .map(|tex| texture_handle_id(registry, tex))
-            .filter(|id| *id != 0),
-        occlusion_texture: material
-            .occlusion_texture
-            .as_ref()
-            .map(|tex| texture_handle_id(registry, tex))
-            .filter(|id| *id != 0),
-    }
-}
-
-fn map_node_transform(transform: &NodeTransform) -> NNodeTransform {
-    NNodeTransform {
-        translation: to_nvector3(transform.translation),
-        rotation: to_nquaternion(transform.rotation),
-        scale: to_nvector3(transform.scale),
-    }
-}
-
-fn map_node(node: &Node) -> NNode {
-    NNode {
-        name: node.name.clone(),
-        parent: node.parent.map(|v| v as i32),
-        children: node.children.iter().map(|v| *v as i32).collect(),
-        transform: map_node_transform(&node.transform),
-    }
-}
-
-fn map_skin(skin: &Skin) -> NSkin {
-    let inverse_bind_matrices = skin
-        .inverse_bind_matrices
-        .iter()
-        .map(|matrix| matrix.to_cols_array().iter().map(|v| *v as f64).collect())
-        .collect();
-
-    NSkin {
-        name: skin.name.clone(),
-        joints: skin.joints.iter().map(|v| *v as i32).collect(),
-        inverse_bind_matrices,
-        skeleton_root: skin.skeleton_root.map(|v| v as i32),
-    }
-}
-
-fn map_interpolation(value: &AnimationInterpolation) -> NAnimationInterpolation {
-    match value {
-        AnimationInterpolation::Linear => NAnimationInterpolation::Linear,
-        AnimationInterpolation::Step => NAnimationInterpolation::Step,
-        AnimationInterpolation::CubicSpline => NAnimationInterpolation::CubicSpline,
-    }
-}
-
-fn map_channel_values(values: &ChannelValues) -> NChannelValues {
-    match values {
-        ChannelValues::Translations(list) => NChannelValues::Translations {
-            values: list.iter().map(|v| to_nvector3(*v)).collect(),
-        },
-        ChannelValues::Rotations(list) => NChannelValues::Rotations {
-            values: list.iter().map(|v| to_nquaternion(*v)).collect(),
-        },
-        ChannelValues::Scales(list) => NChannelValues::Scales {
-            values: list.iter().map(|v| to_nvector3(*v)).collect(),
-        },
-    }
-}
-
-fn map_animation_channel(channel: &AnimationChannel) -> NAnimationChannel {
-    NAnimationChannel {
-        target_node: channel.target_node as i32,
-        times: channel.times.iter().map(|v| *v as f64).collect(),
-        values: map_channel_values(&channel.values),
-        interpolation: map_interpolation(&channel.interpolation),
-    }
-}
-
-fn map_animation(animation: &Animation) -> NAnimation {
-    NAnimation {
-        name: animation.name.clone(),
-        channels: animation.channels.iter().map(map_animation_channel).collect(),
-        duration: animation.duration,
-    }
-}
-
-#[uniffi::export]
-pub fn dropbear_asset_model_get_label(
-    asset_registry: u64,
-    model_handle: u64,
-) -> DropbearNativeResult<String> {
-    let asset = pointer_convert!(asset_registry => AssetRegistryUnwrapped);
-    let label = asset
-        .read()
-        .get_label_from_model_handle(Handle::new(model_handle))
-        .ok_or_else(|| DropbearNativeError::InvalidHandle)?;
-    Ok(label)
-}
-
-#[uniffi::export]
-pub fn dropbear_asset_model_get_meshes(
-    asset_registry: u64,
-    model_handle: u64,
-) -> DropbearNativeResult<Vec<NMesh>> {
-    let asset = pointer_convert!(asset_registry => AssetRegistryUnwrapped);
-    let reader = asset.read();
-    let model = reader
-        .get_model(Handle::new(model_handle))
-        .ok_or(DropbearNativeError::InvalidHandle)?;
-
-    Ok(model.meshes.iter().map(map_mesh).collect())
-}
-
-#[uniffi::export]
-pub fn dropbear_asset_model_get_materials(
-    asset_registry: u64,
-    model_handle: u64,
-) -> DropbearNativeResult<Vec<NMaterial>> {
-    let asset = pointer_convert!(asset_registry => AssetRegistryUnwrapped);
-    let reader = asset.read();
-    let model = reader
-        .get_model(Handle::new(model_handle))
-        .ok_or(DropbearNativeError::InvalidHandle)?;
-
-    Ok(model
-        .materials
-        .iter()
-        .map(|mat| map_material(&reader, mat))
-        .collect())
-}
-
-#[uniffi::export]
-pub fn dropbear_asset_model_get_skins(
-    asset_registry: u64,
-    model_handle: u64,
-) -> DropbearNativeResult<Vec<NSkin>> {
-    let asset = pointer_convert!(asset_registry => AssetRegistryUnwrapped);
-    let reader = asset.read();
-    let model = reader
-        .get_model(Handle::new(model_handle))
-        .ok_or(DropbearNativeError::InvalidHandle)?;
-
-    Ok(model.skins.iter().map(map_skin).collect())
-}
-
-#[uniffi::export]
-pub fn dropbear_asset_model_get_animations(
-    asset_registry: u64,
-    model_handle: u64,
-) -> DropbearNativeResult<Vec<NAnimation>> {
-    let asset = pointer_convert!(asset_registry => AssetRegistryUnwrapped);
-    let reader = asset.read();
-    let model = reader
-        .get_model(Handle::new(model_handle))
-        .ok_or(DropbearNativeError::InvalidHandle)?;
-
-    Ok(model.animations.iter().map(map_animation).collect())
-}
-
-#[uniffi::export]
-pub fn dropbear_asset_model_get_nodes(
-    asset_registry: u64,
-    model_handle: u64,
-) -> DropbearNativeResult<Vec<NNode>> {
-    let asset = pointer_convert!(asset_registry => AssetRegistryUnwrapped);
-    let reader = asset.read();
-    let model = reader
-        .get_model(Handle::new(model_handle))
-        .ok_or(DropbearNativeError::InvalidHandle)?;
-
-    Ok(model.nodes.iter().map(map_node).collect())
-}
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/asset_old/texture.rs b/crates/eucalyptus-core/src/asset_old/texture.rs
deleted file mode 100644
index 0acc2e9..0000000
--- a/crates/eucalyptus-core/src/asset_old/texture.rs
+++ /dev/null
@@ -1,83 +0,0 @@
-pub mod shared {
-    use dropbear_engine::asset::{AssetHandle, AssetRegistry};
-    use crate::scripting::native::DropbearNativeError;
-    use crate::scripting::result::DropbearNativeResult;
-
-    pub fn get_texture_name(
-        asset_registry: &AssetRegistry,
-        handle: u64,
-    ) -> DropbearNativeResult<String> {
-        let texture = asset_registry
-            .get_material(AssetHandle::new(handle))
-            .ok_or_else(|| DropbearNativeError::NoSuchHandle)?;
-
-        Ok(texture.name.clone())
-    }
-}
-
-pub mod jni {
-    #![allow(non_snake_case)]
-    use jni::JNIEnv;
-    use jni::sys::{jlong, jstring};
-    use dropbear_engine::asset::AssetRegistry;
-    use jni::objects::JClass;
-    use crate::asset::texture::shared::get_texture_name;
-    use crate::scripting::native::DropbearNativeError;
-
-    #[unsafe(no_mangle)]
-    pub extern "system" fn Java_com_dropbear_asset_TextureHandleNative_getTextureName(
-        env: JNIEnv,
-        _class: JClass,
-        asset_registry_ptr: jlong,
-        handle: jlong,
-    ) -> jstring {
-        let asset_registry = crate::convert_ptr!(asset_registry_ptr => AssetRegistry);
-        let result = get_texture_name(asset_registry, handle as u64);
-        match result {
-            Ok(name) => {
-                let output = env.new_string(name).map_err(|_| DropbearNativeError::JNIFailedToCreateObject);
-                match output {
-                    Ok(jstr) => jstr.into_raw(),
-                    Err(e) => {
-                        crate::ffi_error_return!("[ERROR] Failed to create Java string: {}", e)
-                    }
-                }
-            }
-            Err(e) => {
-                crate::ffi_error_return!("[ERROR] {}", e)
-            }
-        }
-    }
-}
-
-#[dropbear_macro::impl_c_api]
-pub mod native {
-    use std::ffi::CString;
-    use crate::ptr::AssetRegistryPtr;
-    use crate::scripting::native::DropbearNativeError;
-    use crate::scripting::result::DropbearNativeResult;
-    use std::ffi::c_char;
-    use dropbear_engine::asset::AssetRegistry;
-    use crate::asset::texture::shared::get_texture_name;
-
-    pub fn dropbear_get_texture_name(
-        asset_registry_ptr: AssetRegistryPtr,
-        handle: u64,
-    ) -> DropbearNativeResult<*mut c_char> {
-        let asset_registry = crate::convert_ptr!(asset_registry_ptr => AssetRegistry);
-
-        let result = get_texture_name(asset_registry, handle)
-            .map(|name| {
-                match CString::new(name) {
-                    Ok(c_str) => {
-                        Ok(c_str.into_raw())
-                    },
-                    Err(_) => {
-                        Err(DropbearNativeError::CStringError)
-                    }
-                }
-            })?;
-        
-        result
-    }
-}
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/camera.rs b/crates/eucalyptus-core/src/camera.rs
index a333a40..6b9d280 100644
--- a/crates/eucalyptus-core/src/camera.rs
+++ b/crates/eucalyptus-core/src/camera.rs
@@ -135,7 +135,7 @@ pub mod jni {
     use dropbear_engine::camera::Camera;
     use crate::convert_jlong_to_entity;
     use crate::scripting::jni::utils::{FromJObject, ToJObject};
-    use crate::types::Vector3;
+    use crate::types::NVector3;
 
     #[unsafe(no_mangle)]
     pub extern "system" fn Java_com_dropbear_components_CameraNative_cameraExistsForEntity(
@@ -163,7 +163,7 @@ pub mod jni {
         let world = crate::convert_ptr!(world_ptr => hecs::World);
         let entity = convert_jlong_to_entity!(entity_id);
         if let Ok(camera) = world.get::<&Camera>(entity) {
-            let eye: Vector3 = Vector3::from(camera.eye);
+            let eye: NVector3 = NVector3::from(camera.eye);
             return match eye.to_jobject(&mut env) {
                 Ok(val) => val.into_raw(),
                 Err(_) => std::ptr::null_mut()
@@ -184,7 +184,7 @@ pub mod jni {
     ) {
         let world = crate::convert_ptr!(world_ptr => hecs::World);
         let entity = convert_jlong_to_entity!(entity_id);
-        let new_eye = match Vector3::from_jobject(&mut env, &eye_obj) {
+        let new_eye = match NVector3::from_jobject(&mut env, &eye_obj) {
             Ok(v) => v,
             Err(e) => {
                 let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Invalid Vector3d: {:?}", e));
@@ -209,7 +209,7 @@ pub mod jni {
         let world = crate::convert_ptr!(world_ptr => hecs::World);
         let entity = convert_jlong_to_entity!(entity_id);
         if let Ok(camera) = world.get::<&Camera>(entity) {
-            let target: Vector3 = Vector3::from(camera.target);
+            let target: NVector3 = NVector3::from(camera.target);
             return match target.to_jobject(&mut env) {
                 Ok(val) => val.into_raw(),
                 Err(_) => std::ptr::null_mut()
@@ -230,7 +230,7 @@ pub mod jni {
     ) {
         let world = crate::convert_ptr!(world_ptr => hecs::World);
         let entity = convert_jlong_to_entity!(entity_id);
-        let new_target = match Vector3::from_jobject(&mut env, &target_obj) {
+        let new_target = match NVector3::from_jobject(&mut env, &target_obj) {
             Ok(v) => v,
             Err(_) => return,
         };
@@ -249,7 +249,7 @@ pub mod jni {
         let world = crate::convert_ptr!(world_ptr => hecs::World);
         let entity = convert_jlong_to_entity!(entity_id);
         if let Ok(camera) = world.get::<&Camera>(entity) {
-            let up = Vector3::from(camera.up);
+            let up = NVector3::from(camera.up);
             return match up.to_jobject(&mut env) {
                 Ok(val) => val.into_raw(),
                 Err(_) => std::ptr::null_mut()
@@ -270,7 +270,7 @@ pub mod jni {
     ) {
         let world = crate::convert_ptr!(world_ptr => hecs::World);
         let entity = convert_jlong_to_entity!(entity_id);
-        let new_up = match Vector3::from_jobject(&mut env, &up_obj) {
+        let new_up = match NVector3::from_jobject(&mut env, &up_obj) {
             Ok(v) => v,
             Err(_) => return,
         };
@@ -522,7 +522,7 @@ pub mod native {
     use glam::DVec3;
     use dropbear_engine::camera::Camera;
     use crate::scripting::result::DropbearNativeResult;
-    use crate::types::Vector3;
+    use crate::types::NVector3;
 
     pub fn dropbear_camera_exists_for_entity(
         world_ptr: WorldPtr,
@@ -541,12 +541,12 @@ pub mod native {
     pub fn dropbear_get_camera_eye(
         world_ptr: WorldPtr,
         entity_id: u64,
-    ) -> DropbearNativeResult<Vector3> {
+    ) -> DropbearNativeResult<NVector3> {
         let world = convert_ptr!(world_ptr => hecs::World);
         let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
 
         if let Ok(camera) = world.get::<&Camera>(entity) {
-            DropbearNativeResult::Ok(Vector3::from(camera.eye))
+            DropbearNativeResult::Ok(NVector3::from(camera.eye))
         } else {
             DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
         }
@@ -555,7 +555,7 @@ pub mod native {
     pub fn dropbear_set_camera_eye(
         world_ptr: WorldPtr,
         entity_id: u64,
-        value: Vector3,
+        value: NVector3,
     ) -> DropbearNativeResult<()> {
         let world = convert_ptr!(world_ptr => hecs::World);
         let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
@@ -571,12 +571,12 @@ pub mod native {
     pub fn dropbear_get_camera_target(
         world_ptr: WorldPtr,
         entity_id: u64,
-    ) -> DropbearNativeResult<Vector3> {
+    ) -> DropbearNativeResult<NVector3> {
         let world = convert_ptr!(world_ptr => hecs::World);
         let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
 
         if let Ok(camera) = world.get::<&Camera>(entity) {
-            DropbearNativeResult::Ok(Vector3::from(camera.target))
+            DropbearNativeResult::Ok(NVector3::from(camera.target))
         } else {
             DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
         }
@@ -585,7 +585,7 @@ pub mod native {
     pub fn dropbear_set_camera_target(
         world_ptr: WorldPtr,
         entity_id: u64,
-        value: Vector3,
+        value: NVector3,
     ) -> DropbearNativeResult<()> {
         let world = convert_ptr!(world_ptr => hecs::World);
         let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
@@ -601,12 +601,12 @@ pub mod native {
     pub fn dropbear_get_camera_up(
         world_ptr: WorldPtr,
         entity_id: u64,
-    ) -> DropbearNativeResult<Vector3> {
+    ) -> DropbearNativeResult<NVector3> {
         let world = convert_ptr!(world_ptr => hecs::World);
         let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
 
         if let Ok(camera) = world.get::<&Camera>(entity) {
-            DropbearNativeResult::Ok(Vector3::from(camera.up))
+            DropbearNativeResult::Ok(NVector3::from(camera.up))
         } else {
             DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
         }
@@ -615,7 +615,7 @@ pub mod native {
     pub fn dropbear_set_camera_up(
         world_ptr: WorldPtr,
         entity_id: u64,
-        value: Vector3,
+        value: NVector3,
     ) -> DropbearNativeResult<()> {
         let world = convert_ptr!(world_ptr => hecs::World);
         let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
diff --git a/crates/eucalyptus-core/src/input.rs b/crates/eucalyptus-core/src/input.rs
index 2fa22d3..d765a32 100644
--- a/crates/eucalyptus-core/src/input.rs
+++ b/crates/eucalyptus-core/src/input.rs
@@ -114,7 +114,7 @@ pub mod shared {
     use crate::command::{CommandBuffer, WindowCommand};
     use crate::scripting::native::DropbearNativeError;
     use crate::scripting::result::DropbearNativeResult;
-    use crate::types::Vector2;
+    use crate::types::NVector2;
     use super::*;
 
     pub fn map_ordinal_to_mouse_button(ordinal: i32) -> Option<MouseButton> {
@@ -145,19 +145,19 @@ pub mod shared {
         }
     }
 
-    pub fn get_mouse_position(input: &InputState) -> Vector2 {
-        Vector2 {
+    pub fn get_mouse_position(input: &InputState) -> NVector2 {
+        NVector2 {
             x: input.mouse_pos.0,
             y: input.mouse_pos.1,
         }
     }
 
-    pub fn get_mouse_delta(input: &InputState) -> Vector2 {
-        input.mouse_delta.map(Vector2::from).unwrap_or(Vector2 { x: 0.0, y: 0.0 })
+    pub fn get_mouse_delta(input: &InputState) -> NVector2 {
+        input.mouse_delta.map(NVector2::from).unwrap_or(NVector2 { x: 0.0, y: 0.0 })
     }
 
-    pub fn get_last_mouse_pos(input: &InputState) -> Vector2 {
-        input.last_mouse_pos.map(Vector2::from).unwrap_or(Vector2 { x: 0.0, y: 0.0 })
+    pub fn get_last_mouse_pos(input: &InputState) -> NVector2 {
+        input.last_mouse_pos.map(NVector2::from).unwrap_or(NVector2 { x: 0.0, y: 0.0 })
     }
 
     pub fn set_cursor_locked(
@@ -369,7 +369,7 @@ pub mod native {
     use crate::convert_ptr;
     use crate::scripting::native::DropbearNativeError;
     use crate::scripting::result::DropbearNativeResult;
-    use crate::types::Vector2;
+    use crate::types::NVector2;
 
     pub fn dropbear_print_input_state(input_ptr: InputStatePtr) -> DropbearNativeResult<()> {
         let input = convert_ptr!(input_ptr => InputState);
@@ -382,7 +382,7 @@ pub mod native {
         DropbearNativeResult::Ok(super::shared::is_key_pressed(input, key_ordinal))
     }
 
-    pub fn dropbear_get_mouse_position(input_ptr: InputStatePtr) -> DropbearNativeResult<Vector2> {
+    pub fn dropbear_get_mouse_position(input_ptr: InputStatePtr) -> DropbearNativeResult<NVector2> {
         let input = convert_ptr!(input_ptr => InputState);
         DropbearNativeResult::Ok(super::shared::get_mouse_position(input))
     }
@@ -392,7 +392,7 @@ pub mod native {
         DropbearNativeResult::Ok(super::shared::is_mouse_button_pressed(input, btn_ordinal))
     }
 
-    pub fn dropbear_get_mouse_delta(input_ptr: InputStatePtr) -> DropbearNativeResult<Vector2> {
+    pub fn dropbear_get_mouse_delta(input_ptr: InputStatePtr) -> DropbearNativeResult<NVector2> {
         let input = convert_ptr!(input_ptr => InputState);
         DropbearNativeResult::Ok(super::shared::get_mouse_delta(input))
     }
@@ -412,7 +412,7 @@ pub mod native {
         super::shared::set_cursor_locked(input, sender, locked)
     }
 
-    pub fn dropbear_get_last_mouse_pos(input_ptr: InputStatePtr) -> DropbearNativeResult<Vector2> {
+    pub fn dropbear_get_last_mouse_pos(input_ptr: InputStatePtr) -> DropbearNativeResult<NVector2> {
         let input = convert_ptr!(input_ptr => InputState);
         DropbearNativeResult::Ok(super::shared::get_last_mouse_pos(input))
     }
diff --git a/crates/eucalyptus-core/src/input/gamepad.rs b/crates/eucalyptus-core/src/input/gamepad.rs
index 2b99487..024a932 100644
--- a/crates/eucalyptus-core/src/input/gamepad.rs
+++ b/crates/eucalyptus-core/src/input/gamepad.rs
@@ -7,7 +7,7 @@ pub mod shared {
     use crate::scripting::jni::utils::{FromJObject, ToJObject};
     use crate::scripting::native::DropbearNativeError;
     use crate::scripting::result::DropbearNativeResult;
-    use crate::types::Vector2;
+    use crate::types::NVector2;
 
     fn map_int_to_gamepad_button(ordinal: i32) -> Option<Button> {
         match ordinal {
@@ -51,29 +51,29 @@ pub mod shared {
         }
     }
 
-    pub fn get_left_stick(input: &InputState, gamepad_id: u64) -> Vector2 {
+    pub fn get_left_stick(input: &InputState, gamepad_id: u64) -> NVector2 {
         let Some(id) = get_gamepad_id(input, gamepad_id as usize) else {
-            return Vector2 {
+            return NVector2 {
                 x: 0.0,
                 y: 0.0
             }
         };
         let (x, y) = input.get_left_stick(id);
-        Vector2 { x: x as f64, y: y as f64 }
+        NVector2 { x: x as f64, y: y as f64 }
     }
 
-    pub fn get_right_stick(input: &InputState, gamepad_id: u64) -> Vector2 {
+    pub fn get_right_stick(input: &InputState, gamepad_id: u64) -> NVector2 {
         let Some(id) = get_gamepad_id(input, gamepad_id as usize) else {
-            return Vector2 {
+            return NVector2 {
                 x: 0.0,
                 y: 0.0
             }
         };
         let (x, y) = input.get_right_stick(id);
-        Vector2 { x: x as f64, y: y as f64 }
+        NVector2 { x: x as f64, y: y as f64 }
     }
 
-    impl ToJObject for Vector2 {
+    impl ToJObject for NVector2 {
         fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
             let cls = env.find_class("com/dropbear/math/Vector2d")
                 .map_err(|e| {
@@ -97,7 +97,7 @@ pub mod shared {
         }
     }
 
-    impl FromJObject for Vector2 {
+    impl FromJObject for NVector2 {
         fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self>
         where
             Self: Sized
@@ -114,7 +114,7 @@ pub mod shared {
                 .d()
                 .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
 
-            Ok(Vector2 {
+            Ok(NVector2 {
                 x: x_val,
                 y: y_val,
             })
@@ -186,7 +186,7 @@ pub mod native {
     use crate::input::{InputState};
     use crate::convert_ptr;
     use crate::scripting::result::DropbearNativeResult;
-    use crate::types::Vector2;
+    use crate::types::NVector2;
 
     pub fn dropbear_is_gamepad_button_pressed(
         input_ptr: InputStatePtr,
@@ -201,7 +201,7 @@ pub mod native {
     pub fn dropbear_get_left_stick_position(
         input_ptr: InputStatePtr,
         gamepad_id: u64
-    ) -> DropbearNativeResult<Vector2> {
+    ) -> DropbearNativeResult<NVector2> {
         let input = convert_ptr!(input_ptr => InputState);
         let vec = super::shared::get_left_stick(input, gamepad_id);
         DropbearNativeResult::Ok(vec)
@@ -210,7 +210,7 @@ pub mod native {
     pub fn dropbear_get_right_stick_position(
         input_ptr: InputStatePtr,
         gamepad_id: u64
-    ) -> DropbearNativeResult<Vector2> {
+    ) -> DropbearNativeResult<NVector2> {
         let input = convert_ptr!(input_ptr => InputState);
         let vec = super::shared::get_right_stick(input, gamepad_id);
         DropbearNativeResult::Ok(vec)
diff --git a/crates/eucalyptus-core/src/lighting.rs b/crates/eucalyptus-core/src/lighting.rs
index bfa05f1..0aa1dd2 100644
--- a/crates/eucalyptus-core/src/lighting.rs
+++ b/crates/eucalyptus-core/src/lighting.rs
@@ -214,7 +214,7 @@ pub mod jni {
 
 	use super::{JvmAttenuation, JvmColour, JvmRange};
 	use crate::scripting::jni::utils::{FromJObject, ToJObject};
-	use crate::types::Vector3;
+	use crate::types::NVector3;
 	use crate::{convert_jlong_to_entity, convert_ptr};
 	use dropbear_engine::entity::{EntityTransform, Transform};
 	use dropbear_engine::lighting::{LightComponent, LightType};
@@ -289,7 +289,7 @@ pub mod jni {
 			return std::ptr::null_mut();
 		};
 
-		match Vector3::from(t.position).to_jobject(&mut env) {
+		match NVector3::from(t.position).to_jobject(&mut env) {
 			Ok(obj) => obj.into_raw(),
 			Err(_) => std::ptr::null_mut(),
 		}
@@ -306,7 +306,7 @@ pub mod jni {
 		let world = convert_ptr!(mut world_ptr => World);
 		let entity = convert_jlong_to_entity!(entity_id);
 
-		let position: DVec3 = match Vector3::from_jobject(&mut env, &position) {
+		let position: DVec3 = match NVector3::from_jobject(&mut env, &position) {
 			Ok(v) => v.into(),
 			Err(e) => {
 				let _ = env.throw_new(
@@ -340,7 +340,7 @@ pub mod jni {
 		let forward = DVec3::new(0.0, 0.0, -1.0);
 		let dir = (t.rotation * forward).normalize_or_zero();
 
-		match Vector3::from(dir).to_jobject(&mut env) {
+		match NVector3::from(dir).to_jobject(&mut env) {
 			Ok(obj) => obj.into_raw(),
 			Err(_) => std::ptr::null_mut(),
 		}
@@ -357,7 +357,7 @@ pub mod jni {
 		let world = convert_ptr!(mut world_ptr => World);
 		let entity = convert_jlong_to_entity!(entity_id);
 
-		let dir: DVec3 = match Vector3::from_jobject(&mut env, &direction) {
+		let dir: DVec3 = match NVector3::from_jobject(&mut env, &direction) {
 			Ok(v) => DVec3::from(v),
 			Err(e) => {
 				let _ = env.throw_new(
diff --git a/crates/eucalyptus-core/src/mesh.rs b/crates/eucalyptus-core/src/mesh.rs
index 9624162..554fb03 100644
--- a/crates/eucalyptus-core/src/mesh.rs
+++ b/crates/eucalyptus-core/src/mesh.rs
@@ -212,7 +212,7 @@ pub mod jni {
             };
 
             let Some(model_cache) = asset.get_pointer(Const("model_cache")) else {
-                let _ = env.throw_new("java/lang/IllegalArgumentException", "Unable to locate model_cache pointer within the asset_old registry");
+                let _ = env.throw_new("java/lang/IllegalArgumentException", "Unable to locate model_cache pointer within the asset registry");
                 return;
             };
 
diff --git a/crates/eucalyptus-core/src/physics.rs b/crates/eucalyptus-core/src/physics.rs
index e5d3df1..3532097 100644
--- a/crates/eucalyptus-core/src/physics.rs
+++ b/crates/eucalyptus-core/src/physics.rs
@@ -250,24 +250,24 @@ impl Default for PhysicsState {
 
 pub mod shared {
     use crate::physics::PhysicsState;
-    use crate::types::ColliderFFI;
-    use crate::types::Vector3;
+    use crate::types::NCollider;
+    use crate::types::NVector3;
     use hecs::Entity;
     use rapier3d::prelude::ColliderHandle;
 
-    pub fn get_gravity(physics: &PhysicsState) -> Vector3 {
-        Vector3::from(physics.gravity)
+    pub fn get_gravity(physics: &PhysicsState) -> NVector3 {
+        NVector3::from(physics.gravity)
     }
 
-    pub fn set_gravity(physics: &mut PhysicsState, new: Vector3) {
+    pub fn set_gravity(physics: &mut PhysicsState, new: NVector3) {
         physics.gravity = new.to_float_array();
     }
 
-    fn collider_handle_from_ffi(collider: &ColliderFFI) -> ColliderHandle {
+    fn collider_handle_from_ffi(collider: &NCollider) -> ColliderHandle {
         ColliderHandle::from_raw_parts(collider.index.index, collider.index.generation)
     }
 
-    pub fn overlapping(physics: &PhysicsState, collider1: &ColliderFFI, collider2: &ColliderFFI) -> bool {
+    pub fn overlapping(physics: &PhysicsState, collider1: &NCollider, collider2: &NCollider) -> bool {
         let h1 = collider_handle_from_ffi(collider1);
         let h2 = collider_handle_from_ffi(collider2);
 
@@ -281,7 +281,7 @@ pub mod shared {
             .unwrap_or(false)
     }
 
-    pub fn triggering(physics: &PhysicsState, collider1: &ColliderFFI, collider2: &ColliderFFI) -> bool {
+    pub fn triggering(physics: &PhysicsState, collider1: &NCollider, collider2: &NCollider) -> bool {
         let h1 = collider_handle_from_ffi(collider1);
         let h2 = collider_handle_from_ffi(collider2);
 
@@ -334,7 +334,7 @@ pub mod jni {
     use crate::physics::nalgebra;
     use crate::physics::PhysicsState;
     use crate::scripting::jni::utils::{FromJObject, ToJObject};
-    use crate::types::{ColliderFFI, IndexNative, RayHit, ShapeCastHitFFI, Vector3};
+    use crate::types::{NCollider, IndexNative, RayHit, ShapeCastHitFFI, NVector3};
     use hecs::Entity;
     use jni::objects::{JClass, JObject};
     use jni::sys::{jboolean, jdouble, jlong, jobject};
@@ -372,7 +372,7 @@ pub mod jni {
         new_gravity: JObject,
     ) {
         let mut physics = crate::convert_ptr!(mut physics_handle => PhysicsState);
-        let vec3 = match Vector3::from_jobject(&mut env, &new_gravity) {
+        let vec3 = match NVector3::from_jobject(&mut env, &new_gravity) {
             Ok(v) => v,
             Err(e) => {
                 let _ = env.throw_new("java/lang/RuntimeException", format!("Unable to create new Vector3d object for gravity: {}", e));
@@ -397,7 +397,7 @@ pub mod jni {
 
         let qp = physics.broad_phase.as_query_pipeline(&DefaultQueryDispatcher, &physics.bodies, &physics.colliders, QueryFilter::new());
 
-        let origin = match Vector3::from_jobject(&mut env, &origin) {
+        let origin = match NVector3::from_jobject(&mut env, &origin) {
             Ok(v) => v,
             Err(e) => {
                 let _ = env.throw_new("java/lang/RuntimeException", format!("Unable to create a new rust Vector3 object: {}", e));
@@ -405,7 +405,7 @@ pub mod jni {
             }
         };
 
-        let dir = match Vector3::from_jobject(&mut env, &direction) {
+        let dir = match NVector3::from_jobject(&mut env, &direction) {
             Ok(v) => v,
             Err(e) => {
                 let _ = env.throw_new("java/lang/RuntimeException", format!("Unable to create a new rust Vector3 object: {}", e));
@@ -435,7 +435,7 @@ pub mod jni {
                 let entity = physics.entity_label_map.iter().find(|(_, l)| *l == label);
                 if let Some((e, _)) = entity {
                     let rayhit = RayHit {
-                        collider: crate::types::ColliderFFI {
+                        collider: crate::types::NCollider {
                             index: IndexNative::from(raw),
                             entity_id: e.to_bits().get(),
                             id: raw.into_raw_parts().0,
@@ -457,7 +457,7 @@ pub mod jni {
                 eprintln!("Unknown collider, still returning value without entity_id");
 
                 let rayhit = RayHit {
-                    collider: crate::types::ColliderFFI {
+                    collider: crate::types::NCollider {
                         index: IndexNative::from(raw),
                         entity_id: Entity::DANGLING.to_bits().get(),
                         id: raw.into_raw_parts().0,
@@ -478,7 +478,7 @@ pub mod jni {
         }
     }
 
-    fn collider_ffi_from_handle(physics: &PhysicsState, handle: rapier3d::prelude::ColliderHandle) -> ColliderFFI {
+    fn collider_ffi_from_handle(physics: &PhysicsState, handle: rapier3d::prelude::ColliderHandle) -> NCollider {
         let (idx, generation) = handle.into_raw_parts();
 
         let mut found_label = None;
@@ -505,7 +505,7 @@ pub mod jni {
             Entity::DANGLING.to_bits().get()
         };
 
-        ColliderFFI {
+        NCollider {
             index: IndexNative { index: idx, generation },
             entity_id,
             id: idx,
@@ -550,7 +550,7 @@ pub mod jni {
             QueryFilter::new(),
         );
 
-        let origin = match Vector3::from_jobject(&mut env, &origin) {
+        let origin = match NVector3::from_jobject(&mut env, &origin) {
             Ok(v) => v,
             Err(e) => {
                 let _ = env.throw_new(
@@ -561,7 +561,7 @@ pub mod jni {
             }
         };
 
-        let direction = match Vector3::from_jobject(&mut env, &direction) {
+        let direction = match NVector3::from_jobject(&mut env, &direction) {
             Ok(v) => v,
             Err(e) => {
                 let _ = env.throw_new(
@@ -588,7 +588,7 @@ pub mod jni {
             return std::ptr::null_mut();
         }
 
-        let dir_unit = Vector3 {
+        let dir_unit = NVector3 {
             x: direction.x / dir_len,
             y: direction.y / dir_len,
             z: direction.z / dir_len,
@@ -614,10 +614,10 @@ pub mod jni {
         let hit = ShapeCastHitFFI {
             collider,
             distance: toi.time_of_impact as f64,
-            witness1: Vector3::from([toi.witness1.x, toi.witness1.y, toi.witness1.z]),
-            witness2: Vector3::from([toi.witness2.x, toi.witness2.y, toi.witness2.z]),
-            normal1: Vector3::from([toi.normal1.x, toi.normal1.y, toi.normal1.z]),
-            normal2: Vector3::from([toi.normal2.x, toi.normal2.y, toi.normal2.z]),
+            witness1: NVector3::from([toi.witness1.x, toi.witness1.y, toi.witness1.z]),
+            witness2: NVector3::from([toi.witness2.x, toi.witness2.y, toi.witness2.z]),
+            normal1: NVector3::from([toi.normal1.x, toi.normal1.y, toi.normal1.z]),
+            normal2: NVector3::from([toi.normal2.x, toi.normal2.y, toi.normal2.z]),
             status: toi.status,
         };
 
@@ -642,7 +642,7 @@ pub mod jni {
         collider2: JObject,
     ) -> jboolean {
         let physics = crate::convert_ptr!(physics_handle => PhysicsState);
-        let Ok(collider1) = ColliderFFI::from_jobject(&mut env, &collider1) else {
+        let Ok(collider1) = NCollider::from_jobject(&mut env, &collider1) else {
             let _ = env.throw_new(
                 "java/lang/RuntimeException",
                 "Unable to convert a Collider object [collider1] to a rust ColliderFFI"
@@ -650,7 +650,7 @@ pub mod jni {
             return false.into();
         };
 
-        let Ok(collider2) = ColliderFFI::from_jobject(&mut env, &collider2) else {
+        let Ok(collider2) = NCollider::from_jobject(&mut env, &collider2) else {
             let _ = env.throw_new(
                 "java/lang/RuntimeException",
                 "Unable to convert a Collider object [collider2] to a rust ColliderFFI"
@@ -670,7 +670,7 @@ pub mod jni {
         collider2: JObject,
     ) -> jboolean {
         let physics = crate::convert_ptr!(physics_handle => PhysicsState);
-        let Ok(collider1) = ColliderFFI::from_jobject(&mut env, &collider1) else {
+        let Ok(collider1) = NCollider::from_jobject(&mut env, &collider1) else {
             let _ = env.throw_new(
                 "java/lang/RuntimeException",
                 "Unable to convert a Collider object [collider1] to a rust ColliderFFI"
@@ -678,7 +678,7 @@ pub mod jni {
             return false.into();
         };
 
-        let Ok(collider2) = ColliderFFI::from_jobject(&mut env, &collider2) else {
+        let Ok(collider2) = NCollider::from_jobject(&mut env, &collider2) else {
             let _ = env.throw_new(
                 "java/lang/RuntimeException",
                 "Unable to convert a Collider object [collider2] to a rust ColliderFFI"
diff --git a/crates/eucalyptus-core/src/physics/collider.rs b/crates/eucalyptus-core/src/physics/collider.rs
index cffc72e..3bf52bc 100644
--- a/crates/eucalyptus-core/src/physics/collider.rs
+++ b/crates/eucalyptus-core/src/physics/collider.rs
@@ -612,12 +612,12 @@ impl WireframeGeometry {
 
 pub mod shared {
     use crate::physics::PhysicsState;
-    use crate::types::ColliderFFI;
+    use crate::types::NCollider;
     use rapier3d::prelude::ColliderHandle;
 
     pub fn get_collider_mut<'a>(
         physics: &'a mut PhysicsState,
-        ffi: &ColliderFFI
+        ffi: &NCollider
     ) -> Option<&'a mut rapier3d::prelude::Collider> {
         let handle = ColliderHandle::from_raw_parts(ffi.index.index, ffi.index.generation);
         physics.colliders.get_mut(handle)
@@ -625,7 +625,7 @@ pub mod shared {
 
     pub fn get_collider<'a>(
         physics: &'a PhysicsState,
-        ffi: &ColliderFFI
+        ffi: &NCollider
     ) -> Option<&'a rapier3d::prelude::Collider> {
         let handle = ColliderHandle::from_raw_parts(ffi.index.index, ffi.index.generation);
         physics.colliders.get(handle)
@@ -638,7 +638,7 @@ pub mod jni {
     use crate::physics::collider::ColliderShape;
     use crate::physics::PhysicsState;
     use crate::scripting::jni::utils::{FromJObject, ToJObject};
-    use crate::types::ColliderFFI;
+    use crate::types::NCollider;
     use glam::DQuat;
     use jni::objects::{JClass, JObject};
     use jni::sys::{jboolean, jdouble, jlong, jobject};
@@ -655,7 +655,7 @@ pub mod jni {
     ) -> jobject {
         let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
 
-        let ffi = match ColliderFFI::from_jobject(&mut env, &collider_obj) {
+        let ffi = match NCollider::from_jobject(&mut env, &collider_obj) {
             Ok(v) => v,
             Err(_) => return std::ptr::null_mut(),
         };
@@ -721,7 +721,7 @@ pub mod jni {
     ) {
         let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
 
-        let ffi = match ColliderFFI::from_jobject(&mut env, &collider_obj) {
+        let ffi = match NCollider::from_jobject(&mut env, &collider_obj) {
             Ok(v) => v,
             Err(_) => return,
         };
@@ -767,7 +767,7 @@ pub mod jni {
         collider_obj: JObject,
     ) -> jdouble {
         let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
-        let ffi = ColliderFFI::from_jobject(&mut env, &collider_obj).ok().unwrap();
+        let ffi = NCollider::from_jobject(&mut env, &collider_obj).ok().unwrap();
 
         if let Some(col) = get_collider(&physics, &ffi) {
             col.density() as jdouble
@@ -785,7 +785,7 @@ pub mod jni {
         density: jdouble,
     ) {
         let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
-        if let Ok(ffi) = ColliderFFI::from_jobject(&mut env, &collider_obj) {
+        if let Ok(ffi) = NCollider::from_jobject(&mut env, &collider_obj) {
             if let Some(col) = get_collider_mut(physics, &ffi) {
                 col.set_density(density as f32);
             }
@@ -800,7 +800,7 @@ pub mod jni {
         collider_obj: JObject,
     ) -> jdouble {
         let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
-        let ffi = ColliderFFI::from_jobject(&mut env, &collider_obj).ok().unwrap();
+        let ffi = NCollider::from_jobject(&mut env, &collider_obj).ok().unwrap();
         if let Some(col) = get_collider(&physics, &ffi) {
             col.friction() as jdouble
         } else { 0.0 }
@@ -815,7 +815,7 @@ pub mod jni {
         friction: jdouble,
     ) {
         let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
-        if let Ok(ffi) = ColliderFFI::from_jobject(&mut env, &collider_obj) {
+        if let Ok(ffi) = NCollider::from_jobject(&mut env, &collider_obj) {
             if let Some(col) = get_collider_mut(physics, &ffi) {
                 col.set_friction(friction as f32);
             }
@@ -830,7 +830,7 @@ pub mod jni {
         collider_obj: JObject,
     ) -> jdouble {
         let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
-        let ffi = ColliderFFI::from_jobject(&mut env, &collider_obj).ok().unwrap();
+        let ffi = NCollider::from_jobject(&mut env, &collider_obj).ok().unwrap();
         if let Some(col) = get_collider(&physics, &ffi) {
             col.restitution() as jdouble
         } else { 0.0 }
@@ -845,7 +845,7 @@ pub mod jni {
         restitution: jdouble,
     ) {
         let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
-        if let Ok(ffi) = ColliderFFI::from_jobject(&mut env, &collider_obj) {
+        if let Ok(ffi) = NCollider::from_jobject(&mut env, &collider_obj) {
             if let Some(col) = get_collider_mut(physics, &ffi) {
                 col.set_restitution(restitution as f32);
             }
@@ -860,7 +860,7 @@ pub mod jni {
         collider_obj: JObject,
     ) -> jdouble {
         let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
-        let ffi = ColliderFFI::from_jobject(&mut env, &collider_obj).ok().unwrap();
+        let ffi = NCollider::from_jobject(&mut env, &collider_obj).ok().unwrap();
         if let Some(col) = get_collider(&physics, &ffi) {
             col.mass() as jdouble
         } else { 0.0 }
@@ -875,7 +875,7 @@ pub mod jni {
         mass: jdouble,
     ) {
         let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
-        if let Ok(ffi) = ColliderFFI::from_jobject(&mut env, &collider_obj) {
+        if let Ok(ffi) = NCollider::from_jobject(&mut env, &collider_obj) {
             if let Some(col) = get_collider_mut(physics, &ffi) {
                 col.set_mass(mass as f32);
             }
@@ -890,7 +890,7 @@ pub mod jni {
         collider_obj: JObject,
     ) -> jboolean {
         let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
-        let ffi = ColliderFFI::from_jobject(&mut env, &collider_obj).ok().unwrap();
+        let ffi = NCollider::from_jobject(&mut env, &collider_obj).ok().unwrap();
         if let Some(col) = get_collider(&physics, &ffi) {
             if col.is_sensor() { 1 } else { 0 }
         } else { 0 }
@@ -905,7 +905,7 @@ pub mod jni {
         is_sensor: jboolean,
     ) {
         let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
-        if let Ok(ffi) = ColliderFFI::from_jobject(&mut env, &collider_obj) {
+        if let Ok(ffi) = NCollider::from_jobject(&mut env, &collider_obj) {
             if let Some(col) = get_collider_mut(physics, &ffi) {
                 col.set_sensor(is_sensor != 0);
             }
@@ -920,14 +920,14 @@ pub mod jni {
         collider_obj: JObject,
     ) -> jobject {
         let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
-        let ffi = match ColliderFFI::from_jobject(&mut env, &collider_obj) {
+        let ffi = match NCollider::from_jobject(&mut env, &collider_obj) {
             Ok(v) => v,
             Err(_) => return std::ptr::null_mut(),
         };
 
         if let Some(col) = get_collider(&physics, &ffi) {
             let t: Vector = col.translation();
-            let vec = crate::types::Vector3::new(t.x as f64, t.y as f64, t.z as f64);
+            let vec = crate::types::NVector3::new(t.x as f64, t.y as f64, t.z as f64);
             match vec.to_jobject(&mut env) {
                 Ok(o) => o.into_raw(),
                 Err(_) => std::ptr::null_mut(),
@@ -946,8 +946,8 @@ pub mod jni {
         vec_obj: JObject,
     ) {
         let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
-        if let Ok(ffi) = ColliderFFI::from_jobject(&mut env, &collider_obj) {
-            if let Ok(vec) = crate::types::Vector3::from_jobject(&mut env, &vec_obj) {
+        if let Ok(ffi) = NCollider::from_jobject(&mut env, &collider_obj) {
+            if let Ok(vec) = crate::types::NVector3::from_jobject(&mut env, &vec_obj) {
                 if let Some(col) = get_collider_mut(physics, &ffi) {
                     let t = Vector::new(vec.x as f32, vec.y as f32, vec.z as f32);
                     col.set_translation(t);
@@ -964,7 +964,7 @@ pub mod jni {
         collider_obj: JObject,
     ) -> jobject {
         let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
-        let ffi = match ColliderFFI::from_jobject(&mut env, &collider_obj) {
+        let ffi = match NCollider::from_jobject(&mut env, &collider_obj) {
             Ok(v) => v,
             Err(_) => return std::ptr::null_mut(),
         };
@@ -973,7 +973,7 @@ pub mod jni {
             let r: Rotation = col.rotation();
             let q = DQuat::from_xyzw(r.x as f64, r.y as f64, r.z as f64, r.w as f64);
             let euler = q.to_euler(glam::EulerRot::XYZ);
-            let vec = crate::types::Vector3::new(euler.0, euler.1, euler.2);
+            let vec = crate::types::NVector3::new(euler.0, euler.1, euler.2);
 
             match vec.to_jobject(&mut env) {
                 Ok(o) => o.into_raw(),
@@ -993,8 +993,8 @@ pub mod jni {
         vec_obj: JObject,
     ) {
         let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
-        if let Ok(ffi) = ColliderFFI::from_jobject(&mut env, &collider_obj) {
-            if let Ok(vec) = crate::types::Vector3::from_jobject(&mut env, &vec_obj) {
+        if let Ok(ffi) = NCollider::from_jobject(&mut env, &collider_obj) {
+            if let Ok(vec) = crate::types::NVector3::from_jobject(&mut env, &vec_obj) {
                 if let Some(col) = get_collider_mut(physics, &ffi) {
                     let q = DQuat::from_euler(glam::EulerRot::XYZ, vec.x, vec.y, vec.z);
                     let r = Rotation::from_array([q.w as f32, q.x as f32, q.y as f32, q.z as f32]);
@@ -1012,23 +1012,23 @@ pub mod native {
     use crate::physics::collider::shared::{get_collider, get_collider_mut};
     use crate::physics::PhysicsState;
     use crate::ptr::PhysicsStatePtr;
-    use crate::types::Vector3;
+    use crate::types::NVector3;
 
     use crate::scripting::native::DropbearNativeError;
     use crate::scripting::result::DropbearNativeResult;
-    use crate::types::{ColliderFFI, ColliderShapeFFI, ColliderShapeType};
+    use crate::types::{NCollider, NColliderShape, ColliderShapeType};
     use glam::DQuat;
     use rapier3d::prelude::{SharedShape, TypedShape, Vector};
 
     pub fn dropbear_get_collider_shape(
         physics_ptr: PhysicsStatePtr,
-        ffi: ColliderFFI,
-    ) -> DropbearNativeResult<ColliderShapeFFI> {
+        ffi: NCollider,
+    ) -> DropbearNativeResult<NColliderShape> {
         let physics = convert_ptr!(physics_ptr => PhysicsState);
 
         if let Some(collider) = get_collider(physics, &ffi) {
             let rapier_shape = collider.shape();
-            let mut result = ColliderShapeFFI {
+            let mut result = NColliderShape {
                 shape_type: ColliderShapeType::Box,
                 radius: 0.0, half_height: 0.0,
                 half_extents_x: 0.0, half_extents_y: 0.0, half_extents_z: 0.0,
@@ -1070,8 +1070,8 @@ pub mod native {
 
     pub fn dropbear_set_collider_shape(
         physics_ptr: PhysicsStatePtr,
-        ffi: ColliderFFI,
-        shape: ColliderShapeFFI,
+        ffi: NCollider,
+        shape: NColliderShape,
     ) -> DropbearNativeResult<()> {
         let physics = convert_ptr!(mut physics_ptr => PhysicsState);
 
@@ -1092,7 +1092,7 @@ pub mod native {
 
     pub fn dropbear_get_collider_density(
         physics_ptr: PhysicsStatePtr,
-        ffi: ColliderFFI,
+        ffi: NCollider,
     ) -> DropbearNativeResult<f64> {
         let physics = convert_ptr!(physics_ptr => PhysicsState);
         if let Some(col) = get_collider(physics, &ffi) {
@@ -1104,7 +1104,7 @@ pub mod native {
 
     pub fn dropbear_set_collider_density(
         physics_ptr: PhysicsStatePtr,
-        ffi: ColliderFFI,
+        ffi: NCollider,
         density: f64,
     ) -> DropbearNativeResult<()> {
         let physics = convert_ptr!(mut physics_ptr => PhysicsState);
@@ -1118,7 +1118,7 @@ pub mod native {
 
     pub fn dropbear_get_collider_friction(
         physics_ptr: PhysicsStatePtr,
-        ffi: ColliderFFI,
+        ffi: NCollider,
     ) -> DropbearNativeResult<f64> {
         let physics = convert_ptr!(physics_ptr => PhysicsState);
         if let Some(col) = get_collider(physics, &ffi) {
@@ -1130,7 +1130,7 @@ pub mod native {
 
     pub fn dropbear_set_collider_friction(
         physics_ptr: PhysicsStatePtr,
-        ffi: ColliderFFI,
+        ffi: NCollider,
         friction: f64,
     ) -> DropbearNativeResult<()> {
         let physics = convert_ptr!(mut physics_ptr => PhysicsState);
@@ -1144,7 +1144,7 @@ pub mod native {
 
     pub fn dropbear_get_collider_restitution(
         physics_ptr: PhysicsStatePtr,
-        ffi: ColliderFFI,
+        ffi: NCollider,
     ) -> DropbearNativeResult<f64> {
         let physics = convert_ptr!(physics_ptr => PhysicsState);
         if let Some(col) = get_collider(physics, &ffi) {
@@ -1156,7 +1156,7 @@ pub mod native {
 
     pub fn dropbear_set_collider_restitution(
         physics_ptr: PhysicsStatePtr,
-        ffi: ColliderFFI,
+        ffi: NCollider,
         restitution: f64,
     ) -> DropbearNativeResult<()> {
         let physics = convert_ptr!(mut physics_ptr => PhysicsState);
@@ -1170,7 +1170,7 @@ pub mod native {
 
     pub fn dropbear_get_collider_mass(
         physics_ptr: PhysicsStatePtr,
-        ffi: ColliderFFI,
+        ffi: NCollider,
     ) -> DropbearNativeResult<f64> {
         let physics = convert_ptr!(physics_ptr => PhysicsState);
         if let Some(col) = get_collider(physics, &ffi) {
@@ -1182,7 +1182,7 @@ pub mod native {
 
     pub fn dropbear_set_collider_mass(
         physics_ptr: PhysicsStatePtr,
-        ffi: ColliderFFI,
+        ffi: NCollider,
         mass: f64,
     ) -> DropbearNativeResult<()> {
         let physics = convert_ptr!(mut physics_ptr => PhysicsState);
@@ -1196,7 +1196,7 @@ pub mod native {
 
     pub fn dropbear_get_collider_is_sensor(
         physics_ptr: PhysicsStatePtr,
-        ffi: ColliderFFI,
+        ffi: NCollider,
     ) -> DropbearNativeResult<bool> {
         let physics = convert_ptr!(physics_ptr => PhysicsState);
         if let Some(col) = get_collider(physics, &ffi) {
@@ -1208,7 +1208,7 @@ pub mod native {
 
     pub fn dropbear_set_collider_is_sensor(
         physics_ptr: PhysicsStatePtr,
-        ffi: ColliderFFI,
+        ffi: NCollider,
         is_sensor: bool,
     ) -> DropbearNativeResult<()> {
         let physics = convert_ptr!(mut physics_ptr => PhysicsState);
@@ -1222,12 +1222,12 @@ pub mod native {
 
     pub fn dropbear_get_collider_translation(
         physics_ptr: PhysicsStatePtr,
-        ffi: ColliderFFI,
-    ) -> DropbearNativeResult<Vector3> {
+        ffi: NCollider,
+    ) -> DropbearNativeResult<NVector3> {
         let physics = convert_ptr!(physics_ptr => PhysicsState);
         if let Some(col) = get_collider(physics, &ffi) {
             let t = col.translation();
-            DropbearNativeResult::Ok(Vector3 { x: t.x as f64, y: t.y as f64, z: t.z as f64 })
+            DropbearNativeResult::Ok(NVector3 { x: t.x as f64, y: t.y as f64, z: t.z as f64 })
         } else {
             DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
         }
@@ -1235,8 +1235,8 @@ pub mod native {
 
     pub fn dropbear_set_collider_translation(
         physics_ptr: PhysicsStatePtr,
-        ffi: ColliderFFI,
-        translation: Vector3,
+        ffi: NCollider,
+        translation: NVector3,
     ) -> DropbearNativeResult<()> {
         let physics = convert_ptr!(mut physics_ptr => PhysicsState);
         if let Some(col) = get_collider_mut(physics, &ffi) {
@@ -1250,14 +1250,14 @@ pub mod native {
 
     pub fn dropbear_get_collider_rotation(
         physics_ptr: PhysicsStatePtr,
-        ffi: ColliderFFI,
-    ) -> DropbearNativeResult<Vector3> {
+        ffi: NCollider,
+    ) -> DropbearNativeResult<NVector3> {
         let physics = convert_ptr!(physics_ptr => PhysicsState);
         if let Some(col) = get_collider(physics, &ffi) {
             let r = col.rotation();
             let q: DQuat = DQuat::from_xyzw(r.x as f64, r.y as f64, r.z as f64, r.w as f64);
             let (x, y, z) = q.to_euler(glam::EulerRot::XYZ);
-            DropbearNativeResult::Ok(Vector3 { x, y, z })
+            DropbearNativeResult::Ok(NVector3 { x, y, z })
         } else {
             DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
         }
@@ -1265,8 +1265,8 @@ pub mod native {
 
     pub fn dropbear_set_collider_rotation(
         physics_ptr: PhysicsStatePtr,
-        ffi: ColliderFFI,
-        rotation: Vector3,
+        ffi: NCollider,
+        rotation: NVector3,
     ) -> DropbearNativeResult<()> {
         let physics = convert_ptr!(mut physics_ptr => PhysicsState);
         if let Some(col) = get_collider_mut(physics, &ffi) {
diff --git a/crates/eucalyptus-core/src/physics/collider/collidergroup.rs b/crates/eucalyptus-core/src/physics/collider/collidergroup.rs
index 4395ccf..5251de7 100644
--- a/crates/eucalyptus-core/src/physics/collider/collidergroup.rs
+++ b/crates/eucalyptus-core/src/physics/collider/collidergroup.rs
@@ -65,7 +65,7 @@ pub mod shared {
 pub mod jni {
     #![allow(non_snake_case)]
     use crate::physics::collider::ColliderGroup;
-    use crate::types::{ColliderFFI, IndexNative};
+    use crate::types::{NCollider, IndexNative};
     use hecs::World;
     use jni::objects::{JClass, JObject};
     use jni::sys::{jboolean, jlong, jobjectArray};
@@ -106,13 +106,13 @@ pub mod jni {
                 .get(&entity)
                 .and_then(|label| physics.colliders_entity_map.get(label));
 
-            let mut colliders: Vec<ColliderFFI> = Vec::new();
+            let mut colliders: Vec<NCollider> = Vec::new();
 
             if let Some(handles) = handles_opt {
                 for (_, handle) in handles {
                     let (idx, generation) = handle.into_raw_parts();
 
-                    let col = ColliderFFI {
+                    let col = NCollider {
                         index: IndexNative {
                             index: idx,
                             generation,
@@ -172,7 +172,7 @@ pub mod native {
     use crate::ptr::{PhysicsStatePtr, WorldPtr};
     use crate::scripting::native::DropbearNativeError;
     use crate::scripting::result::DropbearNativeResult;
-    use crate::types::{ColliderFFI, IndexNative};
+    use crate::types::{NCollider, IndexNative};
     use hecs::Entity;
 
     pub fn dropbear_collider_group_exists_for_entity(
@@ -190,7 +190,7 @@ pub mod native {
         physics_ptr: PhysicsStatePtr,
         entity_id: u64,
         out_count: *mut usize,
-    ) -> DropbearNativeResult<*mut ColliderFFI> {
+    ) -> DropbearNativeResult<*mut NCollider> {
         let world = convert_ptr!(world_ptr => hecs::World);
         let physics = convert_ptr!(physics_ptr => PhysicsState);
         let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
@@ -208,13 +208,13 @@ pub mod native {
             .get(&entity)
             .and_then(|label| physics.colliders_entity_map.get(label));
 
-        let mut colliders: Vec<ColliderFFI> = Vec::new();
+        let mut colliders: Vec<NCollider> = Vec::new();
 
         if let Some(handles) = handles_opt {
             for (_, handle) in handles {
                 let (idx, generation) = handle.into_raw_parts();
 
-                let col = ColliderFFI {
+                let col = NCollider {
                     index: IndexNative {
                         index: idx,
                         generation,
@@ -236,7 +236,7 @@ pub mod native {
     }
 
     pub fn dropbear_free_collider_array(
-        ptr: *mut ColliderFFI,
+        ptr: *mut NCollider,
         count: usize,
     ) -> DropbearNativeResult<()> {
         if ptr.is_null() {
diff --git a/crates/eucalyptus-core/src/physics/kcc.rs b/crates/eucalyptus-core/src/physics/kcc.rs
index 6500a5a..60ec4c4 100644
--- a/crates/eucalyptus-core/src/physics/kcc.rs
+++ b/crates/eucalyptus-core/src/physics/kcc.rs
@@ -47,7 +47,7 @@ pub mod jni {
     use crate::physics::PhysicsState;
     use crate::scripting::jni::utils::FromJObject;
     use crate::states::Label;
-    use crate::types::Vector3;
+    use crate::types::NVector3;
     use jni::objects::JValue;
     use jni::sys::{jint, jobjectArray};
     use crate::types::IndexNative;
@@ -81,7 +81,7 @@ pub mod jni {
         let physics_state = convert_ptr!(mut physics_handle => PhysicsState);
         let entity = convert_jlong_to_entity!(entity);
 
-        let movement = match Vector3::from_jobject(&mut env, &translation) {
+        let movement = match NVector3::from_jobject(&mut env, &translation) {
             Ok(v) => v,
             Err(e) => {
                 let _ = env.throw_new("java/lang/RuntimeException", format!("Unable to convert JObject to Vector3: {}", e));
diff --git a/crates/eucalyptus-core/src/physics/kcc/character_collision.rs b/crates/eucalyptus-core/src/physics/kcc/character_collision.rs
index 85e2da3..93f2910 100644
--- a/crates/eucalyptus-core/src/physics/kcc/character_collision.rs
+++ b/crates/eucalyptus-core/src/physics/kcc/character_collision.rs
@@ -3,7 +3,7 @@ use ::jni::objects::{JObject, JValue};
 use crate::scripting::jni::utils::ToJObject;
 use crate::scripting::native::DropbearNativeError;
 use crate::scripting::result::DropbearNativeResult;
-use crate::types::{ColliderFFI, IndexNative, Vector3};
+use crate::types::{NCollider, IndexNative, NVector3};
 use dropbear_engine::entity::Transform as DbTransform;
 use hecs::{Entity, World};
 use rapier3d::control::CharacterCollision;
@@ -27,12 +27,12 @@ fn get_collision_from_world(world: &World, entity: Entity, collision_handle: Ind
 		.ok_or(DropbearNativeError::NoSuchHandle)
 }
 
-fn collider_ffi_from_handle(world: &World, handle: rapier3d::prelude::ColliderHandle) -> Option<ColliderFFI> {
+fn collider_ffi_from_handle(world: &World, handle: rapier3d::prelude::ColliderHandle) -> Option<NCollider> {
 	let (idx, generation) = handle.into_raw_parts();
 
 	for (entity, group) in world.query::<(Entity, &ColliderGroup)>().iter() {
 		if group.colliders.iter().any(|c| c.id == idx) {
-			return Some(ColliderFFI {
+			return Some(NCollider {
 				index: IndexNative { index: idx, generation },
 				entity_id: entity.to_bits().get(),
 				id: idx,
@@ -80,7 +80,7 @@ pub mod shared {
 	use glam::{DQuat, DVec3};
 	use rapier3d::na::Quaternion;
 
-	pub fn get_collider(world: &World, entity: Entity, collision_handle: IndexNative) -> DropbearNativeResult<ColliderFFI> {
+	pub fn get_collider(world: &World, entity: Entity, collision_handle: IndexNative) -> DropbearNativeResult<NCollider> {
 		let collision = super::get_collision_from_world(world, entity, collision_handle)?;
 		collider_ffi_from_handle(world, collision.handle)
 			.ok_or(DropbearNativeError::PhysicsObjectNotFound)
@@ -101,16 +101,16 @@ pub mod shared {
 		})
 	}
 
-	pub fn get_translation_applied(world: &World, entity: Entity, collision_handle: IndexNative) -> DropbearNativeResult<Vector3> {
+	pub fn get_translation_applied(world: &World, entity: Entity, collision_handle: IndexNative) -> DropbearNativeResult<NVector3> {
 		let collision = super::get_collision_from_world(world, entity, collision_handle)?;
 		let v = collision.translation_applied;
-		Ok(Vector3 { x: v.x as f64, y: v.y as f64, z: v.z as f64 })
+		Ok(NVector3 { x: v.x as f64, y: v.y as f64, z: v.z as f64 })
 	}
 
-	pub fn get_translation_remaining(world: &World, entity: Entity, collision_handle: IndexNative) -> DropbearNativeResult<Vector3> {
+	pub fn get_translation_remaining(world: &World, entity: Entity, collision_handle: IndexNative) -> DropbearNativeResult<NVector3> {
 		let collision = super::get_collision_from_world(world, entity, collision_handle)?;
 		let v = collision.translation_remaining;
-		Ok(Vector3 { x: v.x as f64, y: v.y as f64, z: v.z as f64 })
+		Ok(NVector3 { x: v.x as f64, y: v.y as f64, z: v.z as f64 })
 	}
 
 	pub fn get_time_of_impact(world: &World, entity: Entity, collision_handle: IndexNative) -> DropbearNativeResult<f64> {
@@ -118,28 +118,28 @@ pub mod shared {
 		Ok(collision.hit.time_of_impact as f64)
 	}
 
-	pub fn get_witness1(world: &World, entity: Entity, collision_handle: IndexNative) -> DropbearNativeResult<Vector3> {
+	pub fn get_witness1(world: &World, entity: Entity, collision_handle: IndexNative) -> DropbearNativeResult<NVector3> {
 		let collision = super::get_collision_from_world(world, entity, collision_handle)?;
 		let p = collision.hit.witness1;
-		Ok(Vector3 { x: p.x as f64, y: p.y as f64, z: p.z as f64 })
+		Ok(NVector3 { x: p.x as f64, y: p.y as f64, z: p.z as f64 })
 	}
 
-	pub fn get_witness2(world: &World, entity: Entity, collision_handle: IndexNative) -> DropbearNativeResult<Vector3> {
+	pub fn get_witness2(world: &World, entity: Entity, collision_handle: IndexNative) -> DropbearNativeResult<NVector3> {
 		let collision = super::get_collision_from_world(world, entity, collision_handle)?;
 		let p = collision.hit.witness2;
-		Ok(Vector3 { x: p.x as f64, y: p.y as f64, z: p.z as f64 })
+		Ok(NVector3 { x: p.x as f64, y: p.y as f64, z: p.z as f64 })
 	}
 
-	pub fn get_normal1(world: &World, entity: Entity, collision_handle: IndexNative) -> DropbearNativeResult<Vector3> {
+	pub fn get_normal1(world: &World, entity: Entity, collision_handle: IndexNative) -> DropbearNativeResult<NVector3> {
 		let collision = super::get_collision_from_world(world, entity, collision_handle)?;
 		let n = collision.hit.normal1;
-		Ok(Vector3 { x: n.x as f64, y: n.y as f64, z: n.z as f64 })
+		Ok(NVector3 { x: n.x as f64, y: n.y as f64, z: n.z as f64 })
 	}
 
-	pub fn get_normal2(world: &World, entity: Entity, collision_handle: IndexNative) -> DropbearNativeResult<Vector3> {
+	pub fn get_normal2(world: &World, entity: Entity, collision_handle: IndexNative) -> DropbearNativeResult<NVector3> {
 		let collision = super::get_collision_from_world(world, entity, collision_handle)?;
 		let n = collision.hit.normal2;
-		Ok(Vector3 { x: n.x as f64, y: n.y as f64, z: n.z as f64 })
+		Ok(NVector3 { x: n.x as f64, y: n.y as f64, z: n.z as f64 })
 	}
 
 	pub fn get_status(world: &World, entity: Entity, collision_handle: IndexNative) -> DropbearNativeResult<ShapeCastStatus> {
diff --git a/crates/eucalyptus-core/src/physics/rigidbody.rs b/crates/eucalyptus-core/src/physics/rigidbody.rs
index 6809bb5..0e43106 100644
--- a/crates/eucalyptus-core/src/physics/rigidbody.rs
+++ b/crates/eucalyptus-core/src/physics/rigidbody.rs
@@ -203,7 +203,7 @@ pub mod shared {
 	use crate::scripting::native::DropbearNativeError;
 	use crate::scripting::result::DropbearNativeResult;
 	use crate::states::Label;
-	use crate::types::{IndexNative, RigidBodyContext, Vector3};
+	use crate::types::{IndexNative, RigidBodyContext, NVector3};
 	use hecs::{Entity, World};
 	use rapier3d::dynamics::{RigidBodyHandle, RigidBodyType};
 	use rapier3d::prelude::Vector;
@@ -394,17 +394,17 @@ pub mod shared {
 		}
 	}
 
-	pub fn get_rigidbody_linvel(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<Vector3> {
+	pub fn get_rigidbody_linvel(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<NVector3> {
 		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
 		if let Some(rb) = physics.bodies.get(handle) {
 			let linvel = rb.linvel().clone();
-			Ok(Vector3::new(linvel.x as f64, linvel.y as f64, linvel.z as f64))
+			Ok(NVector3::new(linvel.x as f64, linvel.y as f64, linvel.z as f64))
 		} else {
 			Err(DropbearNativeError::PhysicsObjectNotFound)
 		}
 	}
 
-	pub fn set_rigidbody_linvel(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: Vector3) -> DropbearNativeResult<()> {
+	pub fn set_rigidbody_linvel(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: NVector3) -> DropbearNativeResult<()> {
 		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
 		if let Some(rb) = physics.bodies.get_mut(handle) {
 			rb.set_linvel(Vector::new(new.x as f32, new.y as f32, new.z as f32), true);
@@ -421,17 +421,17 @@ pub mod shared {
 		}
 	}
 
-	pub fn get_rigidbody_angvel(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<Vector3> {
+	pub fn get_rigidbody_angvel(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<NVector3> {
 		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
 		if let Some(rb) = physics.bodies.get(handle) {
 			let angvel = rb.angvel().clone();
-			Ok(Vector3::new(angvel.x as f64, angvel.y as f64, angvel.z as f64))
+			Ok(NVector3::new(angvel.x as f64, angvel.y as f64, angvel.z as f64))
 		} else {
 			Err(DropbearNativeError::PhysicsObjectNotFound)
 		}
 	}
 
-	pub fn set_rigidbody_angvel(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: Vector3) -> DropbearNativeResult<()> {
+	pub fn set_rigidbody_angvel(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: NVector3) -> DropbearNativeResult<()> {
 		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
 		if let Some(rb) = physics.bodies.get_mut(handle) {
 			rb.set_angvel(Vector::new(new.x as f32, new.y as f32, new.z as f32), true);
@@ -554,7 +554,7 @@ pub mod shared {
 		}
 	}
 
-	pub fn apply_impulse(physics: &mut PhysicsState, _world: &World, rb_context: RigidBodyContext, new: Vector3) -> DropbearNativeResult<()> {
+	pub fn apply_impulse(physics: &mut PhysicsState, _world: &World, rb_context: RigidBodyContext, new: NVector3) -> DropbearNativeResult<()> {
 		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
 		if let Some(rb) = physics.bodies.get_mut(handle) {
 			rb.apply_impulse(Vector::new(new.x as f32, new.y as f32, new.z as f32), true);
@@ -565,7 +565,7 @@ pub mod shared {
 		}
 	}
 
-	pub fn apply_torque_impulse(physics: &mut PhysicsState, _world: &World, rb_context: RigidBodyContext, new: Vector3) -> DropbearNativeResult<()> {
+	pub fn apply_torque_impulse(physics: &mut PhysicsState, _world: &World, rb_context: RigidBodyContext, new: NVector3) -> DropbearNativeResult<()> {
 		let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation);
 		if let Some(rb) = physics.bodies.get_mut(handle) {
 			rb.apply_torque_impulse(Vector::new(new.x as f32, new.y as f32, new.z as f32), true);
@@ -582,7 +582,7 @@ pub mod jni {
 	use crate::physics::rigidbody::AxisLock;
 	use crate::physics::PhysicsState;
 	use crate::scripting::jni::utils::{FromJObject, ToJObject};
-	use crate::types::{IndexNative, RigidBodyContext, Vector3};
+	use crate::types::{IndexNative, RigidBodyContext, NVector3};
 	use crate::{convert_jlong_to_entity, convert_ptr};
 	use hecs::World;
 	use jni::objects::{JClass, JObject};
@@ -968,7 +968,7 @@ pub mod jni {
 			return;
 		};
 
-		let Ok(velocity) = Vector3::from_jobject(&mut env, &linear_velocity) else {
+		let Ok(velocity) = NVector3::from_jobject(&mut env, &linear_velocity) else {
 			let _ = env.throw_new("java/lang/RuntimeException",
 								  "Unable to convert com/dropbear/math/Vector3d to rust Vector3");
 			return;
@@ -1032,7 +1032,7 @@ pub mod jni {
 			return;
 		};
 
-		let Ok(velocity) = Vector3::from_jobject(&mut env, &angular_velocity) else {
+		let Ok(velocity) = NVector3::from_jobject(&mut env, &angular_velocity) else {
 			let _ = env.throw_new("java/lang/RuntimeException",
 								  "Unable to convert com/dropbear/math/Vector3d to rust Vector3");
 			return;
@@ -1244,7 +1244,7 @@ pub mod jni {
 			return;
 		};
 
-		let impulse = Vector3::new(x as f64, y as f64, z as f64);
+		let impulse = NVector3::new(x as f64, y as f64, z as f64);
 		if let Err(e) = super::shared::apply_impulse(&mut physics, world, rb_context, impulse) {
 			eprintln!("Failed to apply impulse: {}", e);
 			let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to apply impulse: {}", e));
@@ -1271,7 +1271,7 @@ pub mod jni {
 			return;
 		};
 
-		let torque = Vector3::new(x as f64, y as f64, z as f64);
+		let torque = NVector3::new(x as f64, y as f64, z as f64);
 		if let Err(e) = super::shared::apply_torque_impulse(&mut physics, world, rb_context, torque) {
 			eprintln!("Failed to apply torque impulse: {}", e);
 			let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to apply torque impulse: {}", e));
diff --git a/crates/eucalyptus-core/src/properties.rs b/crates/eucalyptus-core/src/properties.rs
index f74f906..12c75f1 100644
--- a/crates/eucalyptus-core/src/properties.rs
+++ b/crates/eucalyptus-core/src/properties.rs
@@ -218,7 +218,7 @@ pub mod jni {
 
     use crate::properties::{CustomProperties, Value};
     use crate::scripting::jni::utils::{FromJObject, ToJObject};
-    use crate::types::Vector3;
+    use crate::types::NVector3;
 
     /// Returns a primitive that is boxed (long => java.lang.Long)
     ///
@@ -411,7 +411,7 @@ pub mod jni {
 
         if let Ok(props) = world.get::<&CustomProperties>(entity) {
             if let Some(Value::Vec3(v)) = props.get_property(&key_str) {
-                return match Vector3::from(*v).to_jobject(&mut env) {
+                return match NVector3::from(*v).to_jobject(&mut env) {
                     Ok(obj) => obj.into_raw(),
                     Err(_) => std::ptr::null_mut()
                 };
@@ -542,7 +542,7 @@ pub mod jni {
         let entity = crate::convert_jlong_to_entity!(entity_id);
         let key_str = crate::convert_jstring!(env, key);
 
-        let vec_val = match Vector3::from_jobject(&mut env, &value) {
+        let vec_val = match NVector3::from_jobject(&mut env, &value) {
             Ok(v) => v,
             Err(_) => return,
         };
@@ -565,7 +565,7 @@ pub mod native {
     use crate::ptr::WorldPtr;
     use crate::scripting::native::DropbearNativeError;
     use crate::scripting::result::DropbearNativeResult;
-    use crate::types::Vector3;
+    use crate::types::NVector3;
     
     pub fn dropbear_custom_properties_exists_for_entity(
         world_ptr: WorldPtr,
@@ -687,14 +687,14 @@ pub mod native {
         world_ptr: WorldPtr,
         entity_id: u64,
         key: *const c_char
-    ) -> DropbearNativeResult<Vector3> {
+    ) -> DropbearNativeResult<NVector3> {
         let world = convert_ptr!(world_ptr => World);
         let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
         let key_str = unsafe { read_key(key)? };
 
         if let Ok(props) = world.get::<&CustomProperties>(entity) {
             if let Some(Value::Vec3(v)) = props.get_property(&key_str) {
-                return DropbearNativeResult::Ok(Vector3::from(*v));
+                return DropbearNativeResult::Ok(NVector3::from(*v));
             }
         }
         DropbearNativeResult::Err(DropbearNativeError::InvalidArgument)
@@ -813,7 +813,7 @@ pub mod native {
         world_ptr: WorldPtr,
         entity_id: u64,
         key: *const c_char,
-        value: Vector3
+        value: NVector3
     ) -> DropbearNativeResult<()> {
         let world = convert_ptr!(world_ptr => World);
         let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
diff --git a/crates/eucalyptus-core/src/ptr.rs b/crates/eucalyptus-core/src/ptr.rs
index 6f490e8..9c79279 100644
--- a/crates/eucalyptus-core/src/ptr.rs
+++ b/crates/eucalyptus-core/src/ptr.rs
@@ -1,10 +1,12 @@
 //! Helper pointers and typedef definitions.
+
+use std::sync::Arc;
 use crate::input::InputState;
 use crate::command::CommandBuffer;
 use crossbeam_channel::Sender;
 use dropbear_engine::asset::AssetRegistry;
 use hecs::World;
-use parking_lot::Mutex;
+use parking_lot::{Mutex, RwLock};
 use crate::physics::PhysicsState;
 use crate::scene::loading::SceneLoader;
 use crate::ui::{UiContext};
@@ -28,7 +30,8 @@ pub type CommandBufferPtr = *const Sender<CommandBuffer>;
 /// A non-mutable pointer to the [`AssetRegistry`].
 ///
 /// Defined in `dropbear_common.h` as `AssetRegistry`
-pub type AssetRegistryPtr = *const AssetRegistry;
+pub type AssetRegistryPtr = *const AssetRegistryUnwrapped;
+pub type AssetRegistryUnwrapped = Arc<RwLock<AssetRegistry>>;
 
 /// A mutable pointer to a [`parking_lot::Mutex<SceneLoader>`]
 ///
diff --git a/crates/eucalyptus-core/src/scripting/jni/utils.rs b/crates/eucalyptus-core/src/scripting/jni/utils.rs
index e75c6f7..93c2daf 100644
--- a/crates/eucalyptus-core/src/scripting/jni/utils.rs
+++ b/crates/eucalyptus-core/src/scripting/jni/utils.rs
@@ -1,7 +1,7 @@
 //! Utilities for JNI and JVM based code.
 
 use crate::scripting::result::DropbearNativeResult;
-use jni::objects::JObject;
+use jni::objects::{JObject, JValue};
 use jni::sys::jint;
 use jni::JNIEnv;
 
@@ -34,4 +34,42 @@ pub trait FromJObject {
 /// Converts a Rust object (struct or enum) into a java [JObject]
 pub trait ToJObject {
     fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>>;
+}
+
+impl<T> ToJObject for Vec<T>
+where
+    T: ToJObject,
+{
+    fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+        let list_class = env.find_class("java/util/ArrayList")?;
+        let list_obj = env.new_object(&list_class, "()V", &[])?;
+
+        for item in self {
+            let obj = item.to_jobject(env)?;
+            let _ = env.call_method(&list_obj, "add", "(Ljava/lang/Object;)Z", &[JValue::Object(&obj)])?;
+        }
+
+        Ok(list_obj)
+    }
+}
+
+impl<T> FromJObject for Vec<T>
+where
+    T: FromJObject,
+{
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self>
+    where
+        Self: Sized,
+    {
+        let size = env.call_method(obj, "size", "()I", &[])?.i()? as jint;
+        let mut out = Vec::with_capacity(size as usize);
+
+        for i in 0..size {
+            let item = env.call_method(obj, "get", "(I)Ljava/lang/Object;", &[JValue::Int(i)])?.l()?;
+            let value = T::from_jobject(env, &item)?;
+            out.push(value);
+        }
+
+        Ok(out)
+    }
 }
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/scripting/native.rs b/crates/eucalyptus-core/src/scripting/native.rs
index e052c83..46b30ae 100644
--- a/crates/eucalyptus-core/src/scripting/native.rs
+++ b/crates/eucalyptus-core/src/scripting/native.rs
@@ -493,7 +493,7 @@ pub enum DropbearNativeError {
     /// When the argument is invalid
     #[error("Invalid argument")]
     InvalidArgument,
-    /// The handle provided does not exist. Could be for an asset_old, entity, or other handle type.
+    /// The handle provided does not exist. Could be for an asset, entity, or other handle type.
     #[error("No such handle")]
     NoSuchHandle,
     /// Failed to create a Java object via JNI.
@@ -511,13 +511,13 @@ pub enum DropbearNativeError {
     /// Failed to unwrap a Java object via JNI.
     #[error("JNI unwrap failed")]
     JNIUnwrapFailed,
-    /// Generic asset_old error. There was an error thrown, however there is no context attached. 
-    #[error("Generic asset_old error")]
+    /// Generic asset error. There was an error thrown, however there is no context attached.
+    #[error("Generic asset error")]
     GenericAssetError,
     /// The provided uri (either euca:// or https) was invalid and formatted wrong.
     #[error("Invalid URI")]
     InvalidURI,
-    /// The asset_old provided by the handle is wrong.
+    /// The asset provided by the handle is wrong.
     #[error("Asset not found")]
     AssetNotFound,
     /// When a handle has been inputted wrongly.
diff --git a/crates/eucalyptus-core/src/transform.rs b/crates/eucalyptus-core/src/transform.rs
index 86977dc..f4c1dcc 100644
--- a/crates/eucalyptus-core/src/transform.rs
+++ b/crates/eucalyptus-core/src/transform.rs
@@ -5,7 +5,7 @@ use dropbear_engine::entity::{EntityTransform, Transform};
 use glam::{DQuat, DVec3};
 use ::jni::objects::{JObject, JValue};
 use ::jni::JNIEnv;
-use crate::types::Vector3;
+use crate::types::NVector3;
 
 impl FromJObject for Transform {
     fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
@@ -27,8 +27,8 @@ impl FromJObject for Transform {
         let scale_obj = scale_val.l()
             .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
 
-        let position: DVec3 = Vector3::from_jobject(env, &pos_obj)?.into();
-        let scale: DVec3 = Vector3::from_jobject(env, &scale_obj)?.into();
+        let position: DVec3 = NVector3::from_jobject(env, &pos_obj)?.into();
+        let scale: DVec3 = NVector3::from_jobject(env, &scale_obj)?.into();
 
         let mut get_double = |field: &str| -> DropbearNativeResult<f64> {
             env.get_field(&rot_obj, field, "D")
@@ -281,7 +281,7 @@ pub mod jni {
 #[dropbear_macro::impl_c_api]
 pub mod native {
     use crate::hierarchy::EntityTransformExt;
-    use crate::types::{TransformNative as Transform};
+    use crate::types::{NTransform as Transform};
     use dropbear_engine::entity::EntityTransform;
     use hecs::{Entity, World};
 
diff --git a/crates/eucalyptus-core/src/types.rs b/crates/eucalyptus-core/src/types.rs
index 0860283..6bedf01 100644
--- a/crates/eucalyptus-core/src/types.rs
+++ b/crates/eucalyptus-core/src/types.rs
@@ -1,5 +1,5 @@
 //! FFI and C types of other library types, as used in the scripting module.
-use glam::{DQuat, DVec3};
+use glam::{DQuat, DVec3, Vec3};
 use hecs::Entity;
 use jni::JNIEnv;
 use jni::objects::{JObject, JValue};
@@ -13,16 +13,68 @@ use crate::scripting::native::DropbearNativeError;
 use crate::scripting::result::DropbearNativeResult;
 
 #[repr(C)]
-#[derive(Clone, Copy)]
-pub struct Vector3 {
+#[derive(Clone, Copy, Debug)]
+pub struct NVector2 {
+    pub x: f64,
+    pub y: f64,
+}
+
+impl NVector2 {
+    pub fn to_array(&self) -> [f64; 2] {
+        [self.x, self.y]
+    }
+}
+
+impl From<glam::DVec2> for NVector2 {
+    fn from(v: glam::DVec2) -> Self {
+        Self { x: v.x, y: v.y }
+    }
+}
+
+impl From<[f64; 2]> for NVector2 {
+    fn from(value: [f64; 2]) -> Self {
+        NVector2 {
+            x: value[0],
+            y: value[1],
+        }
+    }
+}
+
+impl From<[f32; 2]> for NVector2 {
+    fn from(value: [f32; 2]) -> Self {
+        NVector2 {
+            x: value[0] as f64,
+            y: value[1] as f64,
+        }
+    }
+}
+
+impl From<NVector2> for glam::DVec2 {
+    fn from(v: NVector2) -> Self {
+        Self::new(v.x, v.y)
+    }
+}
+
+impl From<(f64, f64)> for NVector2 {
+    fn from(value: (f64, f64)) -> Self {
+        Self {
+            x: value.0,
+            y: value.1,
+        }
+    }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug)]
+pub struct NVector3 {
     pub x: f64,
     pub y: f64,
     pub z: f64,
 }
 
-impl Vector3 {
-    pub fn new(x: f64, y: f64, z: f64) -> Vector3 {
-        Vector3 {
+impl NVector3 {
+    pub fn new(x: f64, y: f64, z: f64) -> NVector3 {
+        NVector3 {
             x, y, z
         }
     }
@@ -35,15 +87,15 @@ impl Vector3 {
     }
 }
 
-impl From<glam::DVec3> for Vector3 {
+impl From<glam::DVec3> for NVector3 {
     fn from(v: glam::DVec3) -> Self {
         Self { x: v.x, y: v.y, z: v.z }
     }
 }
 
-impl From<[f64; 3]> for Vector3 {
+impl From<[f64; 3]> for NVector3 {
     fn from(value: [f64; 3]) -> Self {
-        Vector3 {
+        NVector3 {
             x: value[0],
             y: value[1],
             z: value[2],
@@ -51,9 +103,9 @@ impl From<[f64; 3]> for Vector3 {
     }
 }
 
-impl From<[f32; 3]> for Vector3 {
+impl From<[f32; 3]> for NVector3 {
     fn from(value: [f32; 3]) -> Self {
-        Vector3 {
+        NVector3 {
             x: value[0] as f64,
             y: value[1] as f64,
             z: value[2] as f64,
@@ -61,13 +113,23 @@ impl From<[f32; 3]> for Vector3 {
     }
 }
 
-impl From<Vector3> for glam::DVec3 {
-    fn from(v: Vector3) -> Self {
+impl From<glam::Vec3> for NVector3 {
+    fn from(value: Vec3) -> Self {
+        Self {
+            x: value.x as f64,
+            y: value.y as f64,
+            z: value.z as f64,
+        }
+    }
+}
+
+impl From<NVector3> for glam::DVec3 {
+    fn from(v: NVector3) -> Self {
         Self::new(v.x, v.y, v.z)
     }
 }
 
-impl FromJObject for Vector3 {
+impl FromJObject for NVector3 {
     fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self>
     where
         Self: Sized
@@ -96,11 +158,11 @@ impl FromJObject for Vector3 {
             .d()
             .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
 
-        Ok(Vector3::new(x, y, z))
+        Ok(NVector3::new(x, y, z))
     }
 }
 
-impl ToJObject for Vector3 {
+impl ToJObject for NVector3 {
     fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
         let class = env.find_class("com/dropbear/math/Vector3d")
             .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
@@ -120,16 +182,145 @@ impl ToJObject for Vector3 {
     }
 }
 
+
 #[repr(C)]
-#[derive(Clone, Copy)]
-pub struct Quaternion {
+#[derive(Clone, Copy, Debug)]
+pub struct NVector4 {
+    pub x: f64,
+    pub y: f64,
+    pub z: f64,
+    pub w: f64,
+}
+
+impl NVector4 {
+    pub fn new(x: f64, y: f64, z: f64, w: f64) -> NVector4 {
+        NVector4 {
+            x, y, z, w
+        }
+    }
+
+    pub fn to_array(&self) -> [f64; 3] {
+        [self.x, self.y, self.z]
+    }
+    pub fn to_float_array(&self) -> [f32; 3] {
+        [self.x as f32, self.y as f32, self.z as f32]
+    }
+}
+
+impl From<glam::DVec4> for NVector4 {
+    fn from(v: glam::DVec4) -> Self {
+        Self { x: v.x, y: v.y, z: v.z, w: v.w }
+    }
+}
+
+impl From<[f64; 4]> for NVector4 {
+    fn from(value: [f64; 4]) -> Self {
+        NVector4 {
+            x: value[0],
+            y: value[1],
+            z: value[2],
+            w: value[3],
+        }
+    }
+}
+
+impl From<[f32; 4]> for NVector4 {
+    fn from(value: [f32; 4]) -> Self {
+        NVector4 {
+            x: value[0] as f64,
+            y: value[1] as f64,
+            z: value[2] as f64,
+            w: value[3] as f64,
+        }
+    }
+}
+
+impl From<NVector4> for glam::DVec4 {
+    fn from(v: NVector4) -> Self {
+        Self::new(v.x, v.y, v.z, v.w)
+    }
+}
+
+impl FromJObject for NVector4 {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self>
+    where
+        Self: Sized
+    {
+        let class = env.find_class("com/dropbear/math/Vector4d")
+            .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+        if !env.is_instance_of(obj, &class)
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?
+        {
+            return Err(DropbearNativeError::InvalidArgument);
+        }
+
+        let x = env.get_field(obj, "x", "D")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .d()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let y = env.get_field(obj, "y", "D")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .d()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let z = env.get_field(obj, "z", "D")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .d()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let w = env.get_field(obj, "w", "D")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .d()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        Ok(NVector4::new(x, y, z, w))
+    }
+}
+
+impl ToJObject for NVector4 {
+    fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+        let class = env.find_class("com/dropbear/math/Vector3d")
+            .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+        let constructor_sig = "(DDD)V";
+
+        let args = [
+            jni::objects::JValue::Double(self.x),
+            jni::objects::JValue::Double(self.y),
+            jni::objects::JValue::Double(self.z),
+            jni::objects::JValue::Double(self.w),
+        ];
+
+        let obj = env.new_object(&class, constructor_sig, &args)
+            .map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+        Ok(obj)
+    }
+}
+
+impl From<NQuaternion> for NVector4 {
+    fn from(value: NQuaternion) -> Self {
+        Self {
+            x: value.x,
+            y: value.y,
+            z: value.z,
+            w: value.w,
+        }
+    }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug)]
+pub struct NQuaternion {
     pub x: f64,
     pub y: f64,
     pub z: f64,
     pub w: f64,
 }
 
-impl From<DQuat> for Quaternion {
+impl From<DQuat> for NQuaternion {
     fn from(value: DQuat) -> Self {
         Self {
             x: value.x,
@@ -140,8 +331,8 @@ impl From<DQuat> for Quaternion {
     }
 }
 
-impl From<Quaternion> for glam::DQuat {
-    fn from(value: Quaternion) -> Self {
+impl From<NQuaternion> for glam::DQuat {
+    fn from(value: NQuaternion) -> Self {
         Self {
             x: value.x,
             y: value.y,
@@ -151,7 +342,7 @@ impl From<Quaternion> for glam::DQuat {
     }
 }
 
-impl From<[f64; 4]> for Quaternion {
+impl From<[f64; 4]> for NQuaternion {
     fn from(value: [f64; 4]) -> Self {
         Self {
             x: value[0],
@@ -162,86 +353,66 @@ impl From<[f64; 4]> for Quaternion {
     }
 }
 
-#[repr(C)]
-#[derive(Clone, Copy)]
-pub struct TransformNative {
-    position: Vector3,
-    rotation: Quaternion,
-    scale: Vector3,
-}
-
-impl From<Transform> for TransformNative {
-    fn from(value: Transform) -> Self {
+impl From<glam::Quat> for NQuaternion {
+    fn from(value: glam::Quat) -> Self {
         Self {
-            position: Vector3::from(value.position),
-            rotation: Quaternion::from(value.rotation),
-            scale: Vector3::from(value.scale),
+            x: value.x as f64,
+            y: value.y as f64,
+            z: value.z as f64,
+            w: value.w as f64,
+
         }
     }
 }
 
-impl From<TransformNative> for Transform {
-    fn from(value: TransformNative) -> Self {
+impl From<NVector4> for NQuaternion {
+    fn from(value: NVector4) -> Self {
         Self {
-            position: DVec3::from(value.position),
-            rotation: DQuat::from(value.rotation),
-            scale: DVec3::from(value.scale),
+            x: value.x,
+            y: value.y,
+            z: value.z,
+            w: value.w,
         }
     }
 }
 
 #[repr(C)]
 #[derive(Clone, Copy)]
-pub struct Vector2 {
-    pub(crate) x: f64,
-    pub(crate) y: f64,
+pub struct NTransform {
+    position: NVector3,
+    rotation: NQuaternion,
+    scale: NVector3,
 }
 
-impl Vector2 {
-    pub fn to_array(&self) -> [f64; 2] {
-        [self.x, self.y]
-    }
-}
-
-impl From<glam::DVec2> for Vector2 {
-    fn from(v: glam::DVec2) -> Self {
-        Self { x: v.x, y: v.y }
-    }
-}
-
-impl From<[f64; 2]> for Vector2 {
-    fn from(value: [f64; 2]) -> Self {
-        Vector2 {
-            x: value[0],
-            y: value[1],
+impl From<Transform> for NTransform {
+    fn from(value: Transform) -> Self {
+        Self {
+            position: NVector3::from(value.position),
+            rotation: NQuaternion::from(value.rotation),
+            scale: NVector3::from(value.scale),
         }
     }
 }
 
-impl From<Vector2> for glam::DVec2 {
-    fn from(v: Vector2) -> Self {
-        Self::new(v.x, v.y)
-    }
-}
-
-impl From<(f64, f64)> for Vector2 {
-    fn from(value: (f64, f64)) -> Self {
+impl From<NTransform> for Transform {
+    fn from(value: NTransform) -> Self {
         Self {
-            x: value.0,
-            y: value.1,
+            position: DVec3::from(value.position),
+            rotation: DQuat::from(value.rotation),
+            scale: DVec3::from(value.scale),
         }
     }
 }
 
 #[repr(C)]
 #[derive(Clone, Copy)]
-pub struct ColliderFFI {
+pub struct NCollider {
     pub index: IndexNative,
     pub entity_id: u64,
     pub id: u32,
 }
 
-impl ToJObject for ColliderFFI {
+impl ToJObject for NCollider {
     fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
         let collider_cls = env.find_class("com/dropbear/physics/Collider")
             .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
@@ -281,7 +452,7 @@ impl ToJObject for ColliderFFI {
     }
 }
 
-impl FromJObject for ColliderFFI {
+impl FromJObject for NCollider {
     fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self>
     where
         Self: Sized
@@ -316,7 +487,7 @@ impl FromJObject for ColliderFFI {
             .i()
             .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
 
-        Ok(ColliderFFI {
+        Ok(NCollider {
             index: IndexNative {
                 index: idx_val as u32,
                 generation: gen_val as u32,
@@ -330,8 +501,8 @@ impl FromJObject for ColliderFFI {
 #[repr(C)]
 #[derive(Clone, Copy)]
 pub struct IndexNative {
-    pub(crate) index: u32,
-    pub(crate) generation: u32,
+    pub index: u32,
+    pub generation: u32,
 }
 
 impl From<Index> for IndexNative {
@@ -362,7 +533,7 @@ pub enum ColliderShapeType {
 
 #[repr(C)]
 #[derive(Clone, Copy)]
-pub struct ColliderShapeFFI {
+pub struct NColliderShape {
     pub shape_type: ColliderShapeType,
     pub radius: f32,
     pub half_height: f32,
@@ -478,7 +649,7 @@ impl ToJObject for RigidBodyContext {
 #[repr(C)]
 #[derive(Clone, Copy)]
 pub struct RayHit {
-    pub collider: ColliderFFI,
+    pub collider: NCollider,
     pub distance: f64,
 }
 
@@ -511,12 +682,12 @@ impl ToJObject for RayHit {
 #[repr(C)]
 #[derive(Clone, Copy)]
 pub struct ShapeCastHitFFI {
-    pub collider: ColliderFFI,
+    pub collider: NCollider,
     pub distance: f64,
-    pub witness1: Vector3,
-    pub witness2: Vector3,
-    pub normal1: Vector3,
-    pub normal2: Vector3,
+    pub witness1: NVector3,
+    pub witness2: NVector3,
+    pub normal1: NVector3,
+    pub normal2: NVector3,
     pub status: rapier3d::parry::query::ShapeCastStatus,
 }
 
@@ -600,8 +771,8 @@ impl ToJObject for CollisionEventType {
 #[derive(Clone, Copy)]
 pub struct CollisionEvent {
     pub(crate) event_type: CollisionEventType,
-    pub(crate) collider1: ColliderFFI,
-    pub(crate) collider2: ColliderFFI,
+    pub(crate) collider1: NCollider,
+    pub(crate) collider2: NCollider,
     pub(crate) flags: u64,
 }
 
@@ -658,12 +829,12 @@ impl CollisionEvent {
                 
                 Some(Self {
                     event_type: CollisionEventType::Started,
-                    collider1: ColliderFFI {
+                    collider1: NCollider {
                         index: IndexNative::from(col1.0),
                         entity_id: collider1_info.to_bits().get(),
                         id: col1.into_raw_parts().0,
                     },
-                    collider2: ColliderFFI {
+                    collider2: NCollider {
                         index: IndexNative::from(col2.0),
                         entity_id: collider2_info.to_bits().get(),
                         id: col2.into_raw_parts().0,
@@ -708,12 +879,12 @@ impl CollisionEvent {
 
                 Some(Self {
                     event_type: CollisionEventType::Stopped,
-                    collider1: ColliderFFI {
+                    collider1: NCollider {
                         index: IndexNative::from(col1.0),
                         entity_id: collider1_info.to_bits().get(),
                         id: col1.into_raw_parts().0,
                     },
-                    collider2: ColliderFFI {
+                    collider2: NCollider {
                         index: IndexNative::from(col2.0),
                         entity_id: collider2_info.to_bits().get(),
                         id: col2.into_raw_parts().0,
@@ -755,11 +926,11 @@ impl ToJObject for CollisionEvent {
 #[repr(C)]
 #[derive(Clone, Copy)]
 pub struct ContactForceEvent {
-    pub(crate) collider1: ColliderFFI,
-    pub(crate) collider2: ColliderFFI,
-    pub(crate) total_force: Vector3,
+    pub(crate) collider1: NCollider,
+    pub(crate) collider2: NCollider,
+    pub(crate) total_force: NVector3,
     pub(crate) total_force_magnitude: f64,
-    pub(crate) max_force_direction: Vector3,
+    pub(crate) max_force_direction: NVector3,
     pub(crate) max_force_magnitude: f64,
 }
 
@@ -798,23 +969,23 @@ impl ContactForceEvent {
         };
         
         Some(Self {
-            collider1: ColliderFFI {
+            collider1: NCollider {
                 index: IndexNative::from(event.collider1.0),
                 entity_id: find_entity(event.collider1)?.to_bits().get(),
                 id: event.collider1.into_raw_parts().0,
             },
-            collider2: ColliderFFI {
+            collider2: NCollider {
                 index: IndexNative::from(event.collider2.0),
                 entity_id: find_entity(event.collider2)?.to_bits().get(),
                 id: event.collider2.into_raw_parts().0,
             },
-            total_force: Vector3::new(
+            total_force: NVector3::new(
                 event.total_force.x.into(), 
                 event.total_force.y.into(), 
                 event.total_force.z.into()
             ),
             total_force_magnitude: event.total_force_magnitude as f64,
-            max_force_direction: Vector3::new(
+            max_force_direction: NVector3::new(
                 event.max_force_direction.x.into(), 
                 event.max_force_direction.y.into(), 
                 event.max_force_direction.z.into()
diff --git a/crates/eucalyptus-core/src/utils.rs b/crates/eucalyptus-core/src/utils.rs
index b047c49..6214acc 100644
--- a/crates/eucalyptus-core/src/utils.rs
+++ b/crates/eucalyptus-core/src/utils.rs
@@ -324,7 +324,7 @@ fn resolve_resource_from_root(relative: &str, root: &Path) -> anyhow::Result<Pat
     }
 
     anyhow::bail!(
-        "Resource '{}' was resolved to '{}' but the file does not exist. Ensure the asset_old is packaged under '{}'",
+        "Resource '{}' was resolved to '{}' but the file does not exist. Ensure the asset is packaged under '{}'",
         relative,
         resolved.display(),
         root.display()
diff --git a/crates/eucalyptus-editor/src/editor/asset_viewer.rs b/crates/eucalyptus-editor/src/editor/asset_viewer.rs
index 7e25a01..c2cb0d1 100644
--- a/crates/eucalyptus-editor/src/editor/asset_viewer.rs
+++ b/crates/eucalyptus-editor/src/editor/asset_viewer.rs
@@ -470,7 +470,7 @@ impl<'a> EditorTabViewer<'a> {
     }
 
     fn draw_asset_icon(ui: &mut egui::Ui) {
-        let image = egui::Image::from_bytes("bytes://asset_old-viewer-icon", NO_TEXTURE)
+        let image = egui::Image::from_bytes("bytes://asset-viewer-icon", NO_TEXTURE)
             .max_size(egui::vec2(14.0, 14.0));
         ui.add(image);
     }
diff --git a/crates/eucalyptus-editor/src/editor/dock.rs b/crates/eucalyptus-editor/src/editor/dock.rs
index 0a31f07..b09122e 100644
--- a/crates/eucalyptus-editor/src/editor/dock.rs
+++ b/crates/eucalyptus-editor/src/editor/dock.rs
@@ -1653,7 +1653,7 @@ impl<'a> EditorTabViewer<'a> {
     }
 
     fn draw_asset_icon(ui: &mut egui::Ui) {
-        let image = egui::Image::from_bytes("bytes://asset_old-viewer-icon", NO_TEXTURE)
+        let image = egui::Image::from_bytes("bytes://asset-viewer-icon", NO_TEXTURE)
             .max_size(egui::vec2(14.0, 14.0));
         ui.add(image);
     }
diff --git a/crates/eucalyptus-editor/src/spawn.rs b/crates/eucalyptus-editor/src/spawn.rs
index ab18152..91d3e2a 100644
--- a/crates/eucalyptus-editor/src/spawn.rs
+++ b/crates/eucalyptus-editor/src/spawn.rs
@@ -274,7 +274,7 @@ async fn load_renderer_from_serialized(
 
     let mut mesh_renderer = match &renderer.handle.ref_type {
         ResourceReferenceType::None => anyhow::bail!(
-            "Renderer for '{}' does not specify an asset_old reference",
+            "Renderer for '{}' does not specify an asset reference",
             label
         ),
         ResourceReferenceType::Unassigned { id } => {
diff --git a/crates/kino-ui/src/asset.rs b/crates/kino-ui/src/asset.rs
index dd0a1aa..a0f0ace 100644
--- a/crates/kino-ui/src/asset.rs
+++ b/crates/kino-ui/src/asset.rs
@@ -64,7 +64,7 @@ impl AssetServer {
         self.texture_labels.insert(label.into(), handle.clone());
     }
 
-    /// Updates the asset_old server by inserting the texture provided at the location of the handle,
+    /// Updates the asset server by inserting the texture provided at the location of the handle,
     /// and removing the old texture (by returning it back to you).
     pub fn update_texture(&mut self, handle: Handle<Texture>, texture: Texture) -> Option<Texture> {
         self.textures.insert(handle.id, texture)
diff --git a/include/dropbear.h b/include/dropbear.h
index 2efb84d..8df3735 100644
--- a/include/dropbear.h
+++ b/include/dropbear.h
@@ -4,13 +4,182 @@
 #include <stdbool.h>
 #include <stdint.h>
 
+#include <stddef.h>
+
+typedef void* WorldPtr;
+
+typedef struct NVector4 {
+    double x;
+    double y;
+    double z;
+    double w;
+} NVector4;
+
+typedef struct NVector3 {
+    double x;
+    double y;
+    double z;
+} NVector3;
+
+typedef struct Option Option;// opaque
+
+typedef struct NVector2 {
+    double x;
+    double y;
+} NVector2;
+
+typedef struct NMaterial {
+    const char* name;
+    uint64_t diffuse_texture;
+    uint64_t normal_texture;
+    NVector4 tint;
+    NVector3 emissive_factor;
+    float metallic_factor;
+    float roughness_factor;
+    Option alpha_cutoff;
+    bool double_sided;
+    float occlusion_strength;
+    float normal_scale;
+    NVector2 uv_tiling;
+    Option emissive_texture;
+    Option metallic_roughness_texture;
+    Option occlusion_texture;
+} NMaterial;
+
+typedef struct NMaterialArrayFfi {
+    const NMaterial* ptr;
+    size_t len;
+} NMaterialArrayFfi;
+
+typedef void* AssetRegistryPtr;
+
+typedef void* SceneLoaderPtr;
+
+typedef struct size_t size_t;// opaque
+
 typedef struct Progress {
-    uintptr_t current;
-    uintptr_t total;
+    size_t current;
+    size_t total;
     const char* message;
 } Progress;
 
+typedef struct i32ArrayFfi {
+    const int32_t* ptr;
+    size_t len;
+} i32ArrayFfi;
+
+typedef struct f64ArrayFfiArrayFfi {
+    const double* ptr;
+    size_t len;
+} f64ArrayFfiArrayFfi;
+
+typedef struct NSkin {
+    const char* name;
+    i32ArrayFfi joints;
+    f64ArrayFfiArrayFfi inverse_bind_matrices;
+    Option skeleton_root;
+} NSkin;
+
+typedef struct NSkinArrayFfi {
+    const NSkin* ptr;
+    size_t len;
+} NSkinArrayFfi;
+
+typedef struct f64ArrayFfi {
+    const double* ptr;
+    size_t len;
+} f64ArrayFfi;
+
+typedef struct NChannelValues NChannelValues;// opaque
+
+typedef struct NAnimationInterpolation NAnimationInterpolation;// opaque
+
+typedef struct NAnimationChannel {
+    int32_t target_node;
+    f64ArrayFfi times;
+    NChannelValues values;
+    NAnimationInterpolation interpolation;
+} NAnimationChannel;
+
+typedef struct NAnimationChannelArrayFfi {
+    const NAnimationChannel* ptr;
+    size_t len;
+} NAnimationChannelArrayFfi;
+
+typedef struct NAnimation {
+    const char* name;
+    NAnimationChannelArrayFfi channels;
+    float duration;
+} NAnimation;
+
+typedef struct NAnimationArrayFfi {
+    const NAnimation* ptr;
+    size_t len;
+} NAnimationArrayFfi;
+
+typedef struct AssetKind AssetKind;// opaque
+
+typedef struct NQuaternion {
+    double x;
+    double y;
+    double z;
+    double w;
+} NQuaternion;
+
+typedef struct NNodeTransform {
+    NVector3 translation;
+    NQuaternion rotation;
+    NVector3 scale;
+} NNodeTransform;
+
+typedef struct NNode {
+    const char* name;
+    Option parent;
+    i32ArrayFfi children;
+    NNodeTransform transform;
+} NNode;
+
+typedef struct NNodeArrayFfi {
+    const NNode* ptr;
+    size_t len;
+} NNodeArrayFfi;
+
+typedef struct NModelVertex {
+    NVector3 position;
+    NVector3 normal;
+    NVector4 tangent;
+    NVector2 tex_coords0;
+    NVector2 tex_coords1;
+    NVector4 colour0;
+    i32ArrayFfi joints0;
+    NVector4 weights0;
+} NModelVertex;
+
+typedef struct NModelVertexArrayFfi {
+    const NModelVertex* ptr;
+    size_t len;
+} NModelVertexArrayFfi;
+
+typedef struct NMesh {
+    const char* name;
+    int32_t num_elements;
+    int32_t material_index;
+    NModelVertexArrayFfi vertices;
+} NMesh;
+
+typedef struct NMeshArrayFfi {
+    const NMesh* ptr;
+    size_t len;
+} NMeshArrayFfi;
+
 int32_t dropbear_scene_get_scene_load_progress(SceneLoaderPtr scene_loader, uint64_t scene_id, Progress* out0);
 int32_t dropbear_get_entity(WorldPtr world, const char* label, uint64_t* out0);
+int32_t dropbear_engine_get_asset(AssetRegistryPtr asset, const char* label, const AssetKind* kind, uint64_t* out0, bool* out0_present);
+int32_t dropbear_asset_model_get_label(AssetRegistryPtr asset, uint64_t model_handle, char** out0);
+int32_t dropbear_asset_model_get_meshes(AssetRegistryPtr asset, uint64_t model_handle, NMeshArrayFfi* out0);
+int32_t dropbear_asset_model_get_materials(AssetRegistryPtr asset, uint64_t model_handle, NMaterialArrayFfi* out0);
+int32_t dropbear_asset_model_get_skins(AssetRegistryPtr asset, uint64_t model_handle, NSkinArrayFfi* out0);
+int32_t dropbear_asset_model_get_animations(AssetRegistryPtr asset, uint64_t model_handle, NAnimationArrayFfi* out0);
+int32_t dropbear_asset_model_get_nodes(AssetRegistryPtr asset, uint64_t model_handle, NNodeArrayFfi* out0);
 
 #endif /* DROPBEAR_H */
diff --git a/scripting/jvmMain/java/com/dropbear/asset/ModelNative.java b/scripting/jvmMain/java/com/dropbear/asset/ModelNative.java
new file mode 100644
index 0000000..acf273b
--- /dev/null
+++ b/scripting/jvmMain/java/com/dropbear/asset/ModelNative.java
@@ -0,0 +1,17 @@
+package com.dropbear.asset;
+
+import com.dropbear.EucalyptusCoreLoader;
+import com.dropbear.asset.model.*;
+
+public class ModelNative {
+    static {
+        new EucalyptusCoreLoader().ensureLoaded();
+    }
+
+    public static native String getLabel(long assetManagerHandle, String modelName);
+    public static native Mesh[] getMeshes(long assetManagerHandle, long modelHandle);
+    public static native Material[] getMaterials(long assetManagerHandle, long modelHandle);
+    public static native Skin[] getSkins(long assetManagerHandle, long modelHandle);
+    public static native Animation[] getAnimations(long assetManagerHandle, long modelHandle);
+    public static native Node[] getNodes(long assetManagerHandle, long modelHandle);
+}
diff --git a/scripting/jvmMain/kotlin/com/dropbear/animation/AnimationComponent.jvm.kt b/scripting/jvmMain/kotlin/com/dropbear/animation/AnimationComponent.jvm.kt
new file mode 100644
index 0000000..3901075
--- /dev/null
+++ b/scripting/jvmMain/kotlin/com/dropbear/animation/AnimationComponent.jvm.kt
@@ -0,0 +1,38 @@
+package com.dropbear.animation
+
+import com.dropbear.animation.AnimationComponent
+
+actual fun AnimationComponent.getActiveAnimationIndex(): Int? {
+    TODO("Not yet implemented")
+}
+
+actual fun AnimationComponent.setActiveAnimationIndex(index: Int?) {
+}
+
+actual fun AnimationComponent.getTime(): Float {
+    TODO("Not yet implemented")
+}
+
+actual fun AnimationComponent.setTime(value: Float) {
+}
+
+actual fun AnimationComponent.getSpeed(): Float {
+    TODO("Not yet implemented")
+}
+
+actual fun AnimationComponent.setSpeed(value: Float) {
+}
+
+actual fun AnimationComponent.getLooping(): Boolean {
+    TODO("Not yet implemented")
+}
+
+actual fun AnimationComponent.setLooping(value: Boolean) {
+}
+
+actual fun AnimationComponent.getIsPlaying(): Boolean {
+    TODO("Not yet implemented")
+}
+
+actual fun AnimationComponent.setIsPlaying(value: Boolean) {
+}
\ No newline at end of file
diff --git a/scripting/jvmMain/kotlin/com/dropbear/asset/Model.jvm.kt b/scripting/jvmMain/kotlin/com/dropbear/asset/Model.jvm.kt
index 43092c5..585ee90 100644
--- a/scripting/jvmMain/kotlin/com/dropbear/asset/Model.jvm.kt
+++ b/scripting/jvmMain/kotlin/com/dropbear/asset/Model.jvm.kt
@@ -1,5 +1,6 @@
 package com.dropbear.asset
 
+import com.dropbear.DropbearEngine
 import com.dropbear.asset.model.Animation
 import com.dropbear.asset.model.Material
 import com.dropbear.asset.model.Mesh
@@ -7,25 +8,25 @@ import com.dropbear.asset.model.Node
 import com.dropbear.asset.model.Skin
 
 actual fun Model.getLabel(): String {
-    TODO("Not yet implemented")
+    return ModelNative.getLabel(DropbearEngine.native.assetHandle, label)
 }
 
 actual fun Model.getMeshes(): List<Mesh> {
-    return emptyList()
+    return ModelNative.getMeshes(DropbearEngine.native.assetHandle, this.id).toList()
 }
 
 actual fun Model.getMaterials(): List<Material> {
-    return emptyList()
+    return ModelNative.getMaterials(DropbearEngine.native.assetHandle, this.id).toList()
 }
 
 actual fun Model.getSkins(): List<Skin> {
-    return emptyList()
+    return ModelNative.getSkins(DropbearEngine.native.assetHandle, this.id).toList()
 }
 
 actual fun Model.getAnimations(): List<Animation> {
-    return emptyList()
+    return ModelNative.getAnimations(DropbearEngine.native.assetHandle, this.id).toList()
 }
 
 actual fun Model.getNodes(): List<Node> {
-    return emptyList()
-}
+    return ModelNative.getNodes(DropbearEngine.native.assetHandle, this.id).toList()
+}
\ No newline at end of file
diff --git a/scripting/jvmMain/kotlin/com/dropbear/ui/Response.jvm.kt b/scripting/jvmMain/kotlin/com/dropbear/ui/Response.jvm.kt
new file mode 100644
index 0000000..5c78860
--- /dev/null
+++ b/scripting/jvmMain/kotlin/com/dropbear/ui/Response.jvm.kt
@@ -0,0 +1,11 @@
+package com.dropbear.ui
+
+import com.dropbear.ui.Response
+
+actual fun Response.getClicked(): Boolean {
+    TODO("Not yet implemented")
+}
+
+actual fun Response.getHovering(): Boolean {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/scripting/jvmMain/kotlin/com/dropbear/ui/widgets/Rectangle.jvm.kt b/scripting/jvmMain/kotlin/com/dropbear/ui/widgets/Rectangle.jvm.kt
new file mode 100644
index 0000000..bb7a043
--- /dev/null
+++ b/scripting/jvmMain/kotlin/com/dropbear/ui/widgets/Rectangle.jvm.kt
@@ -0,0 +1,8 @@
+package com.dropbear.ui.widgets
+
+import com.dropbear.ui.Response
+import com.dropbear.ui.widgets.Rectangle
+
+actual fun Rectangle.getResponse(): Response {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/scripting/jvmMain/kotlin/com/dropbear/ui/widgets/Text.jvm.kt b/scripting/jvmMain/kotlin/com/dropbear/ui/widgets/Text.jvm.kt
new file mode 100644
index 0000000..86f4d31
--- /dev/null
+++ b/scripting/jvmMain/kotlin/com/dropbear/ui/widgets/Text.jvm.kt
@@ -0,0 +1,7 @@
+package com.dropbear.ui.widgets
+
+import com.dropbear.ui.Response
+
+actual fun Text.getResponse(): Response {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/scripting/nativeMain/kotlin/com/dropbear/animation/AnimationComponent.native.kt b/scripting/nativeMain/kotlin/com/dropbear/animation/AnimationComponent.native.kt
new file mode 100644
index 0000000..3901075
--- /dev/null
+++ b/scripting/nativeMain/kotlin/com/dropbear/animation/AnimationComponent.native.kt
@@ -0,0 +1,38 @@
+package com.dropbear.animation
+
+import com.dropbear.animation.AnimationComponent
+
+actual fun AnimationComponent.getActiveAnimationIndex(): Int? {
+    TODO("Not yet implemented")
+}
+
+actual fun AnimationComponent.setActiveAnimationIndex(index: Int?) {
+}
+
+actual fun AnimationComponent.getTime(): Float {
+    TODO("Not yet implemented")
+}
+
+actual fun AnimationComponent.setTime(value: Float) {
+}
+
+actual fun AnimationComponent.getSpeed(): Float {
+    TODO("Not yet implemented")
+}
+
+actual fun AnimationComponent.setSpeed(value: Float) {
+}
+
+actual fun AnimationComponent.getLooping(): Boolean {
+    TODO("Not yet implemented")
+}
+
+actual fun AnimationComponent.setLooping(value: Boolean) {
+}
+
+actual fun AnimationComponent.getIsPlaying(): Boolean {
+    TODO("Not yet implemented")
+}
+
+actual fun AnimationComponent.setIsPlaying(value: Boolean) {
+}
\ No newline at end of file
diff --git a/scripting/nativeMain/kotlin/com/dropbear/ui/Response.native.kt b/scripting/nativeMain/kotlin/com/dropbear/ui/Response.native.kt
new file mode 100644
index 0000000..5c78860
--- /dev/null
+++ b/scripting/nativeMain/kotlin/com/dropbear/ui/Response.native.kt
@@ -0,0 +1,11 @@
+package com.dropbear.ui
+
+import com.dropbear.ui.Response
+
+actual fun Response.getClicked(): Boolean {
+    TODO("Not yet implemented")
+}
+
+actual fun Response.getHovering(): Boolean {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/scripting/nativeMain/kotlin/com/dropbear/ui/widgets/Rectangle.native.kt b/scripting/nativeMain/kotlin/com/dropbear/ui/widgets/Rectangle.native.kt
new file mode 100644
index 0000000..bb7a043
--- /dev/null
+++ b/scripting/nativeMain/kotlin/com/dropbear/ui/widgets/Rectangle.native.kt
@@ -0,0 +1,8 @@
+package com.dropbear.ui.widgets
+
+import com.dropbear.ui.Response
+import com.dropbear.ui.widgets.Rectangle
+
+actual fun Rectangle.getResponse(): Response {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/scripting/nativeMain/kotlin/com/dropbear/ui/widgets/Text.native.kt b/scripting/nativeMain/kotlin/com/dropbear/ui/widgets/Text.native.kt
new file mode 100644
index 0000000..86f4d31
--- /dev/null
+++ b/scripting/nativeMain/kotlin/com/dropbear/ui/widgets/Text.native.kt
@@ -0,0 +1,7 @@
+package com.dropbear.ui.widgets
+
+import com.dropbear.ui.Response
+
+actual fun Text.getResponse(): Response {
+    TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/animation/AnimationComponent.jvm.kt b/src/jvmMain/kotlin/com/dropbear/animation/AnimationComponent.jvm.kt
deleted file mode 100644
index 7392abd..0000000
--- a/src/jvmMain/kotlin/com/dropbear/animation/AnimationComponent.jvm.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.dropbear.animation
-
-actual fun AnimationComponent.getActiveAnimationIndex(): Int? {
-    TODO("Not yet implemented")
-}
-
-actual fun AnimationComponent.setActiveAnimationIndex(index: Int?) {
-}
-
-actual fun AnimationComponent.getTime(): Float {
-    TODO("Not yet implemented")
-}
-
-actual fun AnimationComponent.setTime(value: Float) {
-}
-
-actual fun AnimationComponent.getSpeed(): Float {
-    TODO("Not yet implemented")
-}
-
-actual fun AnimationComponent.setSpeed(value: Float) {
-}
-
-actual fun AnimationComponent.getLooping(): Boolean {
-    TODO("Not yet implemented")
-}
-
-actual fun AnimationComponent.setLooping(value: Boolean) {
-}
-
-actual fun AnimationComponent.getIsPlaying(): Boolean {
-    TODO("Not yet implemented")
-}
-
-actual fun AnimationComponent.setIsPlaying(value: Boolean) {
-}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/ui/Response.jvm.kt b/src/jvmMain/kotlin/com/dropbear/ui/Response.jvm.kt
deleted file mode 100644
index 8b0c975..0000000
--- a/src/jvmMain/kotlin/com/dropbear/ui/Response.jvm.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.dropbear.ui
-
-actual fun com.dropbear.ui.Response.getClicked(): Boolean {
-    TODO("Not yet implemented")
-}
-
-actual fun Response.getHovering(): Boolean {
-    TODO("Not yet implemented")
-}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/ui/widgets/Rectangle.jvm.kt b/src/jvmMain/kotlin/com/dropbear/ui/widgets/Rectangle.jvm.kt
deleted file mode 100644
index 6b19308..0000000
--- a/src/jvmMain/kotlin/com/dropbear/ui/widgets/Rectangle.jvm.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.dropbear.ui.widgets
-
-import com.dropbear.ui.Response
-
-actual fun Rectangle.getResponse(): Response {
-    TODO("Not yet implemented")
-}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/ui/widgets/Text.jvm.kt b/src/jvmMain/kotlin/com/dropbear/ui/widgets/Text.jvm.kt
deleted file mode 100644
index 86f4d31..0000000
--- a/src/jvmMain/kotlin/com/dropbear/ui/widgets/Text.jvm.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.dropbear.ui.widgets
-
-import com.dropbear.ui.Response
-
-actual fun Text.getResponse(): Response {
-    TODO("Not yet implemented")
-}
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/animation/AnimationComponent.native.kt b/src/nativeMain/kotlin/com/dropbear/animation/AnimationComponent.native.kt
deleted file mode 100644
index 7392abd..0000000
--- a/src/nativeMain/kotlin/com/dropbear/animation/AnimationComponent.native.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.dropbear.animation
-
-actual fun AnimationComponent.getActiveAnimationIndex(): Int? {
-    TODO("Not yet implemented")
-}
-
-actual fun AnimationComponent.setActiveAnimationIndex(index: Int?) {
-}
-
-actual fun AnimationComponent.getTime(): Float {
-    TODO("Not yet implemented")
-}
-
-actual fun AnimationComponent.setTime(value: Float) {
-}
-
-actual fun AnimationComponent.getSpeed(): Float {
-    TODO("Not yet implemented")
-}
-
-actual fun AnimationComponent.setSpeed(value: Float) {
-}
-
-actual fun AnimationComponent.getLooping(): Boolean {
-    TODO("Not yet implemented")
-}
-
-actual fun AnimationComponent.setLooping(value: Boolean) {
-}
-
-actual fun AnimationComponent.getIsPlaying(): Boolean {
-    TODO("Not yet implemented")
-}
-
-actual fun AnimationComponent.setIsPlaying(value: Boolean) {
-}
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/ui/Response.native.kt b/src/nativeMain/kotlin/com/dropbear/ui/Response.native.kt
deleted file mode 100644
index 8b0c975..0000000
--- a/src/nativeMain/kotlin/com/dropbear/ui/Response.native.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.dropbear.ui
-
-actual fun com.dropbear.ui.Response.getClicked(): Boolean {
-    TODO("Not yet implemented")
-}
-
-actual fun Response.getHovering(): Boolean {
-    TODO("Not yet implemented")
-}
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/ui/widgets/Rectangle.native.kt b/src/nativeMain/kotlin/com/dropbear/ui/widgets/Rectangle.native.kt
deleted file mode 100644
index 6b19308..0000000
--- a/src/nativeMain/kotlin/com/dropbear/ui/widgets/Rectangle.native.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.dropbear.ui.widgets
-
-import com.dropbear.ui.Response
-
-actual fun Rectangle.getResponse(): Response {
-    TODO("Not yet implemented")
-}
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/ui/widgets/Text.native.kt b/src/nativeMain/kotlin/com/dropbear/ui/widgets/Text.native.kt
deleted file mode 100644
index 86f4d31..0000000
--- a/src/nativeMain/kotlin/com/dropbear/ui/widgets/Text.native.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.dropbear.ui.widgets
-
-import com.dropbear.ui.Response
-
-actual fun Text.getResponse(): Response {
-    TODO("Not yet implemented")
-}
\ No newline at end of file