kitgit

tirbofish/dropbear · diff

e99c89b · Thribhu K

wip: JNI header implementation is mostly done, a couple more headers before its done. implemented cbindgen generation, but doesnt look like its any good. FARK

Unverified

diff --git a/Cargo.toml b/Cargo.toml
index 9257b36..299d5a4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -80,6 +80,7 @@ egui_ltreeview = { version = "0.6", features = ["doc"] }
 dyn-hash = "1.0"
 semver = { version = "1.0", features = ["serde"] }
 rapier3d = { version = "0.31", features = [ "simd-stable", "serde-serialize" ] }
+cbindgen = { version = "0.29.2" }
 
 [workspace.dependencies.image]
 version = "0.25"
diff --git a/build.gradle.kts b/build.gradle.kts
index a076e42..9d7c8ea 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -127,7 +127,7 @@ kotlin {
     }
 }
 
-val extractJavaSources by tasks.registering(Sync::class) {
+val extractJavaSources by tasks.registering(Copy::class) {
     group = "jni"
     description = "Copies .java files from jvmMain/kotlin to a temp directory for header generation"
 
@@ -136,9 +136,9 @@ val extractJavaSources by tasks.registering(Sync::class) {
     into(layout.buildDirectory.dir("tmp/java-jni-sources"))
 }
 
-val generateJniHeaders by tasks.registering(JavaCompile::class) {
+val compileJavaForJni by tasks.registering(JavaCompile::class) {
     group = "jni"
-    description = "Generates JNI headers from extracted Java files"
+    description = "Compiles Java sources and generates JNI headers"
 
     dependsOn(extractJavaSources, "compileKotlinJvm")
 
@@ -149,7 +149,7 @@ val generateJniHeaders by tasks.registering(JavaCompile::class) {
         configurations.named("jvmCompileClasspath")
     )
 
-    destinationDirectory.set(layout.buildDirectory.dir("tmp/jni-dummy-classes"))
+    destinationDirectory.set(layout.buildDirectory.dir("tmp/jni-java-classes"))
 
     val headerOutputDir = layout.buildDirectory.dir("generated/jni-headers")
     options.headerOutputDirectory.set(headerOutputDir)
@@ -158,12 +158,78 @@ val generateJniHeaders by tasks.registering(JavaCompile::class) {
         val outDir = headerOutputDir.get().asFile
         if (outDir.exists()) {
             outDir.deleteRecursively()
+        }
+        outDir.mkdirs()
+    }
+}
+
+val generateKotlinJniHeaders by tasks.registering(Exec::class) {
+    group = "jni"
+    description = "Generates JNI headers from Kotlin external functions"
+
+    dependsOn("compileKotlinJvm")
+
+    val headerOutputDir = layout.buildDirectory.dir("generated/jni-headers")
+    val kotlinClassesDir = tasks.named("compileKotlinJvm").map {
+        it.outputs.files.filter { f -> f.isDirectory }
+    }
+
+    inputs.files(kotlinClassesDir)
+    outputs.dir(headerOutputDir)
+
+    doFirst {
+        val outDir = headerOutputDir.get().asFile
+        if (!outDir.exists()) {
             outDir.mkdirs()
         }
+
+        val classFiles = kotlinClassesDir.get().asFileTree
+            .filter { it.name.endsWith(".class") && !it.name.contains("$") }
+            .files
+
+        if (classFiles.isEmpty()) {
+            println("No Kotlin class files found for JNI header generation")
+            return@doFirst
+        }
+
+        val classpath = kotlinClassesDir.get().joinToString(File.pathSeparator) { it.absolutePath }
+
+        classFiles.forEach { classFile ->
+            val baseDir = kotlinClassesDir.get().first { classFile.startsWith(it) }
+            val relativePath = classFile.relativeTo(baseDir).path
+            val className = relativePath.removeSuffix(".class").replace(File.separatorChar, '.')
+
+            // Use javah (Java 8) or javac -h (Java 9+)
+            try {
+                exec {
+                    commandLine(
+                        "javac", "-h", outDir.absolutePath,
+                        "-cp", classpath,
+                        "-d", layout.buildDirectory.dir("tmp/jni-dummy-classes").get().asFile.absolutePath,
+                        classFile.absolutePath
+                    )
+                    isIgnoreExitValue = true
+                }
+            } catch (e: Exception) {
+                println("Warning: Could not generate header for $className: ${e.message}")
+            }
+        }
+
+        println("Generated Kotlin JNI Headers at: ${outDir.absolutePath}")
     }
+}
+
+val generateJniHeaders by tasks.registering {
+    group = "jni"
+    description = "Generates all JNI headers (Java and Kotlin)"
+
+    dependsOn(compileJavaForJni, generateKotlinJniHeaders)
 
     doLast {
-        println("Generated JNI Headers at: ${headerOutputDir.get().asFile.absolutePath}")
+        val headerDir = layout.buildDirectory.dir("generated/jni-headers").get().asFile
+        val headers = headerDir.listFiles()?.filter { it.extension == "h" } ?: emptyList()
+        println("Total JNI headers generated: ${headers.size}")
+        headers.forEach { println("  - ${it.name}") }
     }
 }
 
diff --git a/cbindgen.toml b/cbindgen.toml
new file mode 100644
index 0000000..d028744
--- /dev/null
+++ b/cbindgen.toml
@@ -0,0 +1,13 @@
+# cbindgen.toml
+
+language = "C"
+include_guard = "DROPBEAR_H"
+line_length = 100
+tab_width = 4
+
+[export]
+prefix = "dropbear_"
+
+[parse]
+parse_deps = true
+include = ["eucalyptus_core"]
\ No newline at end of file
diff --git a/dropbear-engine/src/entity.rs b/dropbear-engine/src/entity.rs
index 905522f..b47dc21 100644
--- a/dropbear-engine/src/entity.rs
+++ b/dropbear-engine/src/entity.rs
@@ -333,6 +333,26 @@ impl MeshRenderer {
         self.material_handle_raw(&ASSET_REGISTRY, material_name)
     }
 
+    pub fn collect_all_material_handles_raw(
+        &self,
+        registry: &AssetRegistry,
+    ) -> Vec<AssetHandle> {
+        let model = self.model();
+        let model_id = self.model_id();
+
+        model
+            .materials
+            .iter()
+            .filter_map(|material| {
+                registry.material_handle(model_id, &material.name)
+            })
+            .collect()
+    }
+
+    pub fn collect_all_material_handles(&self) -> Vec<AssetHandle> {
+        self.collect_all_material_handles_raw(&ASSET_REGISTRY)
+    }
+
     pub fn material_handle_raw(
         &self,
         registry: &AssetRegistry,
diff --git a/dropbear-macro/src/lib.rs b/dropbear-macro/src/lib.rs
index 3f4852e..75f2f72 100644
--- a/dropbear-macro/src/lib.rs
+++ b/dropbear-macro/src/lib.rs
@@ -1,6 +1,6 @@
 use proc_macro::TokenStream;
 use quote::quote;
-use syn::{DeriveInput, parse_macro_input};
+use syn::{DeriveInput, parse_macro_input, ItemMod, Item, Type, parse_quote, ItemFn, FnArg, ReturnType, PathArguments, GenericArgument};
 
 /// A `derive` macro that converts a struct to a usable [SerializableComponent].
 ///
@@ -46,3 +46,140 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
 
     TokenStream::from(expanded)
 }
+
+/// Converts a module's functions into C API compatible functions.
+/// 
+/// Each function must require a return type of `DropbearNativeResult<T>`. If the return type
+/// is not `DropbearNativeResult<T>`, it is recommended that you move it to another module (such as `super::shared`). 
+/// 
+/// A function like that of:
+/// ```
+/// pub fn dropbear_mesh_renderer_exists_for_entity(
+///      world_ptr: *mut hecs::World,
+///      entity_id: u64,
+///  ) -> DropbearNativeResult<bool> {...}
+/// ``` 
+/// will get converted to something like:
+/// ```
+/// pub unsafe extern "C" fn dropbear_mesh_renderer_exists_for_entity(world_ptr: WorldPtr, entity_id: u64, out_result: *mut bool) -> i32 {...}
+/// ```
+#[proc_macro_attribute]
+pub fn impl_c_api(_attr: TokenStream, item: TokenStream) -> TokenStream {
+    let mut module = parse_macro_input!(item as ItemMod);
+
+    let mut new_content = Vec::new();
+
+    if let Some((brace, content)) = module.content {
+        for item in content {
+            match item {
+                Item::Fn(func) => {
+                    new_content.push(Item::Fn(transform_function(func)));
+                }
+                _ => new_content.push(item),
+            }
+        }
+        module.content = Some((brace, new_content));
+    }
+
+    TokenStream::from(quote! { #module })
+}
+
+fn transform_function(mut func: ItemFn) -> ItemFn {
+    let inputs = func.sig.inputs.clone();
+    let output = func.sig.output.clone();
+    let block = func.block;
+
+    let inner_type = extract_inner_type(&output);
+    let is_void = is_unit_type(&inner_type);
+
+    let mut new_inputs = inputs.clone();
+
+    if !is_void {
+        let out_ptr_type: Type = parse_quote! { *mut #inner_type };
+
+        new_inputs.push(FnArg::Typed(syn::PatType {
+            attrs: vec![],
+            pat: Box::new(parse_quote! { out_result }),
+            colon_token: Default::default(),
+            ty: Box::new(out_ptr_type),
+        }));
+    }
+    
+    let pointer_check = if !is_void {
+        quote! {
+            if out_result.is_null() {
+                return crate::scripting::native::DropbearNativeError::NullPointer as i32;
+            }
+        }
+    } else {
+        quote! {}
+    };
+
+    let success_handling = if !is_void {
+        quote! {
+            unsafe { *out_result = val; }
+            crate::scripting::native::DropbearNativeError::Success as i32
+        }
+    } else {
+        quote! {
+            crate::scripting::native::DropbearNativeError::Success as i32
+        }
+    };
+
+    let new_body = quote! {
+        {
+            #pointer_check
+            
+            let logic = || #output {
+                #block
+            };
+            
+            match logic() {
+                DropbearNativeResult::Ok(val) => {
+                    #success_handling
+                }
+                DropbearNativeResult::Err(e) => {
+                    e as i32
+                }
+            }
+        }
+    };
+
+    func.sig.inputs = new_inputs;
+    func.sig.output = parse_quote! { -> i32 };
+    func.sig.abi = Some(parse_quote! { extern "C" });
+    func.sig.unsafety = Some(parse_quote! { unsafe });
+
+    func.attrs.push(parse_quote! { #[unsafe(no_mangle)] });
+
+    func.block = Box::new(syn::parse2(new_body).expect("Failed to parse new body"));
+
+    func
+}
+
+/// Helper to dig into Result<T, E> and get T
+fn extract_inner_type(output: &ReturnType) -> Type {
+    match output {
+        ReturnType::Type(_, ty) => {
+            if let Type::Path(type_path) = &**ty {
+                if let Some(segment) = type_path.path.segments.last() {
+                    if let PathArguments::AngleBracketed(args) = &segment.arguments {
+                        if let Some(GenericArgument::Type(inner)) = args.args.first() {
+                            return inner.clone();
+                        }
+                    }
+                }
+            }
+            parse_quote! { () }
+        }
+        ReturnType::Default => parse_quote! { () },
+    }
+}
+
+/// Helper to check if type is ()
+fn is_unit_type(ty: &Type) -> bool {
+    if let Type::Tuple(tuple) = ty {
+        return tuple.elems.is_empty();
+    }
+    false
+}
diff --git a/eucalyptus-core/Cargo.toml b/eucalyptus-core/Cargo.toml
index 77ce714..47b3bcd 100644
--- a/eucalyptus-core/Cargo.toml
+++ b/eucalyptus-core/Cargo.toml
@@ -56,5 +56,6 @@ jvm = []
 jvm_debug = ["jvm"]
 
 [build-dependencies]
-anyhow = "1.0"
-app_dirs2 = "2.5"
+anyhow.workspace = true
+app_dirs2.workspace = true
+cbindgen.workspace = true
diff --git a/eucalyptus-core/build.rs b/eucalyptus-core/build.rs
index 2e3421e..eaba71d 100644
--- a/eucalyptus-core/build.rs
+++ b/eucalyptus-core/build.rs
@@ -1,4 +1,37 @@
+use std::env;
+use std::path::PathBuf;
+
 fn main() -> anyhow::Result<()> {
+    let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
+
+    let root_dir = PathBuf::from(&crate_dir).parent().unwrap().to_path_buf();
+
+    let expanded = std::process::Command::new("cargo")
+        .args(&["expand", "--lib", "-p", "eucalyptus-core"])
+        .current_dir(&root_dir)
+        .output()?;
+
+    let temp_file = "target/generated/expanded.rs";
+    let temp_file = root_dir.join(temp_file);
+    std::fs::create_dir_all(temp_file.parent().unwrap())?;
+    std::fs::write(&root_dir.join(&temp_file), expanded.stderr)?;
+
+    let output_file = root_dir
+        .join("headers")
+        .join("dropbear.h");
+
+    let config = cbindgen::Config::from_file("../cbindgen.toml")
+        .expect("Could not find cbindgen.toml");
+
+    cbindgen::Builder::new()
+        .with_src(temp_file)
+        .with_config(config)
+        .generate()
+        .expect("Unable to generate bindings")
+        .write_to_file(output_file);
+
+    println!("cargo:rerun-if-changed=cbindgen.toml");
+
     // fuck you windows :(
     #[cfg(target_os = "windows")]
     {
@@ -7,5 +40,6 @@ fn main() -> anyhow::Result<()> {
     }
 
     println!("cargo:rerun-if-changed=build.rs");
+    println!("cargo:rerun-if-changed=src/lib.rs");
     Ok(())
 }
diff --git a/eucalyptus-core/src/asset.rs b/eucalyptus-core/src/asset.rs
new file mode 100644
index 0000000..fa00e93
--- /dev/null
+++ b/eucalyptus-core/src/asset.rs
@@ -0,0 +1,93 @@
+pub mod texture;
+
+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 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 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/eucalyptus-core/src/asset/texture.rs b/eucalyptus-core/src/asset/texture.rs
new file mode 100644
index 0000000..0e30f30
--- /dev/null
+++ b/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 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(|e| 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/eucalyptus-core/src/camera.rs b/eucalyptus-core/src/camera.rs
index df3fff2..af4e967 100644
--- a/eucalyptus-core/src/camera.rs
+++ b/eucalyptus-core/src/camera.rs
@@ -97,12 +97,6 @@ impl DebugCamera {
     }
 }
 
-// #[derive(Debug, Default, Clone)]
-// pub struct CameraFollowTarget {
-//     pub follow_target: String,
-//     pub offset: DVec3,
-// }
-
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
 pub enum CameraType {
     Normal,
@@ -122,3 +116,738 @@ pub enum CameraAction {
     ClearPlayerTarget,
     SetCurrentPositionAsOffset(hecs::Entity),
 }
+
+pub mod shared {
+    use dropbear_engine::camera::Camera;
+
+    pub fn camera_exists_for_entity(world: &hecs::World, entity: hecs::Entity) -> bool {
+        world.get::<&Camera>(entity).is_ok()
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+
+    use glam::DVec3;
+    use jni::JNIEnv;
+    use jni::objects::{JClass, JObject};
+    use jni::sys::{jboolean, jdouble, jlong, jobject};
+    use dropbear_engine::camera::Camera;
+    use crate::convert_jlong_to_entity;
+    use crate::scripting::jni::utils::{FromJObject, ToJObject};
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_cameraExistsForEntity(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jboolean {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if world.get::<&Camera>(entity).is_ok() {
+            true.into()
+        } else {
+            false.into()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraEye(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jobject {
+        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: DVec3 = camera.eye;
+            return match eye.to_jobject(&mut env) {
+                Ok(val) => val.into_raw(),
+                Err(_) => std::ptr::null_mut()
+            };
+        } else {
+            let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Entity {} missing Camera", entity_id));
+            std::ptr::null_mut()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraEye(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        eye_obj: JObject,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        let new_eye = match DVec3::from_jobject(&mut env, &eye_obj) {
+            Ok(v) => v,
+            Err(e) => {
+                let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Invalid Vector3d: {:?}", e));
+                return;
+            }
+        };
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.eye = new_eye;
+        } else {
+            let _ = env.throw_new("java/lang/IllegalArgumentException", "Entity missing Camera component");
+        }
+    }
+
+    // TARGET
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraTarget(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jobject {
+        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: DVec3 = camera.target;
+            return match target.to_jobject(&mut env) {
+                Ok(val) => val.into_raw(),
+                Err(_) => std::ptr::null_mut()
+            };
+        } else {
+            let _ = env.throw_new("java/lang/IllegalArgumentException", "Entity missing Camera component");
+            std::ptr::null_mut()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraTarget(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        target_obj: JObject,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        let new_target = match DVec3::from_jobject(&mut env, &target_obj) {
+            Ok(v) => v,
+            Err(_) => return,
+        };
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.target = new_target;
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraUp(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jobject {
+        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: DVec3 = camera.up;
+            return match up.to_jobject(&mut env) {
+                Ok(val) => val.into_raw(),
+                Err(_) => std::ptr::null_mut()
+            };
+        } else {
+            let _ = env.throw_new("java/lang/IllegalArgumentException", "Entity missing Camera component");
+            std::ptr::null_mut()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraUp(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        up_obj: JObject,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        let new_up = match DVec3::from_jobject(&mut env, &up_obj) {
+            Ok(v) => v,
+            Err(_) => return,
+        };
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.up = new_up;
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraAspect(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jdouble {
+        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) {
+            camera.aspect
+        } else {
+            0.0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraFovY(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jdouble {
+        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) {
+            camera.settings.fov_y
+        } else {
+            0.0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraFovY(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        value: jdouble,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.settings.fov_y = value;
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraZNear(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jdouble {
+        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) {
+            camera.znear
+        } else {
+            0.0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraZNear(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        value: jdouble,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.znear = value;
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraZFar(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jdouble {
+        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) {
+            camera.zfar
+        } else {
+            0.0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraZFar(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        value: jdouble,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.zfar = value;
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraYaw(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jdouble {
+        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) {
+            camera.yaw
+        } else {
+            0.0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraYaw(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        value: jdouble,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.yaw = value;
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraPitch(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jdouble {
+        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) {
+            camera.pitch
+        } else {
+            0.0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraPitch(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        value: jdouble,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.pitch = value;
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraSpeed(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jdouble {
+        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) {
+            camera.settings.speed
+        } else {
+            0.0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraSpeed(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        value: jdouble,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.settings.speed = value;
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_getCameraSensitivity(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jdouble {
+        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) {
+            camera.settings.sensitivity
+        } else {
+            0.0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CameraNative_setCameraSensitivity(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        value: jdouble,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let entity = convert_jlong_to_entity!(entity_id);
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.settings.sensitivity = value;
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use crate::ptr::WorldPtr;
+    use crate::convert_ptr;
+    use crate::scripting::native::{DropbearNativeError};
+    use hecs::Entity;
+    use glam::DVec3;
+    use dropbear_engine::camera::Camera;
+    use crate::scripting::result::DropbearNativeResult;
+    use crate::types::Vector3;
+
+    pub fn dropbear_camera_exists_for_entity(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<bool> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if world.get::<&Camera>(entity).is_ok() {
+            DropbearNativeResult::Ok(true)
+        } else {
+            DropbearNativeResult::Ok(false)
+        }
+    }
+
+    pub fn dropbear_get_camera_eye(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<Vector3> {
+        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))
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_eye(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: Vector3,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.eye = DVec3::from(value);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_target(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<Vector3> {
+        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))
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_target(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: Vector3,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.target = DVec3::from(value);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_up(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<Vector3> {
+        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))
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_up(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: Vector3,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.up = DVec3::from(value);
+            Ok(())
+        } else {
+            Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_aspect(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<f64> {
+        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(camera.aspect)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_fov_y(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<f64> {
+        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(camera.settings.fov_y)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_fov_y(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: f64,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.settings.fov_y = value;
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_znear(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<f64> {
+        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(camera.znear)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_znear(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: f64,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.znear = value;
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_zfar(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<f64> {
+        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(camera.zfar)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_zfar(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: f64,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.zfar = value;
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_yaw(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<f64> {
+        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(camera.yaw)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_yaw(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: f64,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.yaw = value;
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_pitch(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<f64> {
+        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(camera.pitch)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_pitch(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: f64,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.pitch = value;
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_speed(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<f64> {
+        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(camera.settings.speed)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_speed(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: f64,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.settings.speed = value;
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_camera_sensitivity(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<f64> {
+        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(camera.settings.sensitivity)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_camera_sensitivity(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: f64,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut camera) = world.get::<&mut Camera>(entity) {
+            camera.settings.sensitivity = value;
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+}
diff --git a/eucalyptus-core/src/engine.rs b/eucalyptus-core/src/engine.rs
new file mode 100644
index 0000000..d946533
--- /dev/null
+++ b/eucalyptus-core/src/engine.rs
@@ -0,0 +1,165 @@
+pub mod shared {
+    use crate::command::CommandBuffer;
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+    use crate::states::Label;
+    use dropbear_engine::asset::AssetRegistry;
+    use dropbear_engine::utils::ResourceReference;
+    use hecs::World;
+    use std::ffi::CStr;
+    use std::os::raw::c_char;
+
+    pub fn get_entity(world: &World, label: &str) -> DropbearNativeResult<u64> {
+        for (id, entity_label) in world.query::<&Label>().iter() {
+            if entity_label.as_str() == label {
+                return Ok(id.to_bits().get());
+            }
+        }
+        Err(DropbearNativeError::EntityNotFound)
+    }
+
+    pub fn get_asset(registry: &AssetRegistry, uri: &str) -> DropbearNativeResult<u64> {
+        let reference = ResourceReference::from_euca_uri(uri)
+            .map_err(|_| DropbearNativeError::InvalidURI)?;
+
+        match registry.get_handle_from_reference(&reference) {
+            Some(handle) => Ok(handle.raw()),
+            None => Err(DropbearNativeError::AssetNotFound),
+        }
+    }
+
+    pub fn quit(command_buffer: &crossbeam_channel::Sender<CommandBuffer>) -> DropbearNativeResult<()> {
+        command_buffer.send(CommandBuffer::Quit)
+            .map_err(|_| DropbearNativeError::SendError)
+    }
+
+    pub unsafe fn read_str(ptr: *const c_char) -> DropbearNativeResult<String> {
+        if ptr.is_null() { return Err(DropbearNativeError::NullPointer); }
+        unsafe { CStr::from_ptr(ptr) }.to_str()
+            .map(|s| s.to_string())
+            .map_err(|_| DropbearNativeError::InvalidUTF8)
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+    use hecs::World;
+    use jni::objects::{JClass, JString};
+    use jni::sys::{jlong, jobject};
+    use jni::JNIEnv;
+    use dropbear_engine::asset::AssetRegistry;
+    use crate::command::CommandBuffer;
+    use crate::return_boxed;
+
+    macro_rules! return_nullable_long {
+        ($env:expr, $res:expr) => {
+            match $res {
+                Ok(val) => {
+                    let cls = $env.find_class("java/lang/Long").unwrap();
+                    let method_id = $env.get_static_method_id(cls, "valueOf", "(J)Ljava/lang/Long;").unwrap();
+                    $env.call_static_method_unchecked(cls, method_id, jni::signature::JavaType::Object, &[JValue::Long(val as i64)])
+                        .unwrap().l().unwrap().into_raw()
+                },
+                Err(_) => std::ptr::null_mut(),
+            }
+        };
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_DropbearEngineNative_getEntity(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        label: JString,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_handle => World);
+        let label_str = crate::convert_jstring!(env, label);
+
+        let value_opt = super::shared::get_entity(&world, &label_str)
+            .ok()
+            .map(|id| id as i64);
+
+        return_boxed!(
+            &mut env,
+            value_opt,
+            "(J)Ljava/lang/Long;",
+            "java/lang/Long"
+        )
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_DropbearEngineNative_getAsset(
+        mut env: JNIEnv,
+        _class: JClass,
+        asset_handle: jlong,
+        label: JString,
+    ) -> jobject {
+        let asset = crate::convert_ptr!(asset_handle => AssetRegistry);
+        let label_str = crate::convert_jstring!(env, label);
+
+        let value_opt = super::shared::get_asset(&asset, &label_str)
+            .ok()
+            .map(|id| id as i64);
+
+        return_boxed!(
+            &mut env,
+            value_opt,
+            "(J)Ljava/lang/Long;",
+            "java/lang/Long"
+        )
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_DropbearEngineNative_quit(
+        mut env: JNIEnv,
+        _class: JClass,
+        command_buffer_ptr: jlong,
+    ) {
+        let sender = crate::convert_ptr!(command_buffer_ptr => crossbeam_channel::Sender<CommandBuffer>);
+
+        if let Err(e) = super::shared::quit(sender) {
+            let _ = env.throw_new(
+                "java/lang/RuntimeException",
+                format!("Failed to send quit command: {:?}", e)
+            );
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use crate::command::CommandBuffer;
+    use crate::convert_ptr;
+    use crate::engine::shared::read_str;
+    use crate::ptr::{AssetRegistryPtr, CommandBufferPtr, WorldPtr};
+    use crate::scripting::result::DropbearNativeResult;
+    use dropbear_engine::asset::AssetRegistry;
+    use hecs::World;
+    use std::os::raw::c_char;
+
+    pub fn dropbear_get_entity(
+        world_ptr: WorldPtr,
+        label: *const c_char,
+    ) -> DropbearNativeResult<u64> {
+        let world = convert_ptr!(world_ptr => World);
+        let label_str = unsafe { read_str(label)? };
+
+        super::shared::get_entity(world, &label_str)
+    }
+
+    pub fn dropbear_get_asset(
+        asset_ptr: AssetRegistryPtr,
+        uri: *const c_char,
+    ) -> DropbearNativeResult<u64> {
+        let asset_registry = convert_ptr!(asset_ptr => AssetRegistry);
+        let uri_str = unsafe { read_str(uri)? };
+        super::shared::get_asset(&asset_registry, &uri_str)
+    }
+
+    pub fn dropbear_quit(
+        command_ptr: CommandBufferPtr,
+    ) -> DropbearNativeResult<()> {
+        let sender = convert_ptr!(command_ptr => crossbeam_channel::Sender<CommandBuffer>);
+        super::shared::quit(sender)
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/entity.rs b/eucalyptus-core/src/entity.rs
new file mode 100644
index 0000000..bb9a87e
--- /dev/null
+++ b/eucalyptus-core/src/entity.rs
@@ -0,0 +1,125 @@
+pub mod jni {
+    #![allow(non_snake_case)]
+    use crate::hierarchy::{Children, Parent};
+    use crate::states::Label;
+    use crate::{convert_jlong_to_entity, convert_jstring, convert_ptr};
+    use hecs::World;
+    use jni::objects::{JClass, JString, JValue};
+    use jni::sys::{jlong, jlongArray, jobject, jsize, jstring};
+    use jni::JNIEnv;
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_EntityRefNative_getEntityLabel(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jstring {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+
+        let Ok(label) = world.get::<&Label>(entity) else {
+            let _ = env.throw_new("java/lang/InvalidArgumentException", "Unable to locate label entity");
+            return std::ptr::null_mut();
+        };
+
+        let label = label.as_str();
+
+        let Ok(string) = env.new_string(label) else {
+            let _ = env.throw_new("java/lang/OutOfMemoryException", "Unable to create new string");
+            return std::ptr::null_mut();
+        };
+
+        string.into_raw()
+    }
+
+    pub fn Java_com_dropbear_EntityRefNative_getChildren(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jlongArray {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+
+        if let Ok(mut q) = world.query_one::<&Children>(entity)
+            && let Some(children) = q.get()
+        {
+            let entity_bytes = children.children().iter().map(|c| c.to_bits().get() as i64).collect::<Vec<_>>();
+
+            let Ok(jarray) = env.new_long_array(entity_bytes.len() as jsize) else {
+                let _ = env.throw_new("java/lang/OutOfMemoryException", "Unable to create new long array");
+                return std::ptr::null_mut();
+            };
+
+            if let Err(e) = env.set_long_array_region(&jarray, 0, entity_bytes.as_slice()) {
+                let _ = env.throw_new("java/lang/RuntimeException", format!("Unable to populate long array: {}", e));
+                return std::ptr::null_mut()
+            }
+
+            jarray.into_raw()
+        } else {
+            // could be that the entity just doesn't have any children.
+            std::ptr::null_mut()
+        }
+    }
+
+    pub fn Java_com_dropbear_EntityRefNative_getChildByLabel(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        label: JString,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let target = convert_jstring!(env, label);
+
+        if let Ok(mut q) = world.query_one::<&Children>(entity)
+            && let Some(children) = q.get()
+        {
+            for child in children.children() {
+                if let Ok(label) = world.get::<&Label>(entity) {
+                    if label.as_str() == target {
+                        let found = child.clone();
+
+                        return crate::return_boxed!(&mut env, Some(JValue::Long(found.to_bits().get() as i64)), "(J)Ljava/lang/Long", "java/lang/Long");
+                    }
+                } else {
+                    // skip if error or no entity
+                    continue;
+                }
+            }
+            std::ptr::null_mut()
+        } else {
+            // could be that the entity just doesn't have any children.
+            std::ptr::null_mut()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_EntityRefNative_getParent(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jobject {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = convert_jlong_to_entity!(entity_id);
+
+        if let Ok(mut q) = world.query_one::<&Parent>(entity) {
+            if let Some(parent) = q.get() {
+                crate::return_boxed!(&mut env, Some(JValue::Long(parent.parent().to_bits().get() as jlong)), "(J)Ljava/lang/Long", "java/lang/Long")
+            } else {
+                std::ptr::null_mut()
+            }
+        } else {
+            crate::ffi_error_return!("No entity exists")
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/input.rs b/eucalyptus-core/src/input.rs
index a66f719..a34adac 100644
--- a/eucalyptus-core/src/input.rs
+++ b/eucalyptus-core/src/input.rs
@@ -1,13 +1,23 @@
-//! Input management and input state. 
+//! Input management and input state.
+
+pub mod gamepad;
+
 use dropbear_engine::gilrs::{Button, GamepadId};
 use std::sync::Arc;
 use std::{
     collections::{HashMap, HashSet},
     time::{Duration, Instant},
 };
+use glam::Vec2;
 use winit::window::Window;
 use winit::{event::MouseButton, keyboard::KeyCode};
-use crate::scripting::native::exports::dropbear_input::Gamepad;
+
+#[derive(Clone, Debug)]
+pub struct Gamepad {
+    id: i32,
+    left_stick_pos: Vec2,
+    right_stick_pos: Vec2,
+}
 
 /// Shows the information about the input at that current time. 
 #[derive(Clone, Debug)]
@@ -97,3 +107,349 @@ impl InputState {
         self.connected_gamepads.contains(&gamepad_id)
     }
 }
+
+pub mod shared {
+    use crossbeam_channel::Sender;
+    use crate::command::{CommandBuffer, WindowCommand};
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+    use crate::types::Vector2;
+    use super::*;
+
+    pub fn map_ordinal_to_mouse_button(ordinal: i32) -> Option<MouseButton> {
+        match ordinal {
+            0 => Some(MouseButton::Left),
+            1 => Some(MouseButton::Right),
+            2 => Some(MouseButton::Middle),
+            3 => Some(MouseButton::Back),
+            4 => Some(MouseButton::Forward),
+            ordinal if ordinal >= 0 => Some(MouseButton::Other(ordinal as u16)),
+            _ => None,
+        }
+    }
+
+    pub fn is_key_pressed(input: &InputState, key_ordinal: i32) -> bool {
+        if let Some(key) = crate::utils::keycode_from_ordinal(key_ordinal) {
+            input.is_key_pressed(key)
+        } else {
+            false
+        }
+    }
+
+    pub fn is_mouse_button_pressed(input: &InputState, btn_ordinal: i32) -> bool {
+        if let Some(btn) = map_ordinal_to_mouse_button(btn_ordinal) {
+            input.mouse_button.contains(&btn)
+        } else {
+            false
+        }
+    }
+
+    pub fn get_mouse_position(input: &InputState) -> Vector2 {
+        Vector2 {
+            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_last_mouse_pos(input: &InputState) -> Vector2 {
+        input.last_mouse_pos.map(Vector2::from).unwrap_or(Vector2 { x: 0.0, y: 0.0 })
+    }
+
+    pub fn set_cursor_locked(
+        input: &mut InputState,
+        sender: &Sender<CommandBuffer>,
+        locked: bool
+    ) -> DropbearNativeResult<()> {
+        input.is_cursor_locked = locked;
+
+        sender.send(CommandBuffer::WindowCommand(WindowCommand::WindowGrab(locked)))
+            .map_err(|_| DropbearNativeError::SendError)?;
+
+        Ok(())
+    }
+
+    pub fn set_cursor_hidden(
+        input: &mut InputState,
+        sender: &Sender<CommandBuffer>,
+        hidden: bool
+    ) -> DropbearNativeResult<()> {
+        input.is_cursor_hidden = hidden;
+        sender.send(CommandBuffer::WindowCommand(WindowCommand::HideCursor(hidden)))
+            .map_err(|_| DropbearNativeError::SendError)?;
+        Ok(())
+    }
+
+    pub fn get_connected_gamepads(input: &InputState) -> Vec<u64> {
+        input.connected_gamepads.iter()
+            .map(|id| Into::<usize>::into(*id) as u64)
+            .collect()
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+    use jni::JNIEnv;
+    use jni::objects::{JClass, JObject};
+    use jni::sys::{jboolean, jint, jlong, jlongArray, jobject};
+    use crate::input::InputState;
+    use crate::command::CommandBuffer;
+    use crate::scripting::jni::utils::ToJObject;
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_printInputState(
+        _env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+    ) {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        println!("Input State: {:?}", input);
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_isKeyPressed(
+        _env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+        key_code: jint,
+    ) -> jboolean {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        if super::shared::is_key_pressed(&input, key_code) { 1 } else { 0 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_getMousePosition(
+        mut env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+    ) -> jobject {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        let vec = super::shared::get_mouse_position(&input);
+        match vec.to_jobject(&mut env) {
+            Ok(obj) => obj.into_raw(),
+            Err(_) => std::ptr::null_mut(),
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_isMouseButtonPressed(
+        mut env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+        mouse_button_obj: JObject,
+    ) -> jboolean {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+
+        let ordinal_res = env.call_method(&mouse_button_obj, "ordinal", "()I", &[]);
+
+        let ordinal = match ordinal_res.and_then(|v| v.i()) {
+            Ok(i) => i,
+            Err(e) => {
+                eprintln!("Failed to get MouseButton ordinal: {:?}", e);
+                return 0;
+            }
+        };
+
+        if super::shared::is_mouse_button_pressed(&input, ordinal) { 1 } else { 0 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_getMouseDelta(
+        mut env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+    ) -> jobject {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        let vec = super::shared::get_mouse_delta(&input);
+        match vec.to_jobject(&mut env) {
+            Ok(obj) => obj.into_raw(),
+            Err(_) => std::ptr::null_mut(),
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_isCursorLocked(
+        _env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+    ) -> jboolean {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        if input.is_cursor_locked { 1 } else { 0 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_setCursorLocked(
+        mut env: JNIEnv,
+        _class: JClass,
+        cmd_ptr: jlong,
+        input_ptr: jlong,
+        locked: jboolean,
+    ) {
+        let input = crate::convert_ptr!(mut input_ptr => InputState);
+        let sender = crate::convert_ptr!(cmd_ptr => crossbeam_channel::Sender<CommandBuffer>);
+
+        if let Err(e) = super::shared::set_cursor_locked(input, sender, locked != 0) {
+            let _ = env.throw_new("java/lang/RuntimeException", format!("{:?}", e));
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_getLastMousePos(
+        mut env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+    ) -> jobject {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        let vec = super::shared::get_last_mouse_pos(&input);
+        match vec.to_jobject(&mut env) {
+            Ok(obj) => obj.into_raw(),
+            Err(_) => std::ptr::null_mut(),
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_isCursorHidden(
+        _env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+    ) -> jboolean {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        if input.is_cursor_hidden { 1 } else { 0 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_setCursorHidden(
+        mut env: JNIEnv,
+        _class: JClass,
+        cmd_ptr: jlong,
+        input_ptr: jlong,
+        hidden: jboolean,
+    ) {
+        let input = crate::convert_ptr!(mut input_ptr => InputState);
+        let sender = crate::convert_ptr!(cmd_ptr => crossbeam_channel::Sender<CommandBuffer>);
+
+        if let Err(e) = super::shared::set_cursor_hidden(input, sender, hidden != 0) {
+            let _ = env.throw_new("java/lang/RuntimeException", format!("{:?}", e));
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_InputStateNative_getConnectedGamepads(
+        env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+    ) -> jlongArray {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        let ids = super::shared::get_connected_gamepads(&input);
+
+        let long_ids: Vec<i64> = ids.iter().map(|&id| id as i64).collect();
+
+        let output = match env.new_long_array(long_ids.len() as i32) {
+            Ok(arr) => arr,
+            Err(_) => return std::ptr::null_mut(),
+        };
+
+        if env.set_long_array_region(&output, 0, &long_ids).is_ok() {
+            output.into_raw()
+        } else {
+            std::ptr::null_mut()
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use crate::ptr::{InputStatePtr, CommandBufferPtr};
+    use crate::input::{InputState};
+    use crate::command::CommandBuffer;
+    use crate::convert_ptr;
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+    use crate::types::Vector2;
+
+    pub fn dropbear_print_input_state(input_ptr: InputStatePtr) -> DropbearNativeResult<()> {
+        let input = convert_ptr!(input_ptr => InputState);
+        println!("Input State: {:?}", input);
+        DropbearNativeResult::Ok(())
+    }
+
+    pub fn dropbear_is_key_pressed(input_ptr: InputStatePtr, key_ordinal: i32) -> DropbearNativeResult<bool> {
+        let input = convert_ptr!(input_ptr => InputState);
+        DropbearNativeResult::Ok(super::shared::is_key_pressed(input, key_ordinal))
+    }
+
+    pub fn dropbear_get_mouse_position(input_ptr: InputStatePtr) -> DropbearNativeResult<Vector2> {
+        let input = convert_ptr!(input_ptr => InputState);
+        DropbearNativeResult::Ok(super::shared::get_mouse_position(input))
+    }
+
+    pub fn dropbear_is_mouse_button_pressed(input_ptr: InputStatePtr, btn_ordinal: i32) -> DropbearNativeResult<bool> {
+        let input = convert_ptr!(input_ptr => InputState);
+        DropbearNativeResult::Ok(super::shared::is_mouse_button_pressed(input, btn_ordinal))
+    }
+
+    pub fn dropbear_get_mouse_delta(input_ptr: InputStatePtr) -> DropbearNativeResult<Vector2> {
+        let input = convert_ptr!(input_ptr => InputState);
+        DropbearNativeResult::Ok(super::shared::get_mouse_delta(input))
+    }
+
+    pub fn dropbear_is_cursor_locked(input_ptr: InputStatePtr) -> DropbearNativeResult<bool> {
+        let input = convert_ptr!(input_ptr => InputState);
+        DropbearNativeResult::Ok(input.is_cursor_locked)
+    }
+
+    pub fn dropbear_set_cursor_locked(
+        cmd_ptr: CommandBufferPtr,
+        input_ptr: InputStatePtr,
+        locked: bool
+    ) -> DropbearNativeResult<()> {
+        let input = convert_ptr!(mut input_ptr => InputState);
+        let sender = convert_ptr!(cmd_ptr => crossbeam_channel::Sender<CommandBuffer>);
+        super::shared::set_cursor_locked(input, sender, locked)
+    }
+
+    pub fn dropbear_get_last_mouse_pos(input_ptr: InputStatePtr) -> DropbearNativeResult<Vector2> {
+        let input = convert_ptr!(input_ptr => InputState);
+        DropbearNativeResult::Ok(super::shared::get_last_mouse_pos(input))
+    }
+
+    pub fn dropbear_is_cursor_hidden(input_ptr: InputStatePtr) -> DropbearNativeResult<bool> {
+        let input = convert_ptr!(input_ptr => InputState);
+        DropbearNativeResult::Ok(input.is_cursor_hidden)
+    }
+
+    pub fn dropbear_set_cursor_hidden(
+        cmd_ptr: CommandBufferPtr,
+        input_ptr: InputStatePtr,
+        hidden: bool
+    ) -> DropbearNativeResult<()> {
+        let input = convert_ptr!(mut input_ptr, InputStatePtr => InputState);
+        let sender = convert_ptr!(cmd_ptr => crossbeam_channel::Sender<CommandBuffer>);
+        super::shared::set_cursor_hidden(input, sender, hidden)
+    }
+
+    pub fn dropbear_get_connected_gamepads(
+        input_ptr: InputStatePtr,
+        out_count: *mut usize,
+    ) -> DropbearNativeResult<*mut u64> {
+        let input = convert_ptr!(input_ptr => InputState);
+
+        let mut ids = super::shared::get_connected_gamepads(input);
+
+        ids.shrink_to_fit();
+
+        if out_count.is_null() {
+            return DropbearNativeResult::Err(DropbearNativeError::NullPointer);
+        }
+
+        unsafe { *out_count = ids.len(); }
+
+        let ptr = ids.as_mut_ptr();
+        std::mem::forget(ids);
+
+        DropbearNativeResult::Ok(ptr)
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/input/gamepad.rs b/eucalyptus-core/src/input/gamepad.rs
new file mode 100644
index 0000000..0455131
--- /dev/null
+++ b/eucalyptus-core/src/input/gamepad.rs
@@ -0,0 +1,233 @@
+pub mod shared {
+    use dropbear_engine::gilrs::{Button, GamepadId};
+    use jni::JNIEnv;
+    use jni::objects::{JObject, JValue};
+    use dropbear_engine::gilrs;
+    use crate::input::InputState;
+    use crate::scripting::jni::utils::{FromJObject, ToJObject};
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+    use crate::types::Vector2;
+
+    fn map_int_to_gamepad_button(ordinal: i32) -> Option<Button> {
+        match ordinal {
+            0 => Some(gilrs::Button::Unknown),
+            1 => Some(gilrs::Button::South),
+            2 => Some(gilrs::Button::East),
+            3 => Some(gilrs::Button::North),
+            4 => Some(gilrs::Button::West),
+            5 => Some(gilrs::Button::C),
+            6 => Some(gilrs::Button::Z),
+            7 => Some(gilrs::Button::LeftTrigger),
+            8 => Some(gilrs::Button::RightTrigger),
+            9 => Some(gilrs::Button::LeftTrigger2),
+            10 => Some(gilrs::Button::RightTrigger2),
+            11 => Some(gilrs::Button::Select),
+            12 => Some(gilrs::Button::Start),
+            13 => Some(gilrs::Button::Mode),
+            14 => Some(gilrs::Button::LeftThumb),
+            15 => Some(gilrs::Button::RightThumb),
+            16 => Some(gilrs::Button::DPadUp),
+            17 => Some(gilrs::Button::DPadDown),
+            18 => Some(gilrs::Button::DPadLeft),
+            19 => Some(gilrs::Button::DPadRight),
+            _ => None,
+        }
+    }
+
+    pub fn get_gamepad_id(input: &InputState, target: usize) -> Option<GamepadId> {
+        input.connected_gamepads.iter().find(|g| usize::from(**g) == target).copied()
+    }
+
+    pub fn is_gamepad_button_pressed(input: &InputState, gamepad_id: u64, button_ordinal: i32) -> bool {
+        let Some(id) = get_gamepad_id(input, gamepad_id as usize) else {
+            return false;
+        };
+
+        if let Some(btn) = map_int_to_gamepad_button(button_ordinal) {
+            input.is_button_pressed(id, btn)
+        } else {
+            false
+        }
+    }
+
+    pub fn get_left_stick(input: &InputState, gamepad_id: u64) -> Vector2 {
+        let Some(id) = get_gamepad_id(input, gamepad_id as usize) else {
+            return Vector2 {
+                x: 0.0,
+                y: 0.0
+            }
+        };
+        let (x, y) = input.get_left_stick(id);
+        Vector2 { x: x as f64, y: y as f64 }
+    }
+
+    pub fn get_right_stick(input: &InputState, gamepad_id: u64) -> Vector2 {
+        let Some(id) = get_gamepad_id(input, gamepad_id as usize) else {
+            return Vector2 {
+                x: 0.0,
+                y: 0.0
+            }
+        };
+        let (x, y) = input.get_right_stick(id);
+        Vector2 { x: x as f64, y: y as f64 }
+    }
+
+    impl ToJObject for Vector2 {
+        fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+            let cls = env.find_class("com/dropbear/math/Vector2d")
+                .map_err(|e| {
+                    eprintln!("Could not find Vector2d class: {:?}", e);
+                    DropbearNativeError::GenericError
+                })?;
+
+            let obj = env.new_object(
+                cls,
+                "(DD)V",
+                &[
+                    JValue::Double(self.x),
+                    JValue::Double(self.y)
+                ]
+            ).map_err(|e| {
+                eprintln!("Failed to create Vector2d object: {:?}", e);
+                DropbearNativeError::GenericError
+            })?;
+
+            Ok(obj)
+        }
+    }
+
+    impl FromJObject for Vector2 {
+        fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self>
+        where
+            Self: Sized
+        {
+            let x_val = env
+                .get_field(obj, "x", "D")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+                .d()
+                .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+            let y_val = env
+                .get_field(obj, "y", "D")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+                .d()
+                .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+            Ok(Vector2 {
+                x: x_val,
+                y: y_val,
+            })
+        }
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+
+    use jni::JNIEnv;
+    use jni::objects::JClass;
+    use jni::sys::{jboolean, jint, jlong, jobject};
+    use crate::input::InputState;
+    use crate::scripting::jni::utils::ToJObject;
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_GamepadNative_isGamepadButtonPressed(
+        _env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+        gamepad_id: jlong,
+        button_ordinal: jint,
+    ) -> jboolean {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        if super::shared::is_gamepad_button_pressed(&input, gamepad_id as u64, button_ordinal) {
+            1
+        } else {
+            0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_GamepadNative_getLeftStickPosition(
+        mut env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+        gamepad_id: jlong,
+    ) -> jobject {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        let vec = super::shared::get_left_stick(&input, gamepad_id as u64);
+
+        match vec.to_jobject(&mut env) {
+            Ok(obj) => obj.into_raw(),
+            Err(_) => std::ptr::null_mut(),
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_input_GamepadNative_getRightStickPosition(
+        mut env: JNIEnv,
+        _class: JClass,
+        input_ptr: jlong,
+        gamepad_id: jlong,
+    ) -> jobject {
+        let input = crate::convert_ptr!(input_ptr => InputState);
+        let vec = super::shared::get_right_stick(&input, gamepad_id as u64);
+
+        match vec.to_jobject(&mut env) {
+            Ok(obj) => obj.into_raw(),
+            Err(_) => std::ptr::null_mut(),
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use crate::ptr::InputStatePtr;
+    use crate::input::{InputState};
+    use crate::convert_ptr;
+    use crate::scripting::result::DropbearNativeResult;
+    use crate::types::Vector2;
+
+    pub fn dropbear_is_gamepad_button_pressed(
+        input_ptr: InputStatePtr,
+        gamepad_id: u64,
+        button_ordinal: i32
+    ) -> DropbearNativeResult<bool> {
+        let input = convert_ptr!(input_ptr => InputState);
+        let result = super::shared::is_gamepad_button_pressed(input, gamepad_id, button_ordinal);
+        DropbearNativeResult::Ok(result)
+    }
+
+    pub fn dropbear_get_left_stick_position(
+        input_ptr: InputStatePtr,
+        gamepad_id: u64
+    ) -> DropbearNativeResult<Vector2> {
+        let input = convert_ptr!(input_ptr => InputState);
+        let vec = super::shared::get_left_stick(input, gamepad_id);
+        DropbearNativeResult::Ok(vec)
+    }
+
+    pub fn dropbear_get_right_stick_position(
+        input_ptr: InputStatePtr,
+        gamepad_id: u64
+    ) -> DropbearNativeResult<Vector2> {
+        let input = convert_ptr!(input_ptr => InputState);
+        let vec = super::shared::get_right_stick(input, gamepad_id);
+        DropbearNativeResult::Ok(vec)
+    }
+
+    pub fn dropbear_free_gamepads_array(
+        ptr: *mut u64,
+        count: usize,
+    ) -> DropbearNativeResult<()> {
+        if ptr.is_null() {
+            return DropbearNativeResult::Ok(());
+        }
+
+        unsafe {
+            let _ = Vec::from_raw_parts(ptr, count, count);
+        }
+
+        DropbearNativeResult::Ok(())
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/lib.rs b/eucalyptus-core/src/lib.rs
index a2a4236..8ade930 100644
--- a/eucalyptus-core/src/lib.rs
+++ b/eucalyptus-core/src/lib.rs
@@ -14,6 +14,13 @@ pub mod states;
 pub mod utils;
 pub mod command;
 pub mod physics;
+pub mod asset;
+pub mod types;
+pub mod properties;
+pub mod mesh;
+pub mod entity;
+pub mod engine;
+pub mod transform;
 
 pub use dropbear_macro as macros;
 pub use dropbear_traits as traits;
@@ -23,10 +30,11 @@ pub use rapier3d;
 use dropbear_engine::camera::Camera;
 use dropbear_engine::entity::{EntityTransform, MeshRenderer};
 use dropbear_traits::registry::ComponentRegistry;
+use properties::CustomProperties;
 use crate::camera::CameraComponent;
-use crate::physics::collider::{ColliderGroup};
+use crate::physics::collider::ColliderGroup;
 use crate::physics::rigidbody::RigidBody;
-use crate::states::{Camera3D, CustomProperties, Light, Script, SerializedMeshRenderer};
+use crate::states::{Camera3D, Light, Script, SerializedMeshRenderer};
 
 /// The appdata directory for storing any information.
 ///
diff --git a/eucalyptus-core/src/mesh.rs b/eucalyptus-core/src/mesh.rs
new file mode 100644
index 0000000..5eda4a0
--- /dev/null
+++ b/eucalyptus-core/src/mesh.rs
@@ -0,0 +1,427 @@
+pub mod shared {
+    use dropbear_engine::entity::MeshRenderer;
+    use hecs::{Entity, World};
+
+    pub fn mesh_renderer_exists_for_entity(world: &World, entity: Entity) -> bool {
+        world.get::<&MeshRenderer>(entity).is_ok()
+    }
+
+    pub fn resolve_target_material_name(
+        renderer: &MeshRenderer,
+        target_identifier: &str
+    ) -> Option<String> {
+        if let Some(cached) = renderer.resolve_texture_identifier(target_identifier) {
+            return Some(cached.to_string());
+        }
+
+        let model = renderer.model();
+
+        model.materials.iter().find_map(|mat| {
+            if mat.name == target_identifier { return Some(mat.name.clone()); }
+            if let Some(tag) = &mat.texture_tag { if tag == target_identifier { return Some(mat.name.clone()); } }
+            None
+        })
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+
+    use crate::return_boxed;
+    use dropbear_engine::asset::PointerKind::Const;
+    use dropbear_engine::asset::{AssetHandle, AssetRegistry};
+    use dropbear_engine::entity::MeshRenderer;
+    use dropbear_engine::model::Model;
+    use hecs::World;
+    use jni::objects::{JClass, JString};
+    use jni::sys::{jboolean, jlong, jlongArray, jobject, jsize};
+    use jni::JNIEnv;
+    use parking_lot::Mutex;
+    use std::collections::HashMap;
+    use std::sync::Arc;
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_MeshRendererNative_meshRendererExistsForEntity(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jboolean {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        super::shared::mesh_renderer_exists_for_entity(&world, entity) as jboolean
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_MeshRendererNative_getModel(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jlong {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+
+        if let Ok(mesh) = world.get::<&MeshRenderer>(entity) {
+            mesh.model_id().raw() as jlong
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity does not have a MeshRenderer component");
+            -1
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_MeshRendererNative_setModel(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        asset_ptr: jlong,
+        entity_id: jlong,
+        model_id: jlong,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let asset = crate::convert_ptr!(asset_ptr => AssetRegistry);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+
+        if let Ok(mut mesh) = world.get::<&mut MeshRenderer>(entity) {
+            match mesh.set_asset_handle_raw(asset, AssetHandle::new(model_id as u32)) {
+                Ok(_) => {}
+                Err(e) => {
+                    let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to set model: {}", e));
+                }
+            }
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity does not have a MeshRenderer component");
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_MeshRendererNative_getAllTextureIds(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        asset_ptr: jlong,
+        entity_id: jlong,
+    ) -> jlongArray {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let asset = crate::convert_ptr!(asset_ptr => AssetRegistry);
+
+        if let Ok(mut renderer) = world.get::<&mut MeshRenderer>(entity) {
+            let handles = renderer.collect_all_material_handles_raw(&asset);
+            let jarray = match env.new_long_array(handles.len() as jsize) {
+                Ok(val) => val,
+                Err(_) => {
+                    let _ = env.throw_new("java/lang/OutOfMemoryError", "Could not allocate texture ID array");
+                    return std::ptr::null_mut();
+                }
+            };
+
+            if let Err(e) = env.set_long_array_region(&jarray, 0, handles.iter().map(|v| v.raw() as jlong).collect::<Vec<_>>().as_slice()) {
+                let _ = env.throw_new("java/lang/RuntimeException", format!("{:?}", e));
+                return std::ptr::null_mut();
+            }
+
+            jarray.into_raw()
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity does not have a MeshRenderer component");
+            std::ptr::null_mut()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_MeshRendererNative_getTexture(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        asset_ptr: jlong,
+        entity_id: jlong,
+        material_name: JString,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let asset = crate::convert_ptr!(asset_ptr => dropbear_engine::asset::AssetRegistry);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let name = crate::convert_jstring!(env, material_name);
+
+        let handle_value: Option<i64> = if let Ok(renderer) = world.get::<&dropbear_engine::entity::MeshRenderer>(entity) {
+            renderer.material_handle_raw(asset, &name)
+                .map(|h| h.raw() as i64)
+        } else {
+            None
+        };
+
+        return_boxed!(&mut env, handle_value, "(J)Ljava/lang/Long;", "java/lang/Long")
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_MeshRendererNative_setTextureOverride(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        asset_handle: jlong,
+        entity_id: jlong,
+        material_name: JString,
+        texture_handle: jlong,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let asset = crate::convert_ptr!(asset_handle => dropbear_engine::asset::AssetRegistry);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let target_identifier = crate::convert_jstring!(env, material_name);
+        let new_texture_handle = dropbear_engine::asset::AssetHandle::new(texture_handle as u64);
+
+        if let Ok(mut mesh) = world.get::<&mut dropbear_engine::entity::MeshRenderer>(entity) {
+            let resolved_target = super::shared::resolve_target_material_name(&mesh, &target_identifier);
+
+            let Some(target_material_name) = resolved_target else {
+                let _ = env.throw_new(
+                    "java/lang/IllegalArgumentException",
+                    format!(
+                        "Could not resolve material identifier '{}' on model '{}'",
+                        target_identifier,
+                        mesh.model().label
+                    ),
+                );
+                return;
+            };
+
+            if !asset.contains_handle(new_texture_handle) {
+                let _ = env.throw_new("java/lang/IllegalArgumentException", "Invalid texture handle provided");
+                return;
+            }
+
+            if !asset.is_material(new_texture_handle) {
+                let _ = env.throw_new("java/lang/IllegalArgumentException", "Handle provided is not a material/texture");
+                return;
+            }
+
+            let Some(source_material) = asset.get_material(new_texture_handle) else { return; };
+
+            let Some(owner_model_id) = asset.material_owner(new_texture_handle) else {
+                let _ = env.throw_new("java/lang/IllegalStateException", "Texture handle has no owner model in registry");
+                return;
+            };
+
+            let Some(owner_model_handle) = asset.model_handle_from_id(owner_model_id) else {
+                let _ = env.throw_new("java/lang/IllegalStateException", "Texture owner model not found in registry");
+                return;
+            };
+
+            let Some(source_reference) = asset.model_reference_for_handle(owner_model_handle) else {
+                let _ = env.throw_new("java/lang/IllegalStateException", "Texture owner model has no resource reference");
+                return;
+            };
+
+            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 registry");
+                return;
+            };
+
+            let model_cache = model_cache as *const Mutex<HashMap<String, Arc<Model>>>;
+            if model_cache.is_null() {
+                let _ = env.throw_new("java/lang/IllegalStateException", "Model cache cannot be null");
+                return;
+            }
+
+            let model_cache = unsafe { &*model_cache };
+
+            if let Err(e) = mesh.apply_material_override_raw(
+                asset,
+                model_cache,
+                &target_material_name,
+                source_reference,
+                &source_material.name,
+            ) {
+                let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to apply texture override: {}", e));
+            }
+
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity does not have a MeshRenderer component");
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use parking_lot::Mutex;
+    use std::collections::HashMap;
+    use std::os::raw::c_char;
+    use std::sync::Arc;
+
+    use hecs::{Entity, World};
+
+    use dropbear_engine::asset::{AssetHandle, AssetRegistry, PointerKind};
+    use dropbear_engine::entity::MeshRenderer;
+    use dropbear_engine::model::Model;
+
+    use crate::convert_ptr;
+    use crate::engine::shared::read_str;
+    use crate::ptr::{AssetRegistryPtr, WorldPtr};
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+
+    pub fn dropbear_mesh_renderer_exists_for_entity(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<bool> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        DropbearNativeResult::Ok(super::shared::mesh_renderer_exists_for_entity(world, entity))
+    }
+    
+    pub fn dropbear_get_model(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<u64> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mesh) = world.get::<&MeshRenderer>(entity) {
+            DropbearNativeResult::Ok(mesh.model_id().raw())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+    
+    pub fn dropbear_set_model(
+        world_ptr: WorldPtr,
+        asset_ptr: AssetRegistryPtr,
+        entity_id: u64,
+        model_id: u64,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => World);
+        let asset = convert_ptr!(asset_ptr => AssetRegistry);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut mesh) = world.get::<&mut MeshRenderer>(entity) {
+            match mesh.set_asset_handle_raw(asset, AssetHandle::new(model_id)) {
+                Ok(_) => DropbearNativeResult::Ok(()),
+                Err(_) => DropbearNativeResult::Err(DropbearNativeError::UnknownError),
+            }
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+    
+    pub fn dropbear_get_all_texture_ids(
+        world_ptr: WorldPtr,
+        asset_ptr: AssetRegistryPtr,
+        entity_id: u64,
+        out_count: *mut usize,
+    ) -> DropbearNativeResult<*mut u64> {
+        let world = convert_ptr!(world_ptr => World);
+        let asset = convert_ptr!(asset_ptr => AssetRegistry);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if out_count.is_null() {
+            return DropbearNativeResult::Err(DropbearNativeError::NullPointer);
+        }
+
+        if let Ok(renderer) = world.get::<&MeshRenderer>(entity) {
+            let handles = renderer.collect_all_material_handles_raw(asset);
+
+            let mut raw_ids: Vec<u64> = handles.iter().map(|h| h.raw()).collect();
+
+            unsafe { *out_count = raw_ids.len(); }
+
+            let ptr = raw_ids.as_mut_ptr();
+            std::mem::forget(raw_ids);
+
+            DropbearNativeResult::Ok(ptr)
+        } else {
+            unsafe { *out_count = 0; }
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+    
+    pub fn dropbear_get_texture(
+        world_ptr: WorldPtr,
+        asset_ptr: AssetRegistryPtr,
+        entity_id: u64,
+        material_name: *const c_char,
+    ) -> DropbearNativeResult<u64> {
+        let world = convert_ptr!(world_ptr => World);
+        let asset = convert_ptr!(asset_ptr => AssetRegistry);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        let target = unsafe { read_str(material_name)? };
+
+        if let Ok(renderer) = world.get::<&MeshRenderer>(entity) {
+            match renderer.material_handle_raw(asset, &target) {
+                Some(handle) => DropbearNativeResult::Ok(handle.raw()),
+                None => DropbearNativeResult::Err(DropbearNativeError::UnknownError),
+            }
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+    
+    pub fn dropbear_set_texture_override(
+        world_ptr: WorldPtr,
+        asset_ptr: AssetRegistryPtr,
+        entity_id: u64,
+        material_name: *const c_char,
+        texture_handle: u64,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => World);
+        let asset = convert_ptr!(asset_ptr => AssetRegistry);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        let target_identifier = unsafe { read_str(material_name)? };
+        let new_texture_handle = AssetHandle::new(texture_handle);
+
+        if let Ok(mut mesh) = world.get::<&mut MeshRenderer>(entity) {
+            let resolved_target = super::shared::resolve_target_material_name(&mesh, &target_identifier);
+            let target_material_name = match resolved_target {
+                Some(name) => name,
+                None => return DropbearNativeResult::Err(DropbearNativeError::InvalidArgument),
+            };
+
+            if !asset.contains_handle(new_texture_handle) || !asset.is_material(new_texture_handle) {
+                return DropbearNativeResult::Err(DropbearNativeError::InvalidArgument);
+            }
+
+            let source_material = match asset.get_material(new_texture_handle) {
+                Some(m) => m,
+                None => return DropbearNativeResult::Err(DropbearNativeError::UnknownError),
+            };
+
+            let owner_model_id = match asset.material_owner(new_texture_handle) {
+                Some(id) => id,
+                None => return DropbearNativeResult::Err(DropbearNativeError::QueryFailed),
+            };
+
+            let owner_model_handle = match asset.model_handle_from_id(owner_model_id) {
+                Some(h) => h,
+                None => return DropbearNativeResult::Err(DropbearNativeError::QueryFailed),
+            };
+
+            let source_reference = match asset.model_reference_for_handle(owner_model_handle) {
+                Some(r) => r,
+                None => return DropbearNativeResult::Err(DropbearNativeError::QueryFailed),
+            };
+
+            let model_cache_ptr = match asset.get_pointer(PointerKind::Const("model_cache")) {
+                Some(ptr) => ptr as *const Mutex<HashMap<String, Arc<Model>>>,
+                None => return DropbearNativeResult::Err(DropbearNativeError::NullPointer), // Cache not found
+            };
+
+            if model_cache_ptr.is_null() {
+                return DropbearNativeResult::Err(DropbearNativeError::NullPointer);
+            }
+
+            let model_cache = unsafe { &*model_cache_ptr };
+
+            match mesh.apply_material_override_raw(
+                asset,
+                model_cache,
+                &target_material_name,
+                source_reference,
+                &source_material.name,
+            ) {
+                Ok(_) => DropbearNativeResult::Ok(()),
+                Err(_) => DropbearNativeResult::Err(DropbearNativeError::WorldInsertError),
+            }
+
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/physics.rs b/eucalyptus-core/src/physics.rs
index 7edb8e8..40de982 100644
--- a/eucalyptus-core/src/physics.rs
+++ b/eucalyptus-core/src/physics.rs
@@ -1,11 +1,13 @@
 //! Components in the eucalyptus-editor and redback-runtime that relate to rapier3d based physics.
 
 use std::collections::HashMap;
+use std::ops::AddAssign;
 use hecs::Entity;
 use dropbear_engine::entity::Transform;
 use rapier3d::na::{Quaternion, UnitQuaternion, Vector3};
 use rapier3d::prelude::*;
 use serde::{Deserialize, Serialize};
+use crate::physics::collider::NEXT_ID;
 use crate::physics::rigidbody::RigidBodyMode;
 use crate::states::Label;
 
@@ -28,7 +30,7 @@ pub struct PhysicsState {
     pub gravity: [f32; 3],
 
     pub bodies_entity_map: HashMap<Label, RigidBodyHandle>,
-    pub colliders_entity_map: HashMap<Label, Vec<ColliderHandle>>,
+    pub colliders_entity_map: HashMap<Label, Vec<(u32, ColliderHandle)>>,
     pub entity_label_map: HashMap<Entity, Label>,
 }
 
@@ -103,7 +105,7 @@ impl PhysicsState {
         if let Some(collider_handles) = self.colliders_entity_map.get(&rigid_body.entity) {
             let handles_to_attach = collider_handles.clone();
 
-            for handle in handles_to_attach {
+            for (id, handle) in handles_to_attach {
                 self.colliders.set_parent(handle, Some(body_handle), &mut self.bodies);
             }
         }
@@ -145,23 +147,23 @@ impl PhysicsState {
         );
         builder = builder.rotation(rotation.scaled_axis());
 
-        // check if entity has rigid body
         let handle = if let Some(&rigid_body_handle) = self.bodies_entity_map.get(&collider_component.entity) {
-            // attach
             self.colliders.insert_with_parent(
                 builder.build(),
                 rigid_body_handle,
                 &mut self.bodies
             )
         } else {
-            // create a static collider if it doesn't exist
             self.colliders.insert(builder.build())
         };
 
+        let mut next_id = *NEXT_ID.get_mut();
+        next_id.add_assign(1);
+
         self.colliders_entity_map
             .entry(collider_component.entity.clone())
             .or_insert_with(Vec::new)
-            .push(handle);
+            .push((next_id as u32, handle));
 
         handle
     }
@@ -169,12 +171,12 @@ impl PhysicsState {
     /// Remove all colliders associated with an entity
     pub fn remove_colliders(&mut self, entity: &Label) {
         if let Some(handles) = self.colliders_entity_map.remove(entity) {
-            for handle in handles {
+            for (_id, handle) in handles {
                 self.colliders.remove(
                     handle,
                     &mut self.islands,
                     &mut self.bodies,
-                    false // wake_up
+                    false
                 );
             }
         }
@@ -189,7 +191,7 @@ impl PhysicsState {
                 &mut self.colliders,
                 &mut self.impulse_joints,
                 &mut self.multibody_joints,
-                false // wake_up
+                false
             );
         }
         self.colliders_entity_map.remove(entity);
diff --git a/eucalyptus-core/src/physics/collider.rs b/eucalyptus-core/src/physics/collider.rs
index 087b046..567e6b5 100644
--- a/eucalyptus-core/src/physics/collider.rs
+++ b/eucalyptus-core/src/physics/collider.rs
@@ -14,17 +14,25 @@
 //!     - `-colonly` (invisible collision mesh)
 
 pub mod shader;
+pub mod collidergroup;
 
-use std::sync::Arc;
-use rapier3d::na::Vector3;
-use rapier3d::prelude::{ColliderBuilder};
-use serde::{Deserialize, Serialize};
+use crate::scripting::jni::utils::{FromJObject, ToJObject};
+use crate::scripting::result::DropbearNativeResult;
+use crate::states::Label;
 use dropbear_engine::graphics::SharedGraphicsContext;
-use dropbear_engine::wgpu::{Buffer, BufferUsages};
 use dropbear_engine::wgpu::util::{BufferInitDescriptor, DeviceExt};
+use dropbear_engine::wgpu::{Buffer, BufferUsages};
 use dropbear_macro::SerializableComponent;
 use dropbear_traits::SerializableComponent;
-use crate::states::Label;
+use ::jni::objects::{JObject, JValue};
+use ::jni::JNIEnv;
+use rapier3d::na::Vector3;
+use rapier3d::prelude::ColliderBuilder;
+use serde::{Deserialize, Serialize};
+use std::ops::AddAssign;
+use std::sync::atomic::AtomicUsize;
+use std::sync::Arc;
+use crate::scripting::native::DropbearNativeError;
 
 #[derive(Debug, Default, Serialize, Deserialize, Clone, SerializableComponent)]
 pub struct ColliderGroup {
@@ -90,6 +98,7 @@ pub enum ColliderShapeType {
     Cone,
 }
 
+#[repr(C)]
 #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
 pub enum ColliderShape {
     /// Box shape with half-extents (half-width, half-height, half-depth).
@@ -106,39 +115,181 @@ pub enum ColliderShape {
 
     /// Cone shape along Y-axis.
     Cone { half_height: f32, radius: f32 },
+}
 
-    // /// Convex hull from a mesh.
-    // ConvexHull { mesh_path: String },
-    //
-    // /// Triangle mesh for static/complex geometry.
-    // TriMesh { mesh_path: String },
-    //
-    // /// Heightfield for terrain.
-    // HeightField {
-    //     heights: Vec<f32>,
-    //     num_rows: usize,
-    //     num_cols: usize,
-    //     scale: [f32; 3],
-    // },
-    //
-    // /// Compound shape (multiple shapes combined).
-    // Compound { shapes: Vec<CompoundShape> },
+impl Default for ColliderShape {
+    fn default() -> Self {
+        ColliderShape::Box {
+            half_extents: [0.5, 0.5, 0.5]
+        }
+    }
 }
 
-// #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
-// pub struct CompoundShape {
-//     pub shape: ColliderShape,
-//     pub translation: [f32; 3],
-//     pub rotation: [f32; 3],
-// }
+impl ToJObject for ColliderShape {
+    fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+        match self {
+            ColliderShape::Box { half_extents } => {
+                let vec_cls = env.find_class("com/dropbear/math/Vector3d")
+                    .map_err(|e| {
+                        eprintln!("[JNI Error] Vector3d class not found: {:?}", e);
+                        DropbearNativeError::JNIClassNotFound
+                    })?;
+
+                let vec_obj = env.new_object(
+                    &vec_cls,
+                    "(DDD)V",
+                    &[
+                        JValue::Double(half_extents[0] as f64),
+                        JValue::Double(half_extents[1] as f64),
+                        JValue::Double(half_extents[2] as f64)
+                    ]
+                ).map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+                let cls = env.find_class("com/dropbear/physics/ColliderShape$Box")
+                    .map_err(|e| {
+                        eprintln!("[JNI Error] ColliderShape$Box class not found: {:?}", e);
+                        DropbearNativeError::JNIClassNotFound
+                    })?;
+
+                let obj = env.new_object(
+                    &cls,
+                    "(Lcom/dropbear/math/Vector3d;)V",
+                    &[JValue::Object(&vec_obj)]
+                ).map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+                Ok(obj)
+            },
+            ColliderShape::Sphere { radius } => {
+                let cls = env.find_class("com/dropbear/physics/ColliderShape$Sphere")
+                    .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+                let obj = env.new_object(
+                    &cls,
+                    "(F)V",
+                    &[JValue::Float(*radius)]
+                ).map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+                Ok(obj)
+            },
+            ColliderShape::Capsule { half_height, radius } => {
+                let cls = env.find_class("com/dropbear/physics/ColliderShape$Capsule")
+                    .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+                let obj = env.new_object(
+                    &cls,
+                    "(FF)V",
+                    &[JValue::Float(*half_height), JValue::Float(*radius)]
+                ).map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+                Ok(obj)
+            },
+            ColliderShape::Cylinder { half_height, radius } => {
+                let cls = env.find_class("com/dropbear/physics/ColliderShape$Cylinder")
+                    .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+                // let ctor = env.get_method_id(&cls, "<init>", "(FF)V")
+                //     .map_err(|_| DropbearNativeError::JNIMethodNotFound)?;
+
+                let obj = env.new_object(
+                    &cls,
+                    "(FF)V",
+                    &[JValue::Float(*half_height), JValue::Float(*radius)]
+                ).map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+                Ok(obj)
+            },
+            ColliderShape::Cone { half_height, radius } => {
+                let cls = env.find_class("com/dropbear/physics/ColliderShape$Cone")
+                    .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+                let obj = env.new_object(
+                    &cls,
+                    "(FF)V",
+                    &[JValue::Float(*half_height), JValue::Float(*radius)]
+                ).map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+                Ok(obj)
+            },
+        }
+    }
+}
+
+impl FromJObject for ColliderShape {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self>
+    where
+        Self: Sized
+    {
+        let is_instance = |env: &mut JNIEnv, obj: &JObject, class_name: &str| -> bool {
+            env.is_instance_of(obj, class_name).unwrap_or(false)
+        };
+
+        if is_instance(env, obj, "com/dropbear/physics/ColliderShape$Box") {
+            let vec_obj_val = env.get_field(obj, "halfExtents", "Lcom/dropbear/math/Vector3d;")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?;
+            let vec_obj = vec_obj_val.l()
+                .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+            let x = env.get_field(&vec_obj, "x", "D")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.d().unwrap_or(0.0);
+            let y = env.get_field(&vec_obj, "y", "D")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.d().unwrap_or(0.0);
+            let z = env.get_field(&vec_obj, "z", "D")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.d().unwrap_or(0.0);
+
+            return Ok(ColliderShape::Box {
+                half_extents: [x as f32, y as f32, z as f32]
+            });
+        }
+
+        if is_instance(env, obj, "com/dropbear/physics/ColliderShape$Sphere") {
+            let radius = env.get_field(obj, "radius", "F")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.f().unwrap_or(0.0);
+
+            return Ok(ColliderShape::Sphere { radius });
+        }
+
+        if is_instance(env, obj, "com/dropbear/physics/ColliderShape$Capsule") {
+            let hh = env.get_field(obj, "halfHeight", "F")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.f().unwrap_or(0.0);
+            let r = env.get_field(obj, "radius", "F")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.f().unwrap_or(0.0);
+
+            return Ok(ColliderShape::Capsule { half_height: hh, radius: r });
+        }
+
+        if is_instance(env, obj, "com/dropbear/physics/ColliderShape$Cylinder") {
+            let hh = env.get_field(obj, "halfHeight", "F")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.f().unwrap_or(0.0);
+            let r = env.get_field(obj, "radius", "F")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.f().unwrap_or(0.0);
+
+            return Ok(ColliderShape::Cylinder { half_height: hh, radius: r });
+        }
+
+        if is_instance(env, obj, "com/dropbear/physics/ColliderShape$Cone") {
+            let hh = env.get_field(obj, "halfHeight", "F")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.f().unwrap_or(0.0);
+            let r = env.get_field(obj, "radius", "F")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?.f().unwrap_or(0.0);
+
+            return Ok(ColliderShape::Cone { half_height: hh, radius: r });
+        }
+
+        Err(DropbearNativeError::GenericError)
+    }
+}
+
+pub const NEXT_ID: AtomicUsize = AtomicUsize::new(0);
 
 impl Collider {
     fn default_density() -> f32 { 1.0 }
     fn default_friction() -> f32 { 0.5 }
 
     pub fn new() -> Self {
+        { NEXT_ID.get_mut().add_assign(1); }
+        let id = { NEXT_ID.get_mut().clone() };
         Self {
-            id: 0,
+            id: id as u32,
             entity: Label::default(),
             shape: ColliderShape::default(),
             density: Self::default_density(),
@@ -235,21 +386,6 @@ impl Collider {
             ColliderShape::Cone { half_height, radius } => {
                 ColliderBuilder::cone(*half_height, *radius)
             }
-            // ColliderShape::ConvexHull { .. } => {
-            //     todo!("Load mesh from path and create convex hull")
-            // }
-            // ColliderShape::TriMesh { .. } => {
-            //     todo!("Load mesh from path and create trimesh")
-            // }
-            // ColliderShape::HeightField { heights, num_rows, num_cols, scale } => {
-            //     ColliderBuilder::heightfield(
-            //         DMatrix::from_vec(*num_rows, *num_cols, heights.clone()),
-            //         Vector3::new(scale[0], scale[1], scale[2])
-            //     )
-            // }
-            // ColliderShape::Compound { .. } => {
-            //     todo!("Build compound collider")
-            // }
         };
 
         shape
@@ -277,14 +413,6 @@ impl Collider {
     }
 }
 
-impl Default for ColliderShape {
-    fn default() -> Self {
-        ColliderShape::Box {
-            half_extents: [0.5, 0.5, 0.5]
-        }
-    }
-}
-
 pub struct WireframeGeometry {
     pub vertex_buffer: Buffer,
     pub index_buffer: Buffer,
@@ -450,105 +578,676 @@ impl WireframeGeometry {
     }
 }
 
+pub mod shared {
+    use crate::physics::PhysicsState;
+    use crate::types::ColliderFFI;
+    use rapier3d::prelude::ColliderHandle;
+
+    pub fn get_collider_mut<'a>(
+        physics: &'a mut PhysicsState,
+        ffi: &ColliderFFI
+    ) -> Option<&'a mut rapier3d::prelude::Collider> {
+        let handle = ColliderHandle::from_raw_parts(ffi.index.index, ffi.index.generation);
+        physics.colliders.get_mut(handle)
+    }
+
+    pub fn get_collider<'a>(
+        physics: &'a PhysicsState,
+        ffi: &ColliderFFI
+    ) -> Option<&'a rapier3d::prelude::Collider> {
+        let handle = ColliderHandle::from_raw_parts(ffi.index.index, ffi.index.generation);
+        physics.colliders.get(handle)
+    }
+}
+
 pub mod jni {
-    use glam::DVec3;
-    use jni::objects::{JObject, JString};
+    #![allow(non_snake_case)]
+    use crate::physics::collider::shared::{get_collider, get_collider_mut};
+    use crate::physics::PhysicsState;
+    use crate::scripting::jni::utils::{FromJObject, ToJObject};
+    use crate::types::ColliderFFI;
+    use glam::{DQuat, DVec3};
+    use jni::objects::{JClass, JObject};
+    use jni::sys::{jboolean, jdouble, jlong, jobject};
     use jni::JNIEnv;
-    use rapier3d::prelude::ColliderHandle;
-    use crate::physics::collider::{Collider, ColliderShape};
-    use crate::scripting::jni::utils::FromJObject;
-    use crate::states::Label;
-
-    impl Collider {
-        pub fn from_jni_object(env: &mut JNIEnv, obj: &JObject) -> anyhow::Result<(ColliderHandle, Self)> {
-            let entity_jobj = env.get_field(obj, "entity", "Ljava/lang/String;")?.l()?;
-            let entity_jstr: JString = entity_jobj.into();
-            let entity_str: String = env.get_string(&entity_jstr)?.into();
-            let entity = Label::from(entity_str);
-
-            let density = env.get_field(obj, "density", "D")?.d()? as f32;
-            let friction = env.get_field(obj, "friction", "D")?.d()? as f32;
-            let restitution = env.get_field(obj, "restitution", "D")?.d()? as f32;
-            let is_sensor = env.get_field(obj, "isSensor", "Z")?.z()?;
-
-            let trans_obj = env.get_field(obj, "translation", "Lcom/dropbear/math/Vector3D;")?.l()?;
-            let translation = DVec3::from_jobject(env, &trans_obj)?.as_vec3().to_array();
-
-            let rot_obj = env.get_field(obj, "rotation", "Lcom/dropbear/math/Vector3D;")?.l()?;
-            let rotation = DVec3::from_jobject(env, &rot_obj)?.as_vec3().to_array();
-
-            let shape_obj = env.get_field(obj, "colliderShape", "Lcom/dropbear/physics/ColliderShape;")?.l()?;
-            let shape = ColliderShape::from_jobject(env, &shape_obj)?;
-
-            let id = env.get_field(obj, "id", "I")?.i()? as u32;
-
-            let index_res: Result<(u32, u32), jni::errors::Error> = (|| {
-                let index_obj = env.get_field(&obj, "index", "Lcom/dropbear/physics/Index;")?.l()?;
-                let idx = env.get_field(&index_obj, "index", "I")?.i()? as u32;
-                let generation = env.get_field(&index_obj, "generation", "I")?.i()? as u32;
-                Ok((idx, generation))
-            })();
-
-            let (raw_index, raw_gen) = match index_res {
-                Ok(v) => v,
-                Err(e) => {
-                    let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Invalid Index in Collider: {:?}", e));
-                    return Err(anyhow::anyhow!("Invalid Index in Collider: {:?}", e));
+    use rapier3d::na::{UnitQuaternion, Vector3};
+    use rapier3d::prelude::{ColliderHandle, SharedShape, TypedShape};
+    use crate::physics::collider::ColliderShape;
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_getColliderShape(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+    ) -> jobject {
+        let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
+
+        let ffi = match ColliderFFI::from_jobject(&mut env, &collider_obj) {
+            Ok(v) => v,
+            Err(_) => return std::ptr::null_mut(),
+        };
+
+        if let Some(collider) = get_collider(&physics, &ffi) {
+            let rapier_shape = collider.shape();
+
+            let my_shape = match rapier_shape.as_typed_shape() {
+                TypedShape::Cuboid(c) => {
+                    let he = c.half_extents;
+                    ColliderShape::Box {
+                        half_extents: [he.x, he.y, he.z]
+                    }
+                },
+                TypedShape::Ball(b) => {
+                     ColliderShape::Sphere {
+                        radius: b.radius
+                     }
+                },
+                TypedShape::Capsule(c) => {
+                    let height = c.segment.length();
+                    ColliderShape::Capsule {
+                        half_height: height * 0.5,
+                        radius: c.radius
+                    }
+                },
+                TypedShape::Cylinder(c) => {
+                    ColliderShape::Cylinder {
+                        half_height: c.half_height,
+                        radius: c.radius
+                    }
+                },
+                TypedShape::Cone(c) => {
+                    ColliderShape::Cone {
+                        half_height: c.half_height,
+                        radius: c.radius
+                    }
+                },
+                _ => {
+                    eprintln!("Unsupported collider shape type found.");
+                    return std::ptr::null_mut();
                 }
             };
 
-            let handle = ColliderHandle::from_raw_parts(raw_index, raw_gen);
-
-            Ok((handle, Self {
-                id,
-                entity,
-                shape,
-                density,
-                friction,
-                restitution,
-                is_sensor,
-                translation,
-                rotation,
-            }))
-        }
-    }
-
-    impl FromJObject for ColliderShape {
-        fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> anyhow::Result<Self>
-        where
-            Self: Sized
-        {
-            if env.is_instance_of(obj, "com/dropbear/physics/ColliderShape$Box")? {
-                let half_extents_obj = env.get_field(obj, "halfExtents", "Lcom/dropbear/math/Vector3D;")?.l()?;
-                let half_extents = DVec3::from_jobject(env, &half_extents_obj)?.as_vec3().to_array();
-                return Ok(ColliderShape::Box { half_extents });
+            match my_shape.to_jobject(&mut env) {
+                Ok(obj) => obj.into_raw(),
+                Err(_) => std::ptr::null_mut(),
             }
 
-            if env.is_instance_of(obj, "com/dropbear/physics/ColliderShape$Sphere")? {
-                let radius = env.get_field(obj, "radius", "F")?.f()?;
-                return Ok(ColliderShape::Sphere { radius });
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Collider handle invalid");
+            std::ptr::null_mut()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_setColliderShape(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+        shape_obj: JObject,
+    ) {
+        let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
+
+        let ffi = match ColliderFFI::from_jobject(&mut env, &collider_obj) {
+            Ok(v) => v,
+            Err(_) => return,
+        };
+
+        let handle = ColliderHandle::from_raw_parts(ffi.index.index, ffi.index.generation);
+
+        let Some(collider) = physics.colliders.get_mut(handle) else {
+            let _ = env.throw_new("java/lang/IllegalArgumentException", "Collider handle invalid");
+            return;
+        };
+
+        let Ok(shape) = ColliderShape::from_jobject(&mut env, &shape_obj) else {
+            let _ = env.throw_new("java/lang/IllegalArgumentException", "Collider shape is invalid");
+            return;
+        };
+
+        let new_shape = match shape {
+            ColliderShape::Box { half_extents } => {
+                SharedShape::cuboid(half_extents[0], half_extents[1], half_extents[2])
+            }
+            ColliderShape::Sphere { radius } => {
+                SharedShape::ball(radius)
+            }
+            ColliderShape::Capsule { half_height, radius } => {
+                SharedShape::capsule_y(half_height, radius)
+            }
+            ColliderShape::Cylinder { half_height, radius } => {
+                SharedShape::cylinder(half_height, radius)
+            }
+            ColliderShape::Cone { half_height, radius } => {
+                SharedShape::cone(half_height, radius)
             }
+        };
+
+        collider.set_shape(new_shape);
+    }
 
-            if env.is_instance_of(obj, "com/dropbear/physics/ColliderShape$Capsule")? {
-                let half_height = env.get_field(obj, "halfHeight", "F")?.f()?;
-                let radius = env.get_field(obj, "radius", "F")?.f()?;
-                return Ok(ColliderShape::Capsule { half_height, radius });
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_getColliderDensity(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+    ) -> jdouble {
+        let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
+        let ffi = ColliderFFI::from_jobject(&mut env, &collider_obj).ok().unwrap();
+
+        if let Some(col) = get_collider(&physics, &ffi) {
+            col.density() as jdouble
+        } else {
+            0.0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_setColliderDensity(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+        density: jdouble,
+    ) {
+        let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Ok(ffi) = ColliderFFI::from_jobject(&mut env, &collider_obj) {
+            if let Some(col) = get_collider_mut(physics, &ffi) {
+                col.set_density(density as f32);
+            }
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_getColliderFriction(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+    ) -> jdouble {
+        let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
+        let ffi = ColliderFFI::from_jobject(&mut env, &collider_obj).ok().unwrap();
+        if let Some(col) = get_collider(&physics, &ffi) {
+            col.friction() as jdouble
+        } else { 0.0 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_setColliderFriction(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+        friction: jdouble,
+    ) {
+        let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Ok(ffi) = ColliderFFI::from_jobject(&mut env, &collider_obj) {
+            if let Some(col) = get_collider_mut(physics, &ffi) {
+                col.set_friction(friction as f32);
+            }
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_getColliderRestitution(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+    ) -> jdouble {
+        let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
+        let ffi = ColliderFFI::from_jobject(&mut env, &collider_obj).ok().unwrap();
+        if let Some(col) = get_collider(&physics, &ffi) {
+            col.restitution() as jdouble
+        } else { 0.0 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_setColliderRestitution(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+        restitution: jdouble,
+    ) {
+        let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Ok(ffi) = ColliderFFI::from_jobject(&mut env, &collider_obj) {
+            if let Some(col) = get_collider_mut(physics, &ffi) {
+                col.set_restitution(restitution as f32);
+            }
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_getColliderMass(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+    ) -> jdouble {
+        let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
+        let ffi = ColliderFFI::from_jobject(&mut env, &collider_obj).ok().unwrap();
+        if let Some(col) = get_collider(&physics, &ffi) {
+            col.mass() as jdouble
+        } else { 0.0 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_setColliderMass(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+        mass: jdouble,
+    ) {
+        let physics = crate::convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Ok(ffi) = ColliderFFI::from_jobject(&mut env, &collider_obj) {
+            if let Some(col) = get_collider_mut(physics, &ffi) {
+                col.set_mass(mass as f32);
+            }
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_getColliderIsSensor(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+    ) -> jboolean {
+        let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
+        let ffi = ColliderFFI::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 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_setColliderIsSensor(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+        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 Some(col) = get_collider_mut(physics, &ffi) {
+                col.set_sensor(is_sensor != 0);
+            }
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_getColliderTranslation(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+    ) -> jobject {
+        let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
+        let ffi = match ColliderFFI::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: &Vector3<f32> = col.translation();
+            let vec = DVec3::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(),
+            }
+        } else {
+            std::ptr::null_mut()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_setColliderTranslation(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+        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) = DVec3::from_jobject(&mut env, &vec_obj) {
+                if let Some(col) = get_collider_mut(physics, &ffi) {
+                    let t = Vector3::new(vec.x as f32, vec.y as f32, vec.z as f32);
+                    col.set_translation(t);
+                }
             }
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_getColliderRotation(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+    ) -> jobject {
+        let physics = crate::convert_ptr!(physics_ptr => PhysicsState);
+        let ffi = match ColliderFFI::from_jobject(&mut env, &collider_obj) {
+            Ok(v) => v,
+            Err(_) => return std::ptr::null_mut(),
+        };
+
+        if let Some(col) = get_collider(&physics, &ffi) {
+            let r: &UnitQuaternion<f32> = col.rotation();
+            let q = DQuat::from_xyzw(r.i as f64, r.j as f64, r.k as f64, r.w as f64);
+            let euler = q.to_euler(glam::EulerRot::XYZ);
+            let vec = DVec3::new(euler.0, euler.1, euler.2);
+
+            match vec.to_jobject(&mut env) {
+                Ok(o) => o.into_raw(),
+                Err(_) => std::ptr::null_mut(),
+            }
+        } else {
+            std::ptr::null_mut()
+        }
+    }
 
-            if env.is_instance_of(obj, "com/dropbear/physics/ColliderShape$Cylinder")? {
-                let half_height = env.get_field(obj, "halfHeight", "F")?.f()?;
-                let radius = env.get_field(obj, "radius", "F")?.f()?;
-                return Ok(ColliderShape::Cylinder { half_height, radius });
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderNative_setColliderRotation(
+        mut env: JNIEnv,
+        _class: JClass,
+        physics_ptr: jlong,
+        collider_obj: JObject,
+        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) = DVec3::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 = rapier3d::na::UnitQuaternion::new_normalize(
+                        rapier3d::na::Quaternion::new(q.w as f32, q.x as f32, q.y as f32, q.z as f32)
+                    );
+                    col.set_rotation(r);
+                }
             }
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use crate::convert_ptr;
+    use crate::physics::collider::shared::{get_collider, get_collider_mut};
+    use crate::physics::PhysicsState;
+    use crate::ptr::PhysicsStatePtr;
+    use crate::types::Vector3;
+
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+    use crate::types::{ColliderFFI, ColliderShapeFFI, ColliderShapeType};
+    use glam::DQuat;
+    use rapier3d::na::{Quaternion, UnitQuaternion};
+    use rapier3d::prelude::{SharedShape, TypedShape};
+
+    pub fn dropbear_get_collider_shape(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+    ) -> DropbearNativeResult<ColliderShapeFFI> {
+        let physics = convert_ptr!(physics_ptr => PhysicsState);
+
+        if let Some(collider) = get_collider(physics, &ffi) {
+            let rapier_shape = collider.shape();
+            let mut result = ColliderShapeFFI {
+                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,
+            };
 
-            if env.is_instance_of(obj, "com/dropbear/physics/ColliderShape$Cone")? {
-                let half_height = env.get_field(obj, "halfHeight", "F")?.f()?;
-                let radius = env.get_field(obj, "radius", "F")?.f()?;
-                return Ok(ColliderShape::Cone { half_height, radius });
+            match rapier_shape.as_typed_shape() {
+                TypedShape::Cuboid(c) => {
+                    result.shape_type = ColliderShapeType::Box;
+                    result.half_extents_x = c.half_extents.x;
+                    result.half_extents_y = c.half_extents.y;
+                    result.half_extents_z = c.half_extents.z;
+                },
+                TypedShape::Ball(b) => {
+                    result.shape_type = ColliderShapeType::Sphere;
+                    result.radius = b.radius;
+                },
+                TypedShape::Capsule(c) => {
+                    result.shape_type = ColliderShapeType::Capsule;
+                    result.radius = c.radius;
+                    result.half_height = c.segment.length() * 0.5;
+                },
+                TypedShape::Cylinder(c) => {
+                    result.shape_type = ColliderShapeType::Cylinder;
+                    result.radius = c.radius;
+                    result.half_height = c.half_height;
+                },
+                TypedShape::Cone(c) => {
+                    result.shape_type = ColliderShapeType::Cone;
+                    result.radius = c.radius;
+                    result.half_height = c.half_height;
+                },
+                _ => return DropbearNativeResult::Err(DropbearNativeError::GenericError),
             }
+            DropbearNativeResult::Ok(result)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_set_collider_shape(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+        shape: ColliderShapeFFI,
+    ) -> DropbearNativeResult<()> {
+        let physics = convert_ptr!(mut physics_ptr => PhysicsState);
+
+        if let Some(collider) = get_collider_mut(physics, &ffi) {
+            let new_shape = match shape.shape_type {
+                ColliderShapeType::Box => SharedShape::cuboid(shape.half_extents_x, shape.half_extents_y, shape.half_extents_z),
+                ColliderShapeType::Sphere => SharedShape::ball(shape.radius),
+                ColliderShapeType::Capsule => SharedShape::capsule_y(shape.half_height, shape.radius),
+                ColliderShapeType::Cylinder => SharedShape::cylinder(shape.half_height, shape.radius),
+                ColliderShapeType::Cone => SharedShape::cone(shape.half_height, shape.radius),
+            };
+            collider.set_shape(new_shape);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchHandle)
+        }
+    }
+
+    pub fn dropbear_get_collider_density(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+    ) -> DropbearNativeResult<f64> {
+        let physics = convert_ptr!(physics_ptr => PhysicsState);
+        if let Some(col) = get_collider(physics, &ffi) {
+            DropbearNativeResult::Ok(col.density() as f64)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_set_collider_density(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+        density: f64,
+    ) -> DropbearNativeResult<()> {
+        let physics = convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Some(col) = get_collider_mut(physics, &ffi) {
+            col.set_density(density as f32);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_get_collider_friction(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+    ) -> DropbearNativeResult<f64> {
+        let physics = convert_ptr!(physics_ptr => PhysicsState);
+        if let Some(col) = get_collider(physics, &ffi) {
+            DropbearNativeResult::Ok(col.friction() as f64)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_set_collider_friction(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+        friction: f64,
+    ) -> DropbearNativeResult<()> {
+        let physics = convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Some(col) = get_collider_mut(physics, &ffi) {
+            col.set_friction(friction as f32);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_get_collider_restitution(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+    ) -> DropbearNativeResult<f64> {
+        let physics = convert_ptr!(physics_ptr => PhysicsState);
+        if let Some(col) = get_collider(physics, &ffi) {
+            DropbearNativeResult::Ok(col.restitution() as f64)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_set_collider_restitution(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+        restitution: f64,
+    ) -> DropbearNativeResult<()> {
+        let physics = convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Some(col) = get_collider_mut(physics, &ffi) {
+            col.set_restitution(restitution as f32);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_get_collider_mass(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+    ) -> DropbearNativeResult<f64> {
+        let physics = convert_ptr!(physics_ptr => PhysicsState);
+        if let Some(col) = get_collider(physics, &ffi) {
+            DropbearNativeResult::Ok(col.mass() as f64)
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_set_collider_mass(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+        mass: f64,
+    ) -> DropbearNativeResult<()> {
+        let physics = convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Some(col) = get_collider_mut(physics, &ffi) {
+            col.set_mass(mass as f32);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_get_collider_is_sensor(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+    ) -> DropbearNativeResult<bool> {
+        let physics = convert_ptr!(physics_ptr => PhysicsState);
+        if let Some(col) = get_collider(physics, &ffi) {
+            DropbearNativeResult::Ok(col.is_sensor())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_set_collider_is_sensor(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+        is_sensor: bool,
+    ) -> DropbearNativeResult<()> {
+        let physics = convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Some(col) = get_collider_mut(physics, &ffi) {
+            col.set_sensor(is_sensor);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_get_collider_translation(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+    ) -> DropbearNativeResult<Vector3> {
+        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 })
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_set_collider_translation(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+        translation: Vector3,
+    ) -> DropbearNativeResult<()> {
+        let physics = convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Some(col) = get_collider_mut(physics, &ffi) {
+            let t = rapier3d::na::Vector3::new(translation.x as f32, translation.y as f32, translation.z as f32);
+            col.set_translation(t);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
+
+    pub fn dropbear_get_collider_rotation(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+    ) -> DropbearNativeResult<Vector3> {
+        let physics = convert_ptr!(physics_ptr => PhysicsState);
+        if let Some(col) = get_collider(physics, &ffi) {
+            let r = col.rotation();
+            let q = DQuat::from_xyzw(r.i as f64, r.j as f64, r.k as f64, r.w as f64);
+            let (x, y, z) = q.to_euler(glam::EulerRot::XYZ);
+            DropbearNativeResult::Ok(Vector3 { x, y, z })
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
+        }
+    }
 
-            env.throw_new("java/lang/IllegalArgumentException", "Unknown or null ColliderShape subclass type")?;
-            Err(anyhow::anyhow!("Unknown ColliderShape type"))
+    pub fn dropbear_set_collider_rotation(
+        physics_ptr: PhysicsStatePtr,
+        ffi: ColliderFFI,
+        rotation: Vector3,
+    ) -> DropbearNativeResult<()> {
+        let physics = convert_ptr!(mut physics_ptr => PhysicsState);
+        if let Some(col) = get_collider_mut(physics, &ffi) {
+            let q = DQuat::from_euler(glam::EulerRot::XYZ, rotation.x, rotation.y, rotation.z);
+            let r = UnitQuaternion::new_normalize(
+                Quaternion::new(q.w as f32, q.x as f32, q.y as f32, q.z as f32)
+            );
+            col.set_rotation(r);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::InvalidHandle)
         }
     }
 }
\ No newline at end of file
diff --git a/eucalyptus-core/src/physics/collider/collidergroup.rs b/eucalyptus-core/src/physics/collider/collidergroup.rs
new file mode 100644
index 0000000..9ca2f16
--- /dev/null
+++ b/eucalyptus-core/src/physics/collider/collidergroup.rs
@@ -0,0 +1,252 @@
+//! Scripting module for collider groups. 
+
+use crate::scripting::jni::utils::{FromJObject, ToJObject};
+use crate::scripting::native::DropbearNativeError;
+use crate::scripting::result::DropbearNativeResult;
+use crate::types::IndexNative;
+use ::jni::objects::{JObject, JValue};
+use ::jni::JNIEnv;
+
+impl ToJObject for IndexNative {
+    fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+        let cls = env.find_class("com/dropbear/physics/Index")
+            .map_err(|e| {
+                eprintln!("[JNI Error] Could not find Index class: {:?}", e);
+                DropbearNativeError::GenericError
+            })?;
+
+        let obj = env.new_object(
+            cls,
+            "(II)V",
+            &[
+                JValue::Int(self.index as i32),
+                JValue::Int(self.generation as i32)
+            ]
+        ).map_err(|e| {
+            eprintln!("[JNI Error] Failed to create Index object: {:?}", e);
+            DropbearNativeError::GenericError
+        })?;
+
+        Ok(obj)
+    }
+}
+
+impl FromJObject for IndexNative {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self>
+    where
+        Self: Sized
+    {
+        let idx_val = env.get_field(obj, "index", "I")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .i()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let gen_val = env.get_field(obj, "generation", "I")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .i()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        Ok(IndexNative {
+            index: idx_val as u32,
+            generation: gen_val as u32,
+        })
+    }
+}
+
+pub mod shared {
+    use crate::physics::collider::ColliderGroup;
+    use hecs::{Entity, World};
+
+    pub fn collider_group_exists_for_entity(world: &World, entity: Entity) -> bool {
+        world.get::<&ColliderGroup>(entity).is_ok()
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+    use crate::physics::collider::ColliderGroup;
+    use crate::types::{ColliderFFI, IndexNative};
+    use hecs::World;
+    use jni::objects::{JClass, JObject};
+    use jni::sys::{jboolean, jlong, jobjectArray};
+    use jni::JNIEnv;
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderGroupNative_colliderGroupExistsForEntity(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jboolean {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+
+        if super::shared::collider_group_exists_for_entity(&world, entity) {
+            true.into()
+        } else {
+            false.into()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_physics_ColliderGroupNative_getColliderGroupColliders(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        physics_ptr: jlong,
+        entity_id: jlong,
+    ) -> jobjectArray {
+        let world = crate::convert_ptr!(world_ptr => hecs::World);
+        let physics = crate::convert_ptr!(physics_ptr => crate::physics::PhysicsState);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+
+        // We check if the ColliderGroup component exists first
+        if world.get::<&ColliderGroup>(entity).is_ok() {
+            let handles_opt = physics.entity_label_map
+                .get(&entity)
+                .and_then(|label| physics.colliders_entity_map.get(label));
+
+            let mut colliders: Vec<ColliderFFI> = Vec::new();
+
+            if let Some(handles) = handles_opt {
+                for (_, handle) in handles {
+                    let (idx, generation) = handle.into_raw_parts();
+
+                    let col = ColliderFFI {
+                        index: IndexNative {
+                            index: idx,
+                            generation,
+                        },
+                        entity_id: entity_id as u64,
+                        id: idx,
+                    };
+                    colliders.push(col);
+                }
+            }
+
+            let collider_cls = match env.find_class("com/dropbear/physics/Collider") {
+                Ok(cls) => cls,
+                Err(e) => {
+                    eprintln!("[JNI Error] Could not find Collider class: {:?}", e);
+                    return std::ptr::null_mut();
+                }
+            };
+
+            let output_array = match env.new_object_array(colliders.len() as i32, &collider_cls, JObject::null()) {
+                Ok(arr) => arr,
+                Err(e) => {
+                    eprintln!("[JNI Error] Failed to allocate Collider array: {:?}", e);
+                    let _ = env.throw_new("java/lang/OutOfMemoryError", "Could not allocate collider array");
+                    return std::ptr::null_mut();
+                }
+            };
+
+            use crate::scripting::jni::utils::ToJObject;
+
+            for (i, ffi) in colliders.iter().enumerate() {
+                let java_obj = match ffi.to_jobject(&mut env) {
+                    Ok(obj) => obj,
+                    Err(_) => return std::ptr::null_mut(),
+                };
+
+                if let Err(e) = env.set_object_array_element(&output_array, i as i32, java_obj) {
+                    eprintln!("[JNI Error] Failed to set array element: {:?}", e);
+                    return std::ptr::null_mut();
+                }
+            }
+
+            output_array.into_raw()
+
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity does not have a ColliderGroup component");
+            std::ptr::null_mut()
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use crate::convert_ptr;
+    use crate::physics::collider::ColliderGroup;
+    use crate::physics::PhysicsState;
+    use crate::ptr::{PhysicsStatePtr, WorldPtr};
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+    use crate::types::{ColliderFFI, IndexNative};
+    use hecs::Entity;
+
+    pub fn dropbear_collider_group_exists_for_entity(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<bool> {
+        let world = convert_ptr!(world_ptr => hecs::World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        DropbearNativeResult::Ok(super::shared::collider_group_exists_for_entity(world, entity))
+    }
+
+    pub fn dropbear_get_collider_group_colliders(
+        world_ptr: WorldPtr,
+        physics_ptr: PhysicsStatePtr,
+        entity_id: u64,
+        out_count: *mut usize,
+    ) -> DropbearNativeResult<*mut ColliderFFI> {
+        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)?;
+
+        if out_count.is_null() {
+            return DropbearNativeResult::Err(DropbearNativeError::NullPointer);
+        }
+
+        if world.get::<&ColliderGroup>(entity).is_err() {
+            unsafe { *out_count = 0; }
+            return DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent);
+        }
+
+        let handles_opt = physics.entity_label_map
+            .get(&entity)
+            .and_then(|label| physics.colliders_entity_map.get(label));
+
+        let mut colliders: Vec<ColliderFFI> = Vec::new();
+
+        if let Some(handles) = handles_opt {
+            for (_, handle) in handles {
+                let (idx, generation) = handle.into_raw_parts();
+
+                let col = ColliderFFI {
+                    index: IndexNative {
+                        index: idx,
+                        generation,
+                    },
+                    entity_id,
+                    id: idx,
+                };
+                colliders.push(col);
+            }
+        }
+
+        unsafe { *out_count = colliders.len(); }
+
+        colliders.shrink_to_fit();
+        let ptr = colliders.as_mut_ptr();
+        std::mem::forget(colliders);
+
+        DropbearNativeResult::Ok(ptr)
+    }
+
+    pub fn dropbear_free_collider_array(
+        ptr: *mut ColliderFFI,
+        count: usize,
+    ) -> DropbearNativeResult<()> {
+        if ptr.is_null() {
+            return DropbearNativeResult::Ok(());
+        }
+
+        unsafe {
+            let _ = Vec::from_raw_parts(ptr, count, count);
+        }
+
+        DropbearNativeResult::Ok(())
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/physics/rigidbody.rs b/eucalyptus-core/src/physics/rigidbody.rs
index ce83bbe..93a6c2b 100644
--- a/eucalyptus-core/src/physics/rigidbody.rs
+++ b/eucalyptus-core/src/physics/rigidbody.rs
@@ -1,3 +1,5 @@
+//! [rapier3d] RigidBodies
+
 use serde::{Deserialize, Serialize};
 use dropbear_macro::SerializableComponent;
 use dropbear_traits::SerializableComponent;
@@ -118,84 +120,15 @@ impl RigidBody {
 	}
 }
 
+pub mod shared {
+	
+}
+
 pub mod jni {
-	use glam::DVec3;
-	use jni::objects::{JObject, JString};
-	use jni::JNIEnv;
-	use rapier3d::prelude::RigidBodyHandle;
-	use crate::physics::rigidbody::{AxisLock, RigidBody, RigidBodyMode};
-	use crate::scripting::jni::utils::FromJObject;
-	use crate::states::Label;
-
-	impl FromJObject for AxisLock {
-		fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> anyhow::Result<Self>
-		where
-			Self: Sized
-		{
-			let x = env.get_field(obj, "x", "Z")?.z()?;
-			let y = env.get_field(obj, "y", "Z")?.z()?;
-			let z = env.get_field(obj, "z", "Z")?.z()?;
-			Ok(AxisLock { x, y, z })
-		}
-	}
+	
+}
 
-	impl RigidBody {
-		pub fn from_jni(env: &mut JNIEnv, obj: &JObject) -> anyhow::Result<(RigidBodyHandle, Self)> {
-			let entity_jobj = env.get_field(obj, "entity", "Ljava/lang/String;")?.l()?;
-			let entity_jstr: JString = entity_jobj.into();
-			let entity_str: String = env.get_string(&entity_jstr)?.into();
-
-			let entity = Label::from(entity_str);
-
-			let gravity_scale: f32 = env.get_field(obj, "gravityScale", "D")?.d()? as f32;
-			let can_sleep: bool = env.get_field(obj, "canSleep", "Z")?.z()?;
-			let ccd_enabled: bool = env.get_field(obj, "ccdEnabled", "Z")?.z()?;
-			let linear_damping: f32 = env.get_field(obj, "linearDamping", "D")?.d()? as f32;
-			let angular_damping: f32 = env.get_field(obj, "angularDamping", "D")?.d()? as f32;
-
-			let mode_obj = env.get_field(obj, "rigidBodyMode", "Lcom/dropbear/physics/RigidBodyMode;")?.l()?;
-			let mode_ordinal = env.call_method(&mode_obj, "ordinal", "()I", &[])?.i()?;
-
-			let mode = match mode_ordinal {
-				0 => RigidBodyMode::Dynamic,
-				1 => RigidBodyMode::Fixed,
-				2 => RigidBodyMode::KinematicPosition,
-				3 => RigidBodyMode::KinematicVelocity,
-				_ => RigidBodyMode::Dynamic,
-			};
-
-			let index_obj = env.get_field(obj, "index", "Lcom/dropbear/physics/Index;")?.l()?;
-			let idx_val = env.get_field(&index_obj, "index", "I")?.i()? as u32;
-			let gen_val = env.get_field(&index_obj, "generation", "I")?.i()? as u32;
-
-			let linvel_obj = env.get_field(obj, "linearVelocity", "Lcom/dropbear/math/Vector3D;")?.l()?;
-			let linvel = DVec3::from_jobject(env, &linvel_obj)?.as_vec3().to_array();
-
-			let angvel_obj = env.get_field(obj, "angularVelocity", "Lcom/dropbear/math/Vector3D;")?.l()?;
-			let angvel = DVec3::from_jobject(env, &angvel_obj)?.as_vec3().to_array();
-
-			let lock_trans_obj = env.get_field(obj, "lockTranslation", "Lcom/dropbear/physics/AxisLock;")?.l()?;
-			let lock_translation = AxisLock::from_jobject(env, &lock_trans_obj)?;
-
-			let lock_rot_obj = env.get_field(obj, "lockRotation", "Lcom/dropbear/physics/AxisLock;")?.l()?;
-			let lock_rotation = AxisLock::from_jobject(env, &lock_rot_obj)?;
-
-			let handle = RigidBodyHandle::from_raw_parts(idx_val, gen_val);
-
-			Ok((handle, Self {
-				entity,
-				disable_physics: false,
-				mode,
-				gravity_scale,
-				can_sleep,
-				ccd_enabled,
-				linvel,
-				angvel,
-				linear_damping,
-				angular_damping,
-				lock_translation,
-				lock_rotation,
-			}))
-		}
-	}
+#[dropbear_macro::impl_c_api]
+pub mod native {
+	
 }
\ No newline at end of file
diff --git a/eucalyptus-core/src/properties.rs b/eucalyptus-core/src/properties.rs
new file mode 100644
index 0000000..858d46d
--- /dev/null
+++ b/eucalyptus-core/src/properties.rs
@@ -0,0 +1,794 @@
+use std::fmt;
+use std::fmt::{Display, Formatter};
+use serde::{Deserialize, Serialize};
+use dropbear_macro::SerializableComponent;
+use dropbear_traits::SerializableComponent;
+use egui::Ui;
+use crate::states::Property;
+
+/// Properties for an entity, typically queries with `entity.getProperty<Float>` and `entity.setProperty(21)`
+#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, SerializableComponent)]
+pub struct CustomProperties {
+    pub custom_properties: Vec<Property>,
+    pub next_id: u64,
+}
+
+impl CustomProperties {
+    /// Creates a new [CustomProperties]
+    pub fn new() -> Self {
+        Self {
+            custom_properties: Vec::new(),
+            next_id: 0,
+        }
+    }
+
+    /// Sets the property based on the [Value] (type) and its key.
+    ///
+    /// If the value does NOT exist, it will be created.
+    /// If the value does exist, it will replace the contents of that item.
+    pub fn set_property(&mut self, key: String, value: Value) {
+        if let Some(prop) = self.custom_properties.iter_mut().find(|p| p.key == key) {
+            prop.value = value;
+        } else {
+            self.custom_properties.push(Property {
+                id: self.next_id,
+                key,
+                value,
+            });
+            self.next_id += 1;
+        }
+    }
+
+    /// Fetches the property by its key.
+    pub fn get_property(&self, key: &str) -> Option<&Value> {
+        self.custom_properties
+            .iter()
+            .find(|p| p.key == key)
+            .map(|p| &p.value)
+    }
+
+    /// Fetches the float property
+    pub fn get_float(&self, key: &str) -> Option<f64> {
+        match self.get_property(key)? {
+            Value::Double(f) => Some(*f),
+            _ => None,
+        }
+    }
+
+    /// Fetches the integer property
+    pub fn get_int(&self, key: &str) -> Option<i64> {
+        match self.get_property(key)? {
+            Value::Int(i) => Some(*i),
+            _ => None,
+        }
+    }
+
+    /// Creates a new property based on a key and a value.
+    ///
+    /// It will push that value again to the property vector.
+    pub fn add_property(&mut self, key: String, value: Value) {
+        self.custom_properties.push(Property {
+            id: self.next_id,
+            key,
+            value,
+        });
+        self.next_id += 1;
+    }
+
+    /// Shows a template of the different values when inspected as a component in the editor.
+    pub fn show_value_editor(ui: &mut Ui, value: &mut Value) -> bool {
+        match value {
+            Value::String(s) => ui.text_edit_singleline(s).changed(),
+            Value::Int(i) => ui
+                .add(egui::Slider::new(i, -1000..=1000).text(""))
+                .changed(),
+            Value::Double(f) => ui
+                .add(egui::Slider::new(f, -100.0..=100.0).text(""))
+                .changed(),
+            Value::Bool(b) => ui.checkbox(b, "").changed(),
+            Value::Vec3(vec) => {
+                let mut changed = false;
+                ui.horizontal(|ui| {
+                    changed |= ui
+                        .add(
+                            egui::Slider::new(&mut vec[0], -10.0..=10.0)
+                                .text("X")
+                                .fixed_decimals(2),
+                        )
+                        .changed();
+                    changed |= ui
+                        .add(
+                            egui::Slider::new(&mut vec[1], -10.0..=10.0)
+                                .text("Y")
+                                .fixed_decimals(2),
+                        )
+                        .changed();
+                    changed |= ui
+                        .add(
+                            egui::Slider::new(&mut vec[2], -10.0..=10.0)
+                                .text("Z")
+                                .fixed_decimals(2),
+                        )
+                        .changed();
+                });
+                changed
+            }
+        }
+    }
+}
+
+impl Default for CustomProperties {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub enum Value {
+    String(String),
+    Int(i64),
+    Double(f64),
+    Bool(bool),
+    Vec3([f64; 3]),
+}
+
+impl Default for Value {
+    fn default() -> Self {
+        Self::String(String::new())
+    }
+}
+
+impl Display for Value {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        let string: String = match self {
+            Value::String(_) => "String".into(),
+            Value::Int(_) => "Int".into(),
+            Value::Double(_) => "Double".into(),
+            Value::Bool(_) => "Bool".into(),
+            Value::Vec3(_) => "Vec3".into(),
+        };
+        write!(f, "{}", string)
+    }
+}
+
+pub mod shared {
+    use std::ffi::CStr;
+    use std::os::raw::c_char;
+    use hecs::World;
+    use crate::properties::CustomProperties;
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+
+    pub fn custom_properties_exists_for_entity(world: &World, entity: hecs::Entity) -> bool {
+        world.get::<&CustomProperties>(entity).is_ok()
+    }
+
+    pub(crate) unsafe fn read_key(ptr: *const c_char) -> DropbearNativeResult<String> {
+        if ptr.is_null() {
+            return DropbearNativeResult::Err(DropbearNativeError::NullPointer);
+        }
+        match { CStr::from_ptr(ptr) }.to_str() {
+            Ok(s) => DropbearNativeResult::Ok(s.to_string()),
+            Err(_) => DropbearNativeResult::Err(DropbearNativeError::InvalidUTF8),
+        }
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+    use hecs::World;
+    use jni::JNIEnv;
+    use jni::objects::{JClass, JObject, JString, JValue};
+    use jni::sys::{jboolean, jdouble, jfloat, jint, jlong, jobject, jstring};
+    use glam::DVec3;
+
+    use crate::properties::{CustomProperties, Value};
+    use crate::scripting::jni::utils::{FromJObject, ToJObject};
+
+    /// Returns a primitive that is boxed (long => java.lang.Long)
+    ///
+    /// ```
+    /// return_boxed!(&mut env, Some(JValue::Int(21 as jint)), "(I)Ljava/lang/Integer;", "java/lang/Integer")
+    /// ```
+    #[macro_export]
+    macro_rules! return_boxed {
+        ($env:expr, $val:expr, $sig:expr, $wrapper:expr) => {
+            match $val {
+                Some(v) => {
+                    let result = |env: &mut jni::JNIEnv| -> jni::errors::Result<jni::sys::jobject> {
+                        let cls = env.find_class($wrapper)?;
+
+                        let param: jni::objects::JValue = v.into();
+                        let ret = env.call_static_method(cls, "valueOf", $sig, &[param])?;
+
+                        Ok(ret.l()?.into_raw())
+                    }($env);
+
+                    match result {
+                        Ok(ptr) => ptr,
+                        Err(e) => {
+                            eprintln!("return_boxed failed for {}: {:?}", $wrapper, e);
+
+                            let _ = $env.throw_new("java/lang/RuntimeException", format!("Boxing failed: {:?}", e));
+
+                            std::ptr::null_mut()
+                        }
+                    }
+                }
+                None => std::ptr::null_mut(),
+            }
+        };
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_customPropertiesExistsForEntity(
+        _env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+    ) -> jboolean {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+
+        if super::shared::custom_properties_exists_for_entity(&world, entity) {
+            1
+        } else {
+            0
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_getStringProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+    ) -> jstring {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::String(s)) = props.get_property(&key_str) {
+                return env.new_string(s).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut());
+            }
+        }
+        std::ptr::null_mut()
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_getIntProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        let val = if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::Int(v)) = props.get_property(&key_str) {
+                Some(JValue::Int(*v as jint))
+            } else { None }
+        } else { None };
+
+        return_boxed!(&mut env, val, "(I)Ljava/lang/Integer;", "java/lang/Integer")
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_getLongProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        let val = if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::Int(v)) = props.get_property(&key_str) {
+                Some(JValue::Long(*v))
+            } else { None }
+        } else { None };
+
+        return_boxed!(&mut env, val, "(J)Ljava/lang/Long;", "java/lang/Long")
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_getDoubleProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        let val = if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::Double(v)) = props.get_property(&key_str) {
+                Some(JValue::Double(*v))
+            } else { None }
+        } else { None };
+
+        return_boxed!(&mut env, val, "(D)Ljava/lang/Double;", "java/lang/Double")
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_getFloatProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        let val = if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::Double(v)) = props.get_property(&key_str) {
+                Some(JValue::Float(*v as jfloat))
+            } else { None }
+        } else { None };
+
+        return_boxed!(&mut env, val, "(F)Ljava/lang/Float;", "java/lang/Float")
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_getBoolProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        let val = if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::Bool(v)) = props.get_property(&key_str) {
+                Some(JValue::Bool(if *v { 1 } else { 0 }))
+            } else { None }
+        } else { None };
+
+        return_boxed!(&mut env, val, "(Z)Ljava/lang/Boolean;", "java/lang/Boolean")
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_getVec3Property(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        if let Ok(props) = world.get::<&CustomProperties>(entity) {
+            if let Some(Value::Vec3(v)) = props.get_property(&key_str) {
+                return match DVec3::from_array(*v).to_jobject(&mut env) {
+                    Ok(obj) => obj.into_raw(),
+                    Err(_) => std::ptr::null_mut()
+                };
+            }
+        }
+        std::ptr::null_mut()
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_setStringProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+        value: JString,
+    ) {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+        let val_str = crate::convert_jstring!(env, value);
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::String(val_str));
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_setIntProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+        value: jint,
+    ) {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Int(value as i64));
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_setLongProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+        value: jlong,
+    ) {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Int(value));
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_setFloatProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+        value: jfloat,
+    ) {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Double(value as f64));
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_setDoubleProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+        value: jdouble,
+    ) {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Double(value));
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_setBoolProperty(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+        value: jboolean,
+    ) {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Bool(value != 0));
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_CustomPropertiesNative_setVec3Property(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_handle: jlong,
+        entity_id: jlong,
+        key: JString,
+        value: JObject,
+    ) {
+        let world = crate::convert_ptr!(world_handle => World);
+        let entity = crate::convert_jlong_to_entity!(entity_id);
+        let key_str = crate::convert_jstring!(env, key);
+
+        let vec_val = match DVec3::from_jobject(&mut env, &value) {
+            Ok(v) => v,
+            Err(_) => return,
+        };
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Vec3(vec_val.to_array()));
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use hecs::{Entity, World};
+    use std::ffi::CString;
+    use std::os::raw::c_char;
+
+    use crate::convert_ptr;
+    use crate::properties::shared::read_key;
+    use crate::properties::{CustomProperties, Value};
+    use crate::ptr::WorldPtr;
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+    use crate::types::Vector3;
+    
+    pub fn dropbear_custom_properties_exists_for_entity(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<bool> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        DropbearNativeResult::Ok(super::shared::custom_properties_exists_for_entity(world, entity))
+    }
+
+
+    pub fn dropbear_get_string_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char
+    ) -> DropbearNativeResult<*mut c_char> {
+        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::String(s)) = props.get_property(&key_str) {
+                return match CString::new(s.clone()) {
+                    Ok(c) => DropbearNativeResult::Ok(c.into_raw()),
+                    Err(_) => DropbearNativeResult::Err(DropbearNativeError::CStringError),
+                };
+            }
+        }
+        DropbearNativeResult::Err(DropbearNativeError::InvalidArgument)
+    }
+
+    pub fn dropbear_get_int_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char
+    ) -> DropbearNativeResult<i32> {
+        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::Int(v)) = props.get_property(&key_str) {
+                return DropbearNativeResult::Ok(*v as i32);
+            }
+        }
+        DropbearNativeResult::Err(DropbearNativeError::InvalidArgument)
+    }
+
+    pub fn dropbear_get_long_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char
+    ) -> DropbearNativeResult<i64> {
+        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::Int(v)) = props.get_property(&key_str) {
+                return DropbearNativeResult::Ok(*v);
+            }
+        }
+        DropbearNativeResult::Err(DropbearNativeError::InvalidArgument)
+    }
+
+    pub fn dropbear_get_double_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char
+    ) -> DropbearNativeResult<f64> {
+        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::Double(v)) = props.get_property(&key_str) {
+                return DropbearNativeResult::Ok(*v);
+            }
+        }
+        DropbearNativeResult::Err(DropbearNativeError::InvalidArgument)
+    }
+
+    pub fn dropbear_get_float_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char
+    ) -> DropbearNativeResult<f32> {
+        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::Double(v)) = props.get_property(&key_str) {
+                return DropbearNativeResult::Ok(*v as f32);
+            }
+        }
+        DropbearNativeResult::Err(DropbearNativeError::InvalidArgument)
+    }
+
+    pub fn dropbear_get_bool_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char
+    ) -> DropbearNativeResult<bool> {
+        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::Bool(v)) = props.get_property(&key_str) {
+                return DropbearNativeResult::Ok(*v);
+            }
+        }
+        DropbearNativeResult::Err(DropbearNativeError::InvalidArgument)
+    }
+
+    pub fn dropbear_get_vec3_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char
+    ) -> DropbearNativeResult<Vector3> {
+        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));
+            }
+        }
+        DropbearNativeResult::Err(DropbearNativeError::InvalidArgument)
+    }
+
+    pub fn dropbear_set_string_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char,
+        value: *const c_char
+    ) -> DropbearNativeResult<()> {
+        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)? };
+        let val_str = unsafe { read_key(value)? };
+
+        if let Ok(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::String(val_str));
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_int_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char,
+        value: i32
+    ) -> DropbearNativeResult<()> {
+        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(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Int(value as i64));
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_long_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char,
+        value: i64
+    ) -> DropbearNativeResult<()> {
+        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(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Int(value));
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_double_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char,
+        value: f64
+    ) -> DropbearNativeResult<()> {
+        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(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Double(value));
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_float_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char,
+        value: f32
+    ) -> DropbearNativeResult<()> {
+        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(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Double(value as f64));
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_bool_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char,
+        value: bool
+    ) -> DropbearNativeResult<()> {
+        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(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Bool(value));
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_vec3_property(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        key: *const c_char,
+        value: Vector3
+    ) -> DropbearNativeResult<()> {
+        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(mut props) = world.get::<&mut CustomProperties>(entity) {
+            props.set_property(key_str, Value::Vec3(value.to_array()));
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/scene.rs b/eucalyptus-core/src/scene.rs
index 1c28ade..798fee9 100644
--- a/eucalyptus-core/src/scene.rs
+++ b/eucalyptus-core/src/scene.rs
@@ -1,17 +1,18 @@
 //! Deals with scene loading and scene metadata.
 
 pub mod loading;
+pub mod scripting;
 
-use crate::camera::{CameraComponent};
+use crate::camera::CameraComponent;
 use crate::hierarchy::{Children, Parent, SceneHierarchy};
-use crate::states::{Camera3D, Label, Light, CustomProperties, PROJECT, Script, SerializedMeshRenderer, WorldLoadingStatus};
+use crate::states::{Camera3D, Label, Light, Script, SerializedMeshRenderer, WorldLoadingStatus, PROJECT};
 use crate::utils::ResolveReference;
 use dropbear_engine::asset::ASSET_REGISTRY;
 use dropbear_engine::camera::{Camera, CameraBuilder};
 use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform};
-use dropbear_engine::graphics::{SharedGraphicsContext};
+use dropbear_engine::graphics::SharedGraphicsContext;
 use dropbear_engine::lighting::{Light as EngineLight, LightComponent};
-use dropbear_engine::model::{Model};
+use dropbear_engine::model::Model;
 use dropbear_engine::utils::{ResourceReference, ResourceReferenceType};
 use dropbear_traits::SerializableComponent;
 use dropbear_traits::registry::ComponentRegistry;
@@ -24,9 +25,10 @@ use std::fs;
 use std::path::{Path, PathBuf};
 use std::sync::Arc;
 use crossbeam_channel::Sender;
-use crate::physics::collider::{ColliderGroup};
+use crate::physics::collider::ColliderGroup;
 use crate::physics::PhysicsState;
 use crate::physics::rigidbody::RigidBody;
+use crate::properties::CustomProperties;
 
 #[derive(Default, Debug, Serialize, Deserialize, Clone)]
 pub struct SceneEntity {
diff --git a/eucalyptus-core/src/scene/scripting.rs b/eucalyptus-core/src/scene/scripting.rs
new file mode 100644
index 0000000..5163e1b
--- /dev/null
+++ b/eucalyptus-core/src/scene/scripting.rs
@@ -0,0 +1,12 @@
+pub mod shared {
+
+}
+
+pub mod jni {
+
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting.rs b/eucalyptus-core/src/scripting.rs
index c5d79fc..6efd977 100644
--- a/eucalyptus-core/src/scripting.rs
+++ b/eucalyptus-core/src/scripting.rs
@@ -6,11 +6,12 @@ pub mod error;
 pub mod jni;
 pub mod native;
 pub mod utils;
+pub mod result;
 
 pub static JVM_ARGS: OnceLock<String> = OnceLock::new();
 
 use std::sync::OnceLock;
-use crate::ptr::{CommandBufferPtr, InputStatePtr, PhysicsStatePtr, WorldPtr};
+use crate::ptr::{AssetRegistryPtr, CommandBufferPtr, InputStatePtr, PhysicsStatePtr, SceneLoaderPtr, WorldPtr};
 use crate::scripting::jni::JavaContext;
 use crate::scripting::native::NativeLibrary;
 use crate::states::Script;
@@ -22,9 +23,10 @@ use std::collections::{HashMap, HashSet};
 use std::path::{Path, PathBuf};
 use tokio::io::{AsyncBufReadExt, BufReader};
 use tokio::process::Command;
+use dropbear_engine::asset::PointerKind::Const;
+use dropbear_engine::model::MODEL_CACHE;
 use magna_carta::Target;
 use crate::scene::loading::SCENE_LOADER;
-use crate::scripting::native::exports::dropbear_common::DropbearContext;
 
 /// The target of the script. This can be either a JVM or a native library.
 #[derive(Default, Clone, Debug)]
@@ -219,6 +221,9 @@ impl ScriptManager {
     ) -> anyhow::Result<()> {
         let assets = &raw const *ASSET_REGISTRY;
         let scene_loader = &raw const *SCENE_LOADER;
+        
+        let model_cache_ptr = &raw const *MODEL_CACHE;
+        ASSET_REGISTRY.add_pointer(Const("model_cache"), model_cache_ptr as usize);
 
         let context = DropbearContext {
             world,
@@ -836,3 +841,15 @@ pub async fn build_native(
         Err(anyhow::anyhow!(err))
     }
 }
+
+/// Describes all the different pointers that can be passed into a scripting
+/// module.
+#[repr(C)]
+pub struct DropbearContext {
+    pub world: WorldPtr,
+    pub input: InputStatePtr,
+    pub graphics: CommandBufferPtr,
+    pub assets: AssetRegistryPtr,
+    pub scene_loader: SceneLoaderPtr,
+    pub physics_state: PhysicsStatePtr,
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/jni.rs b/eucalyptus-core/src/scripting/jni.rs
index 544ef55..419d4ea 100644
--- a/eucalyptus-core/src/scripting/jni.rs
+++ b/eucalyptus-core/src/scripting/jni.rs
@@ -1,7 +1,7 @@
 #![allow(non_snake_case)]
 //! Deals with the Java Native Interface (JNI) with the help of the [`jni`] crate
 
-pub mod exports;
+// pub mod exports;
 pub mod utils;
 
 use crate::APP_INFO;
@@ -17,7 +17,7 @@ use std::net::TcpListener;
 use std::path::PathBuf;
 use once_cell::sync::OnceCell;
 use crate::scripting::JVM_ARGS;
-use crate::scripting::native::exports::dropbear_common::DropbearContext;
+use crate::scripting::DropbearContext;
 
 #[derive(Default, Clone)]
 pub enum RuntimeMode {
diff --git a/eucalyptus-core/src/scripting/jni/utils.rs b/eucalyptus-core/src/scripting/jni/utils.rs
index 4d68f4b..c351823 100644
--- a/eucalyptus-core/src/scripting/jni/utils.rs
+++ b/eucalyptus-core/src/scripting/jni/utils.rs
@@ -1,32 +1,11 @@
 //! Utilities for JNI and JVM based code.
 
-use glam::{DVec3, Vec3};
+use crate::scripting::native::DropbearNativeError;
+use crate::scripting::result::DropbearNativeResult;
+use glam::DVec3;
+use jni::objects::{JObject, JValue};
+use jni::sys::jint;
 use jni::JNIEnv;
-use jni::objects::{JFloatArray, JObject, JValue};
-use jni::sys::{jfloatArray, jint};
-
-pub fn new_float_array(env: &mut JNIEnv, x: f32, y: f32) -> jfloatArray {
-    let java_array: JFloatArray = match env.new_float_array(2) {
-        Ok(v) => v,
-        Err(e) => {
-            eprintln!("[ERROR] Failed to create float array: {}", e);
-            return std::ptr::null_mut();
-        }
-    };
-    let elements: [f32; 2] = [x, y];
-    match env.set_float_array_region(&java_array, 0, &elements) {
-        Ok(()) => java_array.into_raw(),
-        Err(e) => {
-            eprintln!("[ERROR] Error setting float array region: {}", e);
-            env.throw_new(
-                "java/lang/RuntimeException",
-                "Failed to set float array region",
-            )
-                .unwrap();
-            std::ptr::null_mut()
-        }
-    }
-}
 
 const JAVA_MOUSE_BUTTON_LEFT: jint = 0;
 const JAVA_MOUSE_BUTTON_RIGHT: jint = 1;
@@ -41,122 +20,62 @@ pub fn java_button_to_rust(button_code: jint) -> Option<winit::event::MouseButto
         JAVA_MOUSE_BUTTON_MIDDLE => Some(winit::event::MouseButton::Middle),
         JAVA_MOUSE_BUTTON_BACK => Some(winit::event::MouseButton::Back),
         JAVA_MOUSE_BUTTON_FORWARD => Some(winit::event::MouseButton::Forward),
-        other if other >= 0 => Some(winit::event::MouseButton::Other(other as u16)), // Assuming Other uses the int directly
+        other if other >= 0 => Some(winit::event::MouseButton::Other(other as u16)),
         _ => None,
     }
 }
 
-pub fn create_vector3<'a>(
-    env: &mut JNIEnv<'a>,
-    x: f64,
-    y: f64,
-    z: f64,
-) -> anyhow::Result<JObject<'a>> {
-    let vector3_class = env.find_class("com/dropbear/math/Vector3")?;
-
-    let x_obj = env
-        .call_static_method(
-            "java/lang/Double",
-            "valueOf",
-            "(D)Ljava/lang/Double;",
-            &[JValue::Double(x)],
-        )?
-        .l()?;
-
-    let y_obj = env
-        .call_static_method(
-            "java/lang/Double",
-            "valueOf",
-            "(D)Ljava/lang/Double;",
-            &[JValue::Double(y)],
-        )?
-        .l()?;
-
-    let z_obj = env
-        .call_static_method(
-            "java/lang/Double",
-            "valueOf",
-            "(D)Ljava/lang/Double;",
-            &[JValue::Double(z)],
-        )?
-        .l()?;
-
-    let vector3 = env.new_object(
-        vector3_class,
-        "(Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;)V",
-        &[
-            JValue::Object(&x_obj),
-            JValue::Object(&y_obj),
-            JValue::Object(&z_obj),
-        ],
-    )?;
-
-    Ok(vector3)
-}
-
-pub fn extract_vector3(env: &mut JNIEnv, vector_obj: &JObject) -> Option<Vec3> {
-    let x_obj = env
-        .get_field(vector_obj, "x", "Ljava/lang/Number;")
-        .ok()?
-        .l()
-        .ok()?;
-    let y_obj = env
-        .get_field(vector_obj, "y", "Ljava/lang/Number;")
-        .ok()?
-        .l()
-        .ok()?;
-    let z_obj = env
-        .get_field(vector_obj, "z", "Ljava/lang/Number;")
-        .ok()?
-        .l()
-        .ok()?;
-
-    let x = env
-        .call_method(&x_obj, "doubleValue", "()D", &[])
-        .ok()?
-        .d()
-        .ok()?;
-    let y = env
-        .call_method(&y_obj, "doubleValue", "()D", &[])
-        .ok()?
-        .d()
-        .ok()?;
-    let z = env
-        .call_method(&z_obj, "doubleValue", "()D", &[])
-        .ok()?
-        .d()
-        .ok()?;
-
-    Some(Vec3::new(x as f32, y as f32, z as f32))
-}
-
 /// Trait that defines conversion from a Java object to a Rust struct.
 pub trait FromJObject {
     /// Converts a Java object to a Rust struct.
-    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> anyhow::Result<Self>
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self>
     where
         Self: Sized;
 }
 
 impl FromJObject for DVec3 {
-    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> anyhow::Result<Self>
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self>
     where
         Self: Sized
     {
         let x_obj = env
-            .get_field(obj, "x", "Ljava/lang/Number;")?.l()?;
+            .get_field(obj, "x", "Ljava/lang/Number;").map_err(|_| DropbearNativeError::JNIFailedToGetField)?.l().map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
         let y_obj = env
-            .get_field(obj, "y", "Ljava/lang/Number;")?.l()?;
+            .get_field(obj, "y", "Ljava/lang/Number;").map_err(|_| DropbearNativeError::JNIFailedToGetField)?.l().map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
         let z_obj = env
-            .get_field(obj, "z", "Ljava/lang/Number;")?.l()?;
+            .get_field(obj, "z", "Ljava/lang/Number;").map_err(|_| DropbearNativeError::JNIFailedToGetField)?.l().map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
 
         let x = env
-            .call_method(&x_obj, "doubleValue", "()D", &[])?.d()?;
+            .call_method(&x_obj, "doubleValue", "()D", &[]).map_err(|_| DropbearNativeError::JNIMethodNotFound)?.d().map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
         let y = env
-            .call_method(&y_obj, "doubleValue", "()D", &[])?.d()?;
+            .call_method(&y_obj, "doubleValue", "()D", &[]).map_err(|_| DropbearNativeError::JNIMethodNotFound)?.d().map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
         let z = env
-            .call_method(&z_obj, "doubleValue", "()D", &[])?.d()?;
+            .call_method(&z_obj, "doubleValue", "()D", &[]).map_err(|_| DropbearNativeError::JNIMethodNotFound)?.d().map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
 
         Ok(DVec3::new(x, y, z))
     }
+}
+
+/// 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 ToJObject for DVec3 {
+    fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+        let cls = env.find_class("com/dropbear/math/Vector3d")
+            .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+        let obj = env.new_object(
+            cls,
+            "(DDD)V",
+            &[
+                JValue::Double(self.x),
+                JValue::Double(self.y),
+                JValue::Double(self.z)
+            ]
+        ).map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+        Ok(obj)
+    }
 }
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/native.rs b/eucalyptus-core/src/scripting/native.rs
index 03cb8b0..f54f277 100644
--- a/eucalyptus-core/src/scripting/native.rs
+++ b/eucalyptus-core/src/scripting/native.rs
@@ -1,29 +1,18 @@
 //! Deals with Kotlin/Native library loading for different platforms.
 #![allow(clippy::missing_safety_doc)]
 
-pub mod exports;
+// pub mod exports;
 pub mod sig;
 pub mod utils;
 
 use crate::scripting::error::LastErrorMessage;
-use crate::scripting::native::sig::{
-    DestroyAll,
-    DestroyInScopeTagged,
-    DestroyTagged,
-    Init,
-    LoadTagged,
-    PhysicsUpdateAll,
-    PhysicsUpdateTagged,
-    PhysicsUpdateWithEntities,
-    UpdateAll,
-    UpdateTagged,
-    UpdateWithEntities,
-};
+use crate::scripting::native::sig::{DestroyAll, DestroyInScopeTagged, DestroyTagged, Init, LoadTagged, PhysicsUpdateAll, PhysicsUpdateTagged, PhysicsUpdateWithEntities, UpdateAll, UpdateTagged, UpdateWithEntities};
 use anyhow::anyhow;
 use libloading::{Library, Symbol};
 use std::ffi::CString;
+use std::fmt::{Display, Formatter};
 use std::path::Path;
-use crate::scripting::native::exports::dropbear_common::DropbearContext;
+use crate::scripting::DropbearContext;
 
 pub struct NativeLibrary {
     #[allow(dead_code)]
@@ -263,13 +252,12 @@ impl NativeLibrary {
             return Ok(());
         }
 
-        let code_label = DropbearNativeError::code_to_string(result);
         let last_error = self
             .get_last_error()
             .map(|msg| format!(": {msg}"))
             .unwrap_or_default();
 
-        anyhow::bail!("Native script {} failed ({}{})", operation, code_label, last_error);
+        anyhow::bail!("Native script {} failed ({})", operation, last_error);
     }
 }
 
@@ -362,6 +350,7 @@ impl LastErrorMessage for NativeLibrary {
     }
 }
 
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 /// Displays the types of errors that can be returned by the native library.
 pub enum DropbearNativeError {
     /// An error in the case the function returns an unsigned value.
@@ -369,8 +358,7 @@ pub enum DropbearNativeError {
     /// Subtract [`DropbearNativeError::UnsignedGenericError`] with another value
     /// to get the alternative unsigned error.
     UnsignedGenericError = 65535,
-    /// Used to describe an error that doesn't have a particular cause. All it knows
-    /// is that there was an error thrown.
+    /// An error that is thrown, but doesn't have any attached context.
     GenericError = 1,
     /// The default return code for a successful FFI operation.
     Success = 0,
@@ -437,6 +425,30 @@ pub enum DropbearNativeError {
     GamepadNotFound = -11,
     /// When the argument is invalid
     InvalidArgument = -12,
+    /// The handle provided does not exist. Could be for an asset, entity, or other handle type.
+    NoSuchHandle = -13,
+    /// Failed to create a Java object via JNI.
+    JNIFailedToCreateObject = -14,
+    /// Failed to get a field from a Java object via JNI.
+    JNIFailedToGetField = -15,
+    /// Failed to find a Java class via JNI.
+    JNIClassNotFound = -16,
+    /// Failed to find a Java method via JNI.
+    JNIMethodNotFound = -17,
+    /// Failed to unwrap a Java object via JNI.
+    JNIUnwrapFailed = -18,
+    /// Generic asset error. There was an error thrown, however there is no context attached. 
+    GenericAssetError = -19,
+    /// The provided uri (either euca:// or https) was invalid and formatted wrong.
+    InvalidURI = -20,
+    /// The asset provided by the handle is wrong.
+    AssetNotFound = -21,
+    /// When a handle has been inputted wrongly.
+    InvalidHandle = -22,
+    
+    /// The entity provided was invalid, likely not from [hecs::Entity::from_bits].
+    InvalidEntity = -100,
+
 
     /// The CString (or `*const c_char`) contained invalid UTF-8 while being decoded.
     InvalidUTF8 = -108,
@@ -445,26 +457,39 @@ pub enum DropbearNativeError {
     ///
     /// The number `1274` comes from the total sum of the word "UnknownError" in decimal
     UnknownError = -1274,
-
 }
 
-impl DropbearNativeError {
-    /// Attempts to convert an [`i32`] numerical code to a [`String`] for better error displaying
-    pub fn code_to_string(code: i32) -> String {
-        match code {
-            x if x == DropbearNativeError::NullPointer as i32 => "NullPointer (-1)".to_string(),
-            x if x == DropbearNativeError::QueryFailed as i32 => "QueryFailed (-2)".to_string(),
-            x if x == DropbearNativeError::EntityNotFound as i32 => "EntityNotFound (-3)".to_string(),
-            x if x == DropbearNativeError::NoSuchComponent as i32 => "NoSuchComponent (-4)".to_string(),
-            x if x == DropbearNativeError::NoSuchEntity as i32 => "NoSuchEntity (-5)".to_string(),
-            x if x == DropbearNativeError::WorldInsertError as i32 => "WorldInsertError (-6)".to_string(),
-            x if x == DropbearNativeError::SendError as i32 => "SendError (-7)".to_string(),
-            x if x == DropbearNativeError::InvalidUTF8 as i32 => "InvalidUTF8 (-108)".to_string(),
-            x if x == DropbearNativeError::UnknownError as i32 => "UnknownError (-1274)".to_string(),
-            x if x == DropbearNativeError::UnsignedGenericError as i32 => {
-                "UnsignedGenericError (65535)".to_string()
-            }
-            _ => format!("code {code}"),
-        }
+impl Display for DropbearNativeError {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        f.write_str(match self {
+            DropbearNativeError::NullPointer => "NullPointer (-1)",
+            DropbearNativeError::QueryFailed => "QueryFailed (-2)",
+            DropbearNativeError::EntityNotFound => "EntityNotFound (-3)",
+            DropbearNativeError::NoSuchComponent => "NoSuchComponent (-4)",
+            DropbearNativeError::NoSuchEntity => "NoSuchEntity (-5)",
+            DropbearNativeError::WorldInsertError => "WorldInsertError (-6)",
+            DropbearNativeError::SendError => "SendError (-7)",
+            DropbearNativeError::InvalidUTF8 => "InvalidUTF8 (-108)",
+            DropbearNativeError::UnknownError => "UnknownError (-1274)",
+            DropbearNativeError::UnsignedGenericError => "UnsignedGenericError (65535)",
+            DropbearNativeError::Success => "Success (0) [should not be displayed]",
+            DropbearNativeError::CStringError => "CStringError (-8)",
+            DropbearNativeError::BufferTooSmall => "BufferTooSmall (-9)",
+            DropbearNativeError::PrematureSceneSwitch => "PrematureSceneSwitch (-10)",
+            DropbearNativeError::GamepadNotFound => "GamepadNotFound (-11)",
+            DropbearNativeError::InvalidArgument => "InvalidArgument (-12)",
+            DropbearNativeError::NoSuchHandle => "NoSuchHandle (-13)",
+            DropbearNativeError::JNIFailedToCreateObject => "JNIFailedToCreateObject (-14)",
+            DropbearNativeError::JNIFailedToGetField => "JNIFailedToGetField (-15)",
+            DropbearNativeError::JNIClassNotFound => "JNIClassNotFound (-16)",
+            DropbearNativeError::JNIMethodNotFound => "JNIMethodNotFound (-17)",
+            DropbearNativeError::JNIUnwrapFailed => "JNIUnwrapFailed (-18)",
+            DropbearNativeError::InvalidEntity => "InvalidEntity (-100)",
+            DropbearNativeError::GenericAssetError => "GenericAssetError (-19)",
+            DropbearNativeError::InvalidURI => "InvalidURI (-20)",
+            DropbearNativeError::AssetNotFound => "AssetNotFound (-21)",
+            DropbearNativeError::InvalidHandle => "InvalidHandle (-22)",
+            DropbearNativeError::GenericError => "GenericError (1)",
+        })
     }
 }
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/native/exports/dropbear_common.rs b/eucalyptus-core/src/scripting/native/exports/dropbear_common.rs
index f002c80..9856d93 100644
--- a/eucalyptus-core/src/scripting/native/exports/dropbear_common.rs
+++ b/eucalyptus-core/src/scripting/native/exports/dropbear_common.rs
@@ -11,16 +11,4 @@ pub type DropbearNativeReturn = i32;
 pub type Handle = i64;
 
 /// A helper type that defines a value that can either be a 0 or 1.
-pub type Bool = i32;
-
-/// Describes all the different pointers that can be passed into a scripting
-/// module.
-#[repr(C)]
-pub struct DropbearContext {
-    pub world: WorldPtr,
-    pub input: InputStatePtr,
-    pub graphics: CommandBufferPtr,
-    pub assets: AssetRegistryPtr,
-    pub scene_loader: SceneLoaderPtr,
-    pub physics_state: PhysicsStatePtr,
-}
\ No newline at end of file
+pub type Bool = i32;
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/native/sig.rs b/eucalyptus-core/src/scripting/native/sig.rs
index 596a14a..c03a1d0 100644
--- a/eucalyptus-core/src/scripting/native/sig.rs
+++ b/eucalyptus-core/src/scripting/native/sig.rs
@@ -1,7 +1,6 @@
-/// Different signatures for Native implementations
+/// Different input signatures for Native implementations
 use std::ffi::c_char;
-
-use crate::scripting::native::exports::dropbear_common::DropbearContext;
+use crate::scripting::DropbearContext;
 
 /// CName: `dropbear_init`
 pub type Init = unsafe extern "C" fn(dropbear_context: *const DropbearContext) -> i32;
@@ -40,4 +39,5 @@ pub type DestroyAll = unsafe extern "C" fn() -> i32;
 /// CName: `dropbear_get_last_error_message`
 pub type GetLastErrorMessage = unsafe extern "C" fn() -> *const c_char;
 /// CName: `dropbear_set_last_error_message`
-pub type SetLastErrorMessage = unsafe extern "C" fn(msg: *const c_char);
\ No newline at end of file
+pub type SetLastErrorMessage = unsafe extern "C" fn(msg: *const c_char);
+
diff --git a/eucalyptus-core/src/scripting/native/utils.rs b/eucalyptus-core/src/scripting/native/utils.rs
index 9365549..edff3ad 100644
--- a/eucalyptus-core/src/scripting/native/utils.rs
+++ b/eucalyptus-core/src/scripting/native/utils.rs
@@ -1,33 +1,4 @@
-use dropbear_engine::entity::Transform;
-use glam::{DQuat, DVec3};
 use dropbear_engine::gilrs;
-use crate::scripting::native::exports::dropbear_math::NativeTransform;
-
-pub fn write_native_transform(target: &mut NativeTransform, transform: &Transform) {
-    target.position_x = transform.position.x;
-    target.position_y = transform.position.y;
-    target.position_z = transform.position.z;
-    target.rotation_x = transform.rotation.x;
-    target.rotation_y = transform.rotation.y;
-    target.rotation_z = transform.rotation.z;
-    target.rotation_w = transform.rotation.w;
-    target.scale_x = transform.scale.x;
-    target.scale_y = transform.scale.y;
-    target.scale_z = transform.scale.z;
-}
-
-pub fn native_transform_to_transform(native: &NativeTransform) -> Transform {
-    Transform {
-        position: DVec3::new(native.position_x, native.position_y, native.position_z),
-        rotation: DQuat::from_xyzw(
-            native.rotation_x,
-            native.rotation_y,
-            native.rotation_z,
-            native.rotation_w,
-        ),
-        scale: DVec3::new(native.scale_x, native.scale_y, native.scale_z),
-    }
-}
 
 pub fn button_from_ordinal(ordinal: i32) -> Result<gilrs::Button, ()> {
     match ordinal {
@@ -53,4 +24,20 @@ pub fn button_from_ordinal(ordinal: i32) -> Result<gilrs::Button, ()> {
         19 => Ok(gilrs::Button::DPadRight),
         _ => Err(()),
     }
+}
+
+#[dropbear_macro::impl_c_api]
+mod string {
+    use crate::scripting::result::DropbearNativeResult;
+    use std::ffi::{c_char, CString};
+
+    pub fn dropbear_free_string(ptr: *mut c_char) -> DropbearNativeResult<()> {
+        if ptr.is_null() {
+            return Ok(());
+        }
+        unsafe {
+            let _ = CString::from_raw(ptr);
+        }
+        Ok(())
+    }
 }
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/result.rs b/eucalyptus-core/src/scripting/result.rs
new file mode 100644
index 0000000..b723508
--- /dev/null
+++ b/eucalyptus-core/src/scripting/result.rs
@@ -0,0 +1,4 @@
+use crate::scripting::native::DropbearNativeError;
+
+/// A result type for dropbear based native functions.
+pub type DropbearNativeResult<T> = Result<T, DropbearNativeError>;
\ No newline at end of file
diff --git a/eucalyptus-core/src/states.rs b/eucalyptus-core/src/states.rs
index 575f02e..a6ee104 100644
--- a/eucalyptus-core/src/states.rs
+++ b/eucalyptus-core/src/states.rs
@@ -1,4 +1,7 @@
-//!
+//! Different states and objects that exist in the scene. 
+//! 
+//! It's really just a "throw everything in here, organise later". 
+
 use crate::camera::{CameraComponent, CameraType};
 use crate::config::{ProjectConfig, ResourceConfig, SourceConfig};
 use crate::scene::SceneConfig;
@@ -8,7 +11,6 @@ use dropbear_engine::entity::{MaterialOverride, MeshRenderer, Transform};
 use dropbear_engine::lighting::LightComponent;
 use dropbear_engine::utils::ResourceReference;
 use dropbear_macro::SerializableComponent;
-use egui::Ui;
 use once_cell::sync::Lazy;
 use parking_lot::RwLock;
 use serde::{Deserialize, Serialize};
@@ -19,6 +21,7 @@ use std::fmt::{Display, Formatter};
 use std::ops::{Deref, DerefMut};
 use std::path::PathBuf;
 use hecs::World;
+use crate::properties::Value;
 
 /// A global "singleton" that contains the configuration of a project.
 pub static PROJECT: Lazy<RwLock<ProjectConfig>> =
@@ -223,151 +226,6 @@ pub struct Property {
     pub value: Value,
 }
 
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub enum Value {
-    String(String),
-    Int(i64),
-    Float(f64),
-    Bool(bool),
-    Vec3([f32; 3]),
-}
-
-impl Default for Value {
-    fn default() -> Self {
-        Self::String(String::new())
-    }
-}
-
-impl Display for Value {
-    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
-        let string: String = match self {
-            Value::String(_) => "String".into(),
-            Value::Int(_) => "Int".into(),
-            Value::Float(_) => "Float".into(),
-            Value::Bool(_) => "Bool".into(),
-            Value::Vec3(_) => "Vec3".into(),
-        };
-        write!(f, "{}", string)
-    }
-}
-
-/// Properties for an entity, typically queries with `entity.getProperty<Float>` and `entity.setProperty(67)`
-#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, SerializableComponent)]
-pub struct CustomProperties {
-    pub custom_properties: Vec<Property>,
-    pub next_id: u64,
-}
-
-impl CustomProperties {
-    /// Creates a new [CustomProperties]
-    pub fn new() -> Self {
-        Self {
-            custom_properties: Vec::new(),
-            next_id: 0,
-        }
-    }
-
-    /// Sets the property based on the [Value] (type) and its key.
-    ///
-    /// If the value does NOT exist, it will be created.
-    /// If the value does exist, it will replace the contents of that item.
-    pub fn set_property(&mut self, key: String, value: Value) {
-        if let Some(prop) = self.custom_properties.iter_mut().find(|p| p.key == key) {
-            prop.value = value;
-        } else {
-            self.custom_properties.push(Property {
-                id: self.next_id,
-                key,
-                value,
-            });
-            self.next_id += 1;
-        }
-    }
-
-    /// Fetches the property by its key.
-    pub fn get_property(&self, key: &str) -> Option<&Value> {
-        self.custom_properties
-            .iter()
-            .find(|p| p.key == key)
-            .map(|p| &p.value)
-    }
-
-    /// Fetches the float property
-    pub fn get_float(&self, key: &str) -> Option<f64> {
-        match self.get_property(key)? {
-            Value::Float(f) => Some(*f),
-            _ => None,
-        }
-    }
-
-    /// Fetches the integer property
-    pub fn get_int(&self, key: &str) -> Option<i64> {
-        match self.get_property(key)? {
-            Value::Int(i) => Some(*i),
-            _ => None,
-        }
-    }
-
-    /// Creates a new property based on a key and a value.
-    ///
-    /// It will push that value again to the property vector.
-    pub fn add_property(&mut self, key: String, value: Value) {
-        self.custom_properties.push(Property {
-            id: self.next_id,
-            key,
-            value,
-        });
-        self.next_id += 1;
-    }
-
-    /// Shows a template of the different values when inspected as a component in the editor.
-    pub fn show_value_editor(ui: &mut Ui, value: &mut Value) -> bool {
-        match value {
-            Value::String(s) => ui.text_edit_singleline(s).changed(),
-            Value::Int(i) => ui
-                .add(egui::Slider::new(i, -1000..=1000).text(""))
-                .changed(),
-            Value::Float(f) => ui
-                .add(egui::Slider::new(f, -100.0..=100.0).text(""))
-                .changed(),
-            Value::Bool(b) => ui.checkbox(b, "").changed(),
-            Value::Vec3(vec) => {
-                let mut changed = false;
-                ui.horizontal(|ui| {
-                    changed |= ui
-                        .add(
-                            egui::Slider::new(&mut vec[0], -10.0..=10.0)
-                                .text("X")
-                                .fixed_decimals(2),
-                        )
-                        .changed();
-                    changed |= ui
-                        .add(
-                            egui::Slider::new(&mut vec[1], -10.0..=10.0)
-                                .text("Y")
-                                .fixed_decimals(2),
-                        )
-                        .changed();
-                    changed |= ui
-                        .add(
-                            egui::Slider::new(&mut vec[2], -10.0..=10.0)
-                                .text("Z")
-                                .fixed_decimals(2),
-                        )
-                        .changed();
-                });
-                changed
-            }
-        }
-    }
-}
-
-impl Default for CustomProperties {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
 // A serializable configuration struct for the [Light] type
 #[derive(Debug, Serialize, Deserialize, Clone, SerializableComponent)]
 pub struct Light {
diff --git a/eucalyptus-core/src/transform.rs b/eucalyptus-core/src/transform.rs
new file mode 100644
index 0000000..9743bf1
--- /dev/null
+++ b/eucalyptus-core/src/transform.rs
@@ -0,0 +1,376 @@
+use crate::scripting::jni::utils::{FromJObject, ToJObject};
+use crate::scripting::native::DropbearNativeError;
+use crate::scripting::result::DropbearNativeResult;
+use dropbear_engine::entity::{EntityTransform, Transform};
+use glam::{DQuat, DVec3};
+use ::jni::objects::{JObject, JValue};
+use ::jni::JNIEnv;
+
+impl FromJObject for Transform {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let pos_val = env.get_field(obj, "position", "Lcom/dropbear/math/Vector3d;")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?;
+
+        let pos_obj = pos_val.l()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let rot_val = env.get_field(obj, "rotation", "Lcom/dropbear/math/Quaterniond;")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?;
+
+        let rot_obj = rot_val.l()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let scale_val = env.get_field(obj, "scale", "Lcom/dropbear/math/Vector3d;")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?;
+
+        let scale_obj = scale_val.l()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let position = DVec3::from_jobject(env, &pos_obj)?;
+        let scale = DVec3::from_jobject(env, &scale_obj)?;
+
+        let mut get_double = |field: &str| -> DropbearNativeResult<f64> {
+            env.get_field(&rot_obj, field, "D")
+                .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+                .d()
+                .map_err(|_| DropbearNativeError::JNIUnwrapFailed)
+        };
+
+        let rx = get_double("x")?;
+        let ry = get_double("y")?;
+        let rz = get_double("z")?;
+        let rw = get_double("w")?;
+
+        let rotation = DQuat::from_xyzw(rx, ry, rz, rw);
+
+        Ok(Transform {
+            position,
+            rotation,
+            scale,
+        })
+    }
+}
+
+impl ToJObject for Transform {
+    fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+        let cls = env.find_class("com/dropbear/math/Transform")
+            .map_err(|e| {
+                eprintln!("Could not find Transform class: {:?}", e);
+                DropbearNativeError::JNIClassNotFound
+            })?;
+
+        let p = self.position;
+        let r = self.rotation;
+        let s = self.scale;
+
+
+        let args = [
+            JValue::Double(p.x), JValue::Double(p.y), JValue::Double(p.z),
+            JValue::Double(r.x), JValue::Double(r.y), JValue::Double(r.z), JValue::Double(r.w),
+            JValue::Double(s.x), JValue::Double(s.y), JValue::Double(s.z),
+        ];
+
+        let obj = env.new_object(cls, "(DDDDDDDDDD)V", &args)
+            .map_err(|e| {
+                eprintln!("Failed to create Transform object: {:?}", e);
+                DropbearNativeError::JNIFailedToCreateObject
+            })?;
+
+        Ok(obj)
+    }
+}
+
+impl FromJObject for EntityTransform {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
+        let local_val = env.get_field(obj, "local", "Lcom/dropbear/math/Transform;")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?;
+
+        let local_obj = local_val.l()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let world_val = env.get_field(obj, "world", "Lcom/dropbear/math/Transform;")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?;
+
+        let world_obj = world_val.l()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let local = Transform::from_jobject(env, &local_obj)?;
+        let world = Transform::from_jobject(env, &world_obj)?;
+
+        Ok(EntityTransform::new(local, world))
+    }
+}
+
+impl ToJObject for EntityTransform {
+    fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+        let cls = env.find_class("com/dropbear/components/EntityTransform")
+            .map_err(|e| {
+                eprintln!("Could not find EntityTransform class: {:?}", e);
+                DropbearNativeError::JNIClassNotFound
+            })?;
+
+        let local_obj = self.local().to_jobject(env)?;
+        let world_obj = self.world().to_jobject(env)?;
+
+        let args = [
+            JValue::Object(&local_obj),
+            JValue::Object(&world_obj)
+        ];
+
+        let obj = env.new_object(
+            cls,
+            "(Lcom/dropbear/math/Transform;Lcom/dropbear/math/Transform;)V",
+            &args
+        ).map_err(|e| {
+            eprintln!("Failed to create EntityTransform object: {:?}", e);
+            DropbearNativeError::JNIFailedToCreateObject
+        })?;
+
+        Ok(obj)
+    }
+}
+
+pub mod shared {
+    use dropbear_engine::entity::EntityTransform;
+    use hecs::{Entity, World};
+
+    pub fn entity_transform_exists_for_entity(world: &World, entity: Entity) -> bool {
+        world.get::<&EntityTransform>(entity).is_ok()
+    }
+}
+
+pub mod jni {
+    #![allow(non_snake_case)]
+    use crate::convert_jlong_to_entity;
+    use crate::hierarchy::EntityTransformExt;
+    use crate::scripting::jni::utils::{FromJObject, ToJObject};
+    use dropbear_engine::entity::EntityTransform;
+    use hecs::World;
+    use jni::objects::{JClass, JObject};
+    use jni::sys::{jboolean, jlong, jobject};
+    use jni::JNIEnv;
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_EntityTransformNative_entityTransformExistsForEntity(
+        _env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jboolean {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = convert_jlong_to_entity!(entity_id);
+
+        if world.get::<&EntityTransform>(entity).is_ok() { 1 } else { 0 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_EntityTransformNative_getLocalTransform(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = convert_jlong_to_entity!(entity_id);
+
+        if let Ok(et) = world.get::<&EntityTransform>(entity) {
+            match et.local().to_jobject(&mut env) {
+                Ok(obj) => obj.into_raw(),
+                Err(_) => std::ptr::null_mut(),
+            }
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity missing EntityTransform");
+            std::ptr::null_mut()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_EntityTransformNative_setLocalTransform(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        transform_obj: JObject,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = convert_jlong_to_entity!(entity_id);
+
+        let new_transform = match dropbear_engine::entity::Transform::from_jobject(&mut env, &transform_obj) {
+            Ok(t) => t,
+            Err(e) => {
+                let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Invalid Transform: {:?}", e));
+                return;
+            }
+        };
+
+        if let Ok(mut et) = world.get::<&mut EntityTransform>(entity) {
+            *et.local_mut() = new_transform;
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity missing EntityTransform");
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_EntityTransformNative_getWorldTransform(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = convert_jlong_to_entity!(entity_id);
+
+        if let Ok(et) = world.get::<&EntityTransform>(entity) {
+            match et.world().to_jobject(&mut env) {
+                Ok(obj) => obj.into_raw(),
+                Err(_) => std::ptr::null_mut(),
+            }
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity missing EntityTransform");
+            std::ptr::null_mut()
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_EntityTransformNative_setWorldTransform(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+        transform_obj: JObject,
+    ) {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = convert_jlong_to_entity!(entity_id);
+
+        let new_transform = match dropbear_engine::entity::Transform::from_jobject(&mut env, &transform_obj) {
+            Ok(t) => t,
+            Err(_) => return,
+        };
+
+        if let Ok(mut et) = world.get::<&mut EntityTransform>(entity) {
+            *et.world_mut() = new_transform;
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity missing EntityTransform");
+        }
+    }
+
+    #[unsafe(no_mangle)]
+    pub fn Java_com_dropbear_components_EntityTransformNative_propagateTransform(
+        mut env: JNIEnv,
+        _class: JClass,
+        world_ptr: jlong,
+        entity_id: jlong,
+    ) -> jobject {
+        let world = crate::convert_ptr!(world_ptr => World);
+        let entity = convert_jlong_to_entity!(entity_id);
+
+        if let Ok(et) = world.get::<&EntityTransform>(entity) {
+            let propagated = et.propagate(&world, entity);
+            match propagated.to_jobject(&mut env) {
+                Ok(obj) => obj.into_raw(),
+                Err(_) => std::ptr::null_mut(),
+            }
+        } else {
+            let _ = env.throw_new("java/lang/RuntimeException", "Entity missing EntityTransform");
+            std::ptr::null_mut()
+        }
+    }
+}
+
+#[dropbear_macro::impl_c_api]
+pub mod native {
+    use crate::hierarchy::EntityTransformExt;
+    use crate::types::{TransformNative as Transform};
+    use dropbear_engine::entity::EntityTransform;
+    use hecs::{Entity, World};
+
+    use crate::convert_ptr;
+    use crate::ptr::WorldPtr;
+    use crate::scripting::native::DropbearNativeError;
+    use crate::scripting::result::DropbearNativeResult;
+
+    pub fn dropbear_entity_transform_exists_for_entity(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<bool> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+        DropbearNativeResult::Ok(world.get::<&EntityTransform>(entity).is_ok())
+    }
+
+    pub fn dropbear_get_local_transform(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<Transform> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(et) = world.get::<&EntityTransform>(entity) {
+            DropbearNativeResult::Ok(Transform::from(et.local().clone()))
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+
+    pub fn dropbear_set_local_transform(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: Transform,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut et) = world.get::<&mut EntityTransform>(entity) {
+            *et.local_mut() = dropbear_engine::entity::Transform::from(value);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_get_world_transform(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<Transform> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(et) = world.get::<&EntityTransform>(entity) {
+            DropbearNativeResult::Ok(Transform::from(et.world().clone()))
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_set_world_transform(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+        value: Transform,
+    ) -> DropbearNativeResult<()> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(mut et) = world.get::<&mut EntityTransform>(entity) {
+            *et.world_mut() = dropbear_engine::entity::Transform::from(value);
+            DropbearNativeResult::Ok(())
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+
+    pub fn dropbear_propagate_transform(
+        world_ptr: WorldPtr,
+        entity_id: u64,
+    ) -> DropbearNativeResult<Transform> {
+        let world = convert_ptr!(world_ptr => World);
+        let entity = Entity::from_bits(entity_id).ok_or(DropbearNativeError::InvalidEntity)?;
+
+        if let Ok(et) = world.get::<&EntityTransform>(entity) {
+            let propagated = et.propagate(world, entity);
+            DropbearNativeResult::Ok(Transform::from(propagated))
+        } else {
+            DropbearNativeResult::Err(DropbearNativeError::NoSuchComponent)
+        }
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/types.rs b/eucalyptus-core/src/types.rs
new file mode 100644
index 0000000..3c2f319
--- /dev/null
+++ b/eucalyptus-core/src/types.rs
@@ -0,0 +1,302 @@
+//! FFI and C types of other library types, as used in the scripting module.
+use glam::{DQuat, DVec3};
+use jni::JNIEnv;
+use jni::objects::{JObject, JValue};
+use rapier3d::data::Index;
+use dropbear_engine::entity::Transform;
+use crate::scripting::jni::utils::{FromJObject, ToJObject};
+use crate::scripting::native::DropbearNativeError;
+use crate::scripting::result::DropbearNativeResult;
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct Vector3 {
+    pub x: f64,
+    pub y: f64,
+    pub z: f64,
+}
+
+impl Vector3 {
+    pub fn new(x: f64, y: f64, z: f64) -> Vector3 {
+        Vector3 {
+            x, y, z
+        }
+    }
+
+    pub fn to_array(&self) -> [f64; 3] {
+        [self.x, self.y, self.z]
+    }
+}
+
+impl From<glam::DVec3> for Vector3 {
+    fn from(v: glam::DVec3) -> Self {
+        Self { x: v.x, y: v.y, z: v.z }
+    }
+}
+
+impl From<[f64; 3]> for Vector3 {
+    fn from(value: [f64; 3]) -> Self {
+        Vector3 {
+            x: value[0],
+            y: value[1],
+            z: value[2],
+        }
+    }
+}
+
+impl From<Vector3> for glam::DVec3 {
+    fn from(v: Vector3) -> Self {
+        Self::new(v.x, v.y, v.z)
+    }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct Quaternion {
+    pub x: f64,
+    pub y: f64,
+    pub z: f64,
+    pub w: f64,
+}
+
+impl From<DQuat> for Quaternion {
+    fn from(value: DQuat) -> Self {
+        Self {
+            x: value.x,
+            y: value.y,
+            z: value.z,
+            w: value.w,
+        }
+    }
+}
+
+impl From<Quaternion> for glam::DQuat {
+    fn from(value: Quaternion) -> Self {
+        Self {
+            x: value.x,
+            y: value.y,
+            z: value.z,
+            w: value.w,
+        }
+    }
+}
+
+impl From<[f64; 4]> for Quaternion {
+    fn from(value: [f64; 4]) -> Self {
+        Self {
+            x: value[0],
+            y: value[1],
+            z: value[2],
+            w: value[3],
+        }
+    }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct TransformNative {
+    position: Vector3,
+    rotation: Quaternion,
+    scale: Vector3,
+}
+
+impl From<Transform> for TransformNative {
+    fn from(value: Transform) -> Self {
+        Self {
+            position: Vector3::from(value.position),
+            rotation: Quaternion::from(value.rotation),
+            scale: Vector3::from(value.scale),
+        }
+    }
+}
+
+impl From<TransformNative> for Transform {
+    fn from(value: TransformNative) -> Self {
+        Self {
+            position: DVec3::from(value.position),
+            rotation: DQuat::from(value.rotation),
+            scale: DVec3::from(value.scale),
+        }
+    }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct Vector2 {
+    pub(crate) x: f64,
+    pub(crate) y: f64,
+}
+
+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<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 {
+        Self {
+            x: value.0,
+            y: value.1,
+        }
+    }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct ColliderFFI {
+    pub index: IndexNative,
+    pub entity_id: u64,
+    pub id: u32,
+}
+
+impl ToJObject for ColliderFFI {
+    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)?;
+
+        let index_cls = env.find_class("com/dropbear/physics/Index")
+            .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+        let entity_cls = env.find_class("com/dropbear/EntityId")
+            .map_err(|_| DropbearNativeError::JNIClassNotFound)?;
+
+        let entity_obj = env.new_object(
+            &entity_cls,
+            "(J)V",
+            &[JValue::Long(self.entity_id as i64)]
+        ).map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+        let index_obj = env.new_object(
+            &index_cls,
+            "(II)V",
+            &[
+                JValue::Int(self.index.index as i32),
+                JValue::Int(self.index.generation as i32)
+            ]
+        ).map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+        let collider_obj = env.new_object(
+            collider_cls,
+            "(Lcom/dropbear/physics/Index;Lcom/dropbear/EntityId;I)V",
+            &[
+                JValue::Object(&index_obj),
+                JValue::Object(&entity_obj),
+                JValue::Int(self.id as i32)
+            ]
+        ).map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?;
+
+        Ok(collider_obj)
+    }
+}
+
+impl FromJObject for ColliderFFI {
+    fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self>
+    where
+        Self: Sized
+    {
+        let index_obj = env.get_field(obj, "index", "Lcom/dropbear/physics/Index;")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .l()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let entity_obj = env.get_field(obj, "entity", "Lcom/dropbear/EntityId;")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .l()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let id_val = env.get_field(obj, "id", "I")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .i()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let entity_raw = env.get_field(&entity_obj, "raw", "J")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .j()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let idx_val = env.get_field(&index_obj, "index", "I")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .i()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        let gen_val = env.get_field(&index_obj, "generation", "I")
+            .map_err(|_| DropbearNativeError::JNIFailedToGetField)?
+            .i()
+            .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?;
+
+        Ok(ColliderFFI {
+            index: IndexNative {
+                index: idx_val as u32,
+                generation: gen_val as u32,
+            },
+            entity_id: entity_raw as u64,
+            id: id_val as u32,
+        })
+    }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct IndexNative {
+    pub(crate) index: u32,
+    pub(crate) generation: u32,
+}
+
+impl From<Index> for IndexNative {
+    fn from(value: Index) -> Self {
+        let raw = value.into_raw_parts();
+        Self {
+            index: raw.0,
+            generation: raw.1,
+        }
+    }
+}
+
+impl From<IndexNative> for Index {
+    fn from(value: IndexNative) -> Self {
+        Self::from_raw_parts(value.index, value.generation)
+    }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub enum ColliderShapeType {
+    Box = 0,
+    Sphere = 1,
+    Capsule = 2,
+    Cylinder = 3,
+    Cone = 4,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct ColliderShapeFFI {
+    pub shape_type: ColliderShapeType,
+    pub radius: f32,
+    pub half_height: f32,
+    pub half_extents_x: f32,
+    pub half_extents_y: f32,
+    pub half_extents_z: f32,
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/utils.rs b/eucalyptus-core/src/utils.rs
index 54fa539..5744646 100644
--- a/eucalyptus-core/src/utils.rs
+++ b/eucalyptus-core/src/utils.rs
@@ -4,7 +4,6 @@ use crate::states::Node;
 use dropbear_engine::utils::{ResourceReference, ResourceReferenceType, relative_path_from_euca};
 use std::path::{Path, PathBuf};
 use std::time::Duration;
-use serde::{Deserialize, Serialize};
 use winit::keyboard::KeyCode;
 
 pub const PROTO_TEXTURE: &[u8] = include_bytes!("../../resources/textures/proto.png");
@@ -347,7 +346,7 @@ macro_rules! convert_ptr {
 
         if ptr.is_null() {
             let message = format!(
-                "[{}] [ERROR] {} pointer is null",
+                "[{}] {} pointer is null",
                 std::any::type_name::<$target_ty>(),
                 stringify!($ptr)
             );
@@ -545,6 +544,12 @@ macro_rules! ffi_error_return {
             }
         }
 
+        impl<T> ErrorValue for $crate::scripting::result::DropbearNativeResult<T> {
+            fn error_value() -> Self {
+                Err($crate::scripting::native::DropbearNativeError::NullPointer)
+            }
+        }
+
         ErrorValue::error_value()
     }};
 
diff --git a/eucalyptus-editor/src/editor/component.rs b/eucalyptus-editor/src/editor/component.rs
index e81715a..9b997fd 100644
--- a/eucalyptus-editor/src/editor/component.rs
+++ b/eucalyptus-editor/src/editor/component.rs
@@ -1,7 +1,7 @@
 //! This module should describe the different components that are editable in the resource inspector.
 
 use crate::editor::{Signal, StaticallyKept, UndoableAction};
-use dropbear_engine::asset::{ASSET_REGISTRY, AssetHandle};
+use dropbear_engine::asset::{AssetHandle, ASSET_REGISTRY};
 use dropbear_engine::attenuation::ATTENUATION_PRESETS;
 use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform};
 use dropbear_engine::graphics::NO_TEXTURE;
@@ -9,11 +9,12 @@ use dropbear_engine::lighting::LightType;
 use dropbear_engine::utils::ResourceReference;
 use egui::{CollapsingHeader, ComboBox, DragValue, Grid, RichText, TextEdit, Ui, UiBuilder};
 use eucalyptus_core::camera::CameraType;
-use eucalyptus_core::states::{Camera3D, Light, CustomProperties, Property, Script, Value};
+use eucalyptus_core::states::{Camera3D, Light, Property, Script};
 use eucalyptus_core::{fatal, warn};
 use glam::{DVec3, Vec3};
 use hecs::Entity;
 use std::time::Instant;
+use eucalyptus_core::properties::{CustomProperties, Value};
 
 /// A trait that can added to any component that allows you to inspect the value in the editor.
 pub trait InspectableComponent {
@@ -42,7 +43,7 @@ impl From<Value> for ValueType {
         match value {
             Value::String(_) => ValueType::String,
             Value::Int(_) => ValueType::Int,
-            Value::Float(_) => ValueType::Float,
+            Value::Double(_) => ValueType::Float,
             Value::Bool(_) => ValueType::Bool,
             Value::Vec3(_) => ValueType::Vec3,
         }
@@ -54,7 +55,7 @@ impl From<&mut Value> for ValueType {
         match value {
             Value::String(_) => ValueType::String,
             Value::Int(_) => ValueType::Int,
-            Value::Float(_) => ValueType::Float,
+            Value::Double(_) => ValueType::Float,
             Value::Bool(_) => ValueType::Bool,
             Value::Vec3(_) => ValueType::Vec3,
         }
@@ -119,7 +120,7 @@ impl InspectableComponent for CustomProperties {
                     if selected_type != current_type {
                         property.value = match selected_type {
                             ValueType::String => Value::String(String::new()),
-                            ValueType::Float => Value::Float(0.0),
+                            ValueType::Float => Value::Double(0.0),
                             ValueType::Int => Value::Int(0),
                             ValueType::Bool => Value::Bool(false),
                             ValueType::Vec3 => Value::Vec3([0.0, 0.0, 0.0]),
@@ -146,7 +147,7 @@ impl InspectableComponent for CustomProperties {
                         Value::Int(n) => {
                             ui.add(DragValue::new(n).speed(1.0));
                         }
-                        Value::Float(f) => {
+                        Value::Double(f) => {
                             ui.add(DragValue::new(f).speed(speed));
                         }
                         Value::Bool(b) => {
diff --git a/eucalyptus-editor/src/editor/dock.rs b/eucalyptus-editor/src/editor/dock.rs
index d7a9cc1..ee9c351 100644
--- a/eucalyptus-editor/src/editor/dock.rs
+++ b/eucalyptus-editor/src/editor/dock.rs
@@ -1,11 +1,11 @@
 use super::*;
 use crate::editor::{
-    ViewportMode,
     console_error::{ConsoleItem, ErrorLevel},
+    ViewportMode,
 };
 use std::{
     cmp::Ordering,
-    collections::{HashMap, hash_map::DefaultHasher},
+    collections::{hash_map::DefaultHasher, HashMap},
     fs,
     hash::{Hash, Hasher},
     io,
@@ -25,7 +25,7 @@ use egui::{self, CollapsingHeader, Margin, RichText};
 use egui_dock::TabViewer;
 use egui_ltreeview::{Action, NodeBuilder, TreeViewBuilder};
 use eucalyptus_core::hierarchy::{Children, Hierarchy, Parent};
-use eucalyptus_core::states::{Label, Light, CustomProperties, PROJECT, Script};
+use eucalyptus_core::states::{Label, Light, Script, PROJECT};
 use eucalyptus_core::traits::registry::ComponentRegistry;
 use hecs::{Entity, EntityBuilder, World};
 use indexmap::Equivalent;
@@ -34,6 +34,7 @@ use parking_lot::Mutex;
 use transform_gizmo_egui::{EnumSet, Gizmo, GizmoConfig, GizmoExt, GizmoMode, GizmoOrientation};
 use eucalyptus_core::physics::collider::{Collider, ColliderGroup};
 use eucalyptus_core::physics::rigidbody::RigidBody;
+use eucalyptus_core::properties::CustomProperties;
 
 pub struct EditorTabViewer<'a> {
     pub view: egui::TextureId,
@@ -926,8 +927,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                         && let Some(col) = q.get()
                     {
                         if let Some(col) = col {
-                            let mut collider = Collider::new();
-                            collider.id = col.colliders.len() as u32 + 1;
+                            let collider = Collider::new();
                             col.insert(collider);
                         } else {
                             manual_edit = true;
diff --git a/eucalyptus-editor/src/editor/input.rs b/eucalyptus-editor/src/editor/input.rs
index ede1c42..eb322f8 100644
--- a/eucalyptus-editor/src/editor/input.rs
+++ b/eucalyptus-editor/src/editor/input.rs
@@ -3,7 +3,7 @@ use dropbear_engine::{
     entity::MeshRenderer,
     input::{Controller, Keyboard, Mouse},
 };
-use eucalyptus_core::states::{CustomProperties, Label};
+use eucalyptus_core::states::Label;
 use eucalyptus_core::success_without_console;
 use gilrs::{Button, GamepadId};
 use log;
@@ -11,6 +11,7 @@ use transform_gizmo_egui::{GizmoMode, GizmoOrientation};
 use winit::{
     dpi::PhysicalPosition, event::MouseButton, event_loop::ActiveEventLoop, keyboard::KeyCode,
 };
+use eucalyptus_core::properties::CustomProperties;
 
 impl Keyboard for Editor {
     fn key_down(&mut self, key: KeyCode, _event_loop: &ActiveEventLoop) {
diff --git a/eucalyptus-editor/src/editor/mod.rs b/eucalyptus-editor/src/editor/mod.rs
index 5c75751..ea5082b 100644
--- a/eucalyptus-editor/src/editor/mod.rs
+++ b/eucalyptus-editor/src/editor/mod.rs
@@ -16,23 +16,23 @@ use crossbeam_channel::{unbounded, Receiver, Sender};
 use dropbear_engine::entity::EntityTransform;
 use dropbear_engine::mipmap::MipMapGenerator;
 use dropbear_engine::shader::Shader;
-use dropbear_engine::{camera::Camera, entity::{MeshRenderer, Transform}, future::FutureHandle, graphics::{RenderContext, SharedGraphicsContext}, lighting::LightManager, model::{MODEL_CACHE, ModelId}, scene::SceneCommand, DropbearWindowBuilder, WindowData};
+use dropbear_engine::{camera::Camera, entity::{MeshRenderer, Transform}, future::FutureHandle, graphics::{RenderContext, SharedGraphicsContext}, lighting::LightManager, model::{ModelId, MODEL_CACHE}, scene::SceneCommand, DropbearWindowBuilder, WindowData};
 use egui::{self, Context};
 use egui_dock::{DockArea, DockState, NodeIndex, Style};
 use eucalyptus_core::{register_components, APP_INFO};
 use eucalyptus_core::hierarchy::{Children, SceneHierarchy};
 use eucalyptus_core::scene::{SceneConfig, SceneEntity};
-use eucalyptus_core::states::{CustomProperties, Label, SerializedMeshRenderer};
+use eucalyptus_core::states::{Label, SerializedMeshRenderer};
 use eucalyptus_core::traits::SerializableComponent;
 use eucalyptus_core::traits::registry::ComponentRegistry;
 use eucalyptus_core::{
     camera::{CameraComponent, CameraType, DebugCamera},
     fatal, info,
     input::InputState,
-    scripting::{BuildStatus},
+    scripting::BuildStatus,
     states,
     states::{
-        EditorTab, PROJECT, SCENES, Script, WorldLoadingStatus,
+        EditorTab, Script, WorldLoadingStatus, PROJECT, SCENES,
     },
     success,
     utils::ViewportMode,
@@ -46,7 +46,7 @@ use std::{
     fs,
     path::PathBuf,
     sync::Arc,
-    time::{Instant},
+    time::Instant,
 };
 use std::rc::Rc;
 use tokio::sync::oneshot;
@@ -57,6 +57,7 @@ use winit::{keyboard::KeyCode, window::Window};
 use winit::dpi::PhysicalSize;
 use eucalyptus_core::physics::collider::{ColliderShape, WireframeGeometry};
 use eucalyptus_core::physics::collider::shader::ColliderWireframePipeline;
+use eucalyptus_core::properties::CustomProperties;
 use crate::about::AboutWindow;
 
 pub struct Editor {
diff --git a/eucalyptus-editor/src/editor/scene.rs b/eucalyptus-editor/src/editor/scene.rs
index 71aada3..fe51d2a 100644
--- a/eucalyptus-editor/src/editor/scene.rs
+++ b/eucalyptus-editor/src/editor/scene.rs
@@ -2,7 +2,7 @@ use crossbeam_channel::unbounded;
 use super::*;
 use crate::signal::SignalController;
 use crate::spawn::PendingSpawnController;
-use dropbear_engine::asset::{ASSET_REGISTRY, PointerKind};
+use dropbear_engine::asset::{PointerKind, ASSET_REGISTRY};
 use dropbear_engine::graphics::{InstanceRaw, RenderContext};
 use dropbear_engine::model::MODEL_CACHE;
 use dropbear_engine::{
@@ -13,7 +13,7 @@ use dropbear_engine::{
 };
 use eucalyptus_core::hierarchy::EntityTransformExt;
 use eucalyptus_core::logging;
-use eucalyptus_core::states::{CustomProperties, Label, WorldLoadingStatus};
+use eucalyptus_core::states::{Label, WorldLoadingStatus};
 use log;
 use parking_lot::Mutex;
 use wgpu::Color;
@@ -21,6 +21,7 @@ use wgpu::util::DeviceExt;
 use winit::{event_loop::ActiveEventLoop, keyboard::KeyCode};
 use eucalyptus_core::physics::collider::ColliderGroup;
 use eucalyptus_core::physics::collider::shader::ColliderUniform;
+use eucalyptus_core::properties::CustomProperties;
 
 impl Scene for Editor {
     fn load(&mut self, graphics: &mut RenderContext) {
diff --git a/eucalyptus-editor/src/signal.rs b/eucalyptus-editor/src/signal.rs
index 371edcd..49a3875 100644
--- a/eucalyptus-editor/src/signal.rs
+++ b/eucalyptus-editor/src/signal.rs
@@ -7,10 +7,10 @@ use dropbear_engine::utils::{ResourceReference, ResourceReferenceType};
 use egui::Align2;
 use eucalyptus_core::camera::{CameraComponent, CameraType};
 use eucalyptus_core::scene::SceneEntity;
-use eucalyptus_core::scripting::{BuildStatus, build_jvm};
-use eucalyptus_core::spawn::{PendingSpawn, push_pending_spawn};
+use eucalyptus_core::scripting::{build_jvm, BuildStatus};
+use eucalyptus_core::spawn::{push_pending_spawn, PendingSpawn};
 use eucalyptus_core::states::{
-    EditorTab, Label, Light, CustomProperties, PROJECT, Script, SerializedMeshRenderer,
+    EditorTab, Label, Light, Script, SerializedMeshRenderer, PROJECT,
 };
 use eucalyptus_core::traits::SerializableComponent;
 use eucalyptus_core::{fatal, info, success, success_without_console, warn, warn_without_console};
@@ -18,6 +18,7 @@ use std::any::TypeId;
 use std::path::PathBuf;
 use std::sync::Arc;
 use winit::keyboard::KeyCode;
+use eucalyptus_core::properties::CustomProperties;
 
 pub trait SignalController {
     fn run_signal(&mut self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<()>;
diff --git a/eucalyptus-editor/src/spawn.rs b/eucalyptus-editor/src/spawn.rs
index ec3bbd0..89fe6c7 100644
--- a/eucalyptus-editor/src/spawn.rs
+++ b/eucalyptus-editor/src/spawn.rs
@@ -9,14 +9,15 @@ use dropbear_engine::model::Model;
 use dropbear_engine::utils::{ResourceReference, ResourceReferenceType};
 use eucalyptus_core::camera::CameraComponent;
 use eucalyptus_core::scene::SceneEntity;
-pub(crate) use eucalyptus_core::spawn::{PENDING_SPAWNS, PendingSpawnController};
+pub(crate) use eucalyptus_core::spawn::{PendingSpawnController, PENDING_SPAWNS};
 use eucalyptus_core::states::{
-    Light as LightConfig, CustomProperties, Script, SerializedMeshRenderer,
+    Light as LightConfig, Script, SerializedMeshRenderer,
 };
 use eucalyptus_core::utils::ResolveReference;
 use eucalyptus_core::{fatal, success};
 use hecs::EntityBuilder;
 use std::sync::Arc;
+use eucalyptus_core::properties::CustomProperties;
 
 fn component_ref<'a, T: 'static>(entity: &'a SceneEntity) -> Option<&'a T> {
     entity
diff --git a/headers/components/dropbear_camera.h b/headers/components/dropbear_camera.h
deleted file mode 100644
index ad3da02..0000000
--- a/headers/components/dropbear_camera.h
+++ /dev/null
@@ -1,37 +0,0 @@
-#ifndef DROPBEAR_CAMERA_H
-#define DROPBEAR_CAMERA_H
-
-#include "../dropbear_common.h"
-#include "../dropbear_math.h"
-
-typedef struct {
-    const char* label;
-    int64_t entity_id;
-
-    Vector3D eye;
-    Vector3D target;
-    Vector3D up;
-
-    double aspect;
-    double fov_y;
-    double znear;
-    double zfar;
-
-    double yaw;
-    double pitch;
-    double speed;
-    double sensitivity;
-} NativeCamera;
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-DROPBEAR_NATIVE dropbear_get_camera(const World* world_ptr, const char* label, NativeCamera* out_camera);
-DROPBEAR_NATIVE dropbear_get_attached_camera(const World* world_ptr, HANDLE entity_handle, NativeCamera* out_camera);
-DROPBEAR_NATIVE dropbear_set_camera(const World* world_ptr, NativeCamera camera);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_CAMERA_H
\ No newline at end of file
diff --git a/headers/components/dropbear_collider.h b/headers/components/dropbear_collider.h
deleted file mode 100644
index 232d88e..0000000
--- a/headers/components/dropbear_collider.h
+++ /dev/null
@@ -1,79 +0,0 @@
-#ifndef DROPBEAR_COLLIDER_H
-#define DROPBEAR_COLLIDER_H
-
-#include "../dropbear_common.h"
-#include "../dropbear_math.h"
-
-typedef enum ColliderShapeTag {
-    ColliderShape_Box = 0,
-    ColliderShape_Sphere = 1,
-    ColliderShape_Capsule = 2,
-    ColliderShape_Cylinder = 3,
-    ColliderShape_Cone = 4
-} ColliderShapeTag;
-
-// -------------------------------------------------------------- //
-
-typedef struct ColliderShapeBody_Box {
-    Vector3D half_extents;
-} ColliderShapeBody_Box;
-
-typedef struct ColliderShapeBody_Sphere {
-    float radius;
-} ColliderShapeBody_Sphere;
-
-typedef struct ColliderShapeBody_Capsule {
-    float half_height;
-    float radius;
-} ColliderShapeBody_Capsule;
-
-typedef struct ColliderShapeBody_Cylinder {
-    float half_height;
-    float radius;
-} ColliderShapeBody_Cylinder;
-
-typedef struct ColliderShapeBody_Cone {
-    float half_height;
-    float radius;
-} ColliderShapeBody_Cone;
-
-typedef struct ColliderShape {
-    ColliderShapeTag tag;
-
-    union {
-        ColliderShapeBody_Box box;
-        ColliderShapeBody_Sphere sphere;
-        ColliderShapeBody_Capsule capsule;
-        ColliderShapeBody_Cylinder cylinder;
-        ColliderShapeBody_Cone cone;
-    } data;
-} ColliderShape;
-
-// -------------------------------------------------------------- //
-
-typedef struct {
-    Index index;
-    unsigned int id;
-    HANDLE entity;
-    ColliderShape collider_shape;
-    double density;
-    double friction;
-    double restitution;
-    bool is_sensor;
-    Vector3D translation;
-    Vector3D rotation;
-} Collider;
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-DROPBEAR_NATIVE dropbear_set_collider(World* world_handle, PhysicsEngine* physics_engine, Collider collider);
-
-// free the memory of the colliders array
-DROPBEAR_NATIVE dropbear_free_colliders(Collider* colliders, unsigned int count);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_COLLIDER_H
\ No newline at end of file
diff --git a/headers/components/dropbear_entitytransform.h b/headers/components/dropbear_entitytransform.h
deleted file mode 100644
index 6a56825..0000000
--- a/headers/components/dropbear_entitytransform.h
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef DROPBEAR_ENTITYTRANSFORM_H
-#define DROPBEAR_ENTITYTRANSFORM_H
-
-#include "../dropbear_common.h"
-#include "../dropbear_math.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-DROPBEAR_NATIVE dropbear_get_transform(const World* world_ptr, HANDLE entity_handle, NativeEntityTransform* out_transform);
-DROPBEAR_NATIVE dropbear_propagate_transform(const World* world_ptr, HANDLE entity_id, NativeTransform* out_transform);
-DROPBEAR_NATIVE dropbear_set_transform(const World* world_ptr, HANDLE entity_id, NativeEntityTransform transform);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_ENTITYTRANSFORM_H
\ No newline at end of file
diff --git a/headers/components/dropbear_hierarchy.h b/headers/components/dropbear_hierarchy.h
deleted file mode 100644
index af5dc32..0000000
--- a/headers/components/dropbear_hierarchy.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#ifndef DROPBEAR_HIERARCHY_H
-#define DROPBEAR_HIERARCHY_H
-
-#include "../dropbear_common.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-DROPBEAR_NATIVE dropbear_get_children(const World* world_ptr, HANDLE entity_id, HANDLE** out_children, size_t* out_count);
-DROPBEAR_NATIVE dropbear_get_child_by_label(const World* world_ptr, HANDLE entity_id, const char* label, HANDLE* out_child);
-DROPBEAR_NATIVE dropbear_get_parent(const World* world_ptr, HANDLE entity_id, HANDLE* out_parent);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_HIERARCHY_H
\ No newline at end of file
diff --git a/headers/components/dropbear_label.h b/headers/components/dropbear_label.h
deleted file mode 100644
index fe09db5..0000000
--- a/headers/components/dropbear_label.h
+++ /dev/null
@@ -1,15 +0,0 @@
-#ifndef DROPBEAR_LABEL_H
-#define DROPBEAR_LABEL_H
-
-#include "../dropbear_common.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-DROPBEAR_NATIVE dropbear_get_entity_name(const World* world_ptr, HANDLE entity_id, char* out_name, size_t max_len);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_LABEL_H
\ No newline at end of file
diff --git a/headers/components/dropbear_meshrenderer.h b/headers/components/dropbear_meshrenderer.h
deleted file mode 100644
index 9ebb2cf..0000000
--- a/headers/components/dropbear_meshrenderer.h
+++ /dev/null
@@ -1,27 +0,0 @@
-#ifndef DROPBEAR_MESHRENDERER_H
-#define DROPBEAR_MESHRENDERER_H
-
-#include "../dropbear_common.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-// model
-DROPBEAR_NATIVE dropbear_get_model(const World* world_ptr, const AssetRegistry* asset_ptr, HANDLE entity_handle, HANDLE* out_model_id);
-DROPBEAR_NATIVE dropbear_set_model(const World* world_ptr, const AssetRegistry* asset_ptr, HANDLE entity_handle, HANDLE model_id);
-DROPBEAR_NATIVE dropbear_is_model_handle(const AssetRegistry* asset_ptr, HANDLE handle, BOOL* out_is_model);
-DROPBEAR_NATIVE dropbear_is_using_model(const World* world_ptr, HANDLE entity_handle, HANDLE model_handle, BOOL* out_is_using);
-
-// textures
-DROPBEAR_NATIVE dropbear_get_texture(const World* world_ptr, const AssetRegistry* asset_ptr, HANDLE entity_handle, const char* name, HANDLE* out_texture_id);
-DROPBEAR_NATIVE dropbear_get_texture_name(const AssetRegistry* asset_ptr, HANDLE texture_handle, const char** out_name);
-DROPBEAR_NATIVE dropbear_set_texture(const World* world_ptr, const AssetRegistry* asset_ptr, HANDLE entity_handle, const char* old_material_name, HANDLE texture_id);
-DROPBEAR_NATIVE dropbear_is_texture_handle(const AssetRegistry* asset_ptr, HANDLE handle, BOOL* out_is_texture);
-DROPBEAR_NATIVE dropbear_is_using_texture(const World* world_ptr, HANDLE entity_handle, HANDLE texture_handle, BOOL* out_is_using);
-DROPBEAR_NATIVE dropbear_get_all_textures(const World* world_ptr, const AssetRegistry* asset_ptr, HANDLE entity_handle, const char*** out_textures, size_t* out_count);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_MESHRENDERER_H
\ No newline at end of file
diff --git a/headers/components/dropbear_properties.h b/headers/components/dropbear_properties.h
deleted file mode 100644
index dc684aa..0000000
--- a/headers/components/dropbear_properties.h
+++ /dev/null
@@ -1,30 +0,0 @@
-#ifndef DROPBEAR_PROPERTIES_H
-#define DROPBEAR_PROPERTIES_H
-
-#include "../dropbear_common.h"
-#include "../dropbear_math.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-// getters
-DROPBEAR_NATIVE dropbear_get_string_property(const World* world_ptr, HANDLE entity_handle, const char* label, const char** out_value);
-DROPBEAR_NATIVE dropbear_get_int_property(const World* world_ptr, HANDLE entity_handle, const char* label, int32_t* out_value);
-DROPBEAR_NATIVE dropbear_get_long_property(const World* world_ptr, HANDLE entity_handle, const char* label, int64_t* out_value);
-DROPBEAR_NATIVE dropbear_get_float_property(const World* world_ptr, HANDLE entity_handle, const char* label, double* out_value);
-DROPBEAR_NATIVE dropbear_get_bool_property(const World* world_ptr, HANDLE entity_handle, const char* label, BOOL* out_value);
-DROPBEAR_NATIVE dropbear_get_vec3_property(const World* world_ptr, HANDLE entity_handle, const char* label, Vector3D* out_value);
-
-// setters
-DROPBEAR_NATIVE dropbear_set_string_property(const World* world_ptr, HANDLE entity_handle, const char* label, const char* value);
-DROPBEAR_NATIVE dropbear_set_int_property(const World* world_ptr, HANDLE entity_handle, const char* label, int32_t value);
-DROPBEAR_NATIVE dropbear_set_long_property(const World* world_ptr, HANDLE entity_handle, const char* label, int64_t value);
-DROPBEAR_NATIVE dropbear_set_float_property(const World* world_ptr, HANDLE entity_handle, const char* label, double value);
-DROPBEAR_NATIVE dropbear_set_bool_property(const World* world_ptr, HANDLE entity_handle, const char* label, BOOL value);
-DROPBEAR_NATIVE dropbear_set_vec3_property(const World* world_ptr, HANDLE entity_handle, const char* label, Vector3D value);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_PROPERTIES_H
\ No newline at end of file
diff --git a/headers/components/dropbear_rigidbody.h b/headers/components/dropbear_rigidbody.h
deleted file mode 100644
index 4554344..0000000
--- a/headers/components/dropbear_rigidbody.h
+++ /dev/null
@@ -1,43 +0,0 @@
-#ifndef DROPBEAR_RIGIDBODY_H
-#define DROPBEAR_RIGIDBODY_H
-
-#include "../dropbear_common.h"
-#include "../dropbear_math.h"
-#include "dropbear_collider.h"
-
-typedef enum {
-    RIGIDBODY_MODE_DYNAMIC,
-    RIGIDBODY_MODE_FIXED,
-    RIGIDBODY_MODE_KINEMATIC_POSITION,
-    RIGIDBODY_MODE_KINEMATIC_VELOCITY,
-} RigidBodyMode;
-
-typedef struct {
-    Index index;
-    HANDLE entity;
-    RigidBodyMode mode;
-    double gravity_scale;
-    bool can_sleep;
-    bool ccd_enabled;
-    Vector3D linear_velocity;
-    Vector3D angualar_velocity;
-    double linear_damping;
-    double angular_damping;
-    AxisLock lock_translation;
-    AxisLock lock_rotation;
-} RigidBody;
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-DROPBEAR_NATIVE dropbear_apply_impulse(PhysicsEngine* physics_engine, Index index, Vector3D impulse);
-DROPBEAR_NATIVE dropbear_apply_torque_impulse(PhysicsEngine* physics_engine, Index index, Vector3D torque_impulse);
-
-DROPBEAR_NATIVE dropbear_set_rigidbody(World* world_handle, PhysicsEngine* physics_engine, RigidBody rigidbody);
-DROPBEAR_NATIVE dropbear_get_child_colliders(World* world_handle, PhysicsEngine* physics_engine, Index parent_index, Collider** out_colliders, unsigned int* out_count);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_RIGIDBODY_H
\ No newline at end of file
diff --git a/headers/dropbear.h b/headers/dropbear.h
index 47c4a38..90080d8 100644
--- a/headers/dropbear.h
+++ b/headers/dropbear.h
@@ -1,38 +1,450 @@
+#ifndef DROPBEAR_H
+#define DROPBEAR_H
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+typedef enum dropbear_ColliderShapeType {
+    Box = 0,
+    Sphere = 1,
+    Capsule = 2,
+    Cylinder = 3,
+    Cone = 4,
+} dropbear_ColliderShapeType;
+
+typedef struct dropbear_CommandBuffer dropbear_CommandBuffer;
+
+/**
+ * Shows the information about the input at that current time.
+ */
+typedef struct dropbear_InputState dropbear_InputState;
+
+/**
+ * A serializable [rapier3d] state that shows all the different actions and types related
+ * to physics rendering.
+ */
+typedef struct dropbear_PhysicsState dropbear_PhysicsState;
+
 /**
- * dropbear-engine native header definitions. Created by tirbofish as part of the dropbear project.
+ * A mutable pointer to a [`World`].
  *
- * Primarily used for Kotlin/Native, however nothing is stopping you from implementing it to your own language.
- * Exports are located at `eucalyptus_core::scripting::native::exports`.
+ * Defined in `dropbear_common.h` as `World`
+ */
+typedef dropbear_World *dropbear_WorldPtr;
+
+typedef struct dropbear_Vector3 {
+    double x;
+    double y;
+    double z;
+} dropbear_Vector3;
+
+/**
+ * A mutable pointer to an [`InputState`].
  *
- * Note: This does not include JNI definitions, only native exports from the eucalyptus-core dynamic library.
- *       For JNI definitions, take a look at `eucalyptus_core::scripting::jni::exports` or even better, take a
- *       look at the JNINative class for all JNI functions that exist.
+ * Defined in `dropbear_common.h` as `InputState`
+ */
+typedef struct dropbear_InputState *dropbear_InputStatePtr;
+
+typedef struct dropbear_Vector2 {
+    double x;
+    double y;
+} dropbear_Vector2;
+
+/**
+ * A non-mutable pointer to a [`crossbeam_channel::Sender`] that sends
+ * [`CommandBuffer`] signals.
  *
- * Warning: This header file is not always up to date with the existing JNI functions (some funcs may not be implemented),
- *          So please open a issue if there is something missing, or help us by creating a PR implementing them.
+ * Defined in `dropbear_common.h` as `CommandBuffer`
+ */
+typedef const dropbear_Sender<dropbear_CommandBuffer> *dropbear_CommandBufferPtr;
+
+/**
+ * A mutable pointer to a [`PhysicsState`].
  *
- * Licensed under MIT or Apache 2.0 depending on your mood.
+ * Defined in `dropbear_common.h` as `PhysicsEngine`
  */
+typedef struct dropbear_PhysicsState *dropbear_PhysicsStatePtr;
 
-#ifndef DROPBEAR_H
-#define DROPBEAR_H
+typedef struct dropbear_IndexNative {
+    uint32_t index;
+    uint32_t generation;
+} dropbear_IndexNative;
+
+typedef struct dropbear_ColliderFFI {
+    struct dropbear_IndexNative index;
+    uint64_t entity_id;
+    uint32_t id;
+} dropbear_ColliderFFI;
+
+typedef struct dropbear_ColliderShapeFFI {
+    enum dropbear_ColliderShapeType shape_type;
+    float radius;
+    float half_height;
+    float half_extents_x;
+    float half_extents_y;
+    float half_extents_z;
+} dropbear_ColliderShapeFFI;
+
+/**
+ * A non-mutable pointer to the [`AssetRegistry`].
+ *
+ * Defined in `dropbear_common.h` as `AssetRegistry`
+ */
+typedef const dropbear_AssetRegistry *dropbear_AssetRegistryPtr;
+
+const uint8_t *get_rustc_version(void);
+
+int32_t dropbear_camera_exists_for_entity(dropbear_WorldPtr world_ptr,
+                                          uint64_t entity_id,
+                                          bool *out_result);
+
+int32_t dropbear_get_camera_eye(dropbear_WorldPtr world_ptr,
+                                uint64_t entity_id,
+                                struct dropbear_Vector3 *out_result);
+
+int32_t dropbear_set_camera_eye(dropbear_WorldPtr world_ptr,
+                                uint64_t entity_id,
+                                struct dropbear_Vector3 value);
+
+int32_t dropbear_get_camera_target(dropbear_WorldPtr world_ptr,
+                                   uint64_t entity_id,
+                                   struct dropbear_Vector3 *out_result);
+
+int32_t dropbear_set_camera_target(dropbear_WorldPtr world_ptr,
+                                   uint64_t entity_id,
+                                   struct dropbear_Vector3 value);
+
+int32_t dropbear_get_camera_up(dropbear_WorldPtr world_ptr,
+                               uint64_t entity_id,
+                               struct dropbear_Vector3 *out_result);
+
+int32_t dropbear_set_camera_up(dropbear_WorldPtr world_ptr,
+                               uint64_t entity_id,
+                               struct dropbear_Vector3 value);
+
+int32_t dropbear_get_camera_aspect(dropbear_WorldPtr world_ptr,
+                                   uint64_t entity_id,
+                                   double *out_result);
+
+int32_t dropbear_get_camera_fov_y(dropbear_WorldPtr world_ptr,
+                                  uint64_t entity_id,
+                                  double *out_result);
+
+int32_t dropbear_set_camera_fov_y(dropbear_WorldPtr world_ptr, uint64_t entity_id, double value);
+
+int32_t dropbear_get_camera_znear(dropbear_WorldPtr world_ptr,
+                                  uint64_t entity_id,
+                                  double *out_result);
+
+int32_t dropbear_set_camera_znear(dropbear_WorldPtr world_ptr, uint64_t entity_id, double value);
+
+int32_t dropbear_get_camera_zfar(dropbear_WorldPtr world_ptr,
+                                 uint64_t entity_id,
+                                 double *out_result);
+
+int32_t dropbear_set_camera_zfar(dropbear_WorldPtr world_ptr, uint64_t entity_id, double value);
+
+int32_t dropbear_get_camera_yaw(dropbear_WorldPtr world_ptr,
+                                uint64_t entity_id,
+                                double *out_result);
+
+int32_t dropbear_set_camera_yaw(dropbear_WorldPtr world_ptr, uint64_t entity_id, double value);
+
+int32_t dropbear_get_camera_pitch(dropbear_WorldPtr world_ptr,
+                                  uint64_t entity_id,
+                                  double *out_result);
+
+int32_t dropbear_set_camera_pitch(dropbear_WorldPtr world_ptr, uint64_t entity_id, double value);
+
+int32_t dropbear_get_camera_speed(dropbear_WorldPtr world_ptr,
+                                  uint64_t entity_id,
+                                  double *out_result);
+
+int32_t dropbear_set_camera_speed(dropbear_WorldPtr world_ptr, uint64_t entity_id, double value);
+
+int32_t dropbear_get_camera_sensitivity(dropbear_WorldPtr world_ptr,
+                                        uint64_t entity_id,
+                                        double *out_result);
+
+int32_t dropbear_set_camera_sensitivity(dropbear_WorldPtr world_ptr,
+                                        uint64_t entity_id,
+                                        double value);
+
+int32_t dropbear_is_gamepad_button_pressed(dropbear_InputStatePtr input_ptr,
+                                           uint64_t gamepad_id,
+                                           int32_t button_ordinal,
+                                           bool *out_result);
+
+int32_t dropbear_get_left_stick_position(dropbear_InputStatePtr input_ptr,
+                                         uint64_t gamepad_id,
+                                         struct dropbear_Vector2 *out_result);
+
+int32_t dropbear_get_right_stick_position(dropbear_InputStatePtr input_ptr,
+                                          uint64_t gamepad_id,
+                                          struct dropbear_Vector2 *out_result);
+
+int32_t dropbear_free_gamepads_array(uint64_t *ptr, uintptr_t count);
+
+int32_t dropbear_print_input_state(dropbear_InputStatePtr input_ptr);
+
+int32_t dropbear_is_key_pressed(dropbear_InputStatePtr input_ptr,
+                                int32_t key_ordinal,
+                                bool *out_result);
+
+int32_t dropbear_get_mouse_position(dropbear_InputStatePtr input_ptr,
+                                    struct dropbear_Vector2 *out_result);
+
+int32_t dropbear_is_mouse_button_pressed(dropbear_InputStatePtr input_ptr,
+                                         int32_t btn_ordinal,
+                                         bool *out_result);
+
+int32_t dropbear_get_mouse_delta(dropbear_InputStatePtr input_ptr,
+                                 struct dropbear_Vector2 *out_result);
+
+int32_t dropbear_is_cursor_locked(dropbear_InputStatePtr input_ptr, bool *out_result);
+
+int32_t dropbear_set_cursor_locked(dropbear_CommandBufferPtr cmd_ptr,
+                                   dropbear_InputStatePtr input_ptr,
+                                   bool locked);
+
+int32_t dropbear_get_last_mouse_pos(dropbear_InputStatePtr input_ptr,
+                                    struct dropbear_Vector2 *out_result);
+
+int32_t dropbear_is_cursor_hidden(dropbear_InputStatePtr input_ptr, bool *out_result);
+
+int32_t dropbear_set_cursor_hidden(dropbear_CommandBufferPtr cmd_ptr,
+                                   dropbear_InputStatePtr input_ptr,
+                                   bool hidden);
+
+int32_t dropbear_get_connected_gamepads(dropbear_InputStatePtr input_ptr,
+                                        uintptr_t *out_count,
+                                        uint64_t **out_result);
+
+int32_t dropbear_free_string(char *ptr);
+
+int32_t dropbear_collider_group_exists_for_entity(dropbear_WorldPtr world_ptr,
+                                                  uint64_t entity_id,
+                                                  bool *out_result);
+
+int32_t dropbear_get_collider_group_colliders(dropbear_WorldPtr world_ptr,
+                                              dropbear_PhysicsStatePtr physics_ptr,
+                                              uint64_t entity_id,
+                                              uintptr_t *out_count,
+                                              struct dropbear_ColliderFFI **out_result);
+
+int32_t dropbear_free_collider_array(struct dropbear_ColliderFFI *ptr, uintptr_t count);
+
+int32_t dropbear_get_collider_shape(dropbear_PhysicsStatePtr physics_ptr,
+                                    struct dropbear_ColliderFFI ffi,
+                                    struct dropbear_ColliderShapeFFI *out_result);
+
+int32_t dropbear_set_collider_shape(dropbear_PhysicsStatePtr physics_ptr,
+                                    struct dropbear_ColliderFFI ffi,
+                                    struct dropbear_ColliderShapeFFI shape);
+
+int32_t dropbear_get_collider_density(dropbear_PhysicsStatePtr physics_ptr,
+                                      struct dropbear_ColliderFFI ffi,
+                                      double *out_result);
+
+int32_t dropbear_set_collider_density(dropbear_PhysicsStatePtr physics_ptr,
+                                      struct dropbear_ColliderFFI ffi,
+                                      double density);
+
+int32_t dropbear_get_collider_friction(dropbear_PhysicsStatePtr physics_ptr,
+                                       struct dropbear_ColliderFFI ffi,
+                                       double *out_result);
+
+int32_t dropbear_set_collider_friction(dropbear_PhysicsStatePtr physics_ptr,
+                                       struct dropbear_ColliderFFI ffi,
+                                       double friction);
+
+int32_t dropbear_get_collider_restitution(dropbear_PhysicsStatePtr physics_ptr,
+                                          struct dropbear_ColliderFFI ffi,
+                                          double *out_result);
+
+int32_t dropbear_set_collider_restitution(dropbear_PhysicsStatePtr physics_ptr,
+                                          struct dropbear_ColliderFFI ffi,
+                                          double restitution);
+
+int32_t dropbear_get_collider_mass(dropbear_PhysicsStatePtr physics_ptr,
+                                   struct dropbear_ColliderFFI ffi,
+                                   double *out_result);
+
+int32_t dropbear_set_collider_mass(dropbear_PhysicsStatePtr physics_ptr,
+                                   struct dropbear_ColliderFFI ffi,
+                                   double mass);
+
+int32_t dropbear_get_collider_is_sensor(dropbear_PhysicsStatePtr physics_ptr,
+                                        struct dropbear_ColliderFFI ffi,
+                                        bool *out_result);
+
+int32_t dropbear_set_collider_is_sensor(dropbear_PhysicsStatePtr physics_ptr,
+                                        struct dropbear_ColliderFFI ffi,
+                                        bool is_sensor);
+
+int32_t dropbear_get_collider_translation(dropbear_PhysicsStatePtr physics_ptr,
+                                          struct dropbear_ColliderFFI ffi,
+                                          struct dropbear_Vector3 *out_result);
+
+int32_t dropbear_set_collider_translation(dropbear_PhysicsStatePtr physics_ptr,
+                                          struct dropbear_ColliderFFI ffi,
+                                          struct dropbear_Vector3 translation);
+
+int32_t dropbear_get_collider_rotation(dropbear_PhysicsStatePtr physics_ptr,
+                                       struct dropbear_ColliderFFI ffi,
+                                       struct dropbear_Vector3 *out_result);
+
+int32_t dropbear_set_collider_rotation(dropbear_PhysicsStatePtr physics_ptr,
+                                       struct dropbear_ColliderFFI ffi,
+                                       struct dropbear_Vector3 rotation);
+
+int32_t dropbear_get_texture_name(dropbear_AssetRegistryPtr asset_registry_ptr,
+                                  uint64_t handle,
+                                  char **out_result);
+
+int32_t dropbear_is_model_handle(dropbear_AssetRegistryPtr asset_registry_ptr,
+                                 uint64_t handle,
+                                 bool *out_result);
+
+int32_t dropbear_is_texture_handle(dropbear_AssetRegistryPtr asset_registry_ptr,
+                                   uint64_t handle,
+                                   bool *out_result);
+
+int32_t dropbear_custom_properties_exists_for_entity(dropbear_WorldPtr world_ptr,
+                                                     uint64_t entity_id,
+                                                     bool *out_result);
+
+int32_t dropbear_get_string_property(dropbear_WorldPtr world_ptr,
+                                     uint64_t entity_id,
+                                     const char *key,
+                                     char **out_result);
+
+int32_t dropbear_get_int_property(dropbear_WorldPtr world_ptr,
+                                  uint64_t entity_id,
+                                  const char *key,
+                                  int32_t *out_result);
+
+int32_t dropbear_get_long_property(dropbear_WorldPtr world_ptr,
+                                   uint64_t entity_id,
+                                   const char *key,
+                                   int64_t *out_result);
+
+int32_t dropbear_get_double_property(dropbear_WorldPtr world_ptr,
+                                     uint64_t entity_id,
+                                     const char *key,
+                                     double *out_result);
+
+int32_t dropbear_get_float_property(dropbear_WorldPtr world_ptr,
+                                    uint64_t entity_id,
+                                    const char *key,
+                                    float *out_result);
+
+int32_t dropbear_get_bool_property(dropbear_WorldPtr world_ptr,
+                                   uint64_t entity_id,
+                                   const char *key,
+                                   bool *out_result);
+
+int32_t dropbear_get_vec3_property(dropbear_WorldPtr world_ptr,
+                                   uint64_t entity_id,
+                                   const char *key,
+                                   struct dropbear_Vector3 *out_result);
+
+int32_t dropbear_set_string_property(dropbear_WorldPtr world_ptr,
+                                     uint64_t entity_id,
+                                     const char *key,
+                                     const char *value);
+
+int32_t dropbear_set_int_property(dropbear_WorldPtr world_ptr,
+                                  uint64_t entity_id,
+                                  const char *key,
+                                  int32_t value);
+
+int32_t dropbear_set_long_property(dropbear_WorldPtr world_ptr,
+                                   uint64_t entity_id,
+                                   const char *key,
+                                   int64_t value);
+
+int32_t dropbear_set_double_property(dropbear_WorldPtr world_ptr,
+                                     uint64_t entity_id,
+                                     const char *key,
+                                     double value);
+
+int32_t dropbear_set_float_property(dropbear_WorldPtr world_ptr,
+                                    uint64_t entity_id,
+                                    const char *key,
+                                    float value);
+
+int32_t dropbear_set_bool_property(dropbear_WorldPtr world_ptr,
+                                   uint64_t entity_id,
+                                   const char *key,
+                                   bool value);
+
+int32_t dropbear_set_vec3_property(dropbear_WorldPtr world_ptr,
+                                   uint64_t entity_id,
+                                   const char *key,
+                                   struct dropbear_Vector3 value);
+
+int32_t dropbear_mesh_renderer_exists_for_entity(dropbear_WorldPtr world_ptr,
+                                                 uint64_t entity_id,
+                                                 bool *out_result);
+
+int32_t dropbear_get_model(dropbear_WorldPtr world_ptr, uint64_t entity_id, uint64_t *out_result);
+
+int32_t dropbear_set_model(dropbear_WorldPtr world_ptr,
+                           dropbear_AssetRegistryPtr asset_ptr,
+                           uint64_t entity_id,
+                           uint64_t model_id);
+
+int32_t dropbear_get_all_texture_ids(dropbear_WorldPtr world_ptr,
+                                     dropbear_AssetRegistryPtr asset_ptr,
+                                     uint64_t entity_id,
+                                     uintptr_t *out_count,
+                                     uint64_t **out_result);
+
+int32_t dropbear_get_texture(dropbear_WorldPtr world_ptr,
+                             dropbear_AssetRegistryPtr asset_ptr,
+                             uint64_t entity_id,
+                             const char *material_name,
+                             uint64_t *out_result);
+
+int32_t dropbear_set_texture_override(dropbear_WorldPtr world_ptr,
+                                      dropbear_AssetRegistryPtr asset_ptr,
+                                      uint64_t entity_id,
+                                      const char *material_name,
+                                      uint64_t texture_handle);
+
+int32_t dropbear_get_entity(dropbear_WorldPtr world_ptr, const char *label, uint64_t *out_result);
+
+int32_t dropbear_get_asset(dropbear_AssetRegistryPtr asset_ptr,
+                           const char *uri,
+                           uint64_t *out_result);
+
+int32_t dropbear_quit(dropbear_CommandBufferPtr command_ptr);
+
+int32_t dropbear_entity_transform_exists_for_entity(dropbear_WorldPtr world_ptr,
+                                                    uint64_t entity_id,
+                                                    bool *out_result);
+
+int32_t dropbear_get_local_transform(dropbear_WorldPtr world_ptr,
+                                     uint64_t entity_id,
+                                     dropbear_Transform *out_result);
+
+int32_t dropbear_set_local_transform(dropbear_WorldPtr world_ptr,
+                                     uint64_t entity_id,
+                                     dropbear_Transform value);
+
+int32_t dropbear_get_world_transform(dropbear_WorldPtr world_ptr,
+                                     uint64_t entity_id,
+                                     dropbear_Transform *out_result);
+
+int32_t dropbear_set_world_transform(dropbear_WorldPtr world_ptr,
+                                     uint64_t entity_id,
+                                     dropbear_Transform value);
+
+int32_t dropbear_propagate_transform(dropbear_WorldPtr world_ptr,
+                                     uint64_t entity_id,
+                                     dropbear_Transform *out_result);
 
-#include "dropbear_common.h"
-#include "dropbear_math.h"
-#include "dropbear_utils.h"
-
-#include "dropbear_input.h"
-#include "dropbear_scene.h"
-#include "dropbear_engine.h"
-#include "dropbear_physics.h"
-
-#include "components/dropbear_camera.h"
-#include "components/dropbear_entitytransform.h"
-#include "components/dropbear_hierarchy.h"
-#include "components/dropbear_label.h"
-#include "components/dropbear_meshrenderer.h"
-#include "components/dropbear_properties.h"
-#include "components/dropbear_collider.h"
-#include "components/dropbear_rigidbody.h"
-
-#endif // DROPBEAR_H
\ No newline at end of file
+#endif  /* DROPBEAR_H */
diff --git a/headers/dropbear_common.h b/headers/dropbear_common.h
deleted file mode 100644
index c107e89..0000000
--- a/headers/dropbear_common.h
+++ /dev/null
@@ -1,56 +0,0 @@
-#ifndef DROPBEAR_COMMON_H
-#define DROPBEAR_COMMON_H
-
-#include <stddef.h>
-#include <stdint.h>
-#include <stdbool.h>
-
-/**
-* @brief Return function for a DropbearNativeError. 0 on success, otherwise look
-*        at `eucalyptus_core::scripting::native::DropbearNativeError`
-*/
-#define DROPBEAR_NATIVE int
-
-/**
-* @brief The handle/id of an object, as a long.
-*
-* Kotlin/Native requires
-* this to have an int64_t as a Long (or use a long long).
-*/
-#define HANDLE int64_t
-
-/**
-* @brief A helper type that defines a value that can either be a 0 or 1
-*/
-#define BOOL int
-
-typedef struct World World; // opaque pointer
-typedef struct InputState InputState; // opaque pointer
-typedef struct CommandBuffer CommandBuffer; // opaque pointer
-typedef struct AssetRegistry AssetRegistry; // opaque pointer
-typedef struct SceneLoader SceneLoader; // opaque pointer
-typedef struct PhysicsEngine PhysicsEngine; // opaque pointer
-
-/// Describes all the different pointers that can be passed into a scripting
-/// module.
-typedef struct {
-    World* world;
-    InputState* input;
-    CommandBuffer* graphics;
-    AssetRegistry* assets;
-    SceneLoader* scene_loader;
-    PhysicsEngine* physics_engine;
-} DropbearContext;
-
-typedef struct {
-    unsigned int index;
-    unsigned int generation;
-} Index;
-
-typedef struct {
-    bool x;
-    bool y;
-    bool z;
-} AxisLock;
-
-#endif // DROPBEAR_COMMON_H
\ No newline at end of file
diff --git a/headers/dropbear_engine.h b/headers/dropbear_engine.h
deleted file mode 100644
index c1b8563..0000000
--- a/headers/dropbear_engine.h
+++ /dev/null
@@ -1,43 +0,0 @@
-#ifndef DROPBEAR_ENGINE_H
-#define DROPBEAR_ENGINE_H
-
-#include "dropbear_common.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-/**
- * @brief Fetches an entity from the world/current scene by its label.
- *
- * Returns the entity's id.
- */
-DROPBEAR_NATIVE dropbear_get_entity(const char* label, const World* world_ptr, int64_t* out_entity);
-
-/**
- * @brief Fetches an asset from the asset registry as by its name.
- *
- * Returns the asset's handle.
- */
-DROPBEAR_NATIVE dropbear_get_asset(const AssetRegistry* asset_ptr, const char* label, HANDLE* out_asset_id);
-
-/**
- * @brief Quits the currently running app or game.
- *
- * Does not return anything. If any issues occur, the program will terminate.
- *
- * @note Behaviours:
- * - eucalyptus-editor: When called, this exits your Play Mode session and brings
- *                      you back to EditorState::Editing
- * - redback-runtime: When called, this will exit your current process and kill
- *                    the app as is. It will also drop any pointers and do any
- *                    additional clean-up.
- *
- * @return void
- */
-void dropbear_quit(const CommandBuffer* command_ptr);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_ENGINE_H
\ No newline at end of file
diff --git a/headers/dropbear_input.h b/headers/dropbear_input.h
deleted file mode 100644
index 09633cf..0000000
--- a/headers/dropbear_input.h
+++ /dev/null
@@ -1,87 +0,0 @@
-#ifndef DROPBEAR_INPUT_H
-#define DROPBEAR_INPUT_H
-
-#include "dropbear_common.h"
-#include "dropbear_math.h"
-
-/**
- * @brief A struct that represents an external input device in the shape of a controller.
- */
-typedef struct {
-    int id;
-    Vector2D left_stick_pos;
-    Vector2D right_stick_pos;
-} Gamepad;
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-/**
- * @brief Prints the input state to the console. Does not return anything, failure does not do anything.
- *
- * Can be useful for debugging.
- */
-DROPBEAR_NATIVE dropbear_print_input_state(const InputState* input_ptr);
-
-/**
- * @brief Checks if a key is currently pressed. If pressed, returns 1, otherwise 0.
- */
-DROPBEAR_NATIVE dropbear_is_key_pressed(const InputState* input_ptr, int32_t key_ordinal, BOOL* out_pressed);
-
-/**
- * @brief Fetches the current mouse position for that frame.
- */
-DROPBEAR_NATIVE dropbear_get_mouse_position(const InputState* input_ptr, float* out_x, float* out_y);
-
-/**
- * @brief Checks if a mouse button is currently pressed. If pressed, returns 1, otherwise 0.
- */
-DROPBEAR_NATIVE dropbear_is_mouse_button_pressed(const InputState* input_ptr, int32_t button_ordinal, BOOL* out_pressed);
-
-/**
- * @brief Fetches the delta of the mouse position since the last frame.
- */
-DROPBEAR_NATIVE dropbear_get_mouse_delta(const InputState* input_ptr, float* out_dx, float* out_dy);
-
-/**
- * @brief Checks if the cursor is currently locked. If locked, returns 1, otherwise 0.
- */
-DROPBEAR_NATIVE dropbear_is_cursor_locked(const InputState* input_ptr, BOOL* out_locked);
-
-/**
- * @brief Sets the mouse cursor to be locked or unlocked.
- */
-DROPBEAR_NATIVE dropbear_set_cursor_locked(InputState* input_ptr, CommandBuffer* graphics_ptr, BOOL locked);
-
-/**
- * @brief Fetches the mouse position of the previous frame.
- *
- * Can be used to calculate the delta of the mouse position.
- */
-DROPBEAR_NATIVE dropbear_get_last_mouse_pos(const InputState* input_ptr, float* out_x, float* out_y);
-
-/**
- * @brief Checks if the cursor is currently hidden. If hidden, returns 1, otherwise 0.
- */
-DROPBEAR_NATIVE dropbear_is_cursor_hidden(const InputState* input_ptr, BOOL* out_hidden);
-
-/**
- * @brief Sets the cursor to either hidden (invisible) or visible
- */
-DROPBEAR_NATIVE dropbear_set_cursor_hidden(InputState* input_ptr, CommandBuffer* graphics_ptr, BOOL hidden);
-
-/**
- * @brief Fetches all available connected gamepads in the input state.
- */
-DROPBEAR_NATIVE dropbear_get_connected_gamepads(InputState* input_ptr, const Gamepad** out_gamepads, int32_t* out_count);
-
-/**
- * @brief Checks if a button has been pressed on a specific gamepad.
- */
-DROPBEAR_NATIVE dropbear_is_gamepad_button_pressed(const InputState* input_ptr, HANDLE gamepad_id, int ordinal, BOOL* out_pressed);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_INPUT_H
\ No newline at end of file
diff --git a/headers/dropbear_math.h b/headers/dropbear_math.h
deleted file mode 100644
index d4cf5aa..0000000
--- a/headers/dropbear_math.h
+++ /dev/null
@@ -1,54 +0,0 @@
-#ifndef DROPBEAR_MATH_H
-#define DROPBEAR_MATH_H
-
-#include "dropbear_common.h"
-
-/**
- * @brief Represents a `glam::DVec2` in a C struct form.
- */
-typedef struct {
-    double x;
-    double y;
-} Vector2D;
-
-/**
- * @brief Represents a `glam::DVec3` in a C struct form.
- */
-typedef struct {
-    double x;
-    double y;
-    double z;
-} Vector3D;
-
-/**
- * @brief Represents a `dropbear_engine::entity::Transform` in a C struct form.
- */
-typedef struct {
-    double position_x;
-    double position_y;
-    double position_z;
-    double rotation_x;
-    double rotation_y;
-    double rotation_z;
-    double rotation_w;
-    double scale_x;
-    double scale_y;
-    double scale_z;
-} NativeTransform;
-
-/**
- * @brief Represents an `dropbear_engine::entity::EntityTransform` in a C struct form.
- */
-typedef struct {
-    NativeTransform local;
-    NativeTransform world;
-} NativeEntityTransform;
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_MATH_H
\ No newline at end of file
diff --git a/headers/dropbear_physics.h b/headers/dropbear_physics.h
deleted file mode 100644
index 6f9b9ad..0000000
--- a/headers/dropbear_physics.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#ifndef DROPBEAR_PHYSICS_H
-#define DROPBEAR_PHYSICS_H
-
-#include "dropbear_common.h"
-#include "dropbear_math.h"
-#include "components/dropbear_rigidbody.h"
-#include "components/dropbear_collider.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-DROPBEAR_NATIVE dropbear_set_physics_enabled(World* world_handle, PhysicsEngine* physics_handle, HANDLE entity_handle, bool enabled);
-DROPBEAR_NATIVE dropbear_is_physics_enabled(World* world_handle, PhysicsEngine* physics_handle, HANDLE entity_handle, bool* out_enabled);
-DROPBEAR_NATIVE dropbear_get_rigidbody(World* world_handle, PhysicsEngine* physics_handle, HANDLE entity_handle, RigidBody* out_rigidbody);
-DROPBEAR_NATIVE dropbear_get_all_colliders(World* world_handle, PhysicsEngine* physics_handle, HANDLE entity_handle, Collider** out_colliders, unsigned int* out_count);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_PHYSICS_H
\ No newline at end of file
diff --git a/headers/dropbear_scene.h b/headers/dropbear_scene.h
deleted file mode 100644
index e107247..0000000
--- a/headers/dropbear_scene.h
+++ /dev/null
@@ -1,82 +0,0 @@
-#ifndef DROPBEAR_SCENE_H
-#define DROPBEAR_SCENE_H
-
-#include "dropbear_common.h"
-#include "dropbear_utils.h"
-
-/**
- * @brief The sister to `eucalyptus_core::scene::loading::SceneLoadResult`, which provides C-compatible enum values.
- */
-typedef enum {
-    SCENE_LOAD_PENDING,
-    SCENE_LOAD_SUCCESS,
-    SCENE_LOAD_ERROR
-} SceneLoadResult;
-
-/**
- * @brief The sister handle to `eucalyptus_core::scene::loading::SceneLoadHandle`, which provides C-compatible values.
- */
-typedef struct {
-    HANDLE id;
-    const char* name;
-} SceneLoadHandle;
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-/**
- * @brief Loads a scene asynchronously.
- *
- * Returns a handle (in the form of an integer)
- * to the scene load operation.
- */
-DROPBEAR_NATIVE dropbear_load_scene_async_1(const CommandBuffer* command_ptr, const SceneLoader* scene_loader_ptr, const char* name, SceneLoadHandle* sceneLoadHandle);
-
-/**
- * @brief Loads a scene asynchronously. Allows you to include a loading_scene_name,
- * which will be displayed while the scene is loading.
- *
- * Returns a handle (in the form of an integer)
- * to the scene load operation.
- */
-DROPBEAR_NATIVE dropbear_load_scene_async_2(const CommandBuffer* command_ptr, const SceneLoader* scene_loader_ptr, const char* name, const char* loadingScene, SceneLoadHandle* sceneLoadHandle);
-
-/**
- * @brief Switches to a scene asynchronously.
- *
- * This must be run after you initialise the scene loading (using `dropbear_load_scene_async_1`
- * or `dropbear_load_scene_async_2`). If this function is called before you have checked the progress
- * (with the `dropbear_get_scene_load_status` function), it will return `-10` or `DropbearNativeError::PrematureSceneSwitch`.
- */
-DROPBEAR_NATIVE dropbear_switch_to_scene_async(const CommandBuffer* command_ptr, SceneLoadHandle handle);
-
-/**
- * @brief Switches to a scene immediately.
- *
- *
- *
- * This will block your main thread and freeze the window. It will be extremely inconvenient for
- * your players, and is recommended to use `dropbear_load_scene_async_1` or
- * `dropbear_load_scene_async_2`.
- */
-DROPBEAR_NATIVE dropbear_switch_to_scene_immediate(const CommandBuffer* command_ptr, const char* name);
-
-/**
- * @brief Gets the progress of a scene load operation.
- *
- * Returns a `Progress` and a `DropbearNativeReturn`
- */
-DROPBEAR_NATIVE dropbear_get_scene_load_progress(const SceneLoader* scene_loader_ptr, SceneLoadHandle handle, Progress* progress);
-
-/**
- * @brief Gets the status of a scene load operation
- *
- * Returns a `SceneLoadResult` and a `DropbearNativeReturn`
- */
-DROPBEAR_NATIVE dropbear_get_scene_load_status(const SceneLoader* scene_loader_ptr, SceneLoadHandle handle, SceneLoadResult* result);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_SCENE_H
\ No newline at end of file
diff --git a/headers/dropbear_utils.h b/headers/dropbear_utils.h
deleted file mode 100644
index 6ab2f94..0000000
--- a/headers/dropbear_utils.h
+++ /dev/null
@@ -1,23 +0,0 @@
-#ifndef DROPBEAR_UTILS_H
-#define DROPBEAR_UTILS_H
-
-#include "dropbear_common.h"
-
-/**
- * @brief The sister to [`eucalyptus_core::utils::Progress`], which provides C-compatible values.
- */
-typedef struct {
-    double current;
-    double total;
-    char* message;
-} Progress;
-
-#ifdef __cplusplus
-extern "C" {
-#endif // __cplusplus
-
-
-#ifdef __cplusplus
-} // extern "C"
-#endif // __cplusplus
-#endif // DROPBEAR_UTILS_H
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/EntityRef.kt b/src/commonMain/kotlin/com/dropbear/EntityRef.kt
index ee67dfa..6e6e3e9 100644
--- a/src/commonMain/kotlin/com/dropbear/EntityRef.kt
+++ b/src/commonMain/kotlin/com/dropbear/EntityRef.kt
@@ -23,7 +23,7 @@ class EntityRef(val id: EntityId = EntityId(0L)) {
      * to break this. Anyhow, it will throw an [Exception].
      */
     val label: String
-        get() = getEntityLabel(id) ?: throw Exception("Entity has no label, expected to contain")
+        get() = getEntityLabel(id)
 
     override fun toString(): String {
         return "EntityRef(id=$id)"
@@ -80,7 +80,7 @@ class EntityRef(val id: EntityId = EntityId(0L)) {
     }
 }
 
-expect fun EntityRef.getEntityLabel(entity: EntityId): String?
+expect fun EntityRef.getEntityLabel(entity: EntityId): String
 expect fun EntityRef.getChildren(entityId: EntityId): Array<EntityRef>?
 expect fun EntityRef.getChildByLabel(entityId: EntityId, label: String): EntityRef?
 expect fun EntityRef.getParent(entityId: EntityId): EntityRef?
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/components/EntityTransform.kt b/src/commonMain/kotlin/com/dropbear/components/EntityTransform.kt
index 3bfb6dd..4d010e2 100644
--- a/src/commonMain/kotlin/com/dropbear/components/EntityTransform.kt
+++ b/src/commonMain/kotlin/com/dropbear/components/EntityTransform.kt
@@ -24,7 +24,7 @@ class EntityTransform(val id: EntityId): Component(id, "EntityTransform") {
      * Walks up the world hierarchy to find the transform of the parent, then multiply/add
      * to create a propagated [Transform].
      */
-    fun propagate(): Transform {
+    fun propagate(): Transform? {
         return propagateTransform(id)
     }
 
@@ -55,6 +55,6 @@ expect fun EntityTransform.getLocalTransform(entityId: EntityId): Transform
 expect fun EntityTransform.setLocalTransform(entityId: EntityId, transform: Transform)
 expect fun EntityTransform.getWorldTransform(entityId: EntityId): Transform
 expect fun EntityTransform.setWorldTransform(entityId: EntityId, transform: Transform)
-expect fun EntityTransform.propagateTransform(entityId: EntityId): Transform
+expect fun EntityTransform.propagateTransform(entityId: EntityId): Transform?
 
 expect fun entityTransformExistsForEntity(entityId: EntityId): Boolean
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/components/MeshRenderer.kt b/src/commonMain/kotlin/com/dropbear/components/MeshRenderer.kt
index 1b826e4..93af8a3 100644
--- a/src/commonMain/kotlin/com/dropbear/components/MeshRenderer.kt
+++ b/src/commonMain/kotlin/com/dropbear/components/MeshRenderer.kt
@@ -38,7 +38,7 @@ class MeshRenderer(val id: EntityId) : Component(id, "MeshRenderer") {
      */
     fun getTexture(materialName: String): TextureHandle? {
         val rawId = getTexture(id, materialName)
-        return if (rawId == 0L) null else TextureHandle(rawId)
+        return if (rawId == 0L || rawId == null) null else TextureHandle(rawId)
     }
 
     /**
@@ -58,7 +58,7 @@ class MeshRenderer(val id: EntityId) : Component(id, "MeshRenderer") {
 expect fun MeshRenderer.getModel(id: EntityId): ModelHandle?
 expect fun MeshRenderer.setModel(id: EntityId, model: ModelHandle?)
 expect fun MeshRenderer.getAllTextureIds(id: EntityId): List<TextureHandle>?
-expect fun MeshRenderer.getTexture(id: EntityId, materialName: String): Long
+expect fun MeshRenderer.getTexture(id: EntityId, materialName: String): Long?
 expect fun MeshRenderer.setTextureOverride(id: EntityId, materialName: String, textureHandle: Long)
 
 expect fun meshRendererExistsForEntity(entityId: EntityId): Boolean
diff --git a/src/commonMain/kotlin/com/dropbear/math/Transform.kt b/src/commonMain/kotlin/com/dropbear/math/Transform.kt
index 052889e..5cb0d7c 100644
--- a/src/commonMain/kotlin/com/dropbear/math/Transform.kt
+++ b/src/commonMain/kotlin/com/dropbear/math/Transform.kt
@@ -9,6 +9,16 @@ class Transform(
     var rotation: Quaterniond,
     var scale: Vector3d
 ) {
+    companion object {
+        fun identity(): Transform {
+            return Transform(
+                Vector3d.zero(),
+                Quaterniond.identity(),
+                Vector3d.one(),
+            )
+        }
+    }
+
     /**
      * Specific constructor for the individual raw primitive values.
      *
diff --git a/src/jvmMain/kotlin/com/dropbear/DropbearEngine.jvm.kt b/src/jvmMain/kotlin/com/dropbear/DropbearEngine.jvm.kt
index 53751cf..e9774cb 100644
--- a/src/jvmMain/kotlin/com/dropbear/DropbearEngine.jvm.kt
+++ b/src/jvmMain/kotlin/com/dropbear/DropbearEngine.jvm.kt
@@ -7,7 +7,7 @@ actual fun getEntity(label: String): Long? {
 }
 
 actual fun getAsset(eucaURI: String): Long? {
-    return DropbearEngineNative.getAsset(DropbearEngine.native.worldHandle, eucaURI)
+    return DropbearEngineNative.getAsset(DropbearEngine.native.assetHandle, eucaURI)
 }
 
 actual fun quit() {
diff --git a/src/jvmMain/kotlin/com/dropbear/DropbearEngineNative.java b/src/jvmMain/kotlin/com/dropbear/DropbearEngineNative.java
index 1501d04..3068b19 100644
--- a/src/jvmMain/kotlin/com/dropbear/DropbearEngineNative.java
+++ b/src/jvmMain/kotlin/com/dropbear/DropbearEngineNative.java
@@ -6,6 +6,6 @@ public class DropbearEngineNative {
     }
 
     public static native Long getEntity(long worldPtr, String label);
-    public static native Long getAsset(long worldPtr, String label);
+    public static native Long getAsset(long assetHandle, String label);
     public static native void quit(long commandBufferPtr);
 }
diff --git a/src/jvmMain/kotlin/com/dropbear/EntityRef.jvm.kt b/src/jvmMain/kotlin/com/dropbear/EntityRef.jvm.kt
index a607a2c..f7e1970 100644
--- a/src/jvmMain/kotlin/com/dropbear/EntityRef.jvm.kt
+++ b/src/jvmMain/kotlin/com/dropbear/EntityRef.jvm.kt
@@ -1,20 +1,32 @@
 package com.dropbear
 
-actual fun EntityRef.getEntityLabel(entity: EntityId): String? {
-    TODO("Not yet implemented")
+actual fun EntityRef.getEntityLabel(entity: EntityId): String {
+    return EntityRefNative.getEntityLabel(DropbearEngine.native.worldHandle, entity.raw)
+        ?: throw RuntimeException("All entities are expected to contain a \"Label\" component. If not, its an engine bug or you messed around with the scene config...")
 }
 
 actual fun EntityRef.getChildren(entityId: EntityId): Array<EntityRef>? {
-    TODO("Not yet implemented")
+    return EntityRefNative.getChildren(DropbearEngine.native.worldHandle, entityId.raw)
+        .map { EntityRef(EntityId(it)) }.toList().toTypedArray()
 }
 
 actual fun EntityRef.getChildByLabel(
     entityId: EntityId,
     label: String
 ): EntityRef? {
-    TODO("Not yet implemented")
+    val result = EntityRefNative.getChildByLabel(DropbearEngine.native.worldHandle, entityId.raw, label)
+    return if (result != null) {
+        EntityRef(EntityId(result))
+    } else {
+        null
+    }
 }
 
 actual fun EntityRef.getParent(entityId: EntityId): EntityRef? {
-    TODO("Not yet implemented")
+    val result = EntityRefNative.getParent(DropbearEngine.native.worldHandle, entityId.raw)
+    return if (result != null) {
+        EntityRef(EntityId(result))
+    } else {
+        null
+    }
 }
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/EntityRefNative.java b/src/jvmMain/kotlin/com/dropbear/EntityRefNative.java
index 6e2a124..9c79135 100644
--- a/src/jvmMain/kotlin/com/dropbear/EntityRefNative.java
+++ b/src/jvmMain/kotlin/com/dropbear/EntityRefNative.java
@@ -4,4 +4,9 @@ public class EntityRefNative {
     static {
         new EucalyptusCoreLoader().ensureLoaded();
     }
+
+    public static native String getEntityLabel(long worldPtr, long entityId);
+    public static native long[] getChildren(long worldPtr, long entityId);
+    public static native Long getChildByLabel(long worldPtr, long entityId, String label);
+    public static native Long getParent(long worldPtr, long entityId);
 }
diff --git a/src/jvmMain/kotlin/com/dropbear/components/EntityTransform.jvm.kt b/src/jvmMain/kotlin/com/dropbear/components/EntityTransform.jvm.kt
index ae1912d..9f429a3 100644
--- a/src/jvmMain/kotlin/com/dropbear/components/EntityTransform.jvm.kt
+++ b/src/jvmMain/kotlin/com/dropbear/components/EntityTransform.jvm.kt
@@ -10,6 +10,7 @@ actual fun entityTransformExistsForEntity(entityId: EntityId): Boolean {
 
 actual fun EntityTransform.getLocalTransform(entityId: EntityId): Transform {
     return EntityTransformNative.getLocalTransform(DropbearEngine.native.worldHandle, entityId.raw)
+        ?: Transform.identity()
 }
 
 actual fun EntityTransform.setLocalTransform(
@@ -24,7 +25,7 @@ actual fun EntityTransform.setLocalTransform(
 }
 
 actual fun EntityTransform.getWorldTransform(entityId: EntityId): Transform {
-    return EntityTransformNative.getWorldTransform(DropbearEngine.native.worldHandle, entityId.raw)
+    return EntityTransformNative.getWorldTransform(DropbearEngine.native.worldHandle, entityId.raw) ?: Transform.identity()
 }
 
 actual fun EntityTransform.setWorldTransform(
@@ -38,6 +39,6 @@ actual fun EntityTransform.setWorldTransform(
     )
 }
 
-actual fun EntityTransform.propagateTransform(entityId: EntityId): Transform {
+actual fun EntityTransform.propagateTransform(entityId: EntityId): Transform? {
     return EntityTransformNative.propagateTransform(DropbearEngine.native.worldHandle, entityId.raw)
 }
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/components/MeshRenderer.jvm.kt b/src/jvmMain/kotlin/com/dropbear/components/MeshRenderer.jvm.kt
index 20427f0..3058546 100644
--- a/src/jvmMain/kotlin/com/dropbear/components/MeshRenderer.jvm.kt
+++ b/src/jvmMain/kotlin/com/dropbear/components/MeshRenderer.jvm.kt
@@ -11,12 +11,12 @@ actual fun MeshRenderer.getModel(id: EntityId): ModelHandle? {
 
 actual fun MeshRenderer.setModel(id: EntityId, model: ModelHandle?) {
     if (model == null) {
-        MeshRendererNative.setModel(DropbearEngine.native.worldHandle, id.raw, 0L)
-        return
+        throw IllegalArgumentException("ModelHandle cannot be null")
     }
 
     return MeshRendererNative.setModel(
         DropbearEngine.native.worldHandle,
+        DropbearEngine.native.assetHandle,
         id.raw,
         model.raw()
     )
@@ -25,15 +25,17 @@ actual fun MeshRenderer.setModel(id: EntityId, model: ModelHandle?) {
 actual fun MeshRenderer.getAllTextureIds(id: EntityId): List<TextureHandle>? {
     val textureHandles = MeshRendererNative.getAllTextureIds(
         DropbearEngine.native.worldHandle,
+        DropbearEngine.native.assetHandle,
         id.raw
     ) ?: return null
 
     return textureHandles.map { TextureHandle(it) }
 }
 
-actual fun MeshRenderer.getTexture(id: EntityId, materialName: String): Long {
+actual fun MeshRenderer.getTexture(id: EntityId, materialName: String): Long? {
     return MeshRendererNative.getTexture(
         DropbearEngine.native.worldHandle,
+        DropbearEngine.native.assetHandle,
         id.raw,
         materialName
     )
@@ -46,6 +48,7 @@ actual fun MeshRenderer.setTextureOverride(
 ) {
     return MeshRendererNative.setTextureOverride(
         DropbearEngine.native.worldHandle,
+        DropbearEngine.native.assetHandle,
         id.raw,
         materialName,
         textureHandle
diff --git a/src/jvmMain/kotlin/com/dropbear/components/MeshRendererNative.java b/src/jvmMain/kotlin/com/dropbear/components/MeshRendererNative.java
index 2918d24..4bc8052 100644
--- a/src/jvmMain/kotlin/com/dropbear/components/MeshRendererNative.java
+++ b/src/jvmMain/kotlin/com/dropbear/components/MeshRendererNative.java
@@ -10,8 +10,8 @@ public class MeshRendererNative {
     public static native boolean meshRendererExistsForEntity(long worldHandle, long entityId);
 
     public static native long getModel(long worldHandle, long entityId);
-    public static native void setModel(long worldHandle, long entityId, long modelHandle);
-    public static native long[] getAllTextureIds(long worldHandle, long entityId);
-    public static native long getTexture(long worldHandle, long entityId, String materialName);
-    public static native void setTextureOverride(long worldHandle, long entityId, String materialName, long textureHandle);
+    public static native void setModel(long worldHandle, long assetRegistryHandle, long entityId, long modelHandle);
+    public static native long[] getAllTextureIds(long worldHandle, long assetRegistryHandle, long entityId);
+    public static native Long getTexture(long worldHandle, long assetRegistryHandle, long entityId, String materialName);
+    public static native void setTextureOverride(long worldHandle, long assetRegistryHandle, long entityId, String meshName, long textureHandle);
 }
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/com/dropbear/physics/ColliderGroup.jvm.kt b/src/jvmMain/kotlin/com/dropbear/physics/ColliderGroup.jvm.kt
index 2f74089..ef22fb9 100644
--- a/src/jvmMain/kotlin/com/dropbear/physics/ColliderGroup.jvm.kt
+++ b/src/jvmMain/kotlin/com/dropbear/physics/ColliderGroup.jvm.kt
@@ -4,7 +4,7 @@ import com.dropbear.DropbearEngine
 import com.dropbear.EntityId
 
 actual fun ColliderGroup.getColliderGroupColliders(colliderGroup: ColliderGroup): List<Collider> {
-    return ColliderGroupNative.getColliderGroupColliders(DropbearEngine.native.physicsEngineHandle, colliderGroup).toList()
+    return ColliderGroupNative.getColliderGroupColliders(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, colliderGroup.parentEntity.raw).toList()
 }
 
 actual fun colliderGroupExistsForEntity(entityId: EntityId): Boolean {
diff --git a/src/jvmMain/kotlin/com/dropbear/physics/ColliderGroupNative.java b/src/jvmMain/kotlin/com/dropbear/physics/ColliderGroupNative.java
index 40b10f6..faa5998 100644
--- a/src/jvmMain/kotlin/com/dropbear/physics/ColliderGroupNative.java
+++ b/src/jvmMain/kotlin/com/dropbear/physics/ColliderGroupNative.java
@@ -7,6 +7,6 @@ public class ColliderGroupNative {
         new EucalyptusCoreLoader().ensureLoaded();
     }
 
-    public static native Collider[] getColliderGroupColliders(long physicsPtr, ColliderGroup colliderGroup);
+    public static native Collider[] getColliderGroupColliders(long worldPtr, long physicsPtr, long entityId);
     public static native boolean colliderGroupExistsForEntity(long worldPtr, long entityId);
 }
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/EntityRef.native.kt b/src/nativeMain/kotlin/com/dropbear/EntityRef.native.kt
index a607a2c..a0db775 100644
--- a/src/nativeMain/kotlin/com/dropbear/EntityRef.native.kt
+++ b/src/nativeMain/kotlin/com/dropbear/EntityRef.native.kt
@@ -1,6 +1,6 @@
 package com.dropbear
 
-actual fun EntityRef.getEntityLabel(entity: EntityId): String? {
+actual fun EntityRef.getEntityLabel(entity: EntityId): String {
     TODO("Not yet implemented")
 }
 
diff --git a/src/nativeMain/kotlin/com/dropbear/components/EntityTransform.native.kt b/src/nativeMain/kotlin/com/dropbear/components/EntityTransform.native.kt
index d455eb1..a796e97 100644
--- a/src/nativeMain/kotlin/com/dropbear/components/EntityTransform.native.kt
+++ b/src/nativeMain/kotlin/com/dropbear/components/EntityTransform.native.kt
@@ -23,7 +23,7 @@ actual fun EntityTransform.setWorldTransform(
 ) {
 }
 
-actual fun EntityTransform.propagateTransform(entityId: EntityId): Transform {
+actual fun EntityTransform.propagateTransform(entityId: EntityId): Transform? {
     TODO("Not yet implemented")
 }
 
diff --git a/src/nativeMain/kotlin/com/dropbear/components/MeshRenderer.native.kt b/src/nativeMain/kotlin/com/dropbear/components/MeshRenderer.native.kt
index 4803fb3..4525c23 100644
--- a/src/nativeMain/kotlin/com/dropbear/components/MeshRenderer.native.kt
+++ b/src/nativeMain/kotlin/com/dropbear/components/MeshRenderer.native.kt
@@ -15,7 +15,7 @@ actual fun MeshRenderer.getAllTextureIds(id: EntityId): List<TextureHandle>? {
     TODO("Not yet implemented")
 }
 
-actual fun MeshRenderer.getTexture(id: EntityId, materialName: String): Long {
+actual fun MeshRenderer.getTexture(id: EntityId, materialName: String): Long? {
     TODO("Not yet implemented")
 }
 
diff --git a/src/quick.py b/src/quick.py
new file mode 100644
index 0000000..78c8ac2
--- /dev/null
+++ b/src/quick.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+import subprocess
+import sys
+from pathlib import Path
+
+def main():
+    # Get the script directory and workspace root
+    script_dir = Path(__file__).parent.resolve()
+    root_dir = script_dir.parent if script_dir.name == "scripts" else script_dir
+
+    print("Expanding macros for eucalyptus-core...")
+
+    # Run cargo expand
+    result = subprocess.run(
+        ["cargo", "expand", "--lib", "-p", "eucalyptus-core"],
+        cwd=root_dir,
+        capture_output=True,
+        text=True
+    )
+
+    if result.returncode != 0:
+        print(f"Error: cargo expand failed", file=sys.stderr)
+        print(result.stderr, file=sys.stderr)
+        sys.exit(1)
+
+    # The expanded code is in stdout
+    expanded_code = result.stdout
+
+    # Write expanded code to temp file
+    temp_dir = root_dir / "target" / "generated"
+    temp_dir.mkdir(parents=True, exist_ok=True)
+    temp_file = temp_dir / "expanded.rs"
+
+    print(f"Writing expanded code to {temp_file}")
+    temp_file.write_text(expanded_code, encoding="utf-8")
+
+    # Run cbindgen
+    print("Generating C bindings...")
+    output_file = root_dir / "headers" / "dropbear.h"
+    output_file.parent.mkdir(parents=True, exist_ok=True)
+
+    cbindgen_config = root_dir / "cbindgen.toml"
+
+    result = subprocess.run(
+        [
+            "cbindgen",
+            "--config", str(cbindgen_config),
+            "--output", str(output_file),
+            str(temp_file)
+        ],
+        cwd=root_dir,
+        capture_output=True,
+        text=True
+    )
+
+    if result.returncode != 0:
+        print(f"Error: cbindgen failed", file=sys.stderr)
+        print(result.stderr, file=sys.stderr)
+        sys.exit(1)
+
+    print(f"✓ Generated {output_file}")
+
+if __name__ == "__main__":
+    main()
\ No newline at end of file