tirbofish/dropbear · commit
e99c89b5b503ae1c1b761123c882ff474e79bca6
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
Signature present but could not be verified.
Unverified
@@ -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" @@ -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}") } } } @@ -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"] @@ -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, @@ -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 +} @@ -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 @@ -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(()) } @@ -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) + } +} @@ -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 + } +} @@ -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) + } + } +} @@ -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) + } +} @@ -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 { + +} @@ -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) + } +} @@ -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(()) + } +} @@ -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. /// @@ -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) + } + } +} @@ -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); @@ -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) } } } @@ -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(()) + } +} @@ -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 { + } @@ -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) + } + } +} @@ -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 { @@ -0,0 +1,12 @@ +pub mod shared { + +} + +pub mod jni { + +} + +#[dropbear_macro::impl_c_api] +pub mod native { + +} @@ -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, +} @@ -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 { @@ -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) + } } @@ -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)", + }) } } @@ -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, -} +pub type Bool = i32; @@ -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); +pub type SetLastErrorMessage = unsafe extern "C" fn(msg: *const c_char); + @@ -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(()) + } } @@ -0,0 +1,4 @@ +use crate::scripting::native::DropbearNativeError; + +/// A result type for dropbear based native functions. +pub type DropbearNativeResult<T> = Result<T, DropbearNativeError>; @@ -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 { @@ -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) + } + } +} @@ -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, +} @@ -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() }}; @@ -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) => { @@ -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; @@ -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) { @@ -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 { @@ -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) { @@ -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<()>; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 +#endif /* DROPBEAR_H */ @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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? @@ -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 @@ -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 @@ -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. * @@ -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() { @@ -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); } @@ -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 + } } @@ -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); } @@ -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) } @@ -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 @@ -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); } @@ -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 { @@ -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); } @@ -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") } @@ -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") } @@ -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") } @@ -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()