tirbofish/dropbear · commit
2f6997ca90e16b8944c140cc96e6160d43f33099
Merge pull request #74 from tirbofish/physics-scripting
feature: overhaul to scripting and FFI
jarvis, run github actions
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" @@ -2,7 +2,7 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinxSerialization) `maven-publish` - id("org.jetbrains.dokka") version "2.0.0" + id("org.jetbrains.dokka") version "2.1.0" } group = "com.dropbear" @@ -42,9 +42,7 @@ val libPathProvider = provider { } kotlin { - jvm { - withJava() - } + jvm { } val nativeTarget = when { isMacOs && isArm64 -> macosArm64("nativeLib") @@ -73,8 +71,8 @@ kotlin { cinterops { val dropbear by creating { defFile(project.file("src/dropbear.def")) - includeDirs.headerFilterOnly(project.file("headers")) - compilerOpts("-I${project.file("headers").absolutePath}") + includeDirs.headerFilterOnly(project.file("include")) + compilerOpts("-I${project.file("include").absolutePath}") } } } @@ -99,7 +97,7 @@ kotlin { cinterops { val dropbear by creating { defFile(project.file("src/dropbear.def")) - includeDirs.headerFilterOnly(project.file("headers")) + includeDirs.headerFilterOnly(project.file("include")) } } } @@ -109,7 +107,7 @@ kotlin { sourceSets { commonMain { dependencies { - api("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0") + api("org.jetbrains.kotlinx:kotlinx-datetime:0.7.0") } } nativeMain { @@ -138,7 +136,7 @@ kotlin { } tasks.register<JavaCompile>("generateJniHeaders") { - val outputDir = layout.buildDirectory.dir("generated/jni-headers") + val outputDir = layout.buildDirectory.dir("generated/jni-include") options.headerOutputDirectory.set(outputDir.get().asFile) destinationDirectory.set(layout.buildDirectory.dir("classes/java/jni")) @@ -152,6 +150,23 @@ tasks.register<JavaCompile>("generateJniHeaders") { } dependsOn("compileKotlinJvm") + + doFirst { + val javaFiles = source.files + if (javaFiles.isEmpty()) { + println("WARNING: No Java files found in src/jvmMain/kotlin for JNI header generation") + } else { + println("Generating JNI include for ${javaFiles.size} Java files:") + javaFiles.forEach { println(" - ${it.name}") } + } + } + + doLast { + val headerDir = outputDir.get().asFile + val headers = headerDir.listFiles()?.filter { it.extension == "h" } ?: emptyList() + println("Generated ${headers.size} JNI include:") + headers.forEach { println(" - ${it.name}") } + } } publishing { @@ -179,7 +194,7 @@ publishing { developer { id.set("tirbofish") name.set("tk") - email.set("tirbofish@pm.me") + email.set("4tkbytes@pm.me") } } @@ -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,72 +0,0 @@ -import org.gradle.kotlin.dsl.compileOnly - -plugins { - `kotlin-dsl` - `maven-publish` - id("com.gradle.plugin-publish") version "2.0.0" -} - -group = "com.dropbear" -version = "1.0-SNAPSHOT" - -repositories { - mavenCentral() -} - -dependencies { - implementation("de.undercouch:gradle-download-task:5.6.0") - compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:${KotlinVersion.CURRENT}") -} - -gradlePlugin { - website.set("https://github.com/tirbofish/dropbear") - vcsUrl.set("https://github.com/tirbofish/dropbear") - plugins { - - create("dropbearGradlePlugin") { - id = "dropbear-gradle-plugin" - implementationClass = "com.dropbear.gradle.DropbearGradlePlugin" - displayName = "dropbear-gradle-plugin" - description = "Gradle plugin for dependency management for dropbear-based projects" - version = version as String - } - } -} - -publishing { - publications { - withType<MavenPublication>().configureEach { - pom { - name.set("dropbear-gradle-plugin") - description.set("Gradle plugin for dropbear dependency management") - url.set("https://tirbofish.github.io/dropbear/") - - licenses { - license { - name.set("MIT License") - url.set("https://opensource.org/licenses/MIT") - } - } - developers { - developer { - id.set("tirbofish") - name.set("tk") - email.set("tirbofish@pm.me") - } - } - scm { - connection.set("scm:git:git://github.com/tirbofish/dropbear.git") - developerConnection.set("scm:git:ssh://github.com/tirbofish/dropbear.git") - url.set("https://github.com/tirbofish/dropbear") - } - } - } - } - - repositories { - maven { - name = "GitHubPages" - url = uri("${layout.buildDirectory}/repo") - } - } -} @@ -1,10 +0,0 @@ -package com.dropbear.gradle - -import org.gradle.api.Plugin -import org.gradle.api.Project - -class DropbearGradlePlugin: Plugin<Project> { - override fun apply(target: Project) { - TODO("Not yet implemented") - } -} @@ -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 @@ -2,13 +2,39 @@ The core libraries of the eucalyptus editor. Great for embedding into `redback-runtime` and `eucalyptus-editor` as one big change instead of a bunch of features. -This is a library, so if tools are wished to be made, this is the perfect library for you. +It also produces a shared library for Kotlin/Native and the JVM. -it also produces a shared library for Kotlin/Native and the JVM :) +Kotlin Multiplatform is prioritised and fully supported on this library, however that is not to say that you (yes you +the reader) could implement this for another language (as really it just reads from a .dll or a .jar file). -## Features +[//]: # (## Export requirements) -- `editor` - Enables editor only features that the redback-runtime would not be able to access -- `jvm` - Enables the JVM as a ScriptTarget and running the Java Virtual Machine (not possible with non-desktop targets) -- `jvm_debug` - Enables debugging of the JVM through the java debugger. Can pose a risk to tampering, so is disabled by default unless - want to be enabled by developer or enabled by default by the `editor` feature. +[//]: # () +[//]: # (To have your own scripting module to be able to be run, you will have to implement the following exports into) + +[//]: # (your own app:) + +[//]: # () +[//]: # (- `dropbear_init`) + +[//]: # ( - Args: `pointerContext: DropbearContext*`) + +[//]: # ( - Returns: `int`) + +[//]: # (- `dropbear_load_tagged`) + +[//]: # ( - Args: `tag: *mut c_char`) + +[//]: # ( - Returns: `int`) + +[//]: # (- `dropbear_update_all`) + +[//]: # ( - Args: `dt: float`) + +[//]: # ( - Returns: `int`) + +[//]: # (- `dropbear_update_tagged`) + +[//]: # ( - Args: `tag: *mut c_char, `) + +[//]: # ( - ) @@ -1,4 +1,5 @@ fn main() -> anyhow::Result<()> { + // fuck you windows :( #[cfg(target_os = "windows")] { @@ -7,5 +8,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,739 @@ 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}; + use crate::types::Vector3; + + #[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: Vector3 = Vector3::from(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 Vector3::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 = DVec3::from(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: Vector3 = Vector3::from(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 Vector3::from_jobject(&mut env, &target_obj) { + Ok(v) => v, + Err(_) => return, + }; + if let Ok(mut camera) = world.get::<&mut Camera>(entity) { + camera.target = DVec3::from(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 = Vector3::from(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 Vector3::from_jobject(&mut env, &up_obj) { + Ok(v) => v, + Err(_) => return, + }; + if let Ok(mut camera) = world.get::<&mut Camera>(entity) { + camera.up = DVec3::from(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. /// @@ -53,6 +61,8 @@ pub fn register_components( component_registry.register_with_default::<Script>(); component_registry.register_with_default::<SerializedMeshRenderer>(); component_registry.register_with_default::<Camera3D>(); + component_registry.register_with_default::<RigidBody>(); + component_registry.register_with_default::<ColliderGroup>(); component_registry.register_converter::<MeshRenderer, SerializedMeshRenderer, _>( |_, _, renderer| { @@ -77,9 +87,6 @@ pub fn register_components( }, ); - component_registry.register_with_default::<RigidBody>(); - component_registry.register_with_default::<ColliderGroup>(); - // // register plugin defined structs // if let Err(e) = plugin_registry.load_plugins() { // fatal!("Failed to load plugins: {}", e); @@ -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; @@ -16,19 +18,32 @@ pub mod collider; /// to physics rendering. #[derive(Serialize, Deserialize, Clone)] pub struct PhysicsState { + #[serde(default)] pub islands: IslandManager, + #[serde(default)] pub broad_phase: DefaultBroadPhase, + #[serde(default)] pub narrow_phase: NarrowPhase, + #[serde(default)] pub bodies: RigidBodySet, + #[serde(default)] pub colliders: ColliderSet, + #[serde(default)] pub impulse_joints: ImpulseJointSet, + #[serde(default)] pub multibody_joints: MultibodyJointSet, + #[serde(default)] pub ccd_solver: CCDSolver, + #[serde(default)] pub integration_parameters: IntegrationParameters, + pub gravity: [f32; 3], + #[serde(default)] pub bodies_entity_map: HashMap<Label, RigidBodyHandle>, - pub colliders_entity_map: HashMap<Label, Vec<ColliderHandle>>, + #[serde(default)] + pub colliders_entity_map: HashMap<Label, Vec<(u32, ColliderHandle)>>, + #[serde(default)] pub entity_label_map: HashMap<Entity, Label>, } @@ -79,14 +94,14 @@ impl PhysicsState { let pos = transform.position.as_vec3().to_array(); let rot = transform.rotation.as_quat().to_array(); - let scale = transform.scale; - + let body = RigidBodyBuilder::new(mode) - .translation(vector![pos[0] * scale.x as f32, pos[1] * scale.y as f32, pos[2] * scale.z as f32]) + .translation(vector![pos[0], pos[1], pos[2]]) .rotation(UnitQuaternion::from_quaternion(Quaternion::new( rot[3] as f32, rot[0] as f32, rot[1] as f32, rot[2] as f32 )).scaled_axis()) .gravity_scale(rigid_body.gravity_scale) + .sleeping(rigid_body.sleeping) .can_sleep(rigid_body.can_sleep) .ccd_enabled(rigid_body.ccd_enabled) .linvel(Vector3::from_column_slice(&rigid_body.linvel)) @@ -103,7 +118,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 +160,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 +184,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 +204,7 @@ impl PhysicsState { &mut self.colliders, &mut self.impulse_joints, &mut self.multibody_joints, - false // wake_up + false ); } self.colliders_entity_map.remove(entity); @@ -200,4 +215,70 @@ impl Default for PhysicsState { fn default() -> Self { Self::new() } +} + +pub mod shared { + use crate::physics::PhysicsState; + use crate::types::Vector3; + + pub fn get_gravity(physics: &PhysicsState) -> Vector3 { + Vector3::from(physics.gravity) + } + + pub fn set_gravity(physics: &mut PhysicsState, new: Vector3) { + physics.gravity = new.to_float_array(); + } +} + +pub mod jni { + #![allow(non_snake_case)] + + use jni::JNIEnv; + use jni::sys::{jlong, jobject}; + use jni::objects::{JClass, JObject}; + use crate::physics::PhysicsState; + use crate::scripting::jni::utils::{FromJObject, ToJObject}; + use crate::scripting::result::DropbearNativeResult; + use crate::types::Vector3; + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_PhysicsNative_getGravity( + mut env: JNIEnv, + _: JClass, + physics_handle: jlong, + ) -> jobject { + let physics = crate::convert_ptr!(physics_handle => PhysicsState); + + match super::shared::get_gravity(&physics).to_jobject(&mut env) { + Ok(v) => v.into_raw(), + Err(e) => { + let _ = env.throw_new("java/lang/RuntimeException", format!("Unable to create new Vector3d object for gravity: {}", e)); + std::ptr::null_mut() + } + } + + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_PhysicsNative_setGravity( + mut env: JNIEnv, + _: JClass, + physics_handle: jlong, + new_gravity: JObject, + ) { + let mut physics = crate::convert_ptr!(mut physics_handle => PhysicsState); + let vec3 = match Vector3::from_jobject(&mut env, &new_gravity) { + Ok(v) => v, + Err(e) => { + let _ = env.throw_new("java/lang/RuntimeException", format!("Unable to create new Vector3d object for gravity: {}", e)); + return; + } + }; + + super::shared::set_gravity(&mut physics, vec3); + } +} + +pub mod native { + } @@ -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 = crate::types::Vector3::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) = crate::types::Vector3::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 = crate::types::Vector3::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) = crate::types::Vector3::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,15 +1,24 @@ -use serde::{Deserialize, Serialize}; +//! [rapier3d] RigidBodies + +use crate::scripting::jni::utils::{FromJObject, ToJObject}; +use crate::scripting::native::DropbearNativeError; +use crate::scripting::result::DropbearNativeResult; +use crate::states::Label; use dropbear_macro::SerializableComponent; use dropbear_traits::SerializableComponent; -use crate::states::Label; +use ::jni::objects::{JObject, JValue}; +use ::jni::JNIEnv; +use rapier3d::prelude::RigidBodyType; +use serde::{Deserialize, Serialize}; /// How this entity behaves in the physics simulation. /// /// This intentionally mirrors Rapier's rigid-body types, but stays engine-owned and serializable. +#[repr(C)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] pub enum RigidBodyMode { /// A fully simulated body affected by forces and contacts. - Dynamic, + Dynamic = 0, /// An immovable body. Fixed, /// A kinematic body controlled by setting its next position. @@ -24,6 +33,18 @@ impl Default for RigidBodyMode { } } +impl From<RigidBodyType> for RigidBodyMode { + fn from(value: RigidBodyType) -> Self { + match value { + RigidBodyType::Dynamic => RigidBodyMode::Dynamic, + RigidBodyType::Fixed => Self::Fixed, + RigidBodyType::KinematicPositionBased => Self::KinematicPosition, + RigidBodyType::KinematicVelocityBased => Self::KinematicVelocity, + } + } +} + +#[repr(C)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default)] pub struct AxisLock { pub x: bool, @@ -31,6 +52,59 @@ pub struct AxisLock { pub z: bool, } +impl FromJObject for AxisLock { + fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> + where + Self: Sized + { + let class = env.find_class("com/dropbear/physics/AxisLock") + .map_err(|_| DropbearNativeError::JNIClassNotFound)?; + + if !env.is_instance_of(obj, &class) + .map_err(|_| DropbearNativeError::JNIUnwrapFailed)? + { + return Err(DropbearNativeError::InvalidArgument); + } + + let x = env.get_field(obj, "x", "Z") + .map_err(|_| DropbearNativeError::JNIFailedToGetField)? + .z() + .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?; + + let y = env.get_field(obj, "y", "Z") + .map_err(|_| DropbearNativeError::JNIFailedToGetField)? + .z() + .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?; + + let z = env.get_field(obj, "z", "Z") + .map_err(|_| DropbearNativeError::JNIFailedToGetField)? + .z() + .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?; + + Ok(AxisLock { x, y, z }) + } +} + +impl ToJObject for AxisLock { + fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> { + let class = env.find_class("com/dropbear/physics/AxisLock") + .map_err(|_| DropbearNativeError::JNIClassNotFound)?; + + let constructor_sig = "(ZZZ)V"; + + let args = [ + JValue::Bool(self.x as u8), + JValue::Bool(self.y as u8), + JValue::Bool(self.z as u8), + ]; + + let obj = env.new_object(&class, constructor_sig, &args) + .map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?; + + Ok(obj) + } +} + /// A serializable physics rigid-body component. /// /// Notes: @@ -56,8 +130,12 @@ pub struct RigidBody { #[serde(default = "RigidBody::default_gravity_scale")] pub gravity_scale: f32, + #[serde(default)] + /// If the rigidbody is currently sleeping or not. + pub sleeping: bool, + /// Whether this body is allowed to sleep. - #[serde(default = "RigidBody::default_can_sleep")] + #[serde(default = "RigidBody::default_sleep")] pub can_sleep: bool, /// Whether continuous collision detection is enabled. @@ -96,7 +174,8 @@ impl Default for RigidBody { disable_physics: false, mode: RigidBodyMode::default(), gravity_scale: Self::default_gravity_scale(), - can_sleep: Self::default_can_sleep(), + sleeping: false, + can_sleep: Self::default_sleep(), ccd_enabled: false, linvel: [0.0, 0.0, 0.0], angvel: [0.0, 0.0, 0.0], @@ -113,89 +192,1096 @@ impl RigidBody { 1.0 } - const fn default_can_sleep() -> bool { + const fn default_sleep() -> bool { true } } -pub mod jni { - use glam::DVec3; - use jni::objects::{JObject, JString}; - use jni::JNIEnv; - use rapier3d::prelude::RigidBodyHandle; +pub mod shared { use crate::physics::rigidbody::{AxisLock, RigidBody, RigidBodyMode}; - use crate::scripting::jni::utils::FromJObject; + use crate::physics::PhysicsState; + use crate::scripting::native::DropbearNativeError; + use crate::scripting::result::DropbearNativeResult; use crate::states::Label; + use crate::types::{IndexNative, RigidBodyContext, Vector3}; + use hecs::{Entity, World}; + use rapier3d::dynamics::{RigidBodyHandle, RigidBodyType}; + use rapier3d::prelude::vector; + use rapier3d::prelude::{nalgebra, ColliderHandle, LockedAxes}; - impl FromJObject for AxisLock { - fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> anyhow::Result<Self> - where - Self: Sized + pub fn rigid_body_exists_for_entity( + world: &World, + physics: &PhysicsState, + entity: Entity + ) -> Option<IndexNative> { + if let Ok(mut q) = world.query_one::<(&Label, &RigidBody)>(entity) + && let Some((label, _)) = q.get() { - 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 }) + if let Some(handle) = physics.bodies_entity_map.get(label) { + Some(IndexNative::from(handle.0)) + } else { + None + } + } else { + None + } + } + + pub fn get_rigidbody_type(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<RigidBodyType> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get(handle) { + Ok(rb.body_type()) + } else { + Err(DropbearNativeError::PhysicsObjectNotFound) + } + } + + pub fn set_rigidbody_type(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, mode: i64) -> DropbearNativeResult<()> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get_mut(handle) { + let mode = match mode { + 0 => RigidBodyType::Dynamic, + 1 => RigidBodyType::Fixed, + 2 => RigidBodyType::KinematicPositionBased, + 3 => RigidBodyType::KinematicVelocityBased, + _ => { return Err(DropbearNativeError::InvalidArgument); } + }; + rb.set_body_type(mode, true); + + let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?; + + if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() { + let rb_mode = RigidBodyMode::from(mode); + rb.mode = rb_mode; + } + + Ok(()) + } else { + Err(DropbearNativeError::NoSuchHandle) + } + } + + pub fn get_rigidbody_gravity_scale(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<f64> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get(handle) { + Ok(rb.gravity_scale().into()) + } else { + Err(DropbearNativeError::PhysicsObjectNotFound) + } + } + + pub fn set_rigidbody_gravity_scale(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new_scale: f64) -> DropbearNativeResult<()> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get_mut(handle) { + rb.set_gravity_scale(new_scale as f32, true); + + let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?; + + if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() { + rb.gravity_scale = new_scale as f32; + } + + Ok(()) + } else { + Err(DropbearNativeError::NoSuchHandle) + } + } + + pub fn get_rigidbody_linear_damping(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<f64> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get(handle) { + Ok(rb.linear_damping().into()) + } else { + Err(DropbearNativeError::PhysicsObjectNotFound) + } + } + + pub fn set_rigidbody_linear_damping(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: f64) -> DropbearNativeResult<()> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get_mut(handle) { + rb.set_linear_damping(new as f32); + + let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?; + + if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() { + rb.linear_damping = new as f32; + } + + Ok(()) + } else { + Err(DropbearNativeError::NoSuchHandle) + } + } + + pub fn get_rigidbody_angular_damping(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<f64> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get(handle) { + Ok(rb.angular_damping().into()) + } else { + Err(DropbearNativeError::PhysicsObjectNotFound) } } - 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(); + pub fn set_rigidbody_angular_damping(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: f64) -> DropbearNativeResult<()> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get_mut(handle) { + rb.set_angular_damping(new as f32); + + let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?; + + if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() { + rb.angular_damping = new as f32; + } - let entity = Label::from(entity_str); + Ok(()) + } else { + Err(DropbearNativeError::NoSuchHandle) + } + } - 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; + pub fn get_rigidbody_sleep(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<bool> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get(handle) { + Ok(rb.is_sleeping().into()) + } else { + Err(DropbearNativeError::PhysicsObjectNotFound) + } + } - let mode_obj = env.get_field(obj, "rigidBodyMode", "Lcom/dropbear/physics/RigidBodyMode;")?.l()?; - let mode_ordinal = env.call_method(&mode_obj, "ordinal", "()I", &[])?.i()?; + pub fn set_rigidbody_sleep(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: bool) -> DropbearNativeResult<()> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get_mut(handle) { + if new { + rb.sleep(); + } else { + rb.wake_up(true); + } - let mode = match mode_ordinal { - 0 => RigidBodyMode::Dynamic, - 1 => RigidBodyMode::Fixed, - 2 => RigidBodyMode::KinematicPosition, - 3 => RigidBodyMode::KinematicVelocity, - _ => RigidBodyMode::Dynamic, + let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?; + + if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() { + rb.sleeping = new; + } + + Ok(()) + } else { + Err(DropbearNativeError::NoSuchHandle) + } + } + + pub fn get_rigidbody_ccd(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<bool> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get(handle) { + Ok(rb.is_ccd_enabled().into()) + } else { + Err(DropbearNativeError::PhysicsObjectNotFound) + } + } + + pub fn set_rigidbody_ccd(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: bool) -> DropbearNativeResult<()> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get_mut(handle) { + rb.enable_ccd(new); + + let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?; + + if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() { + rb.ccd_enabled = new; + } + + Ok(()) + } else { + Err(DropbearNativeError::NoSuchHandle) + } + } + + pub fn get_rigidbody_linvel(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<Vector3> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get(handle) { + let linvel = rb.linvel().clone(); + Ok(Vector3::new(linvel.x as f64, linvel.y as f64, linvel.z as f64)) + } else { + Err(DropbearNativeError::PhysicsObjectNotFound) + } + } + + pub fn set_rigidbody_linvel(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: Vector3) -> DropbearNativeResult<()> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get_mut(handle) { + rb.set_linvel(vector![new.x as f32, new.y as f32, new.z as f32], true); + + let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?; + + if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() { + rb.linvel = [new.x as f32, new.y as f32, new.z as f32]; + } + + Ok(()) + } else { + Err(DropbearNativeError::NoSuchHandle) + } + } + + pub fn get_rigidbody_angvel(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<Vector3> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get(handle) { + let angvel = rb.angvel().clone(); + Ok(Vector3::new(angvel.x as f64, angvel.y as f64, angvel.z as f64)) + } else { + Err(DropbearNativeError::PhysicsObjectNotFound) + } + } + + pub fn set_rigidbody_angvel(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: Vector3) -> DropbearNativeResult<()> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get_mut(handle) { + rb.set_angvel(vector![new.x as f32, new.y as f32, new.z as f32], true); + + let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?; + + if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() { + rb.angvel = [new.x as f32, new.y as f32, new.z as f32]; + } + + Ok(()) + } else { + Err(DropbearNativeError::NoSuchHandle) + } + } + + pub fn get_rigidbody_lock_translation(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<AxisLock> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get(handle) { + let lock = rb.locked_axes(); + let translation_lock = AxisLock { + x: lock.contains(LockedAxes::TRANSLATION_LOCKED_X), + y: lock.contains(LockedAxes::TRANSLATION_LOCKED_Y), + z: lock.contains(LockedAxes::TRANSLATION_LOCKED_Z), + }; + Ok(translation_lock) + } else { + Err(DropbearNativeError::PhysicsObjectNotFound) + } + } + + pub fn set_rigidbody_lock_translation(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: AxisLock) -> DropbearNativeResult<()> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get_mut(handle) { + let mut bits = rb.locked_axes().clone(); + + bits.remove(LockedAxes::TRANSLATION_LOCKED_X); + bits.remove(LockedAxes::TRANSLATION_LOCKED_Y); + bits.remove(LockedAxes::TRANSLATION_LOCKED_Z); + + if new.x { + bits = bits | LockedAxes::TRANSLATION_LOCKED_X; + } + if new.y { + bits = bits | LockedAxes::TRANSLATION_LOCKED_Y; + } + if new.z { + bits = bits | LockedAxes::TRANSLATION_LOCKED_Z; + } + + rb.set_locked_axes(bits, true); + + let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?; + + if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() { + rb.lock_translation = new; + } + + Ok(()) + } else { + Err(DropbearNativeError::NoSuchHandle) + } + } + + pub fn get_rigidbody_lock_rotation(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<AxisLock> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get(handle) { + let lock = rb.locked_axes(); + let rot_lock = AxisLock { + x: lock.contains(LockedAxes::ROTATION_LOCKED_X), + y: lock.contains(LockedAxes::ROTATION_LOCKED_Y), + z: lock.contains(LockedAxes::ROTATION_LOCKED_Z), }; + Ok(rot_lock) + } else { + Err(DropbearNativeError::PhysicsObjectNotFound) + } + } + + pub fn set_rigidbody_lock_rotation(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: AxisLock) -> DropbearNativeResult<()> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get_mut(handle) { + let mut bits = rb.locked_axes().clone(); + + bits.remove(LockedAxes::ROTATION_LOCKED_X); + bits.remove(LockedAxes::ROTATION_LOCKED_Y); + bits.remove(LockedAxes::ROTATION_LOCKED_Z); + + if new.x { + bits = bits | LockedAxes::ROTATION_LOCKED_X; + } + if new.y { + bits = bits | LockedAxes::ROTATION_LOCKED_Y; + } + if new.z { + bits = bits | LockedAxes::ROTATION_LOCKED_Z; + } + + rb.set_locked_axes(bits, true); + + let entity = Entity::from_bits(rb_context.entity_id).ok_or(DropbearNativeError::InvalidEntity)?; + + if let Ok(mut q) = world.query_one::<&mut RigidBody>(entity) && let Some(rb) = q.get() { + rb.lock_translation = new; + } - 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; + Ok(()) + } else { + Err(DropbearNativeError::NoSuchHandle) + } + } + + pub fn get_rigidbody_children(physics: &PhysicsState, rb_context: RigidBodyContext) -> DropbearNativeResult<Vec<ColliderHandle>> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get(handle) { + let children = rb.colliders().to_vec(); + Ok(children) + } else { + Err(DropbearNativeError::PhysicsObjectNotFound) + } + } + + pub fn apply_impulse(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: Vector3) -> DropbearNativeResult<()> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get_mut(handle) { + rb.apply_impulse(vector![new.x as f32, new.y as f32, new.z as f32], true); + + Ok(()) + } else { + Err(DropbearNativeError::NoSuchHandle) + } + } + + pub fn apply_torque_impulse(physics: &mut PhysicsState, world: &World, rb_context: RigidBodyContext, new: Vector3) -> DropbearNativeResult<()> { + let handle = RigidBodyHandle::from_raw_parts(rb_context.index.index, rb_context.index.generation); + if let Some(rb) = physics.bodies.get_mut(handle) { + rb.apply_torque_impulse(vector![new.x as f32, new.y as f32, new.z as f32], true); + + Ok(()) + } else { + Err(DropbearNativeError::NoSuchHandle) + } + } +} + +pub mod jni { + #![allow(non_snake_case)] + use crate::physics::rigidbody::AxisLock; + use crate::physics::PhysicsState; + use crate::scripting::jni::utils::{FromJObject, ToJObject}; + use crate::types::{IndexNative, RigidBodyContext, Vector3}; + use crate::{convert_jlong_to_entity, convert_ptr}; + use hecs::World; + use jni::objects::{JClass, JObject}; + use jni::sys::{jboolean, jdouble, jint, jlong, jobject, jobjectArray, jsize}; + use jni::JNIEnv; + use rapier3d::dynamics::RigidBodyType; + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_rigidBodyExistsForEntity( + mut env: JNIEnv, + _: JClass, + world_ptr: jlong, + physics_ptr: jlong, + entity_id: jlong, + ) -> jobject { + let world = convert_ptr!(world_ptr => World); + let physics = convert_ptr!(physics_ptr => PhysicsState); + let entity = convert_jlong_to_entity!(entity_id); + + match super::shared::rigid_body_exists_for_entity(world, physics, entity) { + Some(v) => { + match v.to_jobject(&mut env) { + Ok(val) => val.into_raw(), + Err(e) => { + eprintln!("Failed to create new Index jobject: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to create new Index jobject: {}", e)); + std::ptr::null_mut() + } + } + } + None => std::ptr::null_mut() + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyMode( + mut env: JNIEnv, + _: JClass, + _world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + ) -> jint { + let physics = convert_ptr!(physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", "Unable to convert com/dropbear/physics/RigidBody into a Rust rigidbody"); + return -1 as jint; + }; + + match super::shared::get_rigidbody_type(physics, rb_context) { + Ok(v) => { + match v { + RigidBodyType::Dynamic => 0 as jint, + RigidBodyType::Fixed => 1 as jint, + RigidBodyType::KinematicPositionBased => 2 as jint, + RigidBodyType::KinematicVelocityBased => 3 as jint, + } + } + Err(e) => { + eprintln!("Failed to get RigidBody type: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody type: {}", e)); + -1 as jint + } + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodyMode( + mut env: JNIEnv, + _: JClass, + world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + mode: jint, + ) { + let world = convert_ptr!(world_ptr => World); + let mut physics = convert_ptr!(mut physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return; + }; + + if let Err(e) = super::shared::set_rigidbody_type(&mut physics, world, rb_context, mode as i64) { + eprintln!("Failed to set RigidBody Type: {}", e); + let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody type: {}", e)); + return; + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyGravityScale( + mut env: JNIEnv, + _: JClass, + _world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + ) -> jdouble { + let physics = convert_ptr!(physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return -1 as jdouble; + }; + + match super::shared::get_rigidbody_gravity_scale(physics, rb_context) { + Ok(v) => v as jdouble, + Err(e) => { + eprintln!("Failed to get RigidBody component: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody component: {}", e)); + -1 as jdouble + } + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodyGravityScale( + mut env: JNIEnv, + _: JClass, + world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + gravity_scale: jdouble, + ) { + let world = convert_ptr!(world_ptr => World); + let mut physics = convert_ptr!(mut physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return; + }; + + if let Err(e) = super::shared::set_rigidbody_gravity_scale(&mut physics, world, rb_context, gravity_scale as f64) { + eprintln!("Failed to set RigidBody gravity scale: {}", e); + let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody gravity scale: {}", e)); + return; + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyLinearDamping( + mut env: JNIEnv, + _: JClass, + _world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + ) -> jdouble { + let physics = convert_ptr!(physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return -1 as jdouble; + }; + + match super::shared::get_rigidbody_linear_damping(physics, rb_context) { + Ok(v) => v as jdouble, + Err(e) => { + eprintln!("Failed to get RigidBody linear damping: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody linear damping: {}", e)); + -1 as jdouble + } + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodyLinearDamping( + mut env: JNIEnv, + _: JClass, + world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + linear_damping: jdouble, + ) { + let world = convert_ptr!(world_ptr => World); + let mut physics = convert_ptr!(mut physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return; + }; + + if let Err(e) = super::shared::set_rigidbody_linear_damping(&mut physics, world, rb_context, linear_damping as f64) { + eprintln!("Failed to set RigidBody linear damping: {}", e); + let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody linear damping: {}", e)); + return; + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyAngularDamping( + mut env: JNIEnv, + _: JClass, + _world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + ) -> jdouble { + let physics = convert_ptr!(physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return -1 as jdouble; + }; + + match super::shared::get_rigidbody_angular_damping(physics, rb_context) { + Ok(v) => v as jdouble, + Err(e) => { + eprintln!("Failed to get RigidBody angular damping: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody angular damping: {}", e)); + -1 as jdouble + } + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodyAngularDamping( + mut env: JNIEnv, + _: JClass, + world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + angular_damping: jdouble, + ) { + let world = convert_ptr!(world_ptr => World); + let mut physics = convert_ptr!(mut physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return; + }; + + if let Err(e) = super::shared::set_rigidbody_angular_damping(&mut physics, world, rb_context, angular_damping as f64) { + eprintln!("Failed to set RigidBody angular damping: {}", e); + let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody angular damping: {}", e)); + return; + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodySleep( + mut env: JNIEnv, + _: JClass, + _world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + ) -> jboolean { + let physics = convert_ptr!(physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return 0 as jboolean; + }; + + match super::shared::get_rigidbody_sleep(physics, rb_context) { + Ok(v) => v as jboolean, + Err(e) => { + eprintln!("Failed to get RigidBody sleep state: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody sleep state: {}", e)); + 0 as jboolean + } + } + } - 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(); + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodySleep( + mut env: JNIEnv, + _: JClass, + world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + sleep: jboolean, + ) { + let world = convert_ptr!(world_ptr => World); + let mut physics = convert_ptr!(mut physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return; + }; - 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(); + if let Err(e) = super::shared::set_rigidbody_sleep(&mut physics, world, rb_context, sleep != 0) { + eprintln!("Failed to set RigidBody sleep state: {}", e); + let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody sleep state: {}", e)); + return; + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyCcdEnabled( + mut env: JNIEnv, + _: JClass, + _world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + ) -> jboolean { + let physics = convert_ptr!(physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return 0 as jboolean; + }; + + match super::shared::get_rigidbody_ccd(physics, rb_context) { + Ok(v) => v as jboolean, + Err(e) => { + eprintln!("Failed to get RigidBody CCD state: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody CCD state: {}", e)); + 0 as jboolean + } + } + } - let lock_trans_obj = env.get_field(obj, "lockTranslation", "Lcom/dropbear/physics/AxisLock;")?.l()?; - let lock_translation = AxisLock::from_jobject(env, &lock_trans_obj)?; + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodyCcdEnabled( + mut env: JNIEnv, + _: JClass, + world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + ccd_enabled: jboolean, + ) { + let world = convert_ptr!(world_ptr => World); + let mut physics = convert_ptr!(mut physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return; + }; - let lock_rot_obj = env.get_field(obj, "lockRotation", "Lcom/dropbear/physics/AxisLock;")?.l()?; - let lock_rotation = AxisLock::from_jobject(env, &lock_rot_obj)?; + if let Err(e) = super::shared::set_rigidbody_ccd(&mut physics, world, rb_context, ccd_enabled != 0) { + eprintln!("Failed to set RigidBody CCD state: {}", e); + let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody CCD state: {}", e)); + return; + } + } - let handle = RigidBodyHandle::from_raw_parts(idx_val, gen_val); + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyLinearVelocity( + mut env: JNIEnv, + _: JClass, + _world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + ) -> jobject { + let physics = convert_ptr!(physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return std::ptr::null_mut(); + }; - Ok((handle, Self { - entity, - disable_physics: false, - mode, - gravity_scale, - can_sleep, - ccd_enabled, - linvel, - angvel, - linear_damping, - angular_damping, - lock_translation, - lock_rotation, - })) + match super::shared::get_rigidbody_linvel(physics, rb_context) { + Ok(v) => { + match v.to_jobject(&mut env) { + Ok(val) => val.into_raw(), + Err(e) => { + eprintln!("Failed to create Vector3d jobject: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to create Vector3d jobject: {}", e)); + std::ptr::null_mut() + } + } + } + Err(e) => { + eprintln!("Failed to get RigidBody linear velocity: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody linear velocity: {}", e)); + std::ptr::null_mut() + } } } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodyLinearVelocity( + mut env: JNIEnv, + _: JClass, + world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + linear_velocity: JObject, + ) { + let world = convert_ptr!(world_ptr => World); + let mut physics = convert_ptr!(mut physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return; + }; + + let Ok(velocity) = Vector3::from_jobject(&mut env, &linear_velocity) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/math/Vector3d to rust Vector3"); + return; + }; + + if let Err(e) = super::shared::set_rigidbody_linvel(&mut physics, world, rb_context, velocity) { + eprintln!("Failed to set RigidBody linear velocity: {}", e); + let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody linear velocity: {}", e)); + return; + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyAngularVelocity( + mut env: JNIEnv, + _: JClass, + _world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + ) -> jobject { + let physics = convert_ptr!(physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return std::ptr::null_mut(); + }; + + match super::shared::get_rigidbody_angvel(physics, rb_context) { + Ok(v) => { + match v.to_jobject(&mut env) { + Ok(val) => val.into_raw(), + Err(e) => { + eprintln!("Failed to create Vector3d jobject: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to create Vector3d jobject: {}", e)); + std::ptr::null_mut() + } + } + } + Err(e) => { + eprintln!("Failed to get RigidBody angular velocity: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody angular velocity: {}", e)); + std::ptr::null_mut() + } + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodyAngularVelocity( + mut env: JNIEnv, + _: JClass, + world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + angular_velocity: JObject, + ) { + let world = convert_ptr!(world_ptr => World); + let mut physics = convert_ptr!(mut physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return; + }; + + let Ok(velocity) = Vector3::from_jobject(&mut env, &angular_velocity) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/math/Vector3d to rust Vector3"); + return; + }; + + if let Err(e) = super::shared::set_rigidbody_angvel(&mut physics, world, rb_context, velocity) { + eprintln!("Failed to set RigidBody angular velocity: {}", e); + let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody angular velocity: {}", e)); + return; + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyLockTranslation( + mut env: JNIEnv, + _: JClass, + _world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + ) -> jobject { + let physics = convert_ptr!(physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return std::ptr::null_mut(); + }; + + match super::shared::get_rigidbody_lock_translation(physics, rb_context) { + Ok(v) => { + match v.to_jobject(&mut env) { + Ok(val) => val.into_raw(), + Err(e) => { + eprintln!("Failed to create AxisLock jobject: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to create AxisLock jobject: {}", e)); + std::ptr::null_mut() + } + } + } + Err(e) => { + eprintln!("Failed to get RigidBody translation lock: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody translation lock: {}", e)); + std::ptr::null_mut() + } + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodyLockTranslation( + mut env: JNIEnv, + _: JClass, + world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + lock: JObject, + ) { + let world = convert_ptr!(world_ptr => World); + let mut physics = convert_ptr!(mut physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return; + }; + + let Ok(axis_lock) = AxisLock::from_jobject(&mut env, &lock) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/AxisLock to rust AxisLock"); + return; + }; + + if let Err(e) = super::shared::set_rigidbody_lock_translation(&mut physics, world, rb_context, axis_lock) { + eprintln!("Failed to set RigidBody translation lock: {}", e); + let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody translation lock: {}", e)); + return; + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyLockRotation( + mut env: JNIEnv, + _: JClass, + _world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + ) -> jobject { + let physics = convert_ptr!(physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return std::ptr::null_mut(); + }; + + match super::shared::get_rigidbody_lock_rotation(physics, rb_context) { + Ok(v) => { + match v.to_jobject(&mut env) { + Ok(val) => val.into_raw(), + Err(e) => { + eprintln!("Failed to create AxisLock jobject: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to create AxisLock jobject: {}", e)); + std::ptr::null_mut() + } + } + } + Err(e) => { + eprintln!("Failed to get RigidBody rotation lock: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody rotation lock: {}", e)); + std::ptr::null_mut() + } + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_setRigidBodyLockRotation( + mut env: JNIEnv, + _: JClass, + world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + lock: JObject, + ) { + let world = convert_ptr!(world_ptr => World); + let mut physics = convert_ptr!(mut physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return; + }; + + let Ok(axis_lock) = AxisLock::from_jobject(&mut env, &lock) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/AxisLock to rust AxisLock"); + return; + }; + + if let Err(e) = super::shared::set_rigidbody_lock_rotation(&mut physics, world, rb_context, axis_lock) { + eprintln!("Failed to set RigidBody rotation lock: {}", e); + let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to set RigidBody rotation lock: {}", e)); + return; + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_getRigidBodyChildren( + mut env: JNIEnv, + _: JClass, + _world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + ) -> jobjectArray { + let physics = convert_ptr!(physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return std::ptr::null_mut(); + }; + + match super::shared::get_rigidbody_children(physics, rb_context) { + Ok(children) => { + let mut handles: Vec<JObject> = Vec::new(); + for c in children { + match IndexNative::from(c.0).to_jobject(&mut env) { + Ok(val) => { handles.push(val); } + Err(_) => { continue; } + } + } + + let array = match env.new_object_array(handles.len() as i32, "com/dropbear/physics/Collider", JObject::null()) { + Ok(array) => array, + Err(e) => { + eprintln!("Failed to create jlong array: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to create jlong array: {}", e)); + return std::ptr::null_mut(); + } + }; + + for (i, h) in handles.iter().enumerate() { + if let Err(e) = env.set_object_array_element(&array, i as jsize, h) { + eprintln!("Failed to set jlong array region: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to set jobject array region at index {}: {}", i, e)); + return std::ptr::null_mut(); + } + } + + array.into_raw() + } + Err(e) => { + eprintln!("Failed to get RigidBody children: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get RigidBody children: {}", e)); + std::ptr::null_mut() + } + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_applyImpulse( + mut env: JNIEnv, + _: JClass, + world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + x: jdouble, + y: jdouble, + z: jdouble, + ) { + let world = convert_ptr!(world_ptr => World); + let mut physics = convert_ptr!(mut physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return; + }; + + let impulse = Vector3::new(x as f64, y as f64, z as f64); + if let Err(e) = super::shared::apply_impulse(&mut physics, world, rb_context, impulse) { + eprintln!("Failed to apply impulse: {}", e); + let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to apply impulse: {}", e)); + return; + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_physics_RigidBodyNative_applyTorqueImpulse( + mut env: JNIEnv, + _: JClass, + world_ptr: jlong, + physics_ptr: jlong, + rigidbody: JObject, + x: jdouble, + y: jdouble, + z: jdouble, + ) { + let world = convert_ptr!(world_ptr => World); + let mut physics = convert_ptr!(mut physics_ptr => PhysicsState); + let Ok(rb_context) = RigidBodyContext::from_jobject(&mut env, &rigidbody) else { + let _ = env.throw_new("java/lang/RuntimeException", + "Unable to convert com/dropbear/physics/RigidBody to rust eucalyptus_core::physics::RigidBodyContext"); + return; + }; + + let torque = Vector3::new(x as f64, y as f64, z as f64); + if let Err(e) = super::shared::apply_torque_impulse(&mut physics, world, rb_context, torque) { + eprintln!("Failed to apply torque impulse: {}", e); + let _ = env.throw_new("java/lang/IllegalArgumentException", format!("Failed to apply torque impulse: {}", e)); + return; + } + } +} + +#[dropbear_macro::impl_c_api] +pub mod native { + } @@ -0,0 +1,795 @@ +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}; + use crate::types::Vector3; + + /// 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 Vector3::from(*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 Vector3::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,565 @@ +use crate::scripting::result::DropbearNativeResult; +use ::jni::objects::{JObject, JValue}; +use ::jni::JNIEnv; + +pub mod shared { + use crate::command::CommandBuffer; + use crate::scene::loading::{SceneLoadResult, SceneLoader}; + use crate::scripting::native::DropbearNativeError; + use crate::scripting::result::DropbearNativeResult; + use crossbeam_channel::Sender; + use parking_lot::Mutex; + + pub fn load_scene_async( + command_buffer: &Sender<CommandBuffer>, + scene_loader: &Mutex<SceneLoader>, + scene_name: String, + _loading_scene: Option<String>, + ) -> DropbearNativeResult<u64> { + let mut loader = scene_loader.lock(); + + if let Some(existing_id) = loader.find_pending_id_by_name(&scene_name) { + return Ok(existing_id); + } + + let id = loader.register_load(scene_name.clone()); + + let handle = crate::scene::loading::SceneLoadHandle { + id, + scene_name: scene_name.clone(), + }; + + // Send load command + command_buffer.try_send(CommandBuffer::LoadSceneAsync(handle)) + .map_err(|_| DropbearNativeError::SendError)?; + + Ok(id) + } + + pub fn switch_to_scene_immediate( + command_buffer: &Sender<CommandBuffer>, + scene_name: String, + ) -> DropbearNativeResult<()> { + command_buffer.try_send(CommandBuffer::SwitchSceneImmediate(scene_name)) + .map_err(|_| DropbearNativeError::SendError)?; + Ok(()) + } + + pub fn switch_to_scene_async( + command_buffer: &Sender<CommandBuffer>, + scene_loader: &Mutex<SceneLoader>, + scene_id: u64, + ) -> DropbearNativeResult<()> { + let loader = scene_loader.lock(); + + if let Some(entry) = loader.get_entry(scene_id) { + if matches!(entry.result, SceneLoadResult::Success) { + let handle = crate::scene::loading::SceneLoadHandle { + id: scene_id, + scene_name: entry.scene_name.clone(), + }; + + command_buffer.try_send(CommandBuffer::SwitchToAsync(handle)) + .map_err(|_| DropbearNativeError::SendError)?; + Ok(()) + } else { + Err(DropbearNativeError::PrematureSceneSwitch) + } + } else { + Err(DropbearNativeError::NoSuchHandle) + } + } + + pub fn get_scene_load_handle_scene_name( + scene_loader: &Mutex<SceneLoader>, + scene_id: u64, + ) -> DropbearNativeResult<String> { + let loader = scene_loader.lock(); + + if let Some(entry) = loader.get_entry(scene_id) { + Ok(entry.scene_name.clone()) + } else { + Err(DropbearNativeError::NoSuchHandle) + } + } + + pub fn get_scene_load_progress( + scene_loader: &Mutex<SceneLoader>, + scene_id: u64, + ) -> DropbearNativeResult<crate::utils::Progress> { + let mut loader = scene_loader.lock(); + + if let Some(entry) = loader.get_entry_mut(scene_id) { + // Update progress from status channel if available + if let Some(ref rx) = entry.status { + while let Ok(status) = rx.try_recv() { + match status { + crate::states::WorldLoadingStatus::Idle => { + entry.progress.message = "Idle".to_string(); + }, + crate::states::WorldLoadingStatus::LoadingEntity { index, name, total } => { + entry.progress.current = index; + entry.progress.total = total; + entry.progress.message = format!("Loading entity: {}", name); + }, + crate::states::WorldLoadingStatus::Completed => { + entry.progress.current = entry.progress.total; + entry.progress.message = "Completed".to_string(); + } + } + } + } + + Ok(entry.progress.clone()) + } else { + Err(DropbearNativeError::NoSuchHandle) + } + } + + pub fn get_scene_load_status( + scene_loader: &Mutex<SceneLoader>, + scene_id: u64, + ) -> DropbearNativeResult<u32> { + let loader = scene_loader.lock(); + + if let Some(entry) = loader.get_entry(scene_id) { + let status = match entry.result { + SceneLoadResult::Pending => 0, // PENDING + SceneLoadResult::Success => 1, // READY + SceneLoadResult::Error(_) => 2, // FAILED + }; + Ok(status) + } else { + Err(DropbearNativeError::NoSuchHandle) + } + } +} + +pub mod jni { + #![allow(non_snake_case)] + use crate::command::CommandBuffer; + use crate::ptr::{CommandBufferPtr, SceneLoaderPtr}; + use crate::scripting::jni::utils::ToJObject; + use crate::{convert_jstring, convert_ptr}; + use jni::objects::{JClass, JString, JValue}; + use jni::sys::{jint, jlong, jobject}; + use jni::JNIEnv; + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_scene_SceneManagerNative_loadSceneAsyncNative__JJLjava_lang_String_2( + mut env: JNIEnv, + _: JClass, + command_buffer_ptr: jlong, + scene_manager_handle: jlong, + scene_name: JString, + ) -> jobject { + let command_buffer = convert_ptr!(command_buffer_ptr, CommandBufferPtr => crossbeam_channel::Sender<CommandBuffer>); + let scene_loader = convert_ptr!(scene_manager_handle, SceneLoaderPtr => parking_lot::Mutex<crate::scene::loading::SceneLoader>); + + let scene_name_str = convert_jstring!(env, scene_name); + + match super::shared::load_scene_async(command_buffer, scene_loader, scene_name_str, None) { + Ok(scene_id) => { + match env.find_class("java/lang/Long") { + Ok(long_class) => { + match env.new_object(long_class, "(J)V", &[JValue::Long(scene_id as i64)]) { + Ok(obj) => obj.into_raw(), + Err(e) => { + eprintln!("Failed to create Long object: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", + format!("Failed to create Long object: {}", e)); + std::ptr::null_mut() + } + } + } + Err(e) => { + eprintln!("Failed to find Long class: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", + format!("Failed to find Long class: {}", e)); + std::ptr::null_mut() + } + } + } + Err(e) => { + eprintln!("Failed to load scene async: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", + format!("Failed to load scene async: {:?}", e)); + std::ptr::null_mut() + } + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_scene_SceneManagerNative_loadSceneAsyncNative__JJLjava_lang_String_2Ljava_lang_String_2( + mut env: JNIEnv, + _: JClass, + command_buffer_ptr: jlong, + scene_manager_handle: jlong, + scene_name: JString, + loading_scene: JString, + ) -> jobject { + let command_buffer = convert_ptr!(command_buffer_ptr, CommandBufferPtr => crossbeam_channel::Sender<CommandBuffer>); + let scene_loader = convert_ptr!(scene_manager_handle, SceneLoaderPtr => parking_lot::Mutex<crate::scene::loading::SceneLoader>); + + let scene_name_str = convert_jstring!(env, scene_name); + let loading_scene_str = convert_jstring!(env, loading_scene); + + match super::shared::load_scene_async(command_buffer, scene_loader, scene_name_str, Some(loading_scene_str)) { + Ok(scene_id) => { + match env.find_class("java/lang/Long") { + Ok(long_class) => { + match env.new_object(long_class, "(J)V", &[JValue::Long(scene_id as i64)]) { + Ok(obj) => obj.into_raw(), + Err(e) => { + eprintln!("Failed to create Long object: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", + format!("Failed to create Long object: {}", e)); + std::ptr::null_mut() + } + } + } + Err(e) => { + eprintln!("Failed to find Long class: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", + format!("Failed to find Long class: {}", e)); + std::ptr::null_mut() + } + } + } + Err(e) => { + eprintln!("Failed to load scene async with loading scene: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", + format!("Failed to load scene async: {:?}", e)); + std::ptr::null_mut() + } + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_scene_SceneManagerNative_switchToSceneImmediateNative( + mut env: JNIEnv, + _: JClass, + command_buffer_ptr: jlong, + scene_name: JString, + ) { + let command_buffer = convert_ptr!(command_buffer_ptr, CommandBufferPtr => crossbeam_channel::Sender<CommandBuffer>); + + let scene_name_str = convert_jstring!(env, scene_name); + + if let Err(e) = super::shared::switch_to_scene_immediate(command_buffer, scene_name_str) { + eprintln!("Failed to switch scene immediate: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", + format!("Failed to switch scene immediate: {:?}", e)); + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_scene_SceneLoadHandleNative_getSceneLoadHandleSceneName( + mut env: JNIEnv, + _: JClass, + scene_loader_handle: jlong, + scene_id: jlong, + ) -> jobject { + let scene_loader = convert_ptr!(scene_loader_handle, SceneLoaderPtr => parking_lot::Mutex<crate::scene::loading::SceneLoader>); + + match super::shared::get_scene_load_handle_scene_name(scene_loader, scene_id as u64) { + Ok(scene_name) => { + match env.new_string(scene_name) { + Ok(jstring) => jstring.into_raw(), + Err(e) => { + eprintln!("Failed to create Java string: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", + format!("Failed to create Java string: {}", e)); + std::ptr::null_mut() + } + } + } + Err(e) => { + eprintln!("Failed to get scene name: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", + format!("Failed to get scene name: {:?}", e)); + std::ptr::null_mut() + } + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_scene_SceneLoadHandleNative_switchToSceneAsync( + mut env: JNIEnv, + _: JClass, + command_buffer_ptr: jlong, + scene_id: jlong, + ) { + let command_buffer = convert_ptr!(command_buffer_ptr, CommandBufferPtr => crossbeam_channel::Sender<CommandBuffer>); + let scene_loader = convert_ptr!(scene_id as SceneLoaderPtr => parking_lot::Mutex<crate::scene::loading::SceneLoader>); + + if let Err(e) = super::shared::switch_to_scene_async(command_buffer, scene_loader, scene_id as u64) { + eprintln!("Failed to switch scene async: {}", e); + + // Check if it's a premature scene switch error + if let crate::scripting::native::DropbearNativeError::PrematureSceneSwitch = e { + let _ = env.throw_new("com/dropbear/exception/PrematureSceneSwitchException", + "Cannot switch to scene before it has finished loading"); + } else { + let _ = env.throw_new("java/lang/RuntimeException", + format!("Failed to switch scene async: {:?}", e)); + } + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_scene_SceneLoadHandleNative_getSceneLoadProgress( + mut env: JNIEnv, + _: JClass, + scene_loader_handle: jlong, + scene_id: jlong, + ) -> jobject { + let scene_loader = convert_ptr!(scene_loader_handle, SceneLoaderPtr => parking_lot::Mutex<crate::scene::loading::SceneLoader>); + + match super::shared::get_scene_load_progress(scene_loader, scene_id as u64) { + Ok(progress) => { + match progress.to_jobject(&mut env) { + Ok(obj) => obj.into_raw(), + Err(e) => { + eprintln!("Failed to create Progress object: {:?}", e); + let _ = env.throw_new("java/lang/RuntimeException", + format!("Failed to create Progress object: {:?}", e)); + std::ptr::null_mut() + } + } + } + Err(e) => { + eprintln!("Failed to get scene load progress: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", + format!("Failed to get scene load progress: {:?}", e)); + std::ptr::null_mut() + } + } + } + + #[unsafe(no_mangle)] + pub fn Java_com_dropbear_scene_SceneLoadHandleNative_getSceneLoadStatus( + mut env: JNIEnv, + _: JClass, + scene_loader_handle: jlong, + scene_id: jlong, + ) -> jint { + let scene_loader = convert_ptr!(scene_loader_handle, SceneLoaderPtr => parking_lot::Mutex<crate::scene::loading::SceneLoader>); + + match super::shared::get_scene_load_status(scene_loader, scene_id as u64) { + Ok(status) => status as jint, + Err(e) => { + eprintln!("Failed to get scene load status: {}", e); + let _ = env.throw_new("java/lang/RuntimeException", + format!("Failed to get scene load status: {:?}", e)); + -1 as jint + } + } + } +} + +pub mod native { + use crate::ptr::{CommandBufferPtr, SceneLoaderPtr}; + use crate::scripting::native::DropbearNativeError; + use std::ffi::c_char; + use std::ffi::CStr; + + #[unsafe(no_mangle)] + pub extern "C" fn load_scene_async( + command_buffer_ptr: CommandBufferPtr, + scene_loader_ptr: SceneLoaderPtr, + scene_name: *const c_char, + loading_scene: *const c_char, + out_scene_id: *mut u64, + ) -> DropbearNativeError { + if command_buffer_ptr.is_null() || scene_loader_ptr.is_null() || scene_name.is_null() { + return DropbearNativeError::NullPointer; + } + + let command_buffer = unsafe { &*command_buffer_ptr }; + let scene_loader = unsafe { &*scene_loader_ptr }; + + let scene_name_cstr = unsafe { CStr::from_ptr(scene_name) }; + let scene_name_str = match scene_name_cstr.to_str() { + Ok(s) => s.to_string(), + Err(_) => return DropbearNativeError::InvalidUTF8, + }; + + let loading_scene_option = if !loading_scene.is_null() { + let loading_scene_cstr = unsafe { CStr::from_ptr(loading_scene) }; + match loading_scene_cstr.to_str() { + Ok(s) => Some(s.to_string()), + Err(_) => return DropbearNativeError::InvalidUTF8, + } + } else { + None + }; + + match super::shared::load_scene_async(command_buffer, scene_loader, scene_name_str, loading_scene_option) { + Ok(scene_id) => { + if !out_scene_id.is_null() { + unsafe { *out_scene_id = scene_id }; + } + DropbearNativeError::Success + } + Err(e) => e, + } + } + + #[unsafe(no_mangle)] + pub extern "C" fn switch_to_scene_immediate( + command_buffer_ptr: CommandBufferPtr, + scene_name: *const c_char, + ) -> DropbearNativeError { + if command_buffer_ptr.is_null() || scene_name.is_null() { + return DropbearNativeError::NullPointer; + } + + let command_buffer = unsafe { &*command_buffer_ptr }; + + let scene_name_cstr = unsafe { CStr::from_ptr(scene_name) }; + let scene_name_str = match scene_name_cstr.to_str() { + Ok(s) => s.to_string(), + Err(_) => return DropbearNativeError::InvalidUTF8, + }; + + match super::shared::switch_to_scene_immediate(command_buffer, scene_name_str) { + Ok(_) => DropbearNativeError::Success, + Err(e) => e, + } + } + + #[unsafe(no_mangle)] + pub extern "C" fn get_scene_load_handle_scene_name( + scene_loader_ptr: SceneLoaderPtr, + scene_id: u64, + out_buffer: *mut c_char, + buffer_size: usize, + ) -> DropbearNativeError { + if scene_loader_ptr.is_null() || out_buffer.is_null() { + return DropbearNativeError::NullPointer; + } + + let scene_loader = unsafe { &*scene_loader_ptr }; + + match super::shared::get_scene_load_handle_scene_name(scene_loader, scene_id) { + Ok(scene_name) => { + let c_string = match std::ffi::CString::new(scene_name) { + Ok(cstr) => cstr, + Err(_) => return DropbearNativeError::CStringError, + }; + + let bytes = c_string.as_bytes_with_nul(); + if bytes.len() > buffer_size { + return DropbearNativeError::BufferTooSmall; + } + + unsafe { + std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_buffer as *mut u8, bytes.len()); + } + + DropbearNativeError::Success + } + Err(e) => e, + } + } + + #[unsafe(no_mangle)] + pub extern "C" fn switch_to_scene_async( + command_buffer_ptr: CommandBufferPtr, + scene_loader_ptr: SceneLoaderPtr, + scene_id: u64, + ) -> DropbearNativeError { + if command_buffer_ptr.is_null() || scene_loader_ptr.is_null() { + return DropbearNativeError::NullPointer; + } + + let command_buffer = unsafe { &*command_buffer_ptr }; + let scene_loader = unsafe { &*scene_loader_ptr }; + + match super::shared::switch_to_scene_async(command_buffer, scene_loader, scene_id) { + Ok(_) => DropbearNativeError::Success, + Err(e) => e, + } + } + + #[unsafe(no_mangle)] + pub extern "C" fn get_scene_load_progress( + scene_loader_ptr: SceneLoaderPtr, + scene_id: u64, + out_current: *mut f64, + out_total: *mut f64, + out_message: *mut c_char, + message_buffer_size: usize, + ) -> DropbearNativeError { + if scene_loader_ptr.is_null() { + return DropbearNativeError::NullPointer; + } + + let scene_loader = unsafe { &*scene_loader_ptr }; + + match super::shared::get_scene_load_progress(scene_loader, scene_id) { + Ok(progress) => { + if !out_current.is_null() { + unsafe { *out_current = progress.current as f64 }; + } + if !out_total.is_null() { + unsafe { *out_total = progress.total as f64 }; + } + + if !out_message.is_null() && message_buffer_size > 0 { + let message_cstr = match std::ffi::CString::new(progress.message) { + Ok(cstr) => cstr, + Err(_) => return DropbearNativeError::CStringError, + }; + + let bytes = message_cstr.as_bytes_with_nul(); + let copy_len = std::cmp::min(bytes.len(), message_buffer_size); + + unsafe { + std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_message as *mut u8, copy_len); + } + } + + DropbearNativeError::Success + } + Err(e) => e, + } + } + + #[unsafe(no_mangle)] + pub extern "C" fn get_scene_load_status( + scene_loader_ptr: SceneLoaderPtr, + scene_id: u64, + ) -> i32 { + if scene_loader_ptr.is_null() { + return DropbearNativeError::NullPointer as i32; + } + + let scene_loader = unsafe { &*scene_loader_ptr }; + + match super::shared::get_scene_load_status(scene_loader, scene_id) { + Ok(status) => status as i32, + Err(e) => e as i32, + } + } +} + +impl crate::scripting::jni::utils::ToJObject for crate::utils::Progress { + fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> { + let class = env.find_class("com/dropbear/utils/Progress") + .map_err(|_| crate::scripting::native::DropbearNativeError::JNIClassNotFound)?; + + let message_jstring = env.new_string(&self.message) + .map_err(|_| crate::scripting::native::DropbearNativeError::JNIFailedToCreateObject)?; + + let obj = env.new_object(&class, "(DDLjava/lang/String;)V", &[ + JValue::Double(self.current as f64), + JValue::Double(self.total as f64), + JValue::Object(&JObject::from(message_jstring)), + ]) + .map_err(|_| crate::scripting::native::DropbearNativeError::JNIFailedToCreateObject)?; + + Ok(obj) + } +} @@ -6,14 +6,15 @@ 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; +use crate::states::{Script}; use anyhow::Context; use crossbeam_channel::Sender; use dropbear_engine::asset::ASSET_REGISTRY; @@ -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, @@ -229,6 +234,13 @@ impl ScriptManager { physics_state, }; + if world.is_null() { log::error!("World pointer is null"); } + if input.is_null() { log::error!("InputState pointer is null"); } + if graphics.is_null() { log::error!("CommandBuffer pointer is null"); } + if assets.is_null() { log::error!("AssetRegistry pointer is null"); } + if scene_loader.is_null() { log::error!("SceneLoader pointer is null"); } + if physics_state.is_null() { log::error!("PhysicsState pointer is null"); } + match &self.script_target { ScriptTarget::JVM { .. } => { if let Some(jvm) = &mut self.jvm { @@ -836,3 +848,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,23 +1,23 @@ #![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; use crate::logging::LOG_LEVEL; use crate::ptr::WorldPtr; use crate::scripting::error::LastErrorMessage; -use jni::objects::{GlobalRef, JClass, JLongArray, JObject, JValue}; +use jni::objects::{GlobalRef, JClass, JLongArray, JObject, JObjectArray, JString, JValue}; use jni::sys::jlong; -use jni::{InitArgsBuilder, JNIVersion, JavaVM}; +use jni::{InitArgsBuilder, JNIEnv, JNIVersion, JavaVM}; use sha2::{Digest, Sha256}; use std::fs; 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 { @@ -295,109 +295,197 @@ impl JavaContext { ) -> anyhow::Result<()> { let mut env = self.jvm.attach_current_thread()?; - let world_handle = context.world as jlong; - let input_handle = context.input as jlong; - let graphics_handle = context.graphics as jlong; - let asset_handle = context.assets as jlong; - let scene_loader_handle = context.scene_loader as jlong; - let physics_handle = context.physics_state as jlong; - - let dropbear_context_class: JClass = env.find_class("com/dropbear/ffi/DropbearContext")?; - let dropbear_context_obj = env.new_object( - dropbear_context_class, - "(JJJJJ)V", - &[ + let result = (|| -> anyhow::Result<()> { + let world_handle = context.world as jlong; + let input_handle = context.input as jlong; + let graphics_handle = context.graphics as jlong; + let asset_handle = context.assets as jlong; + let scene_loader_handle = context.scene_loader as jlong; + let physics_handle = context.physics_state as jlong; + + let args = [ JValue::Long(world_handle), JValue::Long(input_handle), JValue::Long(graphics_handle), JValue::Long(asset_handle), JValue::Long(scene_loader_handle), JValue::Long(physics_handle) - ] - )?; - - log::trace!("Locating \"com/dropbear/ffi/NativeEngine\" class"); - let native_engine_class: JClass = env.find_class("com/dropbear/ffi/NativeEngine")?; + ]; - let native_engine_obj = if let Some(ref native_engine_ref) = self.native_engine_instance { - native_engine_ref.as_obj() - } else { - log::trace!("Creating new instance of NativeEngine"); - let native_engine_obj = env.new_object(native_engine_class, "()V", &[])?; - let native_engine_global_ref = env.new_global_ref(native_engine_obj)?; - self.native_engine_instance = Some(native_engine_global_ref); - self.native_engine_instance - .as_ref() - .expect("NativeEngine global ref must exist") - .as_obj() - }; + let mut sig = String::from("("); + for _ in 0..args.len() { + sig.push('J'); + } + sig.push(')'); + sig.push('V'); + + let dropbear_context_class: JClass = env.find_class("com/dropbear/ffi/DropbearContext")?; + let dropbear_context_obj = env.new_object( + dropbear_context_class, + sig, + &args + )?; - log::trace!("Calling NativeEngine.init() with arg [\"com/dropbear/ffi/DropbearContext\"]"); - env.call_method( - native_engine_obj, - "init", - "(Lcom/dropbear/ffi/DropbearContext;)V", - &[JValue::Object(&dropbear_context_obj)], - )?; - - if self.dropbear_engine_class.is_none() { - let dropbear_class: JClass = env.find_class("com/dropbear/DropbearEngine")?; - log::trace!("Creating DropbearEngine constructor with arg (NativeEngine_object)"); - let dropbear_obj = env.new_object( - dropbear_class, - "(Lcom/dropbear/ffi/NativeEngine;)V", - &[JValue::Object(native_engine_obj)], + log::trace!("Locating \"com/dropbear/ffi/NativeEngine\" class"); + let native_engine_class: JClass = env.find_class("com/dropbear/ffi/NativeEngine")?; + + let native_engine_obj = if let Some(ref native_engine_ref) = self.native_engine_instance { + native_engine_ref.as_obj() + } else { + log::trace!("Creating new instance of NativeEngine"); + let native_engine_obj = env.new_object(native_engine_class, "()V", &[])?; + let native_engine_global_ref = env.new_global_ref(native_engine_obj)?; + self.native_engine_instance = Some(native_engine_global_ref); + self.native_engine_instance + .as_ref() + .expect("NativeEngine global ref must exist") + .as_obj() + }; + + log::trace!("Calling NativeEngine.init() with arg [\"com/dropbear/ffi/DropbearContext\"]"); + env.call_method( + native_engine_obj, + "init", + "(Lcom/dropbear/ffi/DropbearContext;)V", + &[JValue::Object(&dropbear_context_obj)], )?; - log::trace!("Creating new global ref for DropbearEngine"); - let engine_global_ref = env.new_global_ref(dropbear_obj)?; - self.dropbear_engine_class = Some(engine_global_ref); - } + if self.dropbear_engine_class.is_none() { + let dropbear_class: JClass = env.find_class("com/dropbear/DropbearEngine")?; + log::trace!("Creating DropbearEngine constructor with arg (NativeEngine_object)"); + let dropbear_obj = env.new_object( + dropbear_class, + "(Lcom/dropbear/ffi/NativeEngine;)V", + &[JValue::Object(native_engine_obj)], + )?; - let jar_path_jstring = env.new_string(self.jar_path.to_string_lossy())?; - let log_level_str = { LOG_LEVEL.lock().to_string() }; - let log_level_enum_class = env.find_class("com/dropbear/logging/LogLevel")?; - let log_level_enum_instance = env - .get_static_field( - log_level_enum_class, - log_level_str, - "Lcom/dropbear/logging/LogLevel;", - )? - .l()?; - - let std_out_writer_class = env.find_class("com/dropbear/logging/StdoutWriter")?; - let log_writer_obj = env.new_object(std_out_writer_class, "()V", &[])?; - - if self.system_manager_instance.is_none() { - let engine_ref = self - .dropbear_engine_class - .as_ref() - .expect("DropbearEngine global ref must exist") - .as_obj(); - - log::trace!("Locating \"com/dropbear/host/SystemManager\" class"); - let system_manager_class: JClass = env.find_class("com/dropbear/host/SystemManager")?; - log::trace!( + log::trace!("Creating new global ref for DropbearEngine"); + let engine_global_ref = env.new_global_ref(dropbear_obj)?; + self.dropbear_engine_class = Some(engine_global_ref); + } + + let jar_path_jstring = env.new_string(self.jar_path.to_string_lossy())?; + let log_level_str = { LOG_LEVEL.lock().to_string() }; + let log_level_enum_class = env.find_class("com/dropbear/logging/LogLevel")?; + let log_level_enum_instance = env + .get_static_field( + log_level_enum_class, + log_level_str, + "Lcom/dropbear/logging/LogLevel;", + )? + .l()?; + + let std_out_writer_class = env.find_class("com/dropbear/logging/StdoutWriter")?; + let log_writer_obj = env.new_object(std_out_writer_class, "()V", &[])?; + + if self.system_manager_instance.is_none() { + let engine_ref = self + .dropbear_engine_class + .as_ref() + .expect("DropbearEngine global ref must exist") + .as_obj(); + + log::trace!("Locating \"com/dropbear/host/SystemManager\" class"); + let system_manager_class: JClass = env.find_class("com/dropbear/host/SystemManager")?; + log::trace!( "Creating SystemManager constructor with args (jar_path_string, dropbear_engine_object, log_writer_object, log_level_enum, log_target_string)" ); - let log_target_jstring = env.new_string("dropbear_rust_host")?; - - let system_manager_obj = env.new_object( - system_manager_class, - "(Ljava/lang/String;Lcom/dropbear/DropbearEngine;Lcom/dropbear/logging/LogWriter;Lcom/dropbear/logging/LogLevel;Ljava/lang/String;)V", - &[ - JValue::Object(&jar_path_jstring), - JValue::Object(engine_ref), - JValue::Object(&log_writer_obj), - JValue::Object(&log_level_enum_instance), - JValue::Object(&log_target_jstring), - ], + let log_target_jstring = env.new_string("dropbear_rust_host")?; + + let system_manager_obj = env.new_object( + system_manager_class, + "(Ljava/lang/String;Lcom/dropbear/DropbearEngine;Lcom/dropbear/logging/LogWriter;Lcom/dropbear/logging/LogLevel;Ljava/lang/String;)V", + &[ + JValue::Object(&jar_path_jstring), + JValue::Object(engine_ref), + JValue::Object(&log_writer_obj), + JValue::Object(&log_level_enum_instance), + JValue::Object(&log_target_jstring), + ], + )?; + + log::trace!("Creating new global ref for SystemManager"); + let manager_global_ref = env.new_global_ref(system_manager_obj)?; + self.system_manager_instance = Some(manager_global_ref); + } + + Ok(()) + })(); + + Self::get_exception(&mut env)?; + + result + } + + pub fn get_exception(env: &mut JNIEnv) -> anyhow::Result<()> { + if let Ok(ex) = env.exception_occurred() { + if ex.is_null() { + return Ok(()); + } + + env.exception_clear()?; + + let message_result = env.call_method( + &ex, + "toString", + "()Ljava/lang/String;", + &[] )?; + let message_obj = message_result.l()?; + let message_jstring = JString::from(message_obj); + let message_str: String = env.get_string(&message_jstring)?.into(); + + let stack_trace_result = env.call_method( + &ex, + "getStackTrace", + "()[Ljava/lang/StackTraceElement;", + &[] + )?; + let stack_trace_obj = stack_trace_result.l()?; + let stack_trace_array = JObjectArray::from(stack_trace_obj); + let stack_len = env.get_array_length(&stack_trace_array)?; + + let mut error_msg = format!("{}\n", message_str); - log::trace!("Creating new global ref for SystemManager"); - let manager_global_ref = env.new_global_ref(system_manager_obj)?; - self.system_manager_instance = Some(manager_global_ref); + for i in 0..stack_len { + let element = env.get_object_array_element(&stack_trace_array, i)?; + + let element_str_result = env.call_method( + &element, + "toString", + "()Ljava/lang/String;", + &[] + )?; + let element_str_obj = element_str_result.l()?; + let element_jstring = JString::from(element_str_obj); + let element_string: String = env.get_string(&element_jstring)?.into(); + + error_msg.push_str(&format!(" at {}\n", element_string)); + } + + let cause_result = env.call_method( + &ex, + "getCause", + "()Ljava/lang/Throwable;", + &[] + )?; + let cause_obj = cause_result.l()?; + + if !cause_obj.is_null() { + let cause_str_result = env.call_method( + &cause_obj, + "toString", + "()Ljava/lang/String;", + &[] + )?; + let cause_str_obj = cause_str_result.l()?; + let cause_jstring = JString::from(cause_str_obj); + let cause_string: String = env.get_string(&cause_jstring)?.into(); + error_msg.push_str(&format!("Caused by: {}\n", cause_string)); + } + + return Err(anyhow::anyhow!("Java exception: {}", error_msg)); } Ok(()) @@ -412,85 +500,107 @@ impl JavaContext { if let Some(ref manager_ref) = self.system_manager_instance { let mut env = self.jvm.attach_current_thread()?; - log::trace!("Calling SystemManager.reloadJar()"); - let jar_path_jstring = env.new_string(self.jar_path.to_string_lossy())?; - env.call_method( - manager_ref, - "reloadJar", - "(Ljava/lang/String;)V", - &[JValue::Object(&jar_path_jstring)], - )?; + let result = (|| -> anyhow::Result<()> { + log::trace!("Calling SystemManager.reloadJar()"); + let jar_path_jstring = env.new_string(self.jar_path.to_string_lossy())?; + env.call_method( + manager_ref, + "reloadJar", + "(Ljava/lang/String;)V", + &[JValue::Object(&jar_path_jstring)], + )?; + Ok(()) + })(); + + Self::get_exception(&mut env)?; + + Ok(result?) } else { log::warn!("SystemManager instance not found during reload."); // self.init(world)?; return Err(anyhow::anyhow!("SystemManager not initialised for reload.")); } - - log::info!("Reload complete via SystemManager!"); - - Ok(()) } pub fn load_systems_for_tag(&mut self, tag: &str) -> anyhow::Result<()> { if let Some(ref manager_ref) = self.system_manager_instance { let mut env = self.jvm.attach_current_thread()?; - log::trace!( + let result = (|| -> anyhow::Result<()> { + log::trace!( "Calling SystemManager.loadSystemsForTag() with tag: {}", tag ); - let tag_jstring = env.new_string(tag)?; - env.call_method( - manager_ref, - "loadSystemsForTag", - "(Ljava/lang/String;)V", - &[JValue::Object(&tag_jstring)], - )?; + let tag_jstring = env.new_string(tag)?; + env.call_method( + manager_ref, + "loadSystemsForTag", + "(Ljava/lang/String;)V", + &[JValue::Object(&tag_jstring)], + )?; - log::debug!("Loaded systems for tag: {}", tag); + log::debug!("Loaded systems for tag: {}", tag); + Ok(()) + })(); + + Self::get_exception(&mut env)?; + + Ok(result?) } else { return Err(anyhow::anyhow!( "SystemManager not initialised when loading systems for tag: {}", tag )); } - Ok(()) } pub fn update_all_systems(&self, dt: f32) -> anyhow::Result<()> { if let Some(ref manager_ref) = self.system_manager_instance { let mut env = self.jvm.attach_current_thread()?; - log_once::trace_once!("Calling SystemManager.updateAllSystems() with dt: {}", dt); - env.call_method( - manager_ref, - "updateAllSystems", - "(F)V", - &[JValue::Float(dt)], - )?; + let result = (|| -> anyhow::Result<()> { + log_once::trace_once!("Calling SystemManager.updateAllSystems() with dt: {}", dt); + env.call_method( + manager_ref, + "updateAllSystems", + "(F)V", + &[JValue::Float(dt)], + )?; + + log_once::trace_once!("Updated all systems with dt: {}", dt); + + Ok(()) + })(); - log_once::trace_once!("Updated all systems with dt: {}", dt); + Self::get_exception(&mut env)?; + + Ok(result?) } else { return Err(anyhow::anyhow!( "SystemManager not initialised when updating systems." )); } - Ok(()) } pub fn physics_update_all_systems(&self, dt: f32) -> anyhow::Result<()> { if let Some(ref manager_ref) = self.system_manager_instance { let mut env = self.jvm.attach_current_thread()?; - log_once::trace_once!("Calling SystemManager.physicsUpdateAllSystems() with dt: {}", dt); - env.call_method( - manager_ref, - "physicsUpdateAllSystems", - "(F)V", - &[JValue::Float(dt)], - )?; + let result = (|| -> anyhow::Result<()> { + log_once::trace_once!("Calling SystemManager.physicsUpdateAllSystems() with dt: {}", dt); + env.call_method( + manager_ref, + "physicsUpdateAllSystems", + "(F)V", + &[JValue::Float(dt)], + )?; - Ok(()) + Ok(()) + })(); + + Self::get_exception(&mut env)?; + + Ok(result?) } else { Err(anyhow::anyhow!( "SystemManager not initialised when physics updating systems." @@ -502,47 +612,59 @@ impl JavaContext { if let Some(ref manager_ref) = self.system_manager_instance { let mut env = self.jvm.attach_current_thread()?; - log::trace!( + let result = (|| -> anyhow::Result<()> { + log::trace!( "Calling SystemManager.updateSystemsByTag() with tag: {}, dt: {}", tag, dt ); - let tag_jstring = env.new_string(tag)?; - env.call_method( - manager_ref, - "updateSystemsByTag", - "(Ljava/lang/String;F)V", - &[JValue::Object(&tag_jstring), JValue::Float(dt)], - )?; + let tag_jstring = env.new_string(tag)?; + env.call_method( + manager_ref, + "updateSystemsByTag", + "(Ljava/lang/String;F)V", + &[JValue::Object(&tag_jstring), JValue::Float(dt)], + )?; + + log::debug!("Updated systems for tag: {} with dt: {}", tag, dt); + Ok(()) + })(); - log::debug!("Updated systems for tag: {} with dt: {}", tag, dt); + Self::get_exception(&mut env)?; + + Ok(result?) } else { return Err(anyhow::anyhow!( "SystemManager not initialised when updating systems for tag: {}", tag )); } - Ok(()) } pub fn physics_update_systems_for_tag(&self, tag: &str, dt: f32) -> anyhow::Result<()> { if let Some(ref manager_ref) = self.system_manager_instance { let mut env = self.jvm.attach_current_thread()?; - log::trace!( - "Calling SystemManager.physicsUpdateSystemsByTag() with tag: {}, dt: {}", - tag, - dt - ); - let tag_jstring = env.new_string(tag)?; - env.call_method( - manager_ref, - "physicsUpdateSystemsByTag", - "(Ljava/lang/String;F)V", - &[JValue::Object(&tag_jstring), JValue::Float(dt)], - )?; + let result = (|| -> anyhow::Result<()> { + log::trace!( + "Calling SystemManager.physicsUpdateSystemsByTag() with tag: {}, dt: {}", + tag, + dt + ); + let tag_jstring = env.new_string(tag)?; + env.call_method( + manager_ref, + "physicsUpdateSystemsByTag", + "(Ljava/lang/String;F)V", + &[JValue::Object(&tag_jstring), JValue::Float(dt)], + )?; - Ok(()) + Ok(()) + })(); + + Self::get_exception(&mut env)?; + + Ok(result?) } else { Err(anyhow::anyhow!( "SystemManager not initialised when physics updating systems for tag: {}", @@ -560,49 +682,55 @@ impl JavaContext { if let Some(ref manager_ref) = self.system_manager_instance { let mut env = self.jvm.attach_current_thread()?; - log::trace!( - "Calling SystemManager.updateSystemsForEntities() with tag: {}, count: {}, dt: {}", - tag, - entity_ids.len(), - dt - ); + let result = (|| -> anyhow::Result<()> { + log::trace!( + "Calling SystemManager.updateSystemsForEntities() with tag: {}, count: {}, dt: {}", + tag, + entity_ids.len(), + dt + ); - let tag_jstring = env.new_string(tag)?; - let entity_array: JLongArray = env.new_long_array(entity_ids.len() as i32)?; - let entity_array_raw = entity_array.as_raw(); - log::trace!("u64 entity: {:?}", entity_ids); - log::trace!( - "i64 entity: {:?}", - entity_ids.iter().map(|e| *e as i64).collect::<Vec<_>>() - ); - if !entity_ids.is_empty() { - env.set_long_array_region( - entity_array, - 0, - &entity_ids.iter().map(|e| *e as i64).collect::<Vec<_>>(), + let tag_jstring = env.new_string(tag)?; + let entity_array: JLongArray = env.new_long_array(entity_ids.len() as i32)?; + let entity_array_raw = entity_array.as_raw(); + log::trace!("u64 entity: {:?}", entity_ids); + log::trace!( + "i64 entity: {:?}", + entity_ids.iter().map(|e| *e as i64).collect::<Vec<_>>() + ); + if !entity_ids.is_empty() { + env.set_long_array_region( + entity_array, + 0, + &entity_ids.iter().map(|e| *e as i64).collect::<Vec<_>>(), + )?; + } + let entity_array_obj = + unsafe { JObject::from_raw(entity_array_raw.cast::<jni::sys::_jobject>()) }; + + env.call_method( + manager_ref, + "updateSystemsForEntities", + "(Ljava/lang/String;[JF)V", + &[ + JValue::Object(&tag_jstring), + JValue::Object(&entity_array_obj), + JValue::Float(dt), + ], )?; - } - let entity_array_obj = - unsafe { JObject::from_raw(entity_array_raw.cast::<jni::sys::_jobject>()) }; - env.call_method( - manager_ref, - "updateSystemsForEntities", - "(Ljava/lang/String;[JF)V", - &[ - JValue::Object(&tag_jstring), - JValue::Object(&entity_array_obj), - JValue::Float(dt), - ], - )?; + log::trace!( + "Updated systems for tag: {} across {} entities with dt: {}", + tag, + entity_ids.len(), + dt + ); + Ok(()) + })(); - log::trace!( - "Updated systems for tag: {} across {} entities with dt: {}", - tag, - entity_ids.len(), - dt - ); - Ok(()) + Self::get_exception(&mut env)?; + + Ok(result?) } else { Err(anyhow::anyhow!( "SystemManager not initialised when updating systems for tag: {}", @@ -620,38 +748,44 @@ impl JavaContext { if let Some(ref manager_ref) = self.system_manager_instance { let mut env = self.jvm.attach_current_thread()?; - log::trace!( - "Calling SystemManager.physicsUpdateSystemsForEntities() with tag: {}, count: {}, dt: {}", - tag, - entity_ids.len(), - dt - ); + let result = (|| -> anyhow::Result<()> { + log::trace!( + "Calling SystemManager.physicsUpdateSystemsForEntities() with tag: {}, count: {}, dt: {}", + tag, + entity_ids.len(), + dt + ); - let tag_jstring = env.new_string(tag)?; - let entity_array: JLongArray = env.new_long_array(entity_ids.len() as i32)?; - let entity_array_raw = entity_array.as_raw(); - if !entity_ids.is_empty() { - env.set_long_array_region( - entity_array, - 0, - &entity_ids.iter().map(|e| *e as i64).collect::<Vec<_>>(), + let tag_jstring = env.new_string(tag)?; + let entity_array: JLongArray = env.new_long_array(entity_ids.len() as i32)?; + let entity_array_raw = entity_array.as_raw(); + if !entity_ids.is_empty() { + env.set_long_array_region( + entity_array, + 0, + &entity_ids.iter().map(|e| *e as i64).collect::<Vec<_>>(), + )?; + } + let entity_array_obj = + unsafe { JObject::from_raw(entity_array_raw.cast::<jni::sys::_jobject>()) }; + + env.call_method( + manager_ref, + "physicsUpdateSystemsForEntities", + "(Ljava/lang/String;[JF)V", + &[ + JValue::Object(&tag_jstring), + JValue::Object(&entity_array_obj), + JValue::Float(dt), + ], )?; - } - let entity_array_obj = - unsafe { JObject::from_raw(entity_array_raw.cast::<jni::sys::_jobject>()) }; - env.call_method( - manager_ref, - "physicsUpdateSystemsForEntities", - "(Ljava/lang/String;[JF)V", - &[ - JValue::Object(&tag_jstring), - JValue::Object(&entity_array_obj), - JValue::Float(dt), - ], - )?; + Ok(()) + })(); - Ok(()) + Self::get_exception(&mut env)?; + + Ok(result?) } else { Err(anyhow::anyhow!( "SystemManager not initialised when physics updating systems for tag: {}", @@ -664,16 +798,22 @@ impl JavaContext { if let Some(ref manager_ref) = self.system_manager_instance { let mut env = self.jvm.attach_current_thread()?; - log::trace!("Calling SystemManager.unloadSystemsByTag() with tag: {}", tag); - let tag_jstring = env.new_string(tag)?; - env.call_method( - manager_ref, - "unloadSystemsByTag", - "(Ljava/lang/String;)V", - &[JValue::Object(&tag_jstring)], - )?; + let result = (|| -> anyhow::Result<()> { + log::trace!("Calling SystemManager.unloadSystemsByTag() with tag: {}", tag); + let tag_jstring = env.new_string(tag)?; + env.call_method( + manager_ref, + "unloadSystemsByTag", + "(Ljava/lang/String;)V", + &[JValue::Object(&tag_jstring)], + )?; - Ok(()) + Ok(()) + })(); + + Self::get_exception(&mut env)?; + + Ok(result?) } else { Err(anyhow::anyhow!( "SystemManager not initialised when unloading systems for tag: {}", @@ -686,19 +826,25 @@ impl JavaContext { if let Some(ref manager_ref) = self.system_manager_instance { let mut env = self.jvm.attach_current_thread()?; - log::trace!( - "Calling SystemManager.destroySystemsByTag() with tag: {}", - tag - ); - let tag_jstring = env.new_string(tag)?; - env.call_method( - manager_ref, - "destroySystemsByTag", - "(Ljava/lang/String;)V", - &[JValue::Object(&tag_jstring)], - )?; + let result = (|| -> anyhow::Result<()> { + log::trace!( + "Calling SystemManager.destroySystemsByTag() with tag: {}", + tag + ); + let tag_jstring = env.new_string(tag)?; + env.call_method( + manager_ref, + "destroySystemsByTag", + "(Ljava/lang/String;)V", + &[JValue::Object(&tag_jstring)], + )?; - Ok(()) + Ok(()) + })(); + + Self::get_exception(&mut env)?; + + Ok(result?) } else { Err(anyhow::anyhow!( "SystemManager not initialised when destroying systems for tag: {}", @@ -711,9 +857,15 @@ impl JavaContext { if let Some(ref manager_ref) = self.system_manager_instance { let mut env = self.jvm.attach_current_thread()?; - log::trace!("Calling SystemManager.unloadAllSystems()"); - env.call_method(manager_ref, "unloadAllSystems", "()V", &[])?; - Ok(()) + let result = (|| -> anyhow::Result<()> { + log::trace!("Calling SystemManager.unloadAllSystems()"); + env.call_method(manager_ref, "unloadAllSystems", "()V", &[])?; + Ok(()) + })(); + + Self::get_exception(&mut env)?; + + Ok(result?) } else { Err(anyhow::anyhow!( "SystemManager not initialised when unloading all systems." @@ -725,16 +877,22 @@ impl JavaContext { if let Some(ref manager_ref) = self.system_manager_instance { let mut env = self.jvm.attach_current_thread()?; - log::trace!("Calling SystemManager.getSystemCount() for tag: {}", tag); - let tag_jstring = env.new_string(tag)?; - let result = env.call_method( - manager_ref, - "getSystemCount", - "(Ljava/lang/String;)I", - &[JValue::Object(&tag_jstring)], - )?; + let result = (|| -> anyhow::Result<i32> { + log::trace!("Calling SystemManager.getSystemCount() for tag: {}", tag); + let tag_jstring = env.new_string(tag)?; + let result = env.call_method( + manager_ref, + "getSystemCount", + "(Ljava/lang/String;)I", + &[JValue::Object(&tag_jstring)], + )?; - Ok(result.i()?) + Ok(result.i()?) + })(); + + Self::get_exception(&mut env)?; + + Ok(result?) } else { Err(anyhow::anyhow!( "SystemManager not initialised when getting system count for tag: {}", @@ -747,16 +905,22 @@ impl JavaContext { if let Some(ref manager_ref) = self.system_manager_instance { let mut env = self.jvm.attach_current_thread()?; - log::trace!("Calling SystemManager.hasSystemsForTag() for tag: {}", tag); - let tag_jstring = env.new_string(tag)?; - let result = env.call_method( - manager_ref, - "hasSystemsForTag", - "(Ljava/lang/String;)Z", - &[JValue::Object(&tag_jstring)], - )?; + let result = (|| -> anyhow::Result<bool> { + log::trace!("Calling SystemManager.hasSystemsForTag() for tag: {}", tag); + let tag_jstring = env.new_string(tag)?; + let result = env.call_method( + manager_ref, + "hasSystemsForTag", + "(Ljava/lang/String;)Z", + &[JValue::Object(&tag_jstring)], + )?; - Ok(result.z()?) + Ok(result.z()?) + })(); + + Self::get_exception(&mut env)?; + + Ok(result?) } else { Err(anyhow::anyhow!( "SystemManager not initialised when checking for systems for tag: {}", @@ -769,10 +933,16 @@ impl JavaContext { if let Some(ref manager_ref) = self.system_manager_instance { let mut env = self.jvm.attach_current_thread()?; - log::trace!("Calling SystemManager.getTotalSystemCount()"); - let result = env.call_method(manager_ref, "getTotalSystemCount", "()I", &[])?; + let result = (|| -> anyhow::Result<i32> { + log::trace!("Calling SystemManager.getTotalSystemCount()"); + let result = env.call_method(manager_ref, "getTotalSystemCount", "()I", &[])?; + + Ok(result.i()?) + })(); + + Self::get_exception(&mut env)?; - Ok(result.i()?) + Ok(result?) } else { Err(anyhow::anyhow!( "SystemManager not initialised when getting total system count." @@ -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,20 @@ 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> - where - Self: Sized - { - let x_obj = env - .get_field(obj, "x", "Ljava/lang/Number;")?.l()?; - let y_obj = env - .get_field(obj, "y", "Ljava/lang/Number;")?.l()?; - let z_obj = env - .get_field(obj, "z", "Ljava/lang/Number;")?.l()?; - - let x = env - .call_method(&x_obj, "doubleValue", "()D", &[])?.d()?; - let y = env - .call_method(&y_obj, "doubleValue", "()D", &[])?.d()?; - let z = env - .call_method(&z_obj, "doubleValue", "()D", &[])?.d()?; - - 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>>; } @@ -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,8 @@ impl LastErrorMessage for NativeLibrary { } } +#[repr(C)] +#[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 +359,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 +426,31 @@ 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, + PhysicsObjectNotFound = -23, + + /// 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 +459,40 @@ 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)", + DropbearNativeError::PhysicsObjectNotFound => "PhysicsObjectNotFound (-23)", + }) } } @@ -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,377 @@ +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; +use crate::types::Vector3; + +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 = Vector3::from_jobject(env, &pos_obj)?.into(); + let scale: DVec3 = Vector3::from_jobject(env, &scale_obj)?.into(); + + 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,473 @@ +//! 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 jni::sys::jdouble; +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] + } + pub fn to_float_array(&self) -> [f32; 3] { + [self.x as f32, self.y as f32, self.z as f32] + } +} + +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<[f32; 3]> for Vector3 { + fn from(value: [f32; 3]) -> Self { + Vector3 { + x: value[0] as f64, + y: value[1] as f64, + z: value[2] as f64, + } + } +} + +impl From<Vector3> for glam::DVec3 { + fn from(v: Vector3) -> Self { + Self::new(v.x, v.y, v.z) + } +} + +impl FromJObject for Vector3 { + fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> + where + Self: Sized + { + let class = env.find_class("com/dropbear/math/Vector3d") + .map_err(|_| DropbearNativeError::JNIClassNotFound)?; + + if !env.is_instance_of(obj, &class) + .map_err(|_| DropbearNativeError::JNIUnwrapFailed)? + { + return Err(DropbearNativeError::InvalidArgument); + } + + let x = env.get_field(obj, "x", "D") + .map_err(|_| DropbearNativeError::JNIFailedToGetField)? + .d() + .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?; + + let y = env.get_field(obj, "y", "D") + .map_err(|_| DropbearNativeError::JNIFailedToGetField)? + .d() + .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?; + + let z = env.get_field(obj, "z", "D") + .map_err(|_| DropbearNativeError::JNIFailedToGetField)? + .d() + .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?; + + Ok(Vector3::new(x, y, z)) + } +} + +impl ToJObject for Vector3 { + fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> { + let class = env.find_class("com/dropbear/math/Vector3d") + .map_err(|_| DropbearNativeError::JNIClassNotFound)?; + + let constructor_sig = "(DDD)V"; + + let args = [ + jni::objects::JValue::Double(self.x), + jni::objects::JValue::Double(self.y), + jni::objects::JValue::Double(self.z), + ]; + + let obj = env.new_object(&class, constructor_sig, &args) + .map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?; + + Ok(obj) + } +} + +#[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, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct RigidBodyContext { + pub index: IndexNative, + pub entity_id: u64, +} + +impl FromJObject for RigidBodyContext { + 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 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)?; + + let entity_obj = env.get_field(obj, "entity", "Lcom/dropbear/EntityId;") + .map_err(|_| DropbearNativeError::JNIFailedToGetField)? + .l() + .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?; + + let entity_raw = env.get_field(&entity_obj, "raw", "J") + .map_err(|_| DropbearNativeError::JNIFailedToGetField)? + .j() + .map_err(|_| DropbearNativeError::JNIUnwrapFailed)?; + + Ok(RigidBodyContext { + index: IndexNative { + index: idx_val as u32, + generation: gen_val as u32, + }, + entity_id: entity_raw as u64, + }) + } +} + +impl ToJObject for RigidBodyContext { + fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> { + let index_cls = env.find_class("com/dropbear/physics/Index") + .map_err(|e| { + eprintln!("[JNI Error] Class 'com/dropbear/physics/Index' not found: {:?}", e); + DropbearNativeError::JNIClassNotFound + })?; + + let entity_cls = env.find_class("com/dropbear/EntityId") + .map_err(|e| { + eprintln!("[JNI Error] Class 'com/dropbear/EntityId' not found: {:?}", e); + DropbearNativeError::JNIClassNotFound + })?; + + let rb_cls = env.find_class("com/dropbear/physics/RigidBody") + .map_err(|e| { + eprintln!("[JNI Error] Class 'com/dropbear/physics/RigidBody' not found: {:?}", e); + DropbearNativeError::JNIClassNotFound + })?; + + 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(|e| { + eprintln!("[JNI Error] Failed to create Index object: {:?}", e); + DropbearNativeError::JNIFailedToCreateObject + })?; + + let entity_obj = env.new_object( + &entity_cls, + "(J)V", + &[JValue::Long(self.entity_id as i64)] + ).map_err(|e| { + eprintln!("[JNI Error] Failed to create EntityId object: {:?}", e); + DropbearNativeError::JNIFailedToCreateObject + })?; + + let rb_obj = env.new_object( + rb_cls, + "(Lcom/dropbear/physics/Index;Lcom/dropbear/EntityId;)V", + &[ + JValue::Object(&index_obj), + JValue::Object(&entity_obj) + ] + ).map_err(|e| { + eprintln!("[JNI Error] Failed to create RigidBody object: {:?}", e); + DropbearNativeError::JNIFailedToCreateObject + })?; + + Ok(rb_obj) + } +} @@ -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,3 +1,12 @@ # eucalyptus-editor -The primary editor used to make games on the dropbear engine. +The primary editor used to make games on the dropbear engine. It is scripted in Kotlin and is JVM first-party +supported. + +## Other functions + +To run a local play test, run `eucalyptus-editor.exe play {project_dir}` with optional jvm args specified through +`--jvm-args {jvm_args}`. + +eucalyptus-editor does not have a scripting interface, therefore can be run without the Java Virtual Machine. If you do +wish to enter into "Play Mode" or start testing your game, you will require a form of Java Development Kit (JDK). @@ -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, @@ -478,9 +479,9 @@ impl<'a> TabViewer for EditorTabViewer<'a> { log_once::debug_once!("Available components: "); for (id, name) in registry.iter_available_components() { log_once::debug_once!("id: {}, name: {}", id, name); - if name.contains("EntityTransform") { - continue; - } + // if name.contains("EntityTransform") { + // continue; + // } let short_name = name.split("::").last().unwrap_or(name); let display_name = @@ -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 { @@ -1,8 +1,9 @@ use crossbeam_channel::unbounded; +use glam::DMat4; 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 +14,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 +22,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) { @@ -472,18 +474,23 @@ impl Scene for Editor { for (_entity, (entity_transform, group)) in q.iter() { for collider in &group.colliders { - let entity_matrix = entity_transform.sync().matrix().as_mat4(); + let world_tf = entity_transform.sync(); + + let entity_matrix = DMat4::from_rotation_translation( + world_tf.rotation, + world_tf.position + ).as_mat4(); let offset_transform = Transform::new() .with_offset(collider.translation, collider.rotation); + let offset_matrix = offset_transform.matrix().as_mat4(); let final_matrix = entity_matrix * offset_matrix; let color = [0.0, 1.0, 0.0, 1.0]; - let collider_uniform = ColliderUniform::from_matrix(final_matrix, color); - + let collider_buffer = graphics.shared.device.create_buffer_init( &wgpu::util::BufferInitDescriptor { label: Some("Collider Uniform Buffer"), @@ -41,8 +41,11 @@ impl InspectableComponent for RigidBody { .range(0.0..=10.0)); }); + ui.checkbox(&mut self.can_sleep, "Can Sleep"); + ui.checkbox(&mut self.sleeping, "Initially sleeping?"); + ui.checkbox(&mut self.ccd_enabled, "CCD Enabled"); ui.add_space(8.0); @@ -219,8 +219,9 @@ impl PlayMode { .script_manager .init_script(None, entity_tag_map.clone(), target.clone()) { - log::error!("Failed to initialise scripts: {}", e); - return; + panic!("Failed to initialise scripts: {}", e); + } else { + log::debug!("Initialised scripts successfully!"); } let world_ptr = self.world.as_mut() as WorldPtr; @@ -232,12 +233,13 @@ impl PlayMode { .script_manager .load_script(world_ptr, input_ptr, graphics_ptr, physics_ptr) { - log::error!("Failed to load scripts: {}", e); - return; + panic!("Failed to load scripts: {}", e); + } else { + log::debug!("Loaded scripts successfully!"); } self.scripts_ready = true; - log::debug!("Scripts initialised successfully"); + log::debug!("Scripts reloaded successfully!"); } /// Requests an asynchronous scene load, returning immediately and loading the scene in the background. @@ -381,6 +383,7 @@ impl PlayMode { if let Some(physics_state) = self.pending_physics_state.take() { self.physics_state = physics_state; } + self.has_initial_resize_done = false; if let Some(new_camera) = self.pending_camera.take() { self.active_camera = Some(new_camera); } @@ -2,7 +2,7 @@ use std::collections::HashMap; use egui::{CentralPanel, MenuBar, TopBottomPanel}; use eucalyptus_core::physics::collider::ColliderGroup; use eucalyptus_core::physics::collider::shader::ColliderUniform; -use glam::{DQuat, DVec3}; +use glam::{DMat4, DQuat, DVec3}; use hecs::Entity; use wgpu::Color; use wgpu::util::DeviceExt; @@ -93,11 +93,11 @@ impl Scene for PlayMode { let new_local_pos = (inv_p_rot * relative_pos) / p_scale; let new_local_rot = inv_p_rot * new_world_rot; - let local = transform.local_mut(); + let local = transform.world_mut(); local.position = new_local_pos; local.rotation = new_local_rot; } else { - let local = transform.local_mut(); + let local = transform.world_mut(); local.position = new_world_pos; local.rotation = new_world_rot; } @@ -472,16 +472,21 @@ impl Scene for PlayMode { for (_entity, (entity_transform, group)) in q.iter() { for collider in &group.colliders { - let entity_matrix = entity_transform.sync().matrix().as_mat4(); + let world_tf = entity_transform.sync(); + + let entity_matrix = DMat4::from_rotation_translation( + world_tf.rotation, + world_tf.position + ).as_mat4(); let offset_transform = Transform::new() .with_offset(collider.translation, collider.rotation); + let offset_matrix = offset_transform.matrix().as_mat4(); let final_matrix = entity_matrix * offset_matrix; let color = [0.0, 1.0, 0.0, 1.0]; - let collider_uniform = ColliderUniform::from_matrix(final_matrix, color); let collider_buffer = graphics.shared.device.create_buffer_init( @@ -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,5 +1,5 @@ [versions] -kotlin = "2.1.0" +kotlin = "2.3.0" kotlinxSerialization = "1.8.0" [libraries] @@ -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 +0,0 @@ -/** - * dropbear-engine native header definitions. Created by tirbofish as part of the dropbear project. - * - * 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`. - * - * 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. - * - * 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. - * - * Licensed under MIT or Apache 2.0 depending on your mood. - */ - -#ifndef DROPBEAR_H -#define DROPBEAR_H - -#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 @@ -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 @@ -0,0 +1,19 @@ +#pragma once + +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; @@ -52,7 +52,7 @@ impl Generator for KotlinJVMGenerator { writeln!(output, "object RunnableRegistry {{")?; writeln!( output, - " private val tagRegistry = mutableMapOf<String, MutableList<() -> com.dropbear.System>>()" + " private val tagRegistry = mutableMapOf<String, MutableList<() -> com.dropbear.ecs.System>>()" )?; writeln!(output)?; @@ -84,7 +84,7 @@ impl Generator for KotlinJVMGenerator { writeln!( output, - " fun getScriptFactories(tag: String): List<() -> com.dropbear.System> {{" + " fun getScriptFactories(tag: String): List<() -> com.dropbear.ecs.System> {{" )?; writeln!( output, @@ -95,7 +95,7 @@ impl Generator for KotlinJVMGenerator { writeln!( output, - " fun instantiateScripts(tag: String): List<com.dropbear.System> {{" + " fun instantiateScripts(tag: String): List<com.dropbear.ecs.System> {{" )?; writeln!( output, @@ -28,7 +28,7 @@ impl Generator for KotlinNativeGenerator { writeln!( output, r#"import com.dropbear.DropbearEngine -import com.dropbear.System +import com.dropbear.ecs.System import com.dropbear.ffi.NativeEngine import com.dropbear.ffi.generated.DropbearContext import com.dropbear.logging.Logger @@ -6,14 +6,14 @@ pluginManagement { gradlePluginPortal() } } + plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } -include("magna-carta-plugin") +//include("magna-carta-plugin") buildCache { local { isEnabled = true } -} -include("dropbear-gradle-plugin") +} @@ -0,0 +1,4 @@ +# dropbear + +This is the scripting module API interface used for adding logic to entities and scenes on eucalyptus-editor based +games. @@ -1,46 +0,0 @@ -package com.dropbear - -import com.dropbear.math.Vector3D - -/** - * Describes a 3D camera. - */ -class Camera( - val label: String, - val id: EntityId, // it could be attached to nothing or an AdoptedEntity - var eye: Vector3D = Vector3D.zero(), - var target: Vector3D = Vector3D.zero(), - var up: Vector3D = Vector3D.zero(), - val aspect: Double = 0.0, - var fov_y: Double = 0.0, - var znear: Double = 0.0, - var zfar: Double = 0.0, - var yaw: Double = 0.0, - var pitch: Double = 0.0, - var speed: Double = 0.0, - var sensitivity: Double = 0.0 -) { - internal lateinit var engine: DropbearEngine - - /** - * Pushes the camera values to the world to be updated. - */ - fun setCamera() { - engine.native.setCamera(this) - } - - override fun toString(): String { - return "Camera '${label}' of id $id \n" + - "eye: $eye\n" + - "target: $target\n" + - "up: $up\n " + - "aspect: $aspect \n" + - "fov_y: $fov_y \n" + - "znear: $znear \n" + - "zfar: $zfar \n" + - "yaw: $yaw \n" + - "pitch: $pitch" + - "speed: $speed" + - "sensitivity: $sensitivity" - } -} @@ -1,11 +1,11 @@ package com.dropbear import com.dropbear.asset.AssetHandle +import com.dropbear.components.Camera import com.dropbear.ffi.NativeEngine import com.dropbear.input.InputState import com.dropbear.logging.Logger import com.dropbear.scene.SceneManager -import com.dropbear.ui.UIManager internal var exceptionOnError: Boolean = false var lastErrorMessage: String? = null @@ -17,11 +17,16 @@ var lastErrorMessage: String? = null * which contains a [NativeEngine] (contains native functions). */ class DropbearEngine(val native: NativeEngine) { - private var inputState: InputState? = null - private var sceneManager: SceneManager? = null - private var uiManager: UIManager? = null + val inputState: InputState = InputState() + val sceneManager: SceneManager = SceneManager() + + init { + Companion.native = native + } companion object { + lateinit var native: NativeEngine + fun getLastErrMsg(): String? { return lastErrorMessage } @@ -40,57 +45,12 @@ class DropbearEngine(val native: NativeEngine) { * Fetches an [EntityRef] with the given label. */ fun getEntity(label: String): EntityRef? { - val entityId = native.getEntity(label) + val entityId = com.dropbear.getEntity(label) val entityRef = if (entityId != null) EntityRef(EntityId(entityId)) else null - entityRef?.engine = this return entityRef } /** - * Fetches the information of the camera with the given label. - */ - fun getCamera(label: String): Camera? { - val result = native.getCamera(label) - if (result != null) { - result.engine = this - } - return result - } - - /** - * Gets the current [InputState] for that frame. - */ - fun getInputState(): InputState { - if (this.inputState == null) { - Logger.trace("InputState not initialised, creating new one") - this.inputState = InputState(native) - } - return this.inputState!! - } - - /** - * Gets the current [SceneManager] for that frame. - */ - fun getSceneManager(): SceneManager { - if (this.sceneManager == null) { - Logger.trace("SceneManager not initialised, creating new one") - this.sceneManager = SceneManager(native) - } - return this.sceneManager!! - } - - /** - * Gets the current [UIManager] for that frame. - */ - fun getUIManager(): UIManager { - if (this.uiManager == null) { - Logger.trace("UiManager not initialised, creating new one") - this.uiManager = UIManager(native) - } - return this.uiManager!! - } - - /** * Fetches the asset information from the internal AssetRegistry (located in * `dropbear_engine::asset::AssetRegistry`). * @@ -98,7 +58,7 @@ class DropbearEngine(val native: NativeEngine) { * The eucalyptus asset URI (or `euca://`) is case-sensitive. */ fun getAsset(eucaURI: String): AssetHandle? { - val id = native.getAsset(eucaURI) + val id = com.dropbear.getAsset(eucaURI) return if (id != null) AssetHandle(id) else null } @@ -110,7 +70,7 @@ class DropbearEngine(val native: NativeEngine) { fun callExceptionOnError(toggle: Boolean) = DropbearEngine.callExceptionOnError(toggle) /** - * Quits the currently running app or game. + * Quits the currently running app or game elegantly. * * This function can have different behaviours depending on where it is ran. * - eucalyptus-editor - When called, this exits your Play Mode session and returns you back to @@ -119,6 +79,10 @@ class DropbearEngine(val native: NativeEngine) { * also drop any pointers and do any additional cleanup. */ fun quit() { - native.quit() + com.dropbear.quit() } -} +} + +expect fun getEntity(label: String): Long? +expect fun getAsset(eucaURI: String): Long? +expect fun quit() @@ -3,4 +3,4 @@ package com.dropbear /** * The ID of an entity (represented as a [Long]) */ -data class EntityId(val id: Long) +data class EntityId(val raw: Long) @@ -1,12 +1,7 @@ package com.dropbear -import com.dropbear.asset.ModelHandle -import com.dropbear.asset.TextureHandle -import com.dropbear.math.Transform -import com.dropbear.math.Vector3 -import com.dropbear.math.Vector3D -import com.dropbear.physics.Collider -import com.dropbear.physics.RigidBody +import com.dropbear.ecs.Component +import com.dropbear.ecs.ComponentType /** * A reference to an ECS Entity stored inside the dropbear engine. @@ -21,172 +16,21 @@ import com.dropbear.physics.RigidBody * playthroughs, so it is recommended not to store this value. */ class EntityRef(val id: EntityId = EntityId(0L)) { - lateinit var engine: DropbearEngine - - override fun toString(): String { - return "EntityRef(id=$id)" - } - - /** - * Fetches the [EntityTransform] component for the entity. - */ - fun getTransform(): EntityTransform? { - return engine.native.getTransform(id) - } - - /** - * Walks up the world hierarchy to find the transform of the parent, then multiply - * to create a propagated [Transform]. - * - * This will update the entity's world transform based on its parent's world transform. - */ - fun propagate(): Transform? { - return engine.native.propagateTransform(id) - } - - /** - * Sets and replaces the [EntityTransform] component for the entity. - */ - fun setTransform(transform: EntityTransform?) { - if (transform == null) return - return engine.native.setTransform(id, transform) - } - - /** - * Fetches the property of the ModelProperty component on the entity. - */ - inline fun <reified T> getProperty(key: String): T? { - return when (T::class) { - String::class -> engine.native.getStringProperty(id.id, key) as T? - Long::class -> engine.native.getLongProperty(id.id, key) as T? - Int::class -> engine.native.getIntProperty(id.id, key) as T? - Double::class -> engine.native.getDoubleProperty(id.id, key) as T? - - Float::class -> engine.native.getFloatProperty(id.id, key) as T? - Boolean::class -> engine.native.getBoolProperty(id.id, key) as T? - FloatArray::class -> engine.native.getVec3Property(id.id, key) as T? - else -> throw IllegalArgumentException("Unsupported property type: ${T::class}") - } - } - /** - * Sets a property of the ModelProperty component on the entity. + * The `Label` component (the entity name). * - * # Supported types - * - [kotlin.String] - * - [kotlin.Long] - * - [kotlin.Int] - * - [kotlin.Double] - * - [kotlin.Float] - * - [kotlin.Boolean] - * - [com.dropbear.math.Vector3] - */ - /** - * Sets a property of the ModelProperty component on the entity. - * - * # Supported types - * - [kotlin.String] - * - [kotlin.Long] - * - [kotlin.Int] - * - [kotlin.Double] - * - [kotlin.Float] - * - [kotlin.Boolean] - * - [com.dropbear.math.Vector3] - */ - fun setProperty(key: String, value: Any) { - when (value) { - is String -> engine.native.setStringProperty(id.id, key, value) - is Long -> engine.native.setLongProperty(id.id, key, value) - is Int -> engine.native.setIntProperty(id.id, key, value) - is Double -> engine.native.setFloatProperty(id.id, key, value) - is Float -> engine.native.setFloatProperty(id.id, key, value.toDouble()) - is Boolean -> engine.native.setBoolProperty(id.id, key, value) - is Vector3<*> -> { - val vec = value.asDoubleVector() - engine.native.setVec3Property(id.id, key, floatArrayOf(vec.x.toFloat(), vec.y.toFloat(), - vec.z.toFloat() - )) - } - is FloatArray -> { - require(value.size == 3) { "Vec3 property must have exactly 3 elements" } - engine.native.setVec3Property(id.id, key, value) - } - else -> throw IllegalArgumentException("Unsupported property type: ${value::class}") - } - } - - /** - * Fetches the attached camera for the entity. - * - * Returns null if no camera is attached as a component according to the editor. - */ - fun getAttachedCamera(): Camera? { - val result = engine.native.getAttachedCamera(id) - if (result != null) { - result.engine = this.engine - } - return result - } - - /** - * Fetches the texture for the given material name in the model. - */ - fun getTexture(materialName: String): TextureHandle? { - val result = engine.native.getTexture(id.id, materialName) - return if (result == null) { - null - } else { - TextureHandle(result) - } - } - - /** - * Returns an array containing the texture identifiers applied to this entity's model. - */ - fun getAllTextures(): Array<String> { - return engine.native.getAllTextures(id.id) - } - - /** - * Checks if the current model being rendered by this entity contains the texture with the given [TextureHandle] - */ - fun hasTexture(textureHandle: TextureHandle): Boolean { - return engine.native.isUsingTexture(id.id, textureHandle.raw()) - } - - /** - * Fetches the active model that is currently being used - */ - fun getModel(): ModelHandle? { - val result = engine.native.getModel(id.id) - return if (result == null) { - null - } else { - ModelHandle(result) - } - } - - /** - * Sets the active model for the entity from a ModelHandle + * All entities have a `Label` component. If one does not, it is considered a bug in the engine or *you* did something + * to break this. Anyhow, it will throw an [Exception]. */ - fun setModel(modelHandle: ModelHandle) { - engine.native.setModel(id.id, modelHandle.raw()) - } + val label: String + get() = getEntityLabel(id) - /** - * Checks if the entity is currently using the given model handle. - * - * Returns false if not using, true if is. - */ - fun usingModel(modelHandle: ModelHandle): Boolean { - return engine.native.isUsingModel(id.id, modelHandle.raw()) + override fun toString(): String { + return "EntityRef(id=$id)" } - /** - * Sets a texture override for the given material on the active model. - */ - fun setTextureOverride(materialName: String, textureHandle: TextureHandle) { - engine.native.setTextureOverride(id.id, materialName, textureHandle) + fun <T : Component> getComponent(type: ComponentType<T>): T? { + return type.get(id) } /** @@ -204,7 +48,7 @@ class EntityRef(val id: EntityId = EntityId(0L)) { * By running [getChildren] on `cat`, it will return `[ wizard_hat ]`, not `pom_pom`. */ fun getChildren(): Array<EntityRef>? { - return engine.native.getChildren(id) + return getChildren(id) } /** @@ -213,7 +57,7 @@ class EntityRef(val id: EntityId = EntityId(0L)) { * Returns `null` if an error occurred or no child exists, otherwise the entity. */ fun getChildByLabel(label: String): EntityRef? { - return engine.native.getChildByLabel(id, label) + return getChildByLabel(id, label) } /** @@ -232,60 +76,11 @@ class EntityRef(val id: EntityId = EntityId(0L)) { * Calling [getParent] on `cat` will return `null`, as the Scene is not an entity. */ fun getParent(): EntityRef? { - return engine.native.getParent(id) - } - - /** - * Fetches the `Label` component (the entity name). - * - * All entities have a `Label` component. If one does not, it is considered a bug in the engine or *you* did something - * to break this. Anyhow, it will throw an [Exception]. - */ - fun getLabel(): String { - return engine.native.getEntityLabel(id.id) ?: throw Exception("Entity has no label, expected to contain. Likely engine bug") - } - - // physics stuff - - /** - * Sets physics enabled. - * - * If physics is enabled, the entity will be under the influence of gravity. Using position based movement will - * not be possible, and will have to be done with physics based movement. - * - * If physics is disabled, the entity will **not** be under the influence of gravity and will stand still. - * Other physic-based entities (such as walls and floors) will not affect the player, and collision based - * functions will not work. - */ - fun setPhysicsEnabled(enabled: Boolean) { - return engine.native.setPhysicsEnabled(id.id, enabled) - } - - /** - * Checks if physics is applied to the entity. - * - * If physics is enabled, it will return true. If not, it will return false. - */ - fun isPhysicsEnabled(): Boolean { - return engine.native.isPhysicsEnabled(id.id) + return getParent(id) } +} - /** - * Fetches the [`RigidBody`] component. - * - * Can return `null` if no component exists within the entity. - */ - fun getRigidBody(): RigidBody? { - return engine.native.getRigidBody(id.id) - } - - /** - * Fetches all available colliders under the `ColliderGroup` component of the entity. - * - * Can return an [emptyArray] if no Collider's exist within the group, or `null` if no - * such component (`ColliderGroup`) is available on the entity. - */ - fun getAllColliders(): List<Collider>? { - return engine.native.getAllColliders(id.id) - } -} +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? @@ -1,12 +0,0 @@ -package com.dropbear - -import com.dropbear.math.Transform - -/** - * A component that contains the local and world [Transform] of an entity. - */ -class EntityTransform(var local: Transform, var world: Transform) { - override fun toString(): String { - return "EntityTransform(local=$local, world=$world)" - } -} @@ -7,7 +7,10 @@ package com.dropbear * the `magna-carta` manifest generator tool. * * The tags correspond to the tags provided to the entity - * with the Script. + * with the Script. + * + * # Note + * This annotation only works on classes that implement [com.dropbear.ecs.System], **not** [com.dropbear.ecs.Component] */ @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.SOURCE) @@ -1,5 +1,10 @@ package com.dropbear +import com.dropbear.ecs.System + +/** + * Internal data class to register scripts with tags. + */ data class ScriptRegistration( val tags: List<String>, val script: System @@ -1,93 +0,0 @@ -package com.dropbear - -/** - * A class that contains the basic information of a system. - * - * The dropbear engine follows an ECS paradigm, with logic being - * provided as Systems. - */ -open class System { - /** - * The current entity that is being run in the system. Most often than not, it - * will have an [EntityRef] attached (because all Script components must be part - * of an entity). - */ - var currentEntity: EntityRef? = null - private set - - private var engineRef: DropbearEngine? = null - - /** - * This function is called when the script module is initialised. - * - * It is only called once during scene execution. If you re-switch back - * to this scene after running the class, it will be run again. - */ - open fun load(engine: DropbearEngine) {} - - /** - * This function is called for each update. - * - * It is run once for each frame, and for every frame. Since this is synced to the frame rate, using - * the [deltaTime] variable can aid you in creating uniform player speeds (or something like that). - * - * @param deltaTime - This specifies the time elapsed since the last update. - */ - open fun update(engine: DropbearEngine, deltaTime: Float) {} - - /** - * This function is called for each update that is related to physics. - * - * It can be run 0, 1, 2 or more times per frame. Updating physics is done at a constant - * rate/tick (at roughly 50Hz or 0.02), which is why it is not ran as often as a standard [update]. - * - * @param deltaTime - This specifies the time elapsed since the last frame update. Likely, it's going - * to be somewhere around 50Hz. For the most part, you might not need this. - */ - open fun physicsUpdate(engine: DropbearEngine, deltaTime: Float) {} - - /** - * This function is called at the end of the script execution. - * - * It is run at the end of execution of a scene, such as when the scene switches. It is also ran once throughout - * the lifecycle of a script class. It is best to think about it like `sceneExit()` instead of `appExit()` - * - * It would be used to clean up any memory related resources (such as `SceneLoadHandle` or any memory related items). - * - * # Note - * - * The script module does not lose state (such as variables) when destroyed. It is cached internally (within the system manager), - * therefore counters and other related stuff will not lose track. - */ - open fun destroy(engine: DropbearEngine) {} - - /** - * Internal: This attaches the [DropbearEngine] fascade (typically created through some external location) - * to the existing system to be used. - */ - fun attachEngine(engine: DropbearEngine) { - engineRef = engine - currentEntity?.engine = engine - } - - /** - * Internal: Sets the current entity of this [System] to something. - */ - fun setCurrentEntity(entity: Long) { - val engine = engineRef ?: run { - currentEntity = null - return - } - - val reference = EntityRef(EntityId(entity)) - reference.engine = engine - currentEntity = reference - } - - /** - * Internal: Clears the current entity used. - */ - fun clearCurrentEntity() { - currentEntity = null - } -} @@ -11,8 +11,8 @@ class AssetHandle(private val id: Long): Handle(id) { * * It can return null if the asset is not a model. */ - fun asModelHandle(engine: DropbearEngine): ModelHandle? { - val result = engine.native.isModelHandle(id) + fun asModelHandle(): ModelHandle? { + val result = isModelHandle(id) return if (result) { ModelHandle(id) } else { @@ -20,25 +20,24 @@ class AssetHandle(private val id: Long): Handle(id) { } } + override fun asAssetHandle(): AssetHandle { + return this + } + /** * Converts an [AssetHandle] to a [TextureHandle]. * * It can return null if the asset is not a texture. */ - fun asTextureHandle(engine: DropbearEngine): TextureHandle? { - val result = engine.native.isTextureHandle(id) - return if (result) { - TextureHandle(id) - } else { - null - } - } - - override fun asAssetHandle(): AssetHandle { - return this + fun asTextureHandle(): TextureHandle? { + return if (isTextureHandle(id)) TextureHandle(id) else null } override fun toString(): String { return "AssetHandle(id=$id)" } -} +} + + +expect fun isTextureHandle(id: Long): Boolean +expect fun isModelHandle(id: Long): Boolean @@ -5,8 +5,14 @@ package com.dropbear.asset * * Aims to allow people to group up different handle types ([AssetHandle], [ModelHandle] etc...) * into a list or a vector. + * + * All handles must be positive, non-zero values. If the id does not follow that rule, it is considered invalid. */ abstract class Handle(private val id: Long) { + init { + require(id > 0) { "Handle id must be a positive, non-zero value. Got: $id" } + } + /** * Returns the raw id of the handle */ @@ -1,7 +1,5 @@ package com.dropbear.asset -import com.dropbear.DropbearEngine - /** * Another type of asset handle, this wraps the id into * another form that only texture related functions can use. @@ -12,11 +10,13 @@ class TextureHandle(private val id: Long): Handle(id) { /** * Fetches the name of that specific texture */ - fun getName(engine: DropbearEngine): String? { - return engine.native.getTextureName(id) + fun getName(): String? { + return getTextureName(id) } override fun toString(): String { return "TextureHandle(id=$id)" } -} +} + +expect fun TextureHandle.getTextureName(id: Long): String? @@ -0,0 +1,91 @@ +package com.dropbear.components + +import com.dropbear.EntityId +import com.dropbear.ecs.Component +import com.dropbear.ecs.ComponentType +import com.dropbear.math.Vector3d + +/** + * Describes a 3D camera, as defined in `dropbear_engine::camera::Camera` + */ +class Camera( + internal val entity: EntityId, +): Component(entity, "Camera3D") { + var eye: Vector3d + get() = getCameraEye(entity) + set(value) = setCameraEye(entity, value) + var target: Vector3d + get() = getCameraTarget(entity) + set(value) = setCameraTarget(entity, value) + var up: Vector3d + get() = getCameraUp(entity) + set(value) = setCameraUp(entity, value) + val aspect: Double + get() = getCameraAspect(entity) + var fov_y: Double + get() = getCameraFovY(entity) + set(value) = setCameraFovY(entity, value) + var znear: Double + get() = getCameraZNear(entity) + set(value) = setCameraZNear(entity, value) + var zfar: Double + get() = getCameraZFar(entity) + set(value) = setCameraZFar(entity, value) + var yaw: Double + get() = getCameraYaw(entity) + set(value) = setCameraYaw(entity, value) + var pitch: Double + get() = getCameraPitch(entity) + set(value) = setCameraPitch(entity, value) + var speed: Double + get() = getCameraSpeed(entity) + set(value) = setCameraSpeed(entity, value) + var sensitivity: Double + get() = getCameraSensitivity(entity) + set(value) = setCameraSensitivity(entity, value) + + override fun toString(): String { + return "Camera component of entity $entity \n" + + "eye: $eye\n" + + "target: $target\n" + + "up: $up\n " + + "aspect: $aspect \n" + + "fov_y: $fov_y \n" + + "znear: $znear \n" + + "zfar: $zfar \n" + + "yaw: $yaw \n" + + "pitch: $pitch" + + "speed: $speed" + + "sensitivity: $sensitivity" + } + + companion object : ComponentType<Camera> { + override fun get(entityId: EntityId): Camera? { + return if (cameraExistsForEntity(entityId)) Camera(entityId) else null + } + } +} + +expect fun Camera.getCameraEye(entity: EntityId): Vector3d +expect fun Camera.setCameraEye(entity: EntityId, value: Vector3d) +expect fun Camera.getCameraTarget(entity: EntityId): Vector3d +expect fun Camera.setCameraTarget(entity: EntityId, value: Vector3d) +expect fun Camera.getCameraUp(entity: EntityId): Vector3d +expect fun Camera.setCameraUp(entity: EntityId, value: Vector3d) +expect fun Camera.getCameraAspect(entity: EntityId): Double +expect fun Camera.getCameraFovY(entity: EntityId): Double +expect fun Camera.setCameraFovY(entity: EntityId, value: Double) +expect fun Camera.getCameraZNear(entity: EntityId): Double +expect fun Camera.setCameraZNear(entity: EntityId, value: Double) +expect fun Camera.getCameraZFar(entity: EntityId): Double +expect fun Camera.setCameraZFar(entity: EntityId, value: Double) +expect fun Camera.getCameraYaw(entity: EntityId): Double +expect fun Camera.setCameraYaw(entity: EntityId, value: Double) +expect fun Camera.getCameraPitch(entity: EntityId): Double +expect fun Camera.setCameraPitch(entity: EntityId, value: Double) +expect fun Camera.getCameraSpeed(entity: EntityId): Double +expect fun Camera.setCameraSpeed(entity: EntityId, value: Double) +expect fun Camera.getCameraSensitivity(entity: EntityId): Double +expect fun Camera.setCameraSensitivity(entity: EntityId, value: Double) + +expect fun cameraExistsForEntity(entity: EntityId): Boolean @@ -0,0 +1,99 @@ +package com.dropbear.components + +import com.dropbear.EntityId +import com.dropbear.ecs.Component +import com.dropbear.ecs.ComponentType +import com.dropbear.math.Vector3d +import com.dropbear.math.Vector3f +import com.dropbear.math.Vector3i + +class CustomProperties(val id: EntityId): Component(id, "CustomProperties") { + /** + * Fetches the property of the ModelProperty component on the entity. + * + * @param T The type of the property to fetch. + * # Supported types + * - [kotlin.String] + * - [Long] + * - [Int] + * - [Double] + * - [Float] + * - [Boolean] + * - [Vector3d] + * - [Vector3f] + * - [Vector3i] + * @param key The key of the property to fetch. + * @return The property value, or null if the property does not exist. + * @throws IllegalArgumentException if the property type is not supported. + */ + inline fun <reified T> getProperty(key: String): T? { + return when (T::class) { + String::class -> getStringProperty(id.raw, key) as T? + Long::class -> getLongProperty(id.raw, key) as T? + Int::class -> getIntProperty(id.raw, key) as T? + Double::class -> getDoubleProperty(id.raw, key) as T? + + Float::class -> getFloatProperty(id.raw, key) as T? + Boolean::class -> getBoolProperty(id.raw, key) as T? + Vector3d::class -> getVec3Property(id.raw, key) as T? + Vector3f::class -> getVec3Property(id.raw, key)?.toFloat() as T? + Vector3i::class -> getVec3Property(id.raw, key)?.toInt() as T? + else -> throw IllegalArgumentException("Unsupported property type: ${T::class}") + } + } + + /** + * Sets a property of the ModelProperty component on the entity. + * + * @param key The key of the property to set. + * @param value The type of the property to set. + * # Supported types + * - [kotlin.String] + * - [Long] + * - [Int] + * - [Double] + * - [Float] + * - [Boolean] + * - [Vector3d] + * - [Vector3f] + * - [Vector3i] + * @throws IllegalArgumentException if the property type is not supported. + */ + fun setProperty(key: String, value: Any) { + when (value) { + is String -> setStringProperty(id.raw, key, value) + is Long -> setLongProperty(id.raw, key, value) + is Int -> setIntProperty(id.raw, key, value) + is Double -> setFloatProperty(id.raw, key, value) + is Float -> setFloatProperty(id.raw, key, value.toDouble()) + is Boolean -> setBoolProperty(id.raw, key, value) + is Vector3d -> setVec3Property(id.raw, key, value) + is Vector3f -> setVec3Property(id.raw ,key, value.toDouble()) + is Vector3i -> setVec3Property(id.raw, key, value.toDouble()) + else -> throw IllegalArgumentException("Unsupported property type: ${value::class}") + } + } + + companion object : ComponentType<CustomProperties> { + override fun get(entityId: EntityId): CustomProperties? { + return if (customPropertiesExistsForEntity(entityId)) CustomProperties(entityId) else null + } + } +} + +expect fun CustomProperties.getStringProperty(entityHandle: Long, label: String): String? +expect fun CustomProperties.getIntProperty(entityHandle: Long, label: String): Int? +expect fun CustomProperties.getLongProperty(entityHandle: Long, label: String): Long? +expect fun CustomProperties.getDoubleProperty(entityHandle: Long, label: String): Double? +expect fun CustomProperties.getFloatProperty(entityHandle: Long, label: String): Float? +expect fun CustomProperties.getBoolProperty(entityHandle: Long, label: String): Boolean? +expect fun CustomProperties.getVec3Property(entityHandle: Long, label: String): Vector3d? + +expect fun CustomProperties.setStringProperty(entityHandle: Long, label: String, value: String) +expect fun CustomProperties.setIntProperty(entityHandle: Long, label: String, value: Int) +expect fun CustomProperties.setLongProperty(entityHandle: Long, label: String, value: Long) +expect fun CustomProperties.setFloatProperty(entityHandle: Long, label: String, value: Double) +expect fun CustomProperties.setBoolProperty(entityHandle: Long, label: String, value: Boolean) +expect fun CustomProperties.setVec3Property(entityHandle: Long, label: String, value: Vector3d) + +expect fun customPropertiesExistsForEntity(entityId: EntityId): Boolean @@ -0,0 +1,60 @@ +package com.dropbear.components + +import com.dropbear.EntityId +import com.dropbear.ecs.Component +import com.dropbear.ecs.ComponentType +import com.dropbear.math.Transform + +/** + * A component that contains the local and world [Transform] of an entity. + */ +class EntityTransform(val id: EntityId): Component(id, "EntityTransform") { + var local: Transform + get() = getLocalTransform(id) + set(value) = setLocalTransform(id, value) + var world: Transform + get() = getWorldTransform(id) + set(value) = setWorldTransform(id, value) + + override fun toString(): String { + return "EntityTransform(id: $id, local=$local, world=$world)" + } + + /** + * Walks up the world hierarchy to find the transform of the parent, then multiply/add + * to create a propagated [Transform]. + */ + fun propagate(): Transform? { + return propagateTransform(id) + } + + /** + * Merges the local and world transforms and return the merged [Transform] + */ + fun sync(): Transform { + val scaledPos = local.position * world.scale + val rotatedPos = world.rotation * scaledPos + val finalPos = world.position + rotatedPos + val finalRot = world.rotation * local.rotation + val finalScale = world.scale * local.scale + return Transform(finalPos, finalRot, finalScale) + } + + companion object : ComponentType<EntityTransform> { + override fun get(entityId: EntityId): EntityTransform? { + return if (entityTransformExistsForEntity(entityId)) { + EntityTransform(entityId) + } else { + null + } + } + } +} + +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 entityTransformExistsForEntity(entityId: EntityId): Boolean @@ -0,0 +1,64 @@ +package com.dropbear.components + +import com.dropbear.EntityId +import com.dropbear.asset.ModelHandle +import com.dropbear.asset.TextureHandle +import com.dropbear.ecs.Component +import com.dropbear.ecs.ComponentType + +class MeshRenderer(val id: EntityId) : Component(id, "MeshRenderer") { + + /** + * The active model currently assigned to this entity. + * Setting this to null removes the model. + * + * Usage: + * ``` + * val handle = renderer.model + * renderer.model = newHandle + * ``` + */ + var model: ModelHandle? + get() { + return getModel(id) + } + set(value) { + setModel(id, value) + } + + /** + * A list of all active Texture handles currently applied to the model. + */ + val textures: List<TextureHandle>? + get() = getAllTextureIds(id) + + /** + * Fetches the texture assigned to a specific material slot. + * Returns null if the material doesn't exist or has no texture. + */ + fun getTexture(materialName: String): TextureHandle? { + val rawId = getTexture(id, materialName) + return if (rawId == 0L || rawId == null) null else TextureHandle(rawId) + } + + /** + * Overrides the texture for a specific material on the active model. + */ + fun setTextureOverride(materialName: String, textureHandle: TextureHandle) { + setTextureOverride(id, materialName, textureHandle.raw()) + } + + companion object : ComponentType<MeshRenderer> { + override fun get(entityId: EntityId): MeshRenderer? { + return if (meshRendererExistsForEntity(entityId)) MeshRenderer(entityId) else null + } + } +} + +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.setTextureOverride(id: EntityId, materialName: String, textureHandle: Long) + +expect fun meshRendererExistsForEntity(entityId: EntityId): Boolean @@ -0,0 +1,24 @@ +package com.dropbear.ecs + +import com.dropbear.EntityId + +/** + * A custom property that *can* be attached to an entity in the dropbear ECS system. + * + * @param parentEntity - The [com.dropbear.EntityId] to which this component is attached. + * @param typeName - The string name of the component type as defined in Rust. The name is + * found as the type name, such as `component_registry.register_with_default::<Camera3D>();`, + * where the type name would be `"Camera3D"`. + */ +abstract class Component( + val parentEntity: EntityId, + val typeName: String, +) { + override fun toString(): String { + return "Component(parentEntity: ${this.parentEntity}, typeName: $typeName)" + } +} + +interface ComponentType<T : Component> { + fun get(entityId: EntityId): T? +} @@ -0,0 +1,90 @@ +package com.dropbear.ecs + +import com.dropbear.DropbearEngine +import com.dropbear.EntityId +import com.dropbear.EntityRef + +/** + * A class that contains the basic information of a system. + * + * The dropbear engine follows an ECS paradigm, with logic being + * provided as Systems. + */ +open class System { + /** + * The current entity that is being run in the system. Most often than not, it + * will have an [com.dropbear.EntityRef] attached (because all Script components must be part + * of an entity). + */ + var currentEntity: EntityRef? = null + private set + + private var engineRef: DropbearEngine? = null + + /** + * This function is called when the script module is initialised. + * + * It is only called once during scene execution. If you re-switch back + * to this scene after running the class, it will be run again. + */ + open fun load(engine: DropbearEngine) {} + + /** + * This function is called for each update. + * + * It is run once for each frame, and for every frame. Since this is synced to the frame rate, using + * the [deltaTime] variable can aid you in creating uniform player speeds (or something like that). + * + * @param deltaTime - This specifies the time elapsed since the last update. + */ + open fun update(engine: DropbearEngine, deltaTime: Float) {} + + /** + * This function is called for each update that is related to physics. + * + * It can be run 0, 1, 2 or more times per frame. Updating physics is done at a constant + * rate/tick (at roughly 50Hz or 0.02), which is why it is not ran as often as a standard [update]. + * + * @param deltaTime - This specifies the time elapsed since the last frame update. Likely, it's going + * to be somewhere around 50Hz. For the most part, you might not need this. + */ + open fun physicsUpdate(engine: DropbearEngine, deltaTime: Float) {} + + /** + * This function is called at the end of the script execution. + * + * It is run at the end of execution of a scene, such as when the scene switches. It is also ran once throughout + * the lifecycle of a script class. It is best to think about it like `sceneExit()` instead of `appExit()` + * + * It would be used to clean up any memory related resources (such as `SceneLoadHandle` or any memory related items). + * + * # Note + * + * The script module does not lose state (such as variables) when destroyed. It is cached internally (within the system manager), + * therefore counters and other related stuff will not lose track. + */ + open fun destroy(engine: DropbearEngine) {} + + /** + * Internal: This attaches the [DropbearEngine] fascade (typically created through some external location) + * to the existing system to be used. + */ + fun attachEngine(engine: DropbearEngine) { + engineRef = engine + } + + /** + * Internal: Sets the current entity of this [System] to something. + */ + fun setCurrentEntity(entity: Long) { + val reference = EntityRef(EntityId(entity)) + currentEntity = reference + } + + /** + * Internal: Clears the current entity used. + */ + fun clearCurrentEntity() { + currentEntity = null + } +} @@ -1,123 +1,9 @@ package com.dropbear.ffi -import com.dropbear.Camera -import com.dropbear.EntityId -import com.dropbear.EntityRef -import com.dropbear.EntityTransform -import com.dropbear.asset.AssetHandle -import com.dropbear.asset.ModelHandle -import com.dropbear.asset.TextureHandle -import com.dropbear.input.Gamepad -import com.dropbear.input.GamepadButton -import com.dropbear.input.KeyCode -import com.dropbear.input.MouseButton -import com.dropbear.math.Transform -import com.dropbear.math.Vector2D -import com.dropbear.math.Vector3D -import com.dropbear.physics.Collider -import com.dropbear.physics.Index -import com.dropbear.physics.RigidBody -import com.dropbear.scene.SceneLoadHandle -import com.dropbear.scene.SceneLoadStatus -import com.dropbear.utils.Progress - /** - * Native functions + * Native information about dropbear engine. * - * Class that describes all the functions that can - * be communicated with the `eucalyptus_core` dynamic library + * This class contains handles to various native subsystems such as world, input, graphics, and more. Different + * targets (JVM, Native) will have different implementations for managing these handles. Check them out! */ -expect class NativeEngine { - fun getEntity(label: String): Long? - fun getAsset(eucaURI: String): Long? - - fun getEntityLabel(entityHandle: Long): String? - - fun getModel(entityHandle: Long): Long? - fun setModel(entityHandle: Long, modelHandle: Long) - fun isUsingModel(entityHandle: Long, modelHandle: Long): Boolean - fun isModelHandle(id: Long): Boolean - - fun getTexture(entityHandle: Long, name: String): Long? - fun setTextureOverride(entityHandle: Long, oldMaterialName: String, newTextureHandle: TextureHandle) - fun isUsingTexture(entityHandle: Long, textureHandle: Long): Boolean - fun isTextureHandle(id: Long): Boolean - fun getTextureName(textureHandle: Long): String? - fun getAllTextures(entityHandle: Long): Array<String> - - fun getCamera(label: String): Camera? - fun getAttachedCamera(entityId: EntityId): Camera? - fun setCamera(camera: Camera); - - fun getTransform(entityId: EntityId): EntityTransform? - fun propagateTransform(entityId: EntityId): Transform? - fun setTransform(entityId: EntityId, transform: EntityTransform) - - fun getChildren(entityId: EntityId): Array<EntityRef>? - fun getChildByLabel(entityId: EntityId, label: String): EntityRef? - fun getParent(entityId: EntityId): EntityRef? - - fun switchToSceneImmediate(sceneName: String) - fun loadSceneAsync(sceneName: String): SceneLoadHandle? - fun loadSceneAsync(sceneName: String, loadingScene: String): SceneLoadHandle? - fun switchToSceneAsync(sceneLoadHandle: SceneLoadHandle) - fun getSceneLoadProgress(sceneLoadHandle: SceneLoadHandle): Progress - fun getSceneLoadStatus(sceneLoadHandle: SceneLoadHandle): SceneLoadStatus - - // ------------------------ MODEL PROPERTIES ------------------------- - - fun getStringProperty(entityHandle: Long, label: String): String? - fun getIntProperty(entityHandle: Long, label: String): Int? - fun getLongProperty(entityHandle: Long, label: String): Long? - fun getDoubleProperty(entityHandle: Long, label: String): Double? - fun getFloatProperty(entityHandle: Long, label: String): Float? - fun getBoolProperty(entityHandle: Long, label: String): Boolean? - fun getVec3Property(entityHandle: Long, label: String): FloatArray? - - fun setStringProperty(entityHandle: Long, label: String, value: String) - fun setIntProperty(entityHandle: Long, label: String, value: Int) - fun setLongProperty(entityHandle: Long, label: String, value: Long) - fun setFloatProperty(entityHandle: Long, label: String, value: Double) - fun setBoolProperty(entityHandle: Long, label: String, value: Boolean) - fun setVec3Property(entityHandle: Long, label: String, value: FloatArray) - - - // --------------------------- INPUT STATE --------------------------- - - /** - * Prints the input state, typically used for debugging. - */ - fun printInputState() - fun isKeyPressed(key: KeyCode): Boolean - fun getMousePosition(): Vector2D? - fun isMouseButtonPressed(button: MouseButton): Boolean - fun getMouseDelta(): Vector2D? - fun isCursorLocked(): Boolean - fun setCursorLocked(locked: Boolean) - fun isCursorHidden(): Boolean - fun setCursorHidden(hidden: Boolean) - fun getLastMousePos(): Vector2D? - fun getConnectedGamepads(): List<Gamepad> - fun isGamepadButtonPressed(id: Long, button: GamepadButton): Boolean - - // ------------------------------------------------------------------- - - // physics stuff - - // EntityRef.kt - fun setPhysicsEnabled(entityId: Long, enabled: Boolean) - fun isPhysicsEnabled(entityId: Long): Boolean - fun getRigidBody(entityId: Long): RigidBody? - fun getAllColliders(entityId: Long): List<Collider> - - // physics/RigidBody.kt - fun applyImpulse(index: Index, x: Double, y: Double, z: Double) - fun applyTorqueImpulse(index: Index, x: Double, y: Double, z: Double) - fun setRigidbody(rigidBody: RigidBody) - fun getChildColliders(index: Index): List<Collider> - - // physics/Collider.kt - fun setCollider(collider: Collider) - - fun quit() -} +expect class NativeEngine @@ -1,22 +1,28 @@ package com.dropbear.input import com.dropbear.ffi.NativeEngine -import com.dropbear.math.Vector2D +import com.dropbear.math.Vector2d /** * Information about a specific gamepad for that time in frame. */ class Gamepad( val id: Long, - val leftStickPosition: Vector2D, - val rightStickPosition: Vector2D, - val native: NativeEngine, ) { + val leftStickPosition: Vector2d + get() = getLeftStickPosition(id) + val rightStickPosition: Vector2d + get() = getRightStickPosition(id) + fun isButtonPressed(button: GamepadButton): Boolean { - return native.isGamepadButtonPressed(id, button) + return isGamepadButtonPressed(id, button) } override fun toString(): String { - return "Gamepad $id @ ($leftStickPosition ; $rightStickPosition)" + return "Gamepad(id=$id)" } -} +} + +expect fun Gamepad.isGamepadButtonPressed(id: Long, button: GamepadButton): Boolean +expect fun Gamepad.getLeftStickPosition(id: Long): Vector2d +expect fun Gamepad.getRightStickPosition(id: Long): Vector2d @@ -1,8 +1,6 @@ package com.dropbear.input -import com.dropbear.DropbearEngine -import com.dropbear.ffi.NativeEngine -import com.dropbear.math.Vector2D +import com.dropbear.math.Vector2d /** * The current state of the input system. @@ -13,49 +11,16 @@ import com.dropbear.math.Vector2D * The InputState does not have any values that can be mutated, only * queried. */ -class InputState(private val native: NativeEngine) { - - fun printInputState() { - native.printInputState() - } - - fun isKeyPressed(key: KeyCode): Boolean { - return native.isKeyPressed(key) - } - - fun getMousePosition(): Vector2D { - return native.getMousePosition() ?: Vector2D(0.0, 0.0) - } - - fun isMouseButtonPressed(button: MouseButton): Boolean { - return native.isMouseButtonPressed(button) - } - - fun getMouseDelta(): Vector2D { - return native.getMouseDelta() ?: Vector2D(0.0, 0.0) - } - - fun isCursorLocked(): Boolean { - return native.isCursorLocked() - } - - fun setCursorLocked(locked: Boolean) { - return native.setCursorLocked(locked) - } - - fun getLastMousePos(): Vector2D { - return native.getLastMousePos() ?: Vector2D(0.0, 0.0) - } - - fun isCursorHidden(): Boolean { - return native.isCursorHidden() - } - - fun setCursorHidden(hidden: Boolean) { - return native.setCursorHidden(hidden) - } - - fun getConnectedGamepads(): List<Gamepad> { - return native.getConnectedGamepads() - } +expect class InputState() { + fun printInputState() + fun isKeyPressed(key: KeyCode): Boolean + fun getMousePosition(): Vector2d + fun isMouseButtonPressed(button: MouseButton): Boolean + fun getMouseDelta(): Vector2d + fun isCursorLocked(): Boolean + fun setCursorLocked(locked: Boolean) + fun getLastMousePos(): Vector2d + fun isCursorHidden(): Boolean + fun setCursorHidden(hidden: Boolean) + fun getConnectedGamepads(): List<Gamepad> } @@ -1,8 +1,8 @@ package com.dropbear.logging -import kotlinx.datetime.Clock import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime +import kotlin.time.Clock class StdoutWriter: LogWriter { override fun log( @@ -1,18 +1,12 @@ package com.dropbear.math import kotlin.jvm.JvmField -import kotlin.math.PI -import kotlin.math.acos -import kotlin.math.asin -import kotlin.math.atan2 -import kotlin.math.cos -import kotlin.math.max -import kotlin.math.min -import kotlin.math.sin -import kotlin.math.sqrt - -typealias QuaternionD = Quaternion<Double> +import kotlin.jvm.JvmStatic +/** + * Generic Quaternion wrapper. + * WARNING: Uses boxing. Prefer [Quaterniond]/[Quaternionf] for performance. + */ class Quaternion<T: Number>( @JvmField var x: T, @JvmField var y: T, @@ -20,322 +14,34 @@ class Quaternion<T: Number>( @JvmField var w: T ) { companion object { - fun identity(): QuaternionD { - return Quaternion(0.0, 0.0, 0.0, 1.0) - } - - fun fromEulerAngles(pitch: Double, yaw: Double, roll: Double): QuaternionD { - val halfPitch = pitch * 0.5 - val halfYaw = yaw * 0.5 - val halfRoll = roll * 0.5 - val sp = sin(halfPitch) - val cp = cos(halfPitch) - val sy = sin(halfYaw) - val cy = cos(halfYaw) - val sr = sin(halfRoll) - val cr = cos(halfRoll) - - return Quaternion( - x = cy * sr * cp - sy * cr * sp, - y = sy * cr * cp + cy * sr * sp, - z = sy * sr * cp - cy * cr * sp, - w = cy * cr * cp + sy * sr * sp - ) - } - - fun fromAxisAngle(axis: Vector3<Double>, angleRadians: Double): QuaternionD { - val normalizedAxis = axis.normalizedCopy() - val halfAngle = angleRadians * 0.5 - val sinHalf = sin(halfAngle) - return Quaternion( - normalizedAxis.x * sinHalf, - normalizedAxis.y * sinHalf, - normalizedAxis.z * sinHalf, - cos(halfAngle) - ) - } - - fun rotateX(angleRadians: Double): QuaternionD { - return fromAxisAngle(Vector3D(1.0, 0.0, 0.0), angleRadians) - } - - fun rotateY(angleRadians: Double): QuaternionD { - return fromAxisAngle(Vector3D(0.0, 1.0, 0.0), angleRadians) - } - - fun rotateZ(angleRadians: Double): QuaternionD { - return fromAxisAngle(Vector3D(0.0, 0.0, 1.0), angleRadians) - } - - fun fromToRotation(from: Vector3<Double>, to: Vector3<Double>): QuaternionD { - val start = from.normalizedCopy() - val end = to.normalizedCopy() - val dot = (start.x * end.x) + (start.y * end.y) + (start.z * end.z) - if (dot >= 1.0 - 1e-6) { - return identity() - } - if (dot <= -1.0 + 1e-6) { - val orthogonal = if (kotlin.math.abs(start.x) < 0.9) { - Vector3D(1.0, 0.0, 0.0) - } else { - Vector3D(0.0, 1.0, 0.0) - } - val axis = start.cross(orthogonal).normalizedCopy() - return fromAxisAngle(axis, PI) - } - val axis = start.cross(end) - val angle = acos(dot.coerceIn(-1.0, 1.0)) - return fromAxisAngle(axis, angle) - } - } - - fun <T : Number> rotateX(angleRadians: T): Quaternion<T> { - val halfAngle = angleRadians.toDouble() * 0.5 - @Suppress("UNCHECKED_CAST") - return Quaternion(sin(halfAngle), 0.0, 0.0, cos(halfAngle)) as Quaternion<T> - } - - fun <T: Number> rotateY(angleRadians: T): Quaternion<T> { - val halfAngle = angleRadians.toDouble() * 0.5 - @Suppress("UNCHECKED_CAST") - return Quaternion(0.0, sin(halfAngle), 0.0, cos(halfAngle)) as Quaternion<T> - } - - fun <T: Number> rotateZ(angleRadians: T): Quaternion<T> { - val halfAngle = angleRadians.toDouble() * 0.5 - @Suppress("UNCHECKED_CAST") - return Quaternion(0.0, 0.0, sin(halfAngle), cos(halfAngle)) as Quaternion<T> - } - - fun asDoubleQuaternion(): QuaternionD { - return Quaternion(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble()) - } - - fun conjugate(): QuaternionD { - return Quaternion(-x.toDouble(), -y.toDouble(), -z.toDouble(), w.toDouble()) - } - - fun dot(other: Quaternion<*>): Double { - return x.toDouble() * other.x.toDouble() + - y.toDouble() * other.y.toDouble() + - z.toDouble() * other.z.toDouble() + - w.toDouble() * other.w.toDouble() - } - - fun lengthSquared(): Double { - return dot(this) - } - - fun length(): Double { - return sqrt(lengthSquared()) - } - - fun isNormalized(epsilon: Double = 1e-6): Boolean { - return kotlin.math.abs(length() - 1.0) <= epsilon - } - - fun normalized(): QuaternionD { - val len = length() - if (len == 0.0) { - return identity() - } - val invLen = 1.0 / len - return Quaternion( - x.toDouble() * invLen, - y.toDouble() * invLen, - z.toDouble() * invLen, - w.toDouble() * invLen - ) - } - - fun normalizeInPlace(): Quaternion<T> { - val normalized = normalized() - @Suppress("UNCHECKED_CAST") - x = normalized.x as T - @Suppress("UNCHECKED_CAST") - y = normalized.y as T - @Suppress("UNCHECKED_CAST") - z = normalized.z as T - @Suppress("UNCHECKED_CAST") - w = normalized.w as T - return this - } - - operator fun <R: Number> plus(other: Quaternion<R>): QuaternionD { - return Quaternion( - x.toDouble() + other.x.toDouble(), - y.toDouble() + other.y.toDouble(), - z.toDouble() + other.z.toDouble(), - w.toDouble() + other.w.toDouble() - ) - } - - operator fun <R: Number> minus(other: Quaternion<R>): QuaternionD { - return Quaternion( - x.toDouble() - other.x.toDouble(), - y.toDouble() - other.y.toDouble(), - z.toDouble() - other.z.toDouble(), - w.toDouble() - other.w.toDouble() - ) - } - - operator fun unaryMinus(): QuaternionD { - return Quaternion(-x.toDouble(), -y.toDouble(), -z.toDouble(), -w.toDouble()) - } - - operator fun times(scalar: Number): QuaternionD { - val value = scalar.toDouble() - return Quaternion( - x.toDouble() * value, - y.toDouble() * value, - z.toDouble() * value, - w.toDouble() * value - ) - } - - operator fun div(scalar: Number): QuaternionD { - val value = scalar.toDouble() - require(value != 0.0) { "Cannot divide Quaternion by zero." } - val inv = 1.0 / value - return Quaternion( - x.toDouble() * inv, - y.toDouble() * inv, - z.toDouble() * inv, - w.toDouble() * inv - ) - } - - operator fun <R: Number> times(other: Quaternion<R>): QuaternionD { - val ax = x.toDouble() - val ay = y.toDouble() - val az = z.toDouble() - val aw = w.toDouble() - val bx = other.x.toDouble() - val by = other.y.toDouble() - val bz = other.z.toDouble() - val bw = other.w.toDouble() - return Quaternion( - aw * bx + ax * bw + ay * bz - az * by, - aw * by - ax * bz + ay * bw + az * bx, - aw * bz + ax * by - ay * bx + az * bw, - aw * bw - ax * bx - ay * by - az * bz - ) - } - - fun inverse(): QuaternionD { - val lenSq = lengthSquared() - if (lenSq == 0.0) { - return identity() - } - val conjugate = conjugate() - val inv = 1.0 / lenSq - return Quaternion( - conjugate.x * inv, - conjugate.y * inv, - conjugate.z * inv, - conjugate.w * inv - ) - } + @JvmStatic + fun identity(): Quaternion<Double> = Quaternion(0.0, 0.0, 0.0, 1.0) - fun rotate(vector: Vector3<T>): Vector3<Double> { - val doubleVector = vector.asDoubleVector() - val vectorQuat = Quaternion(doubleVector.x, doubleVector.y, doubleVector.z, 0.0) - val rotated = (this * vectorQuat) * conjugate() - return Vector3(rotated.x, rotated.y, rotated.z) - } - - operator fun times(vector: Vector3<T>): Vector3<Double> { - return rotate(vector) - } - - fun nlerp(other: Quaternion<*>, t: Double): QuaternionD { - val alpha = t.coerceIn(0.0, 1.0) - val inverse = 1.0 - alpha - return Quaternion( - x.toDouble() * inverse + other.x.toDouble() * alpha, - y.toDouble() * inverse + other.y.toDouble() * alpha, - z.toDouble() * inverse + other.z.toDouble() * alpha, - w.toDouble() * inverse + other.w.toDouble() * alpha - ).normalized() - } - - fun slerp(other: Quaternion<*>, t: Double): QuaternionD { - val alpha = t.coerceIn(0.0, 1.0) - var q1 = normalized() - var q2 = Quaternion( - other.x.toDouble(), - other.y.toDouble(), - other.z.toDouble(), - other.w.toDouble() - ).normalized() - - var dot = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w - if (dot < 0.0) { - dot = -dot - q2 = Quaternion(-q2.x, -q2.y, -q2.z, -q2.w) - } - - dot = min(1.0, max(-1.0, dot)) - - if (dot > 0.9995) { - return Quaternion( - q1.x + alpha * (q2.x - q1.x), - q1.y + alpha * (q2.y - q1.y), - q1.z + alpha * (q2.z - q1.z), - q1.w + alpha * (q2.w - q1.w) - ).normalized() + @JvmStatic + fun fromEulerAngles(pitch: Double, yaw: Double, roll: Double): Quaternion<Double> { + return Quaterniond.fromEulerAngles(pitch, yaw, roll).toGeneric() } - - val theta0 = acos(dot) - val sinTheta0 = sin(theta0) - val theta = theta0 * alpha - val sinTheta = sin(theta) - val s0 = cos(theta) - dot * sinTheta / sinTheta0 - val s1 = sinTheta / sinTheta0 - - return Quaternion( - q1.x * s0 + q2.x * s1, - q1.y * s0 + q2.y * s1, - q1.z * s0 + q2.z * s1, - q1.w * s0 + q2.w * s1 - ).normalized() } - fun toEulerAngles(): Vector3D { - val q = normalized() - val xx = q.x * q.x - val yy = q.y * q.y - val zz = q.z * q.z - - val sinPitch = 2.0 * (q.w * q.x - q.y * q.z) - val pitch = asin(max(-1.0, min(1.0, sinPitch))) - - val sinYaw = 2.0 * (q.w * q.y + q.z * q.x) - val cosYaw = 1.0 - 2.0 * (xx + zz) - val yaw = atan2(sinYaw, cosYaw) + fun asQuaterniond() = Quaterniond(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble()) + fun asQuaternionf() = Quaternionf(x.toFloat(), y.toFloat(), z.toFloat(), w.toFloat()) - val sinRoll = 2.0 * (q.w * q.z + q.x * q.y) - val cosRoll = 1.0 - 2.0 * (yy + xx) - val roll = atan2(sinRoll, cosRoll) + fun normalize(): Quaternion<Double> = asQuaterniond().normalize().toGeneric() + fun inverse(): Quaternion<Double> = asQuaterniond().inverse().toGeneric() - return Vector3D(pitch, yaw, roll) - } - - fun toAxisAngle(): Pair<Vector3D, Double> { - val q = normalized() - val angle = 2.0 * acos(q.w) - val sinHalfAngle = sqrt(1.0 - q.w * q.w) - if (sinHalfAngle < 1e-6) { - return Vector3D(1.0, 0.0, 0.0) to angle - } - return Vector3D(q.x / sinHalfAngle, q.y / sinHalfAngle, q.z / sinHalfAngle) to angle - } + override fun toString() = "Quaternion($x, $y, $z, $w)" - fun toVector4(): Vector4<Double> { - return Vector4(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble()) + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Quaternion<*>) return false + return x == other.x && y == other.y && z == other.z && w == other.w } - override fun toString(): String { - return "Quaternion(x=$x, y=$y, z=$z, w=$w)" + override fun hashCode(): Int { + var result = x.hashCode() + result = 31 * result + y.hashCode() + result = 31 * result + z.hashCode() + result = 31 * result + w.hashCode() + return result } } @@ -0,0 +1,183 @@ +package com.dropbear.math + +import kotlin.jvm.JvmField +import kotlin.jvm.JvmStatic +import kotlin.math.* + +class Quaterniond( + @JvmField var x: Double, + @JvmField var y: Double, + @JvmField var z: Double, + @JvmField var w: Double +) { + companion object { + @JvmStatic fun identity() = Quaterniond(0.0, 0.0, 0.0, 1.0) + + @JvmStatic + fun fromEulerAngles(pitch: Double, yaw: Double, roll: Double): Quaterniond { + val halfPitch = pitch * 0.5 + val halfYaw = yaw * 0.5 + val halfRoll = roll * 0.5 + val sp = sin(halfPitch) + val cp = cos(halfPitch) + val sy = sin(halfYaw) + val cy = cos(halfYaw) + val sr = sin(halfRoll) + val cr = cos(halfRoll) + + return Quaterniond( + x = cy * sr * cp - sy * cr * sp, + y = sy * cr * cp + cy * sr * sp, + z = sy * sr * cp - cy * cr * sp, + w = cy * cr * cp + sy * sr * sp + ) + } + + @JvmStatic + fun fromAxisAngle(axis: Vector3d, angleRadians: Double): Quaterniond { + val normalizedAxis = axis.normalize() + val halfAngle = angleRadians * 0.5 + val sinHalf = sin(halfAngle) + return Quaterniond( + normalizedAxis.x * sinHalf, + normalizedAxis.y * sinHalf, + normalizedAxis.z * sinHalf, + cos(halfAngle) + ) + } + + @JvmStatic fun rotateX(angle: Double) = fromAxisAngle(Vector3d(1.0, 0.0, 0.0), angle) + @JvmStatic fun rotateY(angle: Double) = fromAxisAngle(Vector3d(0.0, 1.0, 0.0), angle) + @JvmStatic fun rotateZ(angle: Double) = fromAxisAngle(Vector3d(0.0, 0.0, 1.0), angle) + + @JvmStatic + fun fromToRotation(from: Vector3d, to: Vector3d): Quaterniond { + val start = from.normalize() + val end = to.normalize() + val dot = start.dot(end) + + if (dot >= 1.0 - 1e-6) return identity() + if (dot <= -1.0 + 1e-6) { + val orthogonal = if (abs(start.x) < 0.9) Vector3d(1.0, 0.0, 0.0) else Vector3d(0.0, 1.0, 0.0) + val axis = start.cross(orthogonal).normalize() + return fromAxisAngle(axis, PI) + } + val axis = start.cross(end) + val angle = acos(dot.coerceIn(-1.0, 1.0)) + return fromAxisAngle(axis, angle) + } + + private fun copySign(magnitude: Double, sign: Double): Double { + val absMag = abs(magnitude) + return if (sign < 0.0) -absMag else absMag + } + } + + operator fun plus(other: Quaterniond) = Quaterniond(x + other.x, y + other.y, z + other.z, w + other.w) + operator fun minus(other: Quaterniond) = Quaterniond(x - other.x, y - other.y, z - other.z, w - other.w) + operator fun unaryMinus() = Quaterniond(-x, -y, -z, -w) + + operator fun times(scalar: Double) = Quaterniond(x * scalar, y * scalar, z * scalar, w * scalar) + operator fun div(scalar: Double) = Quaterniond(x / scalar, y / scalar, z / scalar, w / scalar) + + /** + * Quaternion Multiplication (Composition). + * Represents applying rotation `other`, then rotation `this`. + */ + operator fun times(other: Quaterniond): Quaterniond { + return Quaterniond( + w * other.x + x * other.w + y * other.z - z * other.y, + w * other.y - x * other.z + y * other.w + z * other.x, + w * other.z + x * other.y - y * other.x + z * other.w, + w * other.w - x * other.x - y * other.y - z * other.z + ) + } + + /** + * Rotates a vector by this quaternion. + */ + operator fun times(vector: Vector3d): Vector3d { + val qVec = Vector3d(x, y, z) + val uv = qVec.cross(vector) + val uuv = qVec.cross(uv) + return vector + ((uv * w) + uuv) * 2.0 + } + + fun dot(other: Quaterniond) = x * other.x + y * other.y + z * other.z + w * other.w + fun lengthSquared() = x * x + y * y + z * z + w * w + fun length() = sqrt(lengthSquared()) + + fun normalize(): Quaterniond { + val len = length() + return if (len == 0.0) identity() else this / len + } + + fun inverse(): Quaterniond { + val lenSq = lengthSquared() + if (lenSq == 0.0) return identity() + val inv = 1.0 / lenSq + return Quaterniond(-x * inv, -y * inv, -z * inv, w * inv) + } + + fun conjugate() = Quaterniond(-x, -y, -z, w) + + fun slerp(other: Quaterniond, t: Double): Quaterniond { + val alpha = t.coerceIn(0.0, 1.0) + var q1 = this.normalize() + var q2 = other.normalize() + + var dot = q1.dot(q2) + + if (dot < 0.0) { + dot = -dot + q2 = -q2 + } + + if (dot > 0.9995) { + return (q1 + (q2 - q1) * alpha).normalize() + } + + val theta0 = acos(dot) + val theta = theta0 * alpha + val sinTheta = sin(theta) + val sinTheta0 = sin(theta0) + + val s0 = cos(theta) - dot * sinTheta / sinTheta0 + val s1 = sinTheta / sinTheta0 + + return (q1 * s0) + (q2 * s1) + } + + fun toEulerAngles(): Vector3d { + val sinr_cosp = 2.0 * (w * z + x * y) + val cosr_cosp = 1.0 - 2.0 * (y * y + z * z) + val roll = atan2(sinr_cosp, cosr_cosp) + + val sinp = 2.0 * (w * x - y * z) + val pitch: Double + if (abs(sinp) >= 1.0) { + pitch = copySign(PI / 2, sinp) + } else { + pitch = asin(sinp) + } + + val siny_cosp = 2.0 * (w * y + z * x) + val cosy_cosp = 1.0 - 2.0 * (x * x + y * y) + val yaw = atan2(siny_cosp, cosy_cosp) + + return Vector3d(pitch, yaw, roll) + } + + fun toFloat() = Quaternionf(x.toFloat(), y.toFloat(), z.toFloat(), w.toFloat()) + fun toGeneric() = Quaternion(x, y, z, w) + + override fun toString() = "Quaterniond($x, $y, $z, $w)" + override fun equals(other: Any?) = other is Quaterniond && x == other.x && y == other.y && z == other.z && w == other.w + override fun hashCode(): Int { + var result = x.hashCode() + result = 31 * result + y.hashCode() + result = 31 * result + z.hashCode() + result = 31 * result + w.hashCode() + return result + } +} @@ -0,0 +1,122 @@ +package com.dropbear.math + +import kotlin.jvm.JvmField +import kotlin.jvm.JvmStatic +import kotlin.math.* + +class Quaternionf( + @JvmField var x: Float, + @JvmField var y: Float, + @JvmField var z: Float, + @JvmField var w: Float +) { + companion object { + @JvmStatic fun identity() = Quaternionf(0f, 0f, 0f, 1f) + + @JvmStatic + fun fromEulerAngles(pitch: Float, yaw: Float, roll: Float): Quaternionf { + val halfPitch = pitch * 0.5f + val halfYaw = yaw * 0.5f + val halfRoll = roll * 0.5f + val sp = sin(halfPitch) + val cp = cos(halfPitch) + val sy = sin(halfYaw) + val cy = cos(halfYaw) + val sr = sin(halfRoll) + val cr = cos(halfRoll) + + return Quaternionf( + x = cy * sr * cp - sy * cr * sp, + y = sy * cr * cp + cy * sr * sp, + z = sy * sr * cp - cy * cr * sp, + w = cy * cr * cp + sy * sr * sp + ) + } + + @JvmStatic + fun fromAxisAngle(axis: Vector3f, angleRadians: Float): Quaternionf { + val normalizedAxis = axis.normalize() + val halfAngle = angleRadians * 0.5f + val sinHalf = sin(halfAngle) + return Quaternionf( + normalizedAxis.x * sinHalf, + normalizedAxis.y * sinHalf, + normalizedAxis.z * sinHalf, + cos(halfAngle) + ) + } + + @JvmStatic fun rotateX(angle: Float) = fromAxisAngle(Vector3f(1f, 0f, 0f), angle) + @JvmStatic fun rotateY(angle: Float) = fromAxisAngle(Vector3f(0f, 1f, 0f), angle) + @JvmStatic fun rotateZ(angle: Float) = fromAxisAngle(Vector3f(0f, 0f, 1f), angle) + + private fun copySign(magnitude: Float, sign: Float): Float { + val absMag = abs(magnitude) + return if (sign < 0f) -absMag else absMag + } + } + + operator fun plus(other: Quaternionf) = Quaternionf(x + other.x, y + other.y, z + other.z, w + other.w) + operator fun minus(other: Quaternionf) = Quaternionf(x - other.x, y - other.y, z - other.z, w - other.w) + operator fun unaryMinus() = Quaternionf(-x, -y, -z, -w) + + operator fun times(scalar: Float) = Quaternionf(x * scalar, y * scalar, z * scalar, w * scalar) + operator fun div(scalar: Float) = Quaternionf(x / scalar, y / scalar, z / scalar, w / scalar) + + operator fun times(other: Quaternionf): Quaternionf { + return Quaternionf( + w * other.x + x * other.w + y * other.z - z * other.y, + w * other.y - x * other.z + y * other.w + z * other.x, + w * other.z + x * other.y - y * other.x + z * other.w, + w * other.w - x * other.x - y * other.y - z * other.z + ) + } + + operator fun times(vector: Vector3f): Vector3f { + val qVec = Vector3f(x, y, z) + val uv = qVec.cross(vector) + val uuv = qVec.cross(uv) + return vector + ((uv * w) + uuv) * 2f + } + + fun dot(other: Quaternionf) = x * other.x + y * other.y + z * other.z + w * other.w + fun lengthSquared() = x * x + y * y + z * z + w * w + fun length() = sqrt(lengthSquared()) + + fun normalize(): Quaternionf { + val len = length() + return if (len == 0f) identity() else this / len + } + + fun inverse(): Quaternionf { + val lenSq = lengthSquared() + if (lenSq == 0f) return identity() + val inv = 1.0f / lenSq + return Quaternionf(-x * inv, -y * inv, -z * inv, w * inv) + } + + fun conjugate() = Quaternionf(-x, -y, -z, w) + + fun toDouble() = Quaterniond(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble()) + + fun toEulerAngles(): Vector3f { + val sinr_cosp = 2f * (w * z + x * y) + val cosr_cosp = 1f - 2f * (y * y + z * z) + val roll = atan2(sinr_cosp, cosr_cosp) + + val sinp = 2f * (w * x - y * z) + val pitch = if (abs(sinp) >= 1f) { + copySign(PI.toFloat() / 2f, sinp) + } else { + asin(sinp) + } + + val siny_cosp = 2f * (w * y + z * x) + val cosy_cosp = 1f - 2f * (x * x + y * y) + val yaw = atan2(siny_cosp, cosy_cosp) + + return Vector3f(pitch, yaw, roll) + } + + override fun toString() = "Quaternionf($x, $y, $z, $w)" +} @@ -5,10 +5,20 @@ package com.dropbear.math * attached to an entity. */ class Transform( - var position: Vector3D, - var rotation: QuaternionD, - var scale: Vector3D + var position: Vector3d, + 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. * @@ -18,9 +28,9 @@ class Transform( rx: Double, ry: Double, rz: Double, rw: Double, sx: Double, sy: Double, sz: Double) : this( - Vector3D(px, py, pz), - QuaternionD(rx, ry, rz, rw), - Vector3D(sx, sy, sz) + Vector3d(px, py, pz), + Quaterniond(rx, ry, rz, rw), + Vector3d(sx, sy, sz) ) override fun toString(): String { @@ -1,24 +0,0 @@ -package com.dropbear.math - -/** - * Abstract class all Vectors inherit. - */ -abstract class Vector<T: Number, SELF: Vector<T, SELF>> { - abstract fun normalize(): SELF - - abstract operator fun plus(other: SELF): SELF - abstract operator fun plus(scalar: T): SELF - - abstract operator fun minus(other: SELF): SELF - abstract operator fun minus(scalar: T): SELF - - abstract operator fun times(other: SELF): SELF - abstract operator fun times(scalar: T): SELF - - abstract operator fun div(other: SELF): SELF - abstract operator fun div(scalar: T): SELF - - abstract fun length(): Double - - abstract fun copy(): SELF -} @@ -1,180 +1,32 @@ package com.dropbear.math import kotlin.jvm.JvmField -import kotlin.math.sqrt +import kotlin.jvm.JvmStatic /** - * A class for holding a vector of `2` values of the same type. + * A Generic Vector2. + * WARNING: Uses boxing. Prefer [Vector2d]/[Vector2f]/[Vector2i] for performance. */ -class Vector2<T: Number>( +class Vector2<T : Number>( @JvmField var x: T, - @JvmField var y: T, -) : Vector<T, Vector2<T>>() { + @JvmField var y: T +) { companion object { - /** - * Creates a new [com.dropbear.math.Vector2] of type `T` with one value. - */ - fun <T: Number> uniform(value: T): Vector2<T> { - return Vector2(value, value) - } - - /** - * Creates a [com.dropbear.math.Vector2] of type [Double] filled with only zeroes - */ - fun zero(): Vector2<Double> { - return Vector2(0.0, 0.0) - } - } - - fun asDoubleVector(): Vector2<Double> { - return Vector2(this.x.toDouble(), this.y.toDouble()) - } - - override fun normalize(): Vector2<T> { - val length = length() - if (length > 0.0) { - val invLength = 1.0 / length - @Suppress("UNCHECKED_CAST") - x = (x.toDouble() * invLength) as T - @Suppress("UNCHECKED_CAST") - y = (y.toDouble() * invLength) as T - } - return this - } - - override operator fun plus(other: Vector2<T>): Vector2<T> { - @Suppress("UNCHECKED_CAST") - return Vector2( - x.toDouble() + other.x.toDouble(), - y.toDouble() + other.y.toDouble() - ) as Vector2<T> - } - - override operator fun plus(scalar: T): Vector2<T> { - @Suppress("UNCHECKED_CAST") - return Vector2( - x.toDouble() + scalar.toDouble(), - y.toDouble() + scalar.toDouble() - ) as Vector2<T> - } - - override operator fun minus(other: Vector2<T>): Vector2<T> { - @Suppress("UNCHECKED_CAST") - return Vector2( - x.toDouble() - other.x.toDouble(), - y.toDouble() - other.y.toDouble() - ) as Vector2<T> - } - - override operator fun minus(scalar: T): Vector2<T> { - @Suppress("UNCHECKED_CAST") - return Vector2( - x.toDouble() - scalar.toDouble(), - y.toDouble() - scalar.toDouble() - ) as Vector2<T> - } - - override operator fun times(other: Vector2<T>): Vector2<T> { - @Suppress("UNCHECKED_CAST") - return Vector2( - x.toDouble() * other.x.toDouble(), - y.toDouble() * other.y.toDouble() - ) as Vector2<T> - } - - override operator fun times(scalar: T): Vector2<T> { - @Suppress("UNCHECKED_CAST") - return Vector2( - x.toDouble() * scalar.toDouble(), - y.toDouble() * scalar.toDouble() - ) as Vector2<T> - } - - override operator fun div(other: Vector2<T>): Vector2<T> { - @Suppress("UNCHECKED_CAST") - return Vector2( - x.toDouble() / other.x.toDouble(), - y.toDouble() / other.y.toDouble() - ) as Vector2<T> - } - - override operator fun div(scalar: T): Vector2<T> { - @Suppress("UNCHECKED_CAST") - return Vector2( - x.toDouble() / scalar.toDouble(), - y.toDouble() / scalar.toDouble(), - ) as Vector2<T> - } - - fun lengthSquared(): Double { - val dx = x.toDouble() - val dy = y.toDouble() - return dx * dx + dy * dy - } - - fun dot(other: Vector2<T>): Double { - return x.toDouble() * other.x.toDouble() + y.toDouble() * other.y.toDouble() - } - - fun distanceTo(other: Vector2<T>): Double { - val dx = x.toDouble() - other.x.toDouble() - val dy = y.toDouble() - other.y.toDouble() - return sqrt(dx * dx + dy * dy) - } - - fun normalizedCopy(): Vector2<Double> { - val length = length() - if (length == 0.0) { - return zero() - } - val invLength = 1.0 / length - return Vector2( - x.toDouble() * invLength, - y.toDouble() * invLength - ) - } - - fun lerp(target: Vector2<T>, alpha: Double): Vector2<Double> { - val inverse = 1.0 - alpha - return Vector2( - x.toDouble() * inverse + target.x.toDouble() * alpha, - y.toDouble() * inverse + target.y.toDouble() * alpha - ) - } - - operator fun unaryMinus(): Vector2<T> { - @Suppress("UNCHECKED_CAST") - return Vector2(-x.toDouble(), -y.toDouble()) as Vector2<T> - } - - fun toVector3(z: T): Vector3<T> { - return Vector3(x, y, z) + @JvmStatic + fun fromDoubles(x: Double, y: Double): Vector2<Double> = Vector2(x, y) } - fun toVector4(z: T, w: T): Vector4<T> { - return Vector4(x, y, z, w) - } - - operator fun component1(): T = x - operator fun component2(): T = y + fun asVector2d(): Vector2d = Vector2d(x.toDouble(), y.toDouble()) + fun asVector2f(): Vector2f = Vector2f(x.toFloat(), y.toFloat()) + fun asVector2i(): Vector2i = Vector2i(x.toInt(), y.toInt()) - /** - * Returns the magnitude/length of the vector - * - * ## Math - * `sqrt(x^2 + y^2)` - */ - override fun length(): Double { - return sqrt(x.toDouble() * x.toDouble() + y.toDouble() * y.toDouble()) - } - - override fun copy(): Vector2<T> { - return Vector2(x, y) - } + override fun toString() = "Vector2($x, $y)" - override fun toString(): String { - return "Vector2(x=$x, y=$y)" + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Vector2<*>) return false + return x == other.x && y == other.y } -} -typealias Vector2D = Vector2<Double> + override fun hashCode() = 31 * x.hashCode() + y.hashCode() +} @@ -0,0 +1,62 @@ +package com.dropbear.math + +import kotlin.math.sqrt +import kotlin.jvm.JvmField +import kotlin.jvm.JvmStatic + +/** + * A class for holding a vector of `2` double values. + */ +class Vector2d( + @JvmField var x: Double, + @JvmField var y: Double +) { + companion object { + @JvmStatic fun zero() = Vector2d(0.0, 0.0) + @JvmStatic fun one() = Vector2d(1.0, 1.0) + } + + operator fun plus(other: Vector2d) = Vector2d(x + other.x, y + other.y) + operator fun plus(scalar: Double) = Vector2d(x + scalar, y + scalar) + + operator fun minus(other: Vector2d) = Vector2d(x - other.x, y - other.y) + operator fun minus(scalar: Double) = Vector2d(x - scalar, y - scalar) + + operator fun times(other: Vector2d) = Vector2d(x * other.x, y * other.y) + operator fun times(scalar: Double) = Vector2d(x * scalar, y * scalar) + + operator fun div(other: Vector2d) = Vector2d(x / other.x, y / other.y) + operator fun div(scalar: Double) = Vector2d(x / scalar, y / scalar) + + operator fun unaryMinus() = Vector2d(-x, -y) + + fun length() = sqrt(x * x + y * y) + fun lengthSquared() = x * x + y * y + + fun dot(other: Vector2d) = x * other.x + y * other.y + + fun distanceTo(other: Vector2d): Double { + val dx = x - other.x + val dy = y - other.y + return sqrt(dx * dx + dy * dy) + } + + fun normalize(): Vector2d { + val l = length() + return if (l != 0.0) this / l else zero() + } + + fun lerp(target: Vector2d, alpha: Double): Vector2d { + val inv = 1.0 - alpha + return Vector2d(x * inv + target.x * alpha, y * inv + target.y * alpha) + } + + fun toFloat() = Vector2f(x.toFloat(), y.toFloat()) + fun toInt() = Vector2i(x.toInt(), y.toInt()) + + fun toGeneric() = Vector2(x, y) + + override fun toString() = "Vector2d($x, $y)" + override fun equals(other: Any?) = other is Vector2d && x == other.x && y == other.y + override fun hashCode() = 31 * x.hashCode() + y.hashCode() +} @@ -0,0 +1,46 @@ +package com.dropbear.math + +import kotlin.math.sqrt +import kotlin.jvm.JvmField +import kotlin.jvm.JvmStatic + +/** + * A class for holding a vector of `2` float values. + */ +class Vector2f( + @JvmField var x: Float, + @JvmField var y: Float +) { + companion object { + @JvmStatic + fun zero() = Vector2f(0f, 0f) + } + + operator fun plus(other: Vector2f) = Vector2f(x + other.x, y + other.y) + operator fun plus(scalar: Float) = Vector2f(x + scalar, y + scalar) + + operator fun minus(other: Vector2f) = Vector2f(x - other.x, y - other.y) + operator fun minus(scalar: Float) = Vector2f(x - scalar, y - scalar) + + operator fun times(other: Vector2f) = Vector2f(x * other.x, y * other.y) + operator fun times(scalar: Float) = Vector2f(x * scalar, y * scalar) + + operator fun div(other: Vector2f) = Vector2f(x / other.x, y / other.y) + operator fun div(scalar: Float) = Vector2f(x / scalar, y / scalar) + + operator fun unaryMinus() = Vector2f(-x, -y) + + fun length(): Float = sqrt(x * x + y * y) + fun lengthSquared(): Float = x * x + y * y + + fun normalize(): Vector2f { + val l = length() + return if (l != 0f) Vector2f(x / l, y / l) else zero() + } + + // --- Conversions --- + fun toDouble() = Vector2d(x.toDouble(), y.toDouble()) + fun toInt() = Vector2i(x.toInt(), y.toInt()) + + override fun toString() = "Vector2f($x, $y)" +} @@ -0,0 +1,32 @@ +package com.dropbear.math + +import kotlin.math.sqrt +import kotlin.jvm.JvmField +import kotlin.jvm.JvmStatic + +/** + * A class for holding a vector of `2` integer values. + */ +class Vector2i( + @JvmField var x: Int, + @JvmField var y: Int +) { + companion object { + @JvmStatic + fun zero() = Vector2i(0, 0) + } + + operator fun plus(other: Vector2i) = Vector2i(x + other.x, y + other.y) + operator fun minus(other: Vector2i) = Vector2i(x - other.x, y - other.y) + + operator fun times(scalar: Int) = Vector2i(x * scalar, y * scalar) + + operator fun div(scalar: Int) = Vector2i(x / scalar, y / scalar) + + fun length(): Double = sqrt((x * x + y * y).toDouble()) + + fun toFloat() = Vector2f(x.toFloat(), y.toFloat()) + fun toDouble() = Vector2d(x.toDouble(), y.toDouble()) + + override fun toString() = "Vector2i($x, $y)" +} @@ -1,227 +1,41 @@ package com.dropbear.math import kotlin.jvm.JvmField -import kotlin.math.sqrt +import kotlin.jvm.JvmStatic /** - * A class for holding a vector of `3` values of the same type. + * A Generic Vector3. + * WARNING: Uses boxing. Prefer [Vector3d]/[Vector3f]/[Vector3i] for performance. */ -class Vector3<T: Number>( +class Vector3<T : Number>( @JvmField var x: T, @JvmField var y: T, - @JvmField var z: T, -) : Vector<T, Vector3<T>>() { + @JvmField var z: T +) { companion object { - /** - * Creates a new [com.dropbear.math.Vector3] of type `T` with one value. - */ - fun <T: Number> uniform(value: T): Vector3<T> { - return Vector3(value, value, value) - } + @JvmStatic + fun fromDoubles(x: Double, y: Double, z: Double): Vector3<Double> = Vector3(x, y, z) - /** - * Creates a [com.dropbear.math.Vector3] of type [Double] filled with only zeroes - */ - fun zero(): Vector3<Double> { - return Vector3(0.0, 0.0, 0.0) - } - - fun x(): Vector3D { - return Vector3D(1.0, 0.0, 0.0) - } - - fun y(): Vector3D { - return Vector3D(0.0, 1.0, 0.0) - } - - fun z(): Vector3D { - return Vector3D(0.0, 0.0, 1.0) - } - } - - /** - * Returns the [Vector3] to a [Vector3D] (Vector3 of type `Double`) - */ - fun asDoubleVector(): Vector3<Double> { - return Vector3(this.x.toDouble(), this.y.toDouble(), this.z.toDouble()) - } - - override fun normalize(): Vector3<T> { - val length = length() - if (length > 0.0) { - val invLength = 1.0 / length - @Suppress("UNCHECKED_CAST") - x = (x.toDouble() * invLength) as T - @Suppress("UNCHECKED_CAST") - y = (y.toDouble() * invLength) as T - @Suppress("UNCHECKED_CAST") - z = (z.toDouble() * invLength) as T - } - return this - } - - override operator fun plus(other: Vector3<T>): Vector3<T> { - @Suppress("UNCHECKED_CAST") - return Vector3( - x.toDouble() + other.x.toDouble(), - y.toDouble() + other.y.toDouble(), - z.toDouble() + other.z.toDouble() - ) as Vector3<T> - } - - override operator fun plus(scalar: T): Vector3<T> { - @Suppress("UNCHECKED_CAST") - return Vector3( - x.toDouble() + scalar.toDouble(), - y.toDouble() + scalar.toDouble(), - z.toDouble() + scalar.toDouble() - ) as Vector3<T> + @JvmStatic + fun zero(): Vector3<Double> = Vector3(0.0, 0.0, 0.0) } - override operator fun minus(other: Vector3<T>): Vector3<T> { - @Suppress("UNCHECKED_CAST") - return Vector3( - x.toDouble() - other.x.toDouble(), - y.toDouble() - other.y.toDouble(), - z.toDouble() - other.z.toDouble() - ) as Vector3<T> - } - - override operator fun minus(scalar: T): Vector3<T> { - @Suppress("UNCHECKED_CAST") - return Vector3( - x.toDouble() - scalar.toDouble(), - y.toDouble() - scalar.toDouble(), - z.toDouble() - scalar.toDouble() - ) as Vector3<T> - } + fun asVector3d(): Vector3d = Vector3d(x.toDouble(), y.toDouble(), z.toDouble()) + fun asVector3f(): Vector3f = Vector3f(x.toFloat(), y.toFloat(), z.toFloat()) + fun asVector3i(): Vector3i = Vector3i(x.toInt(), y.toInt(), z.toInt()) - override operator fun times(other: Vector3<T>): Vector3<T> { - @Suppress("UNCHECKED_CAST") - return Vector3( - x.toDouble() * other.x.toDouble(), - y.toDouble() * other.y.toDouble(), - z.toDouble() * other.z.toDouble() - ) as Vector3<T> - } - - override operator fun times(scalar: T): Vector3<T> { - @Suppress("UNCHECKED_CAST") - return Vector3( - x.toDouble() * scalar.toDouble(), - y.toDouble() * scalar.toDouble(), - z.toDouble() * scalar.toDouble() - ) as Vector3<T> - } + override fun toString() = "Vector3($x, $y, $z)" - override operator fun div(other: Vector3<T>): Vector3<T> { - @Suppress("UNCHECKED_CAST") - return Vector3( - x.toDouble() / other.x.toDouble(), - y.toDouble() / other.y.toDouble(), - z.toDouble() / other.z.toDouble() - ) as Vector3<T> + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Vector3<*>) return false + return x == other.x && y == other.y && z == other.z } - override operator fun div(scalar: T): Vector3<T> { - @Suppress("UNCHECKED_CAST") - return Vector3( - x.toDouble() / scalar.toDouble(), - y.toDouble() / scalar.toDouble(), - z.toDouble() / scalar.toDouble() - ) as Vector3<T> + override fun hashCode(): Int { + var result = x.hashCode() + result = 31 * result + y.hashCode() + result = 31 * result + z.hashCode() + return result } - - fun lengthSquared(): Double { - val dx = x.toDouble() - val dy = y.toDouble() - val dz = z.toDouble() - return dx * dx + dy * dy + dz * dz - } - - fun dot(other: Vector3<T>): Double { - return x.toDouble() * other.x.toDouble() + - y.toDouble() * other.y.toDouble() + - z.toDouble() * other.z.toDouble() - } - - fun cross(other: Vector3<T>): Vector3<Double> { - val ax = x.toDouble() - val ay = y.toDouble() - val az = z.toDouble() - val bx = other.x.toDouble() - val by = other.y.toDouble() - val bz = other.z.toDouble() - return Vector3( - ay * bz - az * by, - az * bx - ax * bz, - ax * by - ay * bx - ) - } - - fun distanceTo(other: Vector3<T>): Double { - val dx = x.toDouble() - other.x.toDouble() - val dy = y.toDouble() - other.y.toDouble() - val dz = z.toDouble() - other.z.toDouble() - return sqrt(dx * dx + dy * dy + dz * dz) - } - - fun normalizedCopy(): Vector3<Double> { - val length = length() - if (length == 0.0) { - return zero() - } - val invLength = 1.0 / length - return Vector3( - x.toDouble() * invLength, - y.toDouble() * invLength, - z.toDouble() * invLength - ) - } - - fun lerp(target: Vector3<T>, alpha: Double): Vector3<Double> { - val inverse = 1.0 - alpha - return Vector3( - x.toDouble() * inverse + target.x.toDouble() * alpha, - y.toDouble() * inverse + target.y.toDouble() * alpha, - z.toDouble() * inverse + target.z.toDouble() * alpha - ) - } - - operator fun unaryMinus(): Vector3<T> { - @Suppress("UNCHECKED_CAST") - return Vector3(-x.toDouble(), -y.toDouble(), -z.toDouble()) as Vector3<T> - } - - fun toVector2(): Vector2<T> { - return Vector2(x, y) - } - - fun toVector4(w: T): Vector4<T> { - return Vector4(x, y, z, w) - } - - operator fun component1(): T = x - operator fun component2(): T = y - operator fun component3(): T = z - - /** - * Returns the magnitude/length of the vector - * - * ## Math - * `sqrt(x^2 + y^2 + z^2)` - */ - override fun length(): Double { - return sqrt(x.toDouble() * x.toDouble() + y.toDouble() * y.toDouble() + z.toDouble() * z.toDouble()) - } - - override fun copy(): Vector3<T> { - return Vector3(x, y, z) - } - - override fun toString(): String { - return "Vector3(x=$x, y=$y, z=$z)" - } -} - -typealias Vector3D = Vector3<Double> +} @@ -0,0 +1,87 @@ +package com.dropbear.math + +import kotlin.math.sqrt +import kotlin.jvm.JvmField +import kotlin.jvm.JvmStatic + +class Vector3d( + @JvmField var x: Double, + @JvmField var y: Double, + @JvmField var z: Double +) { + companion object { + @JvmStatic fun zero() = Vector3d(0.0, 0.0, 0.0) + @JvmStatic fun one() = Vector3d(1.0, 1.0, 1.0) + + @JvmStatic fun right() = Vector3d(1.0, 0.0, 0.0) // X + @JvmStatic fun up() = Vector3d(0.0, 1.0, 0.0) // Y + @JvmStatic fun forward() = Vector3d(0.0, 0.0, 1.0) // Z + } + + operator fun plus(other: Vector3d) = Vector3d(x + other.x, y + other.y, z + other.z) + operator fun plus(scalar: Double) = Vector3d(x + scalar, y + scalar, z + scalar) + + operator fun minus(other: Vector3d) = Vector3d(x - other.x, y - other.y, z - other.z) + operator fun minus(scalar: Double) = Vector3d(x - scalar, y - scalar, z - scalar) + + operator fun times(other: Vector3d) = Vector3d(x * other.x, y * other.y, z * other.z) + operator fun times(scalar: Double) = Vector3d(x * scalar, y * scalar, z * scalar) + + operator fun div(other: Vector3d) = Vector3d(x / other.x, y / other.y, z / other.z) + operator fun div(scalar: Double) = Vector3d(x / scalar, y / scalar, z / scalar) + + operator fun unaryMinus() = Vector3d(-x, -y, -z) + + fun length() = sqrt(x * x + y * y + z * z) + fun lengthSquared() = x * x + y * y + z * z + + fun dot(other: Vector3d) = x * other.x + y * other.y + z * other.z + + /** + * Calculates the Cross Product. + * Result is perpendicular to both this vector and the other. + */ + fun cross(other: Vector3d): Vector3d { + return Vector3d( + y * other.z - z * other.y, + z * other.x - x * other.z, + x * other.y - y * other.x + ) + } + + fun distanceTo(other: Vector3d): Double { + val dx = x - other.x + val dy = y - other.y + val dz = z - other.z + return sqrt(dx * dx + dy * dy + dz * dz) + } + + fun normalize(): Vector3d { + val l = length() + return if (l != 0.0) this / l else zero() + } + + fun lerp(target: Vector3d, alpha: Double): Vector3d { + val inv = 1.0 - alpha + return Vector3d( + x * inv + target.x * alpha, + y * inv + target.y * alpha, + z * inv + target.z * alpha + ) + } + + fun toFloat() = Vector3f(x.toFloat(), y.toFloat(), z.toFloat()) + fun toInt() = Vector3i(x.toInt(), y.toInt(), z.toInt()) + fun toVector2d() = Vector2d(x, y) + + fun toGeneric() = Vector3(x, y, z) + + override fun toString() = "Vector3d($x, $y, $z)" + override fun equals(other: Any?) = other is Vector3d && x == other.x && y == other.y && z == other.z + override fun hashCode(): Int { + var result = x.hashCode() + result = 31 * result + y.hashCode() + result = 31 * result + z.hashCode() + return result + } +} @@ -0,0 +1,54 @@ +package com.dropbear.math + +import kotlin.math.sqrt +import kotlin.jvm.JvmField +import kotlin.jvm.JvmStatic + +class Vector3f( + @JvmField var x: Float, + @JvmField var y: Float, + @JvmField var z: Float +) { + companion object { + @JvmStatic + fun zero() = Vector3f(0f, 0f, 0f) + } + + operator fun plus(other: Vector3f) = Vector3f(x + other.x, y + other.y, z + other.z) + operator fun plus(scalar: Float) = Vector3f(x + scalar, y + scalar, z + scalar) + + operator fun minus(other: Vector3f) = Vector3f(x - other.x, y - other.y, z - other.z) + operator fun minus(scalar: Float) = Vector3f(x - scalar, y - scalar, z - scalar) + + operator fun times(other: Vector3f) = Vector3f(x * other.x, y * other.y, z * other.z) + operator fun times(scalar: Float) = Vector3f(x * scalar, y * scalar, z * scalar) + + operator fun div(other: Vector3f) = Vector3f(x / other.x, y / other.y, z / other.z) + operator fun div(scalar: Float) = Vector3f(x / scalar, y / scalar, z / scalar) + + operator fun unaryMinus() = Vector3f(-x, -y, -z) + + fun length(): Float = sqrt(x * x + y * y + z * z) + fun lengthSquared(): Float = x * x + y * y + z * z + + fun dot(other: Vector3f) = x * other.x + y * other.y + z * other.z + + fun cross(other: Vector3f): Vector3f { + return Vector3f( + y * other.z - z * other.y, + z * other.x - x * other.z, + x * other.y - y * other.x + ) + } + + fun normalize(): Vector3f { + val l = length() + return if (l != 0f) Vector3f(x / l, y / l, z / l) else zero() + } + + fun toDouble() = Vector3d(x.toDouble(), y.toDouble(), z.toDouble()) + fun toInt() = Vector3i(x.toInt(), y.toInt(), z.toInt()) + fun toVector2f() = Vector2f(x, y) + + override fun toString() = "Vector3f($x, $y, $z)" +} @@ -0,0 +1,28 @@ +package com.dropbear.math + +import kotlin.math.sqrt +import kotlin.jvm.JvmField +import kotlin.jvm.JvmStatic + +class Vector3i( + @JvmField var x: Int, + @JvmField var y: Int, + @JvmField var z: Int +) { + companion object { + @JvmStatic + fun zero() = Vector3i(0, 0, 0) + } + + operator fun plus(other: Vector3i) = Vector3i(x + other.x, y + other.y, z + other.z) + operator fun minus(other: Vector3i) = Vector3i(x - other.x, y - other.y, z - other.z) + operator fun times(scalar: Int) = Vector3i(x * scalar, y * scalar, z * scalar) + operator fun div(scalar: Int) = Vector3i(x / scalar, y / scalar, z / scalar) + + fun length(): Double = sqrt((x * x + y * y + z * z).toDouble()) + + fun toFloat() = Vector3f(x.toFloat(), y.toFloat(), z.toFloat()) + fun toDouble() = Vector3d(x.toDouble(), y.toDouble(), z.toDouble()) + + override fun toString() = "Vector3i($x, $y, $z)" +} @@ -1,228 +1,48 @@ package com.dropbear.math import kotlin.jvm.JvmField -import kotlin.math.sqrt +import kotlin.jvm.JvmStatic /** - * A class for holding a vector of `4` values of the same type. + * A Generic Vector4. + * WARNING: Uses boxing. Prefer [Vector4d]/[Vector4f]/[Vector4i] for performance. */ -class Vector4<T: Number>( +class Vector4<T : Number>( @JvmField var x: T, @JvmField var y: T, @JvmField var z: T, - @JvmField var w: T, -) : Vector<T, Vector4<T>>() { + @JvmField var w: T +) { companion object { - /** - * Creates a new [com.dropbear.math.Vector4] of type `T` with one value. - */ - fun <T: Number> uniform(value: T): Vector4<T> { - return Vector4(value, value, value, value) - } + @JvmStatic + fun fromDoubles(x: Double, y: Double, z: Double, w: Double): Vector4<Double> = Vector4(x, y, z, w) - /** - * Creates a [com.dropbear.math.Vector4] of type [Double] filled with only zeroes - */ - fun zero(): Vector4<Double> { - return Vector4(0.0, 0.0, 0.0, 0.0) - } - - fun x(): Vector4D { - return Vector4D(1.0, 0.0, 0.0, 0.0) - } - - fun y(): Vector4D { - return Vector4D(0.0, 1.0, 0.0, 0.0) - } - - fun z(): Vector4D { - return Vector4D(0.0, 0.0, 1.0, 0.0) - } - - fun w(): Vector4D { - return Vector4D(0.0, 0.0, 0.0, 1.0) - } - } - - fun asDoubleVector(): Vector4<Double> { - return Vector4(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble()) - } - - override fun normalize(): Vector4<T> { - val length = length() - if (length > 0.0) { - val invLength = 1.0 / length - @Suppress("UNCHECKED_CAST") - x = (x.toDouble() * invLength) as T - @Suppress("UNCHECKED_CAST") - y = (y.toDouble() * invLength) as T - @Suppress("UNCHECKED_CAST") - z = (z.toDouble() * invLength) as T - @Suppress("UNCHECKED_CAST") - w = (w.toDouble() * invLength) as T - } - return this - } - - override operator fun plus(other: Vector4<T>): Vector4<T> { - @Suppress("UNCHECKED_CAST") - return Vector4( - x.toDouble() + other.x.toDouble(), - y.toDouble() + other.y.toDouble(), - z.toDouble() + other.z.toDouble(), - w.toDouble() + other.w.toDouble() - ) as Vector4<T> + @JvmStatic + fun zero(): Vector4<Double> = Vector4(0.0, 0.0, 0.0, 0.0) } - override operator fun plus(scalar: T): Vector4<T> { - val value = scalar.toDouble() - @Suppress("UNCHECKED_CAST") - return Vector4( - x.toDouble() + value, - y.toDouble() + value, - z.toDouble() + value, - w.toDouble() + value - ) as Vector4<T> - } - - override operator fun minus(other: Vector4<T>): Vector4<T> { - @Suppress("UNCHECKED_CAST") - return Vector4( - x.toDouble() - other.x.toDouble(), - y.toDouble() - other.y.toDouble(), - z.toDouble() - other.z.toDouble(), - w.toDouble() - other.w.toDouble() - ) as Vector4<T> - } - - override operator fun minus(scalar: T): Vector4<T> { - val value = scalar.toDouble() - @Suppress("UNCHECKED_CAST") - return Vector4( - x.toDouble() - value, - y.toDouble() - value, - z.toDouble() - value, - w.toDouble() - value - ) as Vector4<T> - } + fun asVector4d(): Vector4d = Vector4d(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble()) + fun asVector4f(): Vector4f = Vector4f(x.toFloat(), y.toFloat(), z.toFloat(), w.toFloat()) + fun asVector4i(): Vector4i = Vector4i(x.toInt(), y.toInt(), z.toInt(), w.toInt()) - override operator fun times(other: Vector4<T>): Vector4<T> { - @Suppress("UNCHECKED_CAST") - return Vector4( - x.toDouble() * other.x.toDouble(), - y.toDouble() * other.y.toDouble(), - z.toDouble() * other.z.toDouble(), - w.toDouble() * other.w.toDouble() - ) as Vector4<T> - } + operator fun component1() = x + operator fun component2() = y + operator fun component3() = z + operator fun component4() = w - override operator fun times(scalar: T): Vector4<T> { - val value = scalar.toDouble() - @Suppress("UNCHECKED_CAST") - return Vector4( - x.toDouble() * value, - y.toDouble() * value, - z.toDouble() * value, - w.toDouble() * value - ) as Vector4<T> - } + override fun toString() = "Vector4($x, $y, $z, $w)" - override operator fun div(other: Vector4<T>): Vector4<T> { - @Suppress("UNCHECKED_CAST") - return Vector4( - x.toDouble() / other.x.toDouble(), - y.toDouble() / other.y.toDouble(), - z.toDouble() / other.z.toDouble(), - w.toDouble() / other.w.toDouble() - ) as Vector4<T> + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Vector4<*>) return false + return x == other.x && y == other.y && z == other.z && w == other.w } - override operator fun div(scalar: T): Vector4<T> { - val value = scalar.toDouble() - @Suppress("UNCHECKED_CAST") - return Vector4( - x.toDouble() / value, - y.toDouble() / value, - z.toDouble() / value, - w.toDouble() / value - ) as Vector4<T> + override fun hashCode(): Int { + var result = x.hashCode() + result = 31 * result + y.hashCode() + result = 31 * result + z.hashCode() + result = 31 * result + w.hashCode() + return result } - - fun lengthSquared(): Double { - val dx = x.toDouble() - val dy = y.toDouble() - val dz = z.toDouble() - val dw = w.toDouble() - return dx * dx + dy * dy + dz * dz + dw * dw - } - - fun dot(other: Vector4<T>): Double { - return x.toDouble() * other.x.toDouble() + - y.toDouble() * other.y.toDouble() + - z.toDouble() * other.z.toDouble() + - w.toDouble() * other.w.toDouble() - } - - fun distanceTo(other: Vector4<T>): Double { - val dx = x.toDouble() - other.x.toDouble() - val dy = y.toDouble() - other.y.toDouble() - val dz = z.toDouble() - other.z.toDouble() - val dw = w.toDouble() - other.w.toDouble() - return sqrt(dx * dx + dy * dy + dz * dz + dw * dw) - } - - fun normalizedCopy(): Vector4<Double> { - val length = length() - if (length == 0.0) { - return zero() - } - val invLength = 1.0 / length - return Vector4( - x.toDouble() * invLength, - y.toDouble() * invLength, - z.toDouble() * invLength, - w.toDouble() * invLength - ) - } - - fun lerp(target: Vector4<T>, alpha: Double): Vector4<Double> { - val inverse = 1.0 - alpha - return Vector4( - x.toDouble() * inverse + target.x.toDouble() * alpha, - y.toDouble() * inverse + target.y.toDouble() * alpha, - z.toDouble() * inverse + target.z.toDouble() * alpha, - w.toDouble() * inverse + target.w.toDouble() * alpha - ) - } - - operator fun unaryMinus(): Vector4<T> { - @Suppress("UNCHECKED_CAST") - return Vector4(-x.toDouble(), -y.toDouble(), -z.toDouble(), -w.toDouble()) as Vector4<T> - } - - fun toVector3(): Vector3<T> { - return Vector3(x, y, z) - } - - operator fun component1(): T = x - operator fun component2(): T = y - operator fun component3(): T = z - operator fun component4(): T = w - - /** - * Returns the magnitude/length of the vector - */ - override fun length(): Double { - return sqrt(lengthSquared()) - } - - override fun copy(): Vector4<T> { - return Vector4(x, y, z, w) - } - - override fun toString(): String { - return "Vector4(x=$x, y=$y, z=$z, w=$w)" - } -} - -typealias Vector4D = Vector4<Double> +} @@ -0,0 +1,81 @@ +package com.dropbear.math + +import kotlin.math.sqrt +import kotlin.jvm.JvmField +import kotlin.jvm.JvmStatic + +class Vector4d( + @JvmField var x: Double, + @JvmField var y: Double, + @JvmField var z: Double, + @JvmField var w: Double +) { + companion object { + @JvmStatic fun zero() = Vector4d(0.0, 0.0, 0.0, 0.0) + @JvmStatic fun one() = Vector4d(1.0, 1.0, 1.0, 1.0) + } + + operator fun plus(other: Vector4d) = Vector4d(x + other.x, y + other.y, z + other.z, w + other.w) + operator fun plus(scalar: Double) = Vector4d(x + scalar, y + scalar, z + scalar, w + scalar) + + operator fun minus(other: Vector4d) = Vector4d(x - other.x, y - other.y, z - other.z, w - other.w) + operator fun minus(scalar: Double) = Vector4d(x - scalar, y - scalar, z - scalar, w - scalar) + + operator fun times(other: Vector4d) = Vector4d(x * other.x, y * other.y, z * other.z, w * other.w) + operator fun times(scalar: Double) = Vector4d(x * scalar, y * scalar, z * scalar, w * scalar) + + operator fun div(other: Vector4d) = Vector4d(x / other.x, y / other.y, z / other.z, w / other.w) + operator fun div(scalar: Double) = Vector4d(x / scalar, y / scalar, z / scalar, w / scalar) + + operator fun unaryMinus() = Vector4d(-x, -y, -z, -w) + + fun length() = sqrt(x * x + y * y + z * z + w * w) + fun lengthSquared() = x * x + y * y + z * z + w * w + + fun dot(other: Vector4d) = x * other.x + y * other.y + z * other.z + w * other.w + + fun distanceTo(other: Vector4d): Double { + val dx = x - other.x + val dy = y - other.y + val dz = z - other.z + val dw = w - other.w + return sqrt(dx * dx + dy * dy + dz * dz + dw * dw) + } + + fun normalize(): Vector4d { + val l = length() + return if (l != 0.0) this / l else zero() + } + + fun lerp(target: Vector4d, alpha: Double): Vector4d { + val inv = 1.0 - alpha + return Vector4d( + x * inv + target.x * alpha, + y * inv + target.y * alpha, + z * inv + target.z * alpha, + w * inv + target.w * alpha + ) + } + + fun toFloat() = Vector4f(x.toFloat(), y.toFloat(), z.toFloat(), w.toFloat()) + fun toInt() = Vector4i(x.toInt(), y.toInt(), z.toInt(), w.toInt()) + fun toVector3d() = Vector3d(x, y, z) + fun toVector2d() = Vector2d(x, y) + + operator fun component1() = x + operator fun component2() = y + operator fun component3() = z + operator fun component4() = w + + fun toGeneric() = Vector4(x, y, z, w) + + override fun toString() = "Vector4d($x, $y, $z, $w)" + override fun equals(other: Any?) = other is Vector4d && x == other.x && y == other.y && z == other.z && w == other.w + override fun hashCode(): Int { + var result = x.hashCode() + result = 31 * result + y.hashCode() + result = 31 * result + z.hashCode() + result = 31 * result + w.hashCode() + return result + } +} @@ -0,0 +1,52 @@ +package com.dropbear.math + +import kotlin.math.sqrt +import kotlin.jvm.JvmField +import kotlin.jvm.JvmStatic + +class Vector4f( + @JvmField var x: Float, + @JvmField var y: Float, + @JvmField var z: Float, + @JvmField var w: Float +) { + companion object { + @JvmStatic + fun zero() = Vector4f(0f, 0f, 0f, 0f) + } + + operator fun plus(other: Vector4f) = Vector4f(x + other.x, y + other.y, z + other.z, w + other.w) + operator fun plus(scalar: Float) = Vector4f(x + scalar, y + scalar, z + scalar, w + scalar) + + operator fun minus(other: Vector4f) = Vector4f(x - other.x, y - other.y, z - other.z, w - other.w) + operator fun minus(scalar: Float) = Vector4f(x - scalar, y - scalar, z - scalar, w - scalar) + + operator fun times(other: Vector4f) = Vector4f(x * other.x, y * other.y, z * other.z, w * other.w) + operator fun times(scalar: Float) = Vector4f(x * scalar, y * scalar, z * scalar, w * scalar) + + operator fun div(other: Vector4f) = Vector4f(x / other.x, y / other.y, z / other.z, w / other.w) + operator fun div(scalar: Float) = Vector4f(x / scalar, y / scalar, z / scalar, w / scalar) + + operator fun unaryMinus() = Vector4f(-x, -y, -z, -w) + + fun length(): Float = sqrt(x * x + y * y + z * z + w * w) + fun lengthSquared(): Float = x * x + y * y + z * z + w * w + + fun dot(other: Vector4f) = x * other.x + y * other.y + z * other.z + w * other.w + + fun normalize(): Vector4f { + val l = length() + return if (l != 0f) Vector4f(x / l, y / l, z / l, w / l) else zero() + } + + fun toDouble() = Vector4d(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble()) + fun toInt() = Vector4i(x.toInt(), y.toInt(), z.toInt(), w.toInt()) + fun toVector3f() = Vector3f(x, y, z) + + operator fun component1() = x + operator fun component2() = y + operator fun component3() = z + operator fun component4() = w + + override fun toString() = "Vector4f($x, $y, $z, $w)" +} @@ -0,0 +1,35 @@ +package com.dropbear.math + +import kotlin.math.sqrt +import kotlin.jvm.JvmField +import kotlin.jvm.JvmStatic + +class Vector4i( + @JvmField var x: Int, + @JvmField var y: Int, + @JvmField var z: Int, + @JvmField var w: Int +) { + companion object { + @JvmStatic + fun zero() = Vector4i(0, 0, 0, 0) + } + + operator fun plus(other: Vector4i) = Vector4i(x + other.x, y + other.y, z + other.z, w + other.w) + operator fun minus(other: Vector4i) = Vector4i(x - other.x, y - other.y, z - other.z, w - other.w) + + operator fun times(scalar: Int) = Vector4i(x * scalar, y * scalar, z * scalar, w * scalar) + operator fun div(scalar: Int) = Vector4i(x / scalar, y / scalar, z / scalar, w / scalar) + + fun length(): Double = sqrt((x * x + y * y + z * z + w * w).toDouble()) + + fun toFloat() = Vector4f(x.toFloat(), y.toFloat(), z.toFloat(), w.toFloat()) + fun toDouble() = Vector4d(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble()) + + operator fun component1() = x + operator fun component2() = y + operator fun component3() = z + operator fun component4() = w + + override fun toString() = "Vector4i($x, $y, $z, $w)" +} @@ -1,82 +1,52 @@ package com.dropbear.physics import com.dropbear.EntityId -import com.dropbear.exceptionOnError -import com.dropbear.ffi.NativeEngine -import com.dropbear.math.Vector3D +import com.dropbear.math.Vector3d class Collider( internal val index: Index, internal val entity: EntityId, internal val id: UInt, - var colliderShape: ColliderShape, - var density: Double, - var friction: Double, - var restitution: Double, - var isSensor: Boolean, - var translation: Vector3D, - var rotation: Vector3D, ) { - internal constructor( - index: Index, - entity: EntityId, - id: UInt, - colliderShape: ColliderShape, - density: Double, - friction: Double, - restitution: Double, - isSensor: Boolean, - translation: Vector3D, - rotation: Vector3D, - native: NativeEngine, - ): this( - index, - entity, - id, - colliderShape, - density, - friction, - restitution, - isSensor, - translation, - rotation - ) { - this.native = native - } + var colliderShape: ColliderShape + get() = getColliderShape(this) + set(value) = setColliderShape(this, value) + var density: Double + get() = getColliderDensity(this) + set(value) = setColliderDensity(this, value) + var friction: Double + get() = getColliderFriction(this) + set(value) = setColliderFriction(this, value) + var restitution: Double + get() = getColliderRestitution(this) + set(value) = setColliderRestitution(this, value) + var isSensor: Boolean + get() = getColliderIsSensor(this) + set(value) = setColliderIsSensor(this, value) + var translation: Vector3d + get() = getColliderTranslation(this) + set(value) = setColliderTranslation(this, value) + var rotation: Vector3d + get() = getColliderRotation(this) + set(value) = setColliderRotation(this, value) + var mass: Double + get() = getColliderMass(this) + set(value) = setColliderMass(this, value) +} - internal var native: NativeEngine? = null - - /** - * Calculates the mass of the [Collider] based on its shape (determined based on colliderShape) and density. - * - * @return Mass of the collider in kilograms. - */ - fun mass(): Double { - return when (colliderShape) { - is ColliderShape.Box -> { - (colliderShape as ColliderShape.Box).volume() * density - } - is ColliderShape.Sphere -> { - (colliderShape as ColliderShape.Sphere).volume() * density - } - is ColliderShape.Capsule -> { - (colliderShape as ColliderShape.Capsule).volume() * density - } - - is ColliderShape.Cone -> (colliderShape as ColliderShape.Cone).volume() * density - is ColliderShape.Cylinder -> (colliderShape as ColliderShape.Cylinder).volume() * density - } - } - - /** - * Pushes your changes to the [Collider] back to the physics engine. - */ - fun setCollider() { - val native = native ?: if (exceptionOnError) { - throw IllegalStateException("Native engine is not initialised") - } else { - return - } - native.setCollider(this) - } -} +expect fun Collider.getColliderShape(collider: Collider): ColliderShape +expect fun Collider.setColliderShape(collider: Collider, shape: ColliderShape) +expect fun Collider.getColliderDensity(collider: Collider): Double +expect fun Collider.setColliderDensity(collider: Collider, density: Double) +expect fun Collider.getColliderFriction(collider: Collider): Double +expect fun Collider.setColliderFriction(collider: Collider, friction: Double) +expect fun Collider.getColliderRestitution(collider: Collider): Double +expect fun Collider.setColliderRestitution(collider: Collider, restitution: Double) +expect fun Collider.getColliderIsSensor(collider: Collider): Boolean +expect fun Collider.setColliderIsSensor(collider: Collider, isSensor: Boolean) +expect fun Collider.getColliderTranslation(collider: Collider): Vector3d +expect fun Collider.setColliderTranslation(collider: Collider, translation: Vector3d) +expect fun Collider.getColliderRotation(collider: Collider): Vector3d +expect fun Collider.setColliderRotation(collider: Collider, rotation: Vector3d) +expect fun Collider.getColliderMass(collider: Collider): Double +expect fun Collider.setColliderMass(collider: Collider, mass: Double) @@ -0,0 +1,23 @@ +package com.dropbear.physics + +import com.dropbear.ecs.Component +import com.dropbear.EntityId +import com.dropbear.ecs.ComponentType + +class ColliderGroup( + entity: EntityId, +) : Component(entity, "ColliderGroup") { + fun getColliders(): List<Collider> { + return getColliderGroupColliders(this) + } + + companion object : ComponentType<ColliderGroup> { + override fun get(entityId: EntityId): ColliderGroup? { + return if (colliderGroupExistsForEntity(entityId)) ColliderGroup(entityId) else null + } + } +} + +expect fun ColliderGroup.getColliderGroupColliders(colliderGroup: ColliderGroup): List<Collider> + +expect fun colliderGroupExistsForEntity(entityId: EntityId): Boolean @@ -1,80 +1,30 @@ package com.dropbear.physics -import com.dropbear.math.Vector3D -import kotlin.math.PI -import kotlin.math.pow +import com.dropbear.math.Vector3d sealed class ColliderShape { /** * Box shape with half-extents (half-width, half-height, half-depth). */ - data class Box(val halfExtents: Vector3D) : ColliderShape() { - /** - * Returns the volume of the shape/box. - * - * In this case, volume is calculated as: [halfExtents].x * [halfExtents].y * [halfExtents].z - */ - fun volume(): Double { - return halfExtents.x * halfExtents.y * halfExtents.z - } - } + data class Box(val halfExtents: Vector3d) : ColliderShape() /** * Sphere shape with radius. */ - data class Sphere(val radius: Float) : ColliderShape() { - /** - * Returns the volume of the shape/sphere. - * - * In this case, volume is calculated as: (4/3) * π * [radius]^3 - */ - fun volume(): Double { - return (4.0 / 3.0) * PI * radius.toDouble().pow(3.0) - } - } + data class Sphere(val radius: Float) : ColliderShape() /** * Capsule shape along Y-axis. */ - data class Capsule(val halfHeight: Float, val radius: Float) : ColliderShape() { - /** - * Returns the volume of the shape/capsule. - * - * In this case, volume is calculated as: (4/3) * π * [radius]^3 + π * [radius]^2 * (2 * [halfHeight]) - */ - fun volume(): Double { - val sphereVolume = (4.0 / 3.0) * PI * radius.toDouble().pow(3.0) - val cylinderVolume = PI * radius.toDouble().pow(2.0) * (2.0 * halfHeight.toDouble()) - return sphereVolume + cylinderVolume - } - } + data class Capsule(val halfHeight: Float, val radius: Float) : ColliderShape() /** * Cylinder shape along Y-axis. */ - data class Cylinder(val halfHeight: Float, val radius: Float) : ColliderShape() { - /** - * Returns the volume of the shape/cylinder. - * - * In this case, volume is calculated as: π * [radius]^2 * (2 * [halfHeight]) - */ - fun volume(): Double { - return PI * radius.toDouble().pow(2.0) * (2.0 * halfHeight.toDouble()) - } - } - + data class Cylinder(val halfHeight: Float, val radius: Float) : ColliderShape() /** * Cone shape along Y-axis. */ - data class Cone(val halfHeight: Float, val radius: Float) : ColliderShape() { - /** - * Returns the volume of the shape/cone. - * - * In this case, volume is calculated as: (1/3) * π * [radius]^2 * (2 * [halfHeight]) - */ - fun volume(): Double { - return (1.0 / 3.0) * PI * radius.toDouble().pow(2.0) * (2.0 * halfHeight.toDouble()) - } - } + data class Cone(val halfHeight: Float, val radius: Float) : ColliderShape() } @@ -0,0 +1,10 @@ +package com.dropbear.physics + +import com.dropbear.math.Vector3d + +var gravity: Vector3d + get() = getGravity() + set(value) = setGravity(value) + +internal expect fun getGravity(): Vector3d +internal expect fun setGravity(gravity: Vector3d) @@ -1,63 +1,53 @@ package com.dropbear.physics +import com.dropbear.ecs.Component import com.dropbear.EntityId -import com.dropbear.exceptionOnError -import com.dropbear.ffi.NativeEngine -import com.dropbear.math.Vector3D +import com.dropbear.ecs.ComponentType +import com.dropbear.math.Vector3d class RigidBody( internal val index: Index, internal val entity: EntityId, - val rigidBodyMode: RigidBodyMode, - var gravityScale: Double, - var canSleep: Boolean, - var ccdEnabled: Boolean, - var linearVelocity: Vector3D, - var angularVelocity: Vector3D, - var linearDamping: Double, - var angularDamping: Double, - var lockTranslation: AxisLock, - var lockRotation: AxisLock, -) { - internal constructor( - index: Index, - entity: EntityId, - rigidBodyMode: RigidBodyMode, - gravityScale: Double, - canSleep: Boolean, - ccdEnabled: Boolean, - linearVelocity: Vector3D, - angularVelocity: Vector3D, - linearDamping: Double, - angularDamping: Double, - lockTranslation: AxisLock, - lockRotation: AxisLock, - native: NativeEngine - ): this( - index, - entity, - rigidBodyMode, - gravityScale, - canSleep, - ccdEnabled, - linearVelocity, - angularVelocity, - linearDamping, - angularDamping, - lockTranslation, - lockRotation - ) { - this.native = native - } - - internal var native: NativeEngine? = null +) : Component(entity, "RigidBody") { + var rigidBodyMode: RigidBodyMode + get() = getRigidbodyMode(this) + set(value) = setRigidbodyMode(this, value) + var gravityScale: Double + get() = getRigidbodyGravityScale(this) + set(value) = setRigidbodyGravityScale(this, value) + var canSleep: Boolean + get() = getRigidBodySleep(this) + set(value) = setRigidBodySleep(this, value) + var ccdEnabled: Boolean + get() = getRigidbodyCcdEnabled(this) + set(value) = setRigidbodyCcdEnabled(this, value) + var linearVelocity: Vector3d + get() = getRigidbodyLinearVelocity(this) + set(value) = setRigidbodyLinearVelocity(this, value) + var angularVelocity: Vector3d + get() = getRigidbodyAngularVelocity(this) + set(value) = setRigidbodyAngularVelocity(this, value) + var linearDamping: Double + get() = getRigidbodyLinearDamping(this) + set(value) = setRigidbodyLinearDamping(this, value) + var angularDamping: Double + get() = getRigidbodyAngularDamping(this) + set(value) = setRigidbodyAngularDamping(this, value) + var lockTranslation: AxisLock + get() = getRigidbodyLockTranslation(this) + set(value) = setRigidbodyLockTranslation(this, value) + var lockRotation: AxisLock + get() = getRigidbodyLockRotation(this) + set(value) = setRigidbodyLockRotation(this, value) + val childColliders: List<Collider> + get() = getRigidbodyChildren(this) /** * Applies an instant force. * * Typically used for jumping or explosions. */ - fun applyImpulse(impulse: Vector3D) { + fun applyImpulse(impulse: Vector3d) { applyImpulse(impulse.x, impulse.y, impulse.z) } @@ -67,19 +57,15 @@ class RigidBody( * Typically used for jumping or explosions. */ fun applyImpulse(x: Double, y: Double, z: Double) { - val native = native ?: if (exceptionOnError) { - throw IllegalStateException("Native engine is not initialised") - } else { - return - } - return native.applyImpulse(index, x, y, z) + return applyImpulse(index, x, y, z) } + /** * Applies an instant torque/rotational impulse. * * Typically used for spinning objects. */ - fun applyTorqueImpulse(impulse: Vector3D) { + fun applyTorqueImpulse(impulse: Vector3d) { applyTorqueImpulse(impulse.x, impulse.y, impulse.z) } @@ -89,39 +75,39 @@ class RigidBody( * Typically used for spinning objects. */ fun applyTorqueImpulse(x: Double, y: Double, z: Double) { - val native = native ?: if (exceptionOnError) { - throw IllegalStateException("Native engine is not initialised") - } else { - return - } - return native.applyTorqueImpulse(index, x, y, z) + return applyTorqueImpulse(index, x, y, z) } - /** - * Pushes your changes to the [RigidBody] back to the physics engine. - */ - fun setRigidbody() { - val native = native ?: if (exceptionOnError) { - throw IllegalStateException("Native engine is not initialised") - } else { - return + companion object : ComponentType<RigidBody> { + override fun get(entityId: EntityId): RigidBody? { + val index = rigidBodyExistsForEntity(entityId) + return if (index != null) RigidBody(index, entityId) else null } - - native.setRigidbody(this) } +} - /** - * Fetches all child [Collider] under this [RigidBody]. - * - * Returns `null` if there is no component such as `ColliderGroup` attached to the - * entity, or an [emptyList] if there are no child colliders. - */ - fun getChildColliders(): List<Collider>? { - val native = native ?: if (exceptionOnError) { - throw IllegalStateException("Native engine is not initialised") - } else { - return null - } - return native.getChildColliders(index) - } -} +expect fun RigidBody.getRigidbodyMode(rigidBody: RigidBody): RigidBodyMode +expect fun RigidBody.setRigidbodyMode(rigidBody: RigidBody, mode: RigidBodyMode) +expect fun RigidBody.getRigidbodyGravityScale(rigidBody: RigidBody): Double +expect fun RigidBody.setRigidbodyGravityScale(rigidBody: RigidBody, gravityScale: Double) +expect fun RigidBody.getRigidBodySleep(rigidBody: RigidBody): Boolean +expect fun RigidBody.setRigidBodySleep(rigidBody: RigidBody, canSleep: Boolean) +expect fun RigidBody.getRigidbodyCcdEnabled(rigidBody: RigidBody): Boolean +expect fun RigidBody.setRigidbodyCcdEnabled(rigidBody: RigidBody, ccdEnabled: Boolean) +expect fun RigidBody.getRigidbodyLinearVelocity(rigidBody: RigidBody): Vector3d +expect fun RigidBody.setRigidbodyLinearVelocity(rigidBody: RigidBody, linearVelocity: Vector3d) +expect fun RigidBody.getRigidbodyAngularVelocity(rigidBody: RigidBody): Vector3d +expect fun RigidBody.setRigidbodyAngularVelocity(rigidBody: RigidBody, angularVelocity: Vector3d) +expect fun RigidBody.getRigidbodyLinearDamping(rigidBody: RigidBody): Double +expect fun RigidBody.setRigidbodyLinearDamping(rigidBody: RigidBody, linearDamping: Double) +expect fun RigidBody.getRigidbodyAngularDamping(rigidBody: RigidBody): Double +expect fun RigidBody.setRigidbodyAngularDamping(rigidBody: RigidBody, angularDamping: Double) +expect fun RigidBody.getRigidbodyLockTranslation(rigidBody: RigidBody): AxisLock +expect fun RigidBody.setRigidbodyLockTranslation(rigidBody: RigidBody, lockTranslation: AxisLock) +expect fun RigidBody.getRigidbodyLockRotation(rigidBody: RigidBody): AxisLock +expect fun RigidBody.setRigidbodyLockRotation(rigidBody: RigidBody, lockRotation: AxisLock) +expect fun RigidBody.getRigidbodyChildren(rigidBody: RigidBody): List<Collider> +expect fun RigidBody.applyImpulse(index: Index, x: Double, y: Double, z: Double) +expect fun RigidBody.applyTorqueImpulse(index: Index, x: Double, y: Double, z: Double) + +expect fun rigidBodyExistsForEntity(entityId: EntityId): Index? @@ -8,7 +8,9 @@ import com.dropbear.ffi.NativeEngine /** * A handle that allows you to check the state of an async scene load. */ -class SceneLoadHandle(val id: Long, val sceneName: String, val native: NativeEngine) { +class SceneLoadHandle(val id: Long) { + val sceneName: String + get() = getSceneLoadHandleSceneName(id) /** * Switches the scene to the requested scene. * @@ -19,18 +21,18 @@ class SceneLoadHandle(val id: Long, val sceneName: String, val native: NativeEng * is enabled or not. */ fun switchTo() { - native.switchToSceneAsync(this) + switchToSceneAsync() } /** * Returns the progress of scene load. */ fun progress(): Progress { - return native.getSceneLoadProgress(this) + return getSceneLoadProgress() } fun status(): SceneLoadStatus { - return native.getSceneLoadStatus(this) + return getSceneLoadStatus() } /** @@ -61,4 +63,9 @@ class SceneLoadHandle(val id: Long, val sceneName: String, val native: NativeEng fun raw(): Long { return id } -} +} + +expect fun SceneLoadHandle.getSceneLoadHandleSceneName(id: Long): String +expect fun SceneLoadHandle.switchToSceneAsync() +expect fun SceneLoadHandle.getSceneLoadProgress(): Progress +expect fun SceneLoadHandle.getSceneLoadStatus(): SceneLoadStatus @@ -1,8 +1,6 @@ package com.dropbear.scene -import com.dropbear.ffi.NativeEngine - -class SceneManager(val native: NativeEngine) { +class SceneManager() { /** * Loads the resources of a scene asynchronously. * @@ -11,20 +9,20 @@ class SceneManager(val native: NativeEngine) { * and check its status. */ fun loadSceneAsync(sceneName: String): SceneLoadHandle? { - return native.loadSceneAsync(sceneName) + return loadSceneAsyncNative(sceneName) } /** * Loads the resources of a scene asynchronously. * - * This function is an overload, which contains a `loading_scene` parameter. + * This function is an overload, which contains a `loadingScene` parameter. * This allows you to specify a scene to display while the resources are being loaded. * * It must be preloaded (through the scene settings menu in eucalyptus-editor). If not preloaded, it will - * block/freeze the main thread/window to load the `loading_scene` + * block/freeze the main thread/window to load the `loadingScene` */ - fun loadSceneAsync(sceneName: String, loading_scene: String): SceneLoadHandle? { - return native.loadSceneAsync(sceneName, loading_scene) + fun loadSceneAsync(sceneName: String, loadingScene: String): SceneLoadHandle? { + return loadSceneAsyncNative(sceneName, loadingScene) } /** @@ -35,13 +33,10 @@ class SceneManager(val native: NativeEngine) { * as 3D objects. Use [loadSceneAsync] instead. */ fun switchToSceneImmediate(sceneName: String) { - return native.switchToSceneImmediate(sceneName) + return switchToSceneImmediateNative(sceneName) } +} - /** - * Fetches the current scene that is currently being rendered. - */ - fun getCurrentScene(): SceneMetadata { - TODO("Not implemented yet...") - } -} +expect fun SceneManager.loadSceneAsyncNative(sceneName: String): SceneLoadHandle? +expect fun SceneManager.loadSceneAsyncNative(sceneName: String, loadingScene: String): SceneLoadHandle? +expect fun SceneManager.switchToSceneImmediateNative(sceneName: String) @@ -1,7 +0,0 @@ -package com.dropbear.ui - -import com.dropbear.ffi.NativeEngine - -class UIManager(native: NativeEngine) { - -} @@ -1,4 +1,4 @@ package: com.dropbear.ffi.generated headers: dropbear.h -headerFilter: *.h components/*.h +headerFilter: *.h libraryPaths: src @@ -0,0 +1,11 @@ +package com.dropbear; + +public class DropbearEngineNative { + static { + new EucalyptusCoreLoader().ensureLoaded(); + } + + public static native Long getEntity(long worldPtr, String label); + public static native Long getAsset(long assetHandle, String label); + public static native void quit(long commandBufferPtr); +} @@ -0,0 +1,12 @@ +package com.dropbear; + +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); +} @@ -0,0 +1,22 @@ +package com.dropbear; + +import com.dropbear.ffi.DynamicLibraryLoader; + +import java.lang.System; + +public class EucalyptusCoreLoader implements DynamicLibraryLoader { + private static boolean loaded = false; + + @Override + public void ensureLoaded() { + if (!loaded) { + System.loadLibrary("eucalyptus_core"); + loaded = true; + } + } + + @Override + public boolean isLoaded() { + return loaded; + } +} @@ -1,16 +0,0 @@ -package com.dropbear; - -import java.lang.System; - -public class NativeEngineLoader { - private static boolean loaded = false; - - /// Ensures that the eucalyptus_core library is loaded before accessing - /// any of the JNI static functions. - public static void ensureLoaded() { - if (!loaded) { - System.loadLibrary("eucalyptus_core"); - loaded = true; - } - } -} @@ -0,0 +1,12 @@ +package com.dropbear.asset; + +import com.dropbear.EucalyptusCoreLoader; + +public class AssetHandleNative { + static { + new EucalyptusCoreLoader().ensureLoaded(); + } + + public static native boolean isModelHandle(long assetRegistryHandle, long handle); + public static native boolean isTextureHandle(long assetRegistryHandle, long handle); +} @@ -0,0 +1,11 @@ +package com.dropbear.asset; + +import com.dropbear.EucalyptusCoreLoader; + +public class TextureHandleNative { + static { + new EucalyptusCoreLoader().ensureLoaded(); + } + + public static native String getTextureName(long assetRegistryHandle, long handle); +} @@ -0,0 +1,44 @@ +package com.dropbear.components; + +import com.dropbear.EucalyptusCoreLoader; +import com.dropbear.math.Vector3d; + +public class CameraNative { + static { + new EucalyptusCoreLoader().ensureLoaded(); + } + + public static native boolean cameraExistsForEntity(long worldHandle, long entityId); + + public static native Vector3d getCameraEye(long worldHandle, long entityId); + public static native void setCameraEye(long worldHandle, long entityId, Vector3d value); + + public static native Vector3d getCameraTarget(long worldHandle, long entityId); + public static native void setCameraTarget(long worldHandle, long entityId, Vector3d value); + + public static native Vector3d getCameraUp(long worldHandle, long entityId); + public static native void setCameraUp(long worldHandle, long entityId, Vector3d value); + + public static native double getCameraAspect(long worldHandle, long entityId); + + public static native double getCameraFovY(long worldHandle, long entityId); + public static native void setCameraFovY(long worldHandle, long entityId, double value); + + public static native double getCameraZNear(long worldHandle, long entityId); + public static native void setCameraZNear(long worldHandle, long entityId, double value); + + public static native double getCameraZFar(long worldHandle, long entityId); + public static native void setCameraZFar(long worldHandle, long entityId, double value); + + public static native double getCameraYaw(long worldHandle, long entityId); + public static native void setCameraYaw(long worldHandle, long entityId, double value); + + public static native double getCameraPitch(long worldHandle, long entityId); + public static native void setCameraPitch(long worldHandle, long entityId, double value); + + public static native double getCameraSpeed(long worldHandle, long entityId); + public static native void setCameraSpeed(long worldHandle, long entityId, double value); + + public static native double getCameraSensitivity(long worldHandle, long entityId); + public static native void setCameraSensitivity(long worldHandle, long entityId, double value); +} @@ -0,0 +1,27 @@ +package com.dropbear.components; + +import com.dropbear.EucalyptusCoreLoader; +import com.dropbear.math.Vector3d; + +public class CustomPropertiesNative { + static { + new EucalyptusCoreLoader().ensureLoaded(); + } + + public static native boolean customPropertiesExistsForEntity(long worldHandle, long entityId); + + public static native String getStringProperty(long worldHandle, long entityId, String label); + public static native Integer getIntProperty(long worldHandle, long entityId, String label); + public static native Long getLongProperty(long worldHandle, long entityId, String label); + public static native Double getDoubleProperty(long worldHandle, long entityId, String label); + public static native Float getFloatProperty(long worldHandle, long entityId, String label); + public static native Boolean getBoolProperty(long worldHandle, long entityId, String label); + public static native Vector3d getVec3Property(long worldHandle, long entityId, String label); + + public static native void setStringProperty(long worldHandle, long entityId, String label, String value); + public static native void setIntProperty(long worldHandle, long entityId, String label, int value); + public static native void setLongProperty(long worldHandle, long entityId, String label, long value); + public static native void setFloatProperty(long worldHandle, long entityId, String label, double value); + public static native void setBoolProperty(long worldHandle, long entityId, String label, boolean value); + public static native void setVec3Property(long worldHandle, long entityId, String label, Vector3d value); +} @@ -0,0 +1,18 @@ +package com.dropbear.components; + +import com.dropbear.EucalyptusCoreLoader; +import com.dropbear.math.Transform; + +public class EntityTransformNative { + static { + new EucalyptusCoreLoader().ensureLoaded(); + } + + public static native boolean entityTransformExistsForEntity(long worldPtr, long entityId); + + public static native Transform getLocalTransform(long worldPtr, long entityId); + public static native void setLocalTransform(long worldPtr, long entityId, Transform transform); + public static native Transform getWorldTransform(long worldPtr, long entityId); + public static native void setWorldTransform(long worldPtr, long entityId, Transform transform); + public static native Transform propagateTransform(long worldPtr, long entityId); +} @@ -0,0 +1,17 @@ +package com.dropbear.components; + +import com.dropbear.EucalyptusCoreLoader; + +public class MeshRendererNative { + static { + new EucalyptusCoreLoader().ensureLoaded(); + } + + 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 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); +} @@ -1,11 +0,0 @@ -package com.dropbear.ffi; - -import com.dropbear.NativeEngineLoader; - -public class DropbearEngineNative { - static { - NativeEngineLoader.ensureLoaded(); - } - - public static native void quit(long graphicsHandle); -} @@ -0,0 +1,12 @@ +package com.dropbear.ffi; + +/// Interface for dynamic library loading, allowing for custom components not within the dropbear component +/// library. +public interface DynamicLibraryLoader { + /// Ensures that the library is loaded before accessing + /// any of the JNI static functions. + void ensureLoaded(); + + /// Forces the library to have a loaded boolean variable. + boolean isLoaded(); +} @@ -1,26 +0,0 @@ -package com.dropbear.ffi; - -import com.dropbear.NativeEngineLoader; -import com.dropbear.input.Gamepad; - -public class InputStateNative { - static { - NativeEngineLoader.ensureLoaded(); - } - - // input - public static native void printInputState(long inputHandle); - public static native boolean isKeyPressed(long inputHandle, int ordinal); - public static native float[] getMousePosition(long inputHandle); - public static native boolean isMouseButtonPressed(long inputHandle, int ordinal); - public static native float[] getMouseDelta(long inputHandle); - public static native boolean isCursorLocked(long inputHandle); - public static native void setCursorLocked(long inputHandle, long graphicsHandle, boolean locked); - public static native float[] getLastMousePos(long inputHandle); - public static native boolean isCursorHidden(long inputHandle); - public static native void setCursorHidden(long inputHandle, long graphicsHandle, boolean hidden); - - // gamepad stuff - public static native Gamepad[] getConnectedGamepads(long inputHandle); - public static native boolean isGamepadButtonPressed(long inputHandle, long gamepadId, int gamepadButtonOrdinal); -} @@ -1,20 +0,0 @@ -package com.dropbear.ffi; - -import com.dropbear.Camera; -import com.dropbear.EntityTransform; -import com.dropbear.NativeEngineLoader; -import com.dropbear.math.Transform; -import com.dropbear.scene.SceneLoadHandle; -import com.dropbear.utils.Progress; - -/** - * Describes all the functions that are available in - * the `eucalyptus_core` dynamic library. - */ -public class JNINative { - static { - NativeEngineLoader.ensureLoaded(); - } - public static native long getEntity(long worldHandle, String label); - public static native long getAsset(long assetRegistryHandle, String eucaURI); -} @@ -1,16 +0,0 @@ -package com.dropbear.ffi; - -import com.dropbear.NativeEngineLoader; -import com.dropbear.physics.Collider; -import com.dropbear.physics.RigidBody; - -public class PhysicsNative { - static { - NativeEngineLoader.ensureLoaded(); - } - - public static native void setPhysicsEnabled(long worldHandle, long physicsEngineHandle, long entityId, boolean enabled); - public static native boolean isPhysicsEnabled(long worldHandle, long physicsEngineHandle, long entityId); - public static native RigidBody getRigidBody(long worldHandle, long physicsEngineHandle, long entityId); - public static native Collider[] getAllColliders(long worldHandle, long physicsEngineHandle, long entityId); -} @@ -1,19 +0,0 @@ -package com.dropbear.ffi; - -import com.dropbear.NativeEngineLoader; -import com.dropbear.scene.SceneLoadHandle; -import com.dropbear.scene.SceneLoadStatus; -import com.dropbear.utils.Progress; - -public class SceneNative { - static { - NativeEngineLoader.ensureLoaded(); - } - - public static native SceneLoadHandle loadSceneAsync(long commandBufferHandle, long sceneLoader, String sceneName, NativeEngine engine); - public static native SceneLoadHandle loadSceneAsync(long commandBufferHandle, long sceneLoader, String sceneName, String loadingScene, NativeEngine engine); - public static native void switchToSceneAsync(long commandBufferHandle, SceneLoadHandle handle); - public static native void switchToSceneImmediate(long commandBufferHandle, String sceneName); - public static native Progress getSceneLoadProgress(long sceneLoader, SceneLoadHandle handle); - public static native SceneLoadStatus getSceneLoadStatus(long sceneLoader, SceneLoadHandle handle); -} @@ -1,14 +0,0 @@ -package com.dropbear.ffi.components; - -import com.dropbear.Camera; -import com.dropbear.NativeEngineLoader; - -public class CameraNative { - static { - NativeEngineLoader.ensureLoaded(); - } - - public static native Camera getCamera(long worldHandle, String label); - public static native Camera getAttachedCamera(long worldHandle, long entityHandle); - public static native void setCamera(long worldHandle, Camera camera); -} @@ -1,11 +0,0 @@ -package com.dropbear.ffi.components; - -import com.dropbear.physics.Collider; - -public class ColliderNative { - static { - com.dropbear.NativeEngineLoader.ensureLoaded(); - } - - public static native void setCollider(long worldHandle, long physicsEngineHandle, Collider collider); -} @@ -1,25 +0,0 @@ -package com.dropbear.ffi.components; - -import com.dropbear.NativeEngineLoader; - -public class CustomPropertiesNative { - static { - NativeEngineLoader.ensureLoaded(); - } - - // properties - public static native String getStringProperty(long worldHandle, long entityHandle, String label); - public static native int getIntProperty(long worldHandle, long entityHandle, String label); - public static native long getLongProperty(long worldHandle, long entityHandle, String label); - public static native double getFloatProperty(long worldHandle, long entityHandle, String label); - public static native boolean getBoolProperty(long worldHandle, long entityHandle, String label); - public static native float[] getVec3Property(long worldHandle, long entityHandle, String label); - - public static native void setStringProperty(long worldHandle, long entityHandle, String label, String value); - public static native void setIntProperty(long worldHandle, long entityHandle, String label, int value); - public static native void setLongProperty(long worldHandle, long entityHandle, String label, long value); - public static native void setFloatProperty(long worldHandle, long entityHandle, String label, double value); - public static native void setBoolProperty(long worldHandle, long entityHandle, String label, boolean value); - public static native void setVec3Property(long worldHandle, long entityHandle, String label, float[] value); - -} @@ -1,15 +0,0 @@ -package com.dropbear.ffi.components; - -import com.dropbear.EntityTransform; -import com.dropbear.NativeEngineLoader; -import com.dropbear.math.Transform; - -public class EntityTransformNative { - static { - NativeEngineLoader.ensureLoaded(); - } - - public static native EntityTransform getTransform(long handle, long entityHandle); - public static native Transform propagateTransform(long worldHandle, long id); - public static native void setTransform(long worldHandle, long id, EntityTransform transform); -} @@ -1,13 +0,0 @@ -package com.dropbear.ffi.components; - -import com.dropbear.NativeEngineLoader; - -public class HierarchyNative { - static { - NativeEngineLoader.ensureLoaded(); - } - - public static native long[] getChildren(long worldHandle, long entityId); - public static native long getChildByLabel(long worldHandle, long entityId, String label); - public static native long getParent(long worldHandle, long entityId); -} @@ -1,11 +0,0 @@ -package com.dropbear.ffi.components; - -import com.dropbear.NativeEngineLoader; - -public class LabelNative { - static { - NativeEngineLoader.ensureLoaded(); - } - - public static native String getEntityLabel(long worldHandle, long entityHandle); -} @@ -1,25 +0,0 @@ -package com.dropbear.ffi.components; - -import com.dropbear.NativeEngineLoader; - -public class MeshRendererNative { - static { - NativeEngineLoader.ensureLoaded(); - } - - // model - public static native long getModel(long worldHandle, long entityHandle); - public static native void setModel(long worldHandle, long assetHandle, long entityHandle, long modelHandle); - public static native boolean isModelHandle(long assetRegistryHandle, long handle); - public static native boolean isUsingModel(long worldHandle, long entityHandle, long modelHandle); - - // texture - public static native long getTexture(long worldHandle, long assetHandle, long entityHandle, String name); - public static native String getTextureName(long assetHandle, long textureHandle); - public static native void setTexture(long worldHandle, long assetRegistryHandle, long entityHandle, - String oldMaterialName, long textureHandle); - public static native boolean isTextureHandle(long assetRegistryHandle, long handle); - public static native boolean isUsingTexture(long worldHandle, long entityHandle, long textureHandle); - - public static native String[] getAllTextures(long worldHandle, long entityHandle); -} @@ -1,17 +0,0 @@ -package com.dropbear.ffi.components; - -import com.dropbear.physics.Collider; -import com.dropbear.physics.Index; -import com.dropbear.physics.RigidBody; - -public class RigidBodyNative { - static { - com.dropbear.NativeEngineLoader.ensureLoaded(); - } - - public static native void applyImpulse(long physicsEngineHandle, Index rigidBodyId, double x, double y, double z); - public static native void applyTorqueImpulse(long physicsEngineHandle, Index rigidBodyId, double x, double y, double z); - - public static native void setRigidBody(long worldHandle, long physicsEngineHandle, RigidBody rigidBody); - public static native Collider[] getChildColliders(long worldHandle, long physicsEngineHandle, Index rigidBodyId); -} @@ -0,0 +1,14 @@ +package com.dropbear.input; + +import com.dropbear.EucalyptusCoreLoader; +import com.dropbear.math.Vector2d; + +public class GamepadNative { + static { + new EucalyptusCoreLoader().ensureLoaded(); + } + + public static native boolean isGamepadButtonPressed(long inputStateHandle, long gamepad, int gamepadButton); + public static native Vector2d getLeftStickPosition(long inputStateHandle, long gamepad); + public static native Vector2d getRightStickPosition(long inputStateHandle, long gamepad); +} @@ -0,0 +1,22 @@ +package com.dropbear.input; + +import com.dropbear.EucalyptusCoreLoader; +import com.dropbear.math.Vector2d; + +public class InputStateNative { + static { + new EucalyptusCoreLoader().ensureLoaded(); + } + + public static native void printInputState(long inputStateHandle); + public static native boolean isKeyPressed(long inputStateHandle, int keyCode); + public static native Vector2d getMousePosition(long inputStateHandle); + public static native boolean isMouseButtonPressed(long inputStateHandle, MouseButton mouseButton); + public static native Vector2d getMouseDelta(long inputStateHandle); + public static native boolean isCursorLocked(long inputStateHandle); + public static native void setCursorLocked(long commandBufferPtr, long inputStateHandle, boolean locked); + public static native Vector2d getLastMousePos(long inputStateHandle); + public static native boolean isCursorHidden(long inputStateHandle); + public static native void setCursorHidden(long commandBufferPtr, long inputStateHandle, boolean hidden); + public static native long[] getConnectedGamepads(long inputStateHandle); +} @@ -0,0 +1,12 @@ +package com.dropbear.physics; + +import com.dropbear.EucalyptusCoreLoader; + +public class ColliderGroupNative { + static { + new EucalyptusCoreLoader().ensureLoaded(); + } + + public static native Collider[] getColliderGroupColliders(long worldPtr, long physicsPtr, long entityId); + public static native boolean colliderGroupExistsForEntity(long worldPtr, long entityId); +} @@ -0,0 +1,34 @@ +package com.dropbear.physics; + +import com.dropbear.EucalyptusCoreLoader; +import com.dropbear.math.Vector3d; + +public class ColliderNative { + static { + new EucalyptusCoreLoader().ensureLoaded(); + } + + public static native ColliderShape getColliderShape(long physicsPtr, Collider collider); + public static native void setColliderShape(long physicsPtr, Collider collider, ColliderShape shape); + + public static native double getColliderDensity(long physicsPtr, Collider collider); + public static native void setColliderDensity(long physicsPtr, Collider collider, double density); + + public static native double getColliderFriction(long physicsPtr, Collider collider); + public static native void setColliderFriction(long physicsPtr, Collider collider, double friction); + + public static native double getColliderRestitution(long physicsPtr, Collider collider); + public static native void setColliderRestitution(long physicsPtr, Collider collider, double restitution); + + public static native double getColliderMass(long physicsPtr, Collider collider); + public static native void setColliderMass(long physicsPtr, Collider collider, double mass); + + public static native boolean getColliderIsSensor(long physicsPtr, Collider collider); + public static native void setColliderIsSensor(long physicsPtr, Collider collider, boolean isSensor); + + public static native Vector3d getColliderTranslation(long physicsPtr, Collider collider); + public static native void setColliderTranslation(long physicsPtr, Collider collider, Vector3d translation); + + public static native Vector3d getColliderRotation(long physicsPtr, Collider collider); + public static native void setColliderRotation(long physicsPtr, Collider collider, Vector3d rotation); +} @@ -0,0 +1,13 @@ +package com.dropbear.physics; + +import com.dropbear.EucalyptusCoreLoader; +import com.dropbear.math.Vector3d; + +public class PhysicsNative { + static { + new EucalyptusCoreLoader().ensureLoaded(); + } + + public static native Vector3d getGravity(long physicsHandle); + public static native void setGravity(long physicsHandle, Vector3d gravity); +} @@ -0,0 +1,47 @@ +package com.dropbear.physics; + +import com.dropbear.EucalyptusCoreLoader; +import com.dropbear.math.Vector3d; + +public class RigidBodyNative { + static { + new EucalyptusCoreLoader().ensureLoaded(); + } + + public static native Index rigidBodyExistsForEntity(long worldPtr, long physicsPtr, long entityId); + + public static native int getRigidBodyMode(long worldPtr, long physicsPtr, RigidBody rigidBody); + public static native void setRigidBodyMode(long worldPtr, long physicsPtr, RigidBody rigidBody, int mode); + + public static native double getRigidBodyGravityScale(long worldPtr, long physicsPtr, RigidBody rigidBody); + public static native void setRigidBodyGravityScale(long worldPtr, long physicsPtr, RigidBody rigidBody, double gravityScale); + + public static native double getRigidBodyLinearDamping(long worldPtr, long physicsPtr, RigidBody rigidBody); + public static native void setRigidBodyLinearDamping(long worldPtr, long physicsPtr, RigidBody rigidBody, double linearDamping); + + public static native double getRigidBodyAngularDamping(long worldPtr, long physicsPtr, RigidBody rigidBody); + public static native void setRigidBodyAngularDamping(long worldPtr, long physicsPtr, RigidBody rigidBody, double angularDamping); + + public static native boolean getRigidBodySleep(long worldPtr, long physicsPtr, RigidBody rigidBody); + public static native void setRigidBodySleep(long worldPtr, long physicsPtr, RigidBody rigidBody, boolean canSleep); + + public static native boolean getRigidBodyCcdEnabled(long worldPtr, long physicsPtr, RigidBody rigidBody); + public static native void setRigidBodyCcdEnabled(long worldPtr, long physicsPtr, RigidBody rigidBody, boolean ccdEnabled); + + public static native Vector3d getRigidBodyLinearVelocity(long worldPtr, long physicsPtr, RigidBody rigidBody); + public static native void setRigidBodyLinearVelocity(long worldPtr, long physicsPtr, RigidBody rigidBody, Vector3d linearVelocity); + + public static native Vector3d getRigidBodyAngularVelocity(long worldPtr, long physicsPtr, RigidBody rigidBody); + public static native void setRigidBodyAngularVelocity(long worldPtr, long physicsPtr, RigidBody rigidBody, Vector3d angularVelocity); + + public static native AxisLock getRigidBodyLockTranslation(long worldPtr, long physicsPtr, RigidBody rigidBody); + public static native void setRigidBodyLockTranslation(long worldPtr, long physicsPtr, RigidBody rigidBody, AxisLock lockTranslation); + + public static native AxisLock getRigidBodyLockRotation(long worldPtr, long physicsPtr, RigidBody rigidBody); + public static native void setRigidBodyLockRotation(long worldPtr, long physicsPtr, RigidBody rigidBody, AxisLock lockRotation); + + public static native Collider[] getRigidBodyChildren(long worldPtr, long physicsPtr, RigidBody rigidBody); + + public static native void applyImpulse(long worldPtr, long physicsPtr, RigidBody rigidBody, double x, double y, double z); + public static native void applyTorqueImpulse(long worldPtr, long physicsPtr, RigidBody rigidBody, double x, double y, double z); +} @@ -0,0 +1,15 @@ +package com.dropbear.scene; + +import com.dropbear.EucalyptusCoreLoader; +import com.dropbear.utils.Progress; + +public class SceneLoadHandleNative { + static { + new EucalyptusCoreLoader().ensureLoaded(); + } + + public static native String getSceneLoadHandleSceneName(long sceneLoaderHandle, long sceneId); + public static native void switchToSceneAsync(long commandBufferPtr, long sceneId); + public static native Progress getSceneLoadProgress(long sceneLoaderHandle, long sceneId); + public static native int getSceneLoadStatus(long sceneLoaderHandle, long sceneId); +} @@ -0,0 +1,13 @@ +package com.dropbear.scene; + +import com.dropbear.EucalyptusCoreLoader; + +public class SceneManagerNative { + static { + new EucalyptusCoreLoader().ensureLoaded(); + } + + public static native Long loadSceneAsyncNative(long commandBufferPtr, long sceneManagerHandle, String sceneName); + public static native Long loadSceneAsyncNative(long commandBufferPtr, long sceneManagerHandle, String sceneName, String loadingScene); + public static native void switchToSceneImmediateNative(long commandBufferPtr, String sceneName); +} @@ -0,0 +1,15 @@ +package com.dropbear + +import com.dropbear.components.Camera + +actual fun getEntity(label: String): Long? { + return DropbearEngineNative.getEntity(DropbearEngine.native.worldHandle, label) +} + +actual fun getAsset(eucaURI: String): Long? { + return DropbearEngineNative.getAsset(DropbearEngine.native.assetHandle, eucaURI) +} + +actual fun quit() { + DropbearEngineNative.quit(DropbearEngine.native.commandBufferHandle) +} @@ -0,0 +1,32 @@ +package com.dropbear + +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>? { + return EntityRefNative.getChildren(DropbearEngine.native.worldHandle, entityId.raw) + .map { EntityRef(EntityId(it)) }.toList().toTypedArray() +} + +actual fun EntityRef.getChildByLabel( + entityId: EntityId, + label: String +): EntityRef? { + 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? { + val result = EntityRefNative.getParent(DropbearEngine.native.worldHandle, entityId.raw) + return if (result != null) { + EntityRef(EntityId(result)) + } else { + null + } +} @@ -0,0 +1,11 @@ +package com.dropbear.asset + +import com.dropbear.DropbearEngine + +actual fun isModelHandle(id: Long): Boolean { + return AssetHandleNative.isModelHandle(DropbearEngine.native.assetHandle, id) +} + +actual fun isTextureHandle(id: Long): Boolean { + return AssetHandleNative.isTextureHandle(DropbearEngine.native.assetHandle, id) +} @@ -0,0 +1,7 @@ +package com.dropbear.asset + +import com.dropbear.DropbearEngine + +actual fun TextureHandle.getTextureName(id: Long): String? { + return TextureHandleNative.getTextureName(DropbearEngine.native.assetHandle, id) +} @@ -0,0 +1,96 @@ +package com.dropbear.components + +import com.dropbear.DropbearEngine +import com.dropbear.EntityId +import com.dropbear.math.Vector3d + +actual fun Camera.getCameraEye(entity: EntityId): Vector3d { + return CameraNative.getCameraEye(DropbearEngine.native.worldHandle, entity.raw) + ?: Vector3d.zero() +} + +actual fun Camera.setCameraEye(entity: EntityId, value: Vector3d) { + CameraNative.setCameraEye(DropbearEngine.native.worldHandle, entity.raw, value) +} + +actual fun Camera.getCameraTarget(entity: EntityId): Vector3d { + return CameraNative.getCameraTarget(DropbearEngine.native.worldHandle, entity.raw) + ?: Vector3d.zero() +} + +actual fun Camera.setCameraTarget(entity: EntityId, value: Vector3d) { + CameraNative.setCameraTarget(DropbearEngine.native.worldHandle, entity.raw, value) +} + +actual fun Camera.getCameraUp(entity: EntityId): Vector3d { + return CameraNative.getCameraUp(DropbearEngine.native.worldHandle, entity.raw) + ?: Vector3d.up() +} + +actual fun Camera.setCameraUp(entity: EntityId, value: Vector3d) { + CameraNative.setCameraUp(DropbearEngine.native.worldHandle, entity.raw, value) +} + +actual fun Camera.getCameraAspect(entity: EntityId): Double { + return CameraNative.getCameraAspect(DropbearEngine.native.worldHandle, entity.raw) +} + +actual fun Camera.getCameraFovY(entity: EntityId): Double { + return CameraNative.getCameraFovY(DropbearEngine.native.worldHandle, entity.raw) +} + +actual fun Camera.setCameraFovY(entity: EntityId, value: Double) { + CameraNative.setCameraFovY(DropbearEngine.native.worldHandle, entity.raw, value) +} + +actual fun Camera.getCameraZNear(entity: EntityId): Double { + return CameraNative.getCameraZNear(DropbearEngine.native.worldHandle, entity.raw) +} + +actual fun Camera.setCameraZNear(entity: EntityId, value: Double) { + CameraNative.setCameraZNear(DropbearEngine.native.worldHandle, entity.raw, value) +} + +actual fun Camera.getCameraZFar(entity: EntityId): Double { + return CameraNative.getCameraZFar(DropbearEngine.native.worldHandle, entity.raw) +} + +actual fun Camera.setCameraZFar(entity: EntityId, value: Double) { + CameraNative.setCameraZFar(DropbearEngine.native.worldHandle, entity.raw, value) +} + +actual fun Camera.getCameraYaw(entity: EntityId): Double { + return CameraNative.getCameraYaw(DropbearEngine.native.worldHandle, entity.raw) +} + +actual fun Camera.setCameraYaw(entity: EntityId, value: Double) { + CameraNative.setCameraYaw(DropbearEngine.native.worldHandle, entity.raw, value) +} + +actual fun Camera.getCameraPitch(entity: EntityId): Double { + return CameraNative.getCameraPitch(DropbearEngine.native.worldHandle, entity.raw) +} + +actual fun Camera.setCameraPitch(entity: EntityId, value: Double) { + CameraNative.setCameraPitch(DropbearEngine.native.worldHandle, entity.raw, value) +} + +actual fun Camera.getCameraSpeed(entity: EntityId): Double { + return CameraNative.getCameraSpeed(DropbearEngine.native.worldHandle, entity.raw) +} + +actual fun Camera.setCameraSpeed(entity: EntityId, value: Double) { + CameraNative.setCameraSpeed(DropbearEngine.native.worldHandle, entity.raw, value) +} + +actual fun Camera.getCameraSensitivity(entity: EntityId): Double { + return CameraNative.getCameraSensitivity(DropbearEngine.native.worldHandle, entity.raw) +} + +actual fun Camera.setCameraSensitivity(entity: EntityId, value: Double) { + CameraNative.setCameraSensitivity(DropbearEngine.native.worldHandle, entity.raw, value) +} + +actual fun cameraExistsForEntity(entity: EntityId): Boolean { + return CameraNative.cameraExistsForEntity(DropbearEngine.native.worldHandle, entity.raw) +} @@ -0,0 +1,103 @@ +package com.dropbear.components + +import com.dropbear.DropbearEngine +import com.dropbear.EntityId +import com.dropbear.math.Vector3d + +actual fun CustomProperties.getStringProperty( + entityHandle: Long, + label: String +): String? { + return CustomPropertiesNative.getStringProperty(DropbearEngine.native.worldHandle, entityHandle, label) +} + +actual fun CustomProperties.getIntProperty(entityHandle: Long, label: String): Int? { + return CustomPropertiesNative.getIntProperty(DropbearEngine.native.worldHandle, entityHandle, label) +} + +actual fun CustomProperties.getLongProperty( + entityHandle: Long, + label: String +): Long? { + return CustomPropertiesNative.getLongProperty(DropbearEngine.native.worldHandle, entityHandle, label) +} + +actual fun CustomProperties.getDoubleProperty( + entityHandle: Long, + label: String +): Double? { + return CustomPropertiesNative.getDoubleProperty(DropbearEngine.native.worldHandle, entityHandle, label) +} + +actual fun CustomProperties.getFloatProperty( + entityHandle: Long, + label: String +): Float? { + return CustomPropertiesNative.getFloatProperty(DropbearEngine.native.worldHandle, entityHandle, label) +} + +actual fun CustomProperties.getBoolProperty( + entityHandle: Long, + label: String +): Boolean? { + return CustomPropertiesNative.getBoolProperty(DropbearEngine.native.worldHandle, entityHandle, label) +} + +actual fun CustomProperties.getVec3Property( + entityHandle: Long, + label: String +): Vector3d? { + return CustomPropertiesNative.getVec3Property(DropbearEngine.native.worldHandle, entityHandle, label) +} + +actual fun CustomProperties.setStringProperty( + entityHandle: Long, + label: String, + value: String +) { + CustomPropertiesNative.setStringProperty(DropbearEngine.native.worldHandle, entityHandle, label, value) +} + +actual fun CustomProperties.setIntProperty( + entityHandle: Long, + label: String, + value: Int +) { + CustomPropertiesNative.setIntProperty(DropbearEngine.native.worldHandle, entityHandle, label, value) +} + +actual fun CustomProperties.setLongProperty( + entityHandle: Long, + label: String, + value: Long +) { + CustomPropertiesNative.setLongProperty(DropbearEngine.native.worldHandle, entityHandle, label, value) +} + +actual fun CustomProperties.setFloatProperty( + entityHandle: Long, + label: String, + value: Double +) { + CustomPropertiesNative.setFloatProperty(DropbearEngine.native.worldHandle, entityHandle, label, value) +} + +actual fun CustomProperties.setBoolProperty( + entityHandle: Long, + label: String, + value: Boolean +) { + CustomPropertiesNative.setBoolProperty(DropbearEngine.native.worldHandle, entityHandle, label, value) +} + +actual fun CustomProperties.setVec3Property( + entityHandle: Long, + label: String, + value: Vector3d +) { + CustomPropertiesNative.setVec3Property(DropbearEngine.native.worldHandle, entityHandle, label, value) +} + +actual fun customPropertiesExistsForEntity(entityId: EntityId): Boolean { + return CustomPropertiesNative.customPropertiesExistsForEntity(DropbearEngine.native.worldHandle, entityId.raw) +} @@ -0,0 +1,44 @@ +package com.dropbear.components + +import com.dropbear.DropbearEngine +import com.dropbear.EntityId +import com.dropbear.math.Transform + +actual fun entityTransformExistsForEntity(entityId: EntityId): Boolean { + return EntityTransformNative.entityTransformExistsForEntity(DropbearEngine.native.worldHandle, entityId.raw) +} + +actual fun EntityTransform.getLocalTransform(entityId: EntityId): Transform { + return EntityTransformNative.getLocalTransform(DropbearEngine.native.worldHandle, entityId.raw) + ?: Transform.identity() +} + +actual fun EntityTransform.setLocalTransform( + entityId: EntityId, + transform: Transform +) { + EntityTransformNative.setLocalTransform( + DropbearEngine.native.worldHandle, + entityId.raw, + transform + ) +} + +actual fun EntityTransform.getWorldTransform(entityId: EntityId): Transform { + return EntityTransformNative.getWorldTransform(DropbearEngine.native.worldHandle, entityId.raw) ?: Transform.identity() +} + +actual fun EntityTransform.setWorldTransform( + entityId: EntityId, + transform: Transform +) { + EntityTransformNative.setWorldTransform( + DropbearEngine.native.worldHandle, + entityId.raw, + transform + ) +} + +actual fun EntityTransform.propagateTransform(entityId: EntityId): Transform? { + return EntityTransformNative.propagateTransform(DropbearEngine.native.worldHandle, entityId.raw) +} @@ -0,0 +1,63 @@ +package com.dropbear.components + +import com.dropbear.DropbearEngine +import com.dropbear.EntityId +import com.dropbear.asset.ModelHandle +import com.dropbear.asset.TextureHandle + +actual fun MeshRenderer.getModel(id: EntityId): ModelHandle? { + return ModelHandle(MeshRendererNative.getModel(DropbearEngine.native.worldHandle, id.raw)) +} + +actual fun MeshRenderer.setModel(id: EntityId, model: ModelHandle?) { + if (model == null) { + throw IllegalArgumentException("ModelHandle cannot be null") + } + + return MeshRendererNative.setModel( + DropbearEngine.native.worldHandle, + DropbearEngine.native.assetHandle, + id.raw, + model.raw() + ) +} + +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? { + return MeshRendererNative.getTexture( + DropbearEngine.native.worldHandle, + DropbearEngine.native.assetHandle, + id.raw, + materialName + ) +} + +actual fun MeshRenderer.setTextureOverride( + id: EntityId, + materialName: String, + textureHandle: Long +) { + return MeshRendererNative.setTextureOverride( + DropbearEngine.native.worldHandle, + DropbearEngine.native.assetHandle, + id.raw, + materialName, + textureHandle + ) +} + +actual fun meshRendererExistsForEntity(entityId: EntityId): Boolean { + return MeshRendererNative.meshRendererExistsForEntity( + DropbearEngine.native.worldHandle, + entityId.raw + ) +} @@ -1,45 +1,14 @@ package com.dropbear.ffi -import com.dropbear.Camera -import com.dropbear.EntityId -import com.dropbear.EntityRef -import com.dropbear.EntityTransform -import com.dropbear.asset.TextureHandle -import com.dropbear.exception.DropbearNativeException -import com.dropbear.exceptionOnError -import com.dropbear.ffi.JNINative.* -import com.dropbear.ffi.InputStateNative.* -import com.dropbear.ffi.DropbearEngineNative.* -import com.dropbear.ffi.components.HierarchyNative.* -import com.dropbear.ffi.components.CameraNative.* -import com.dropbear.ffi.components.ColliderNative -import com.dropbear.ffi.components.LabelNative.* -import com.dropbear.ffi.components.MeshRendererNative.* -import com.dropbear.ffi.components.EntityTransformNative.* -import com.dropbear.ffi.components.CustomPropertiesNative.* -import com.dropbear.ffi.components.RigidBodyNative -import com.dropbear.input.Gamepad -import com.dropbear.input.GamepadButton -import com.dropbear.input.KeyCode -import com.dropbear.input.MouseButton -import com.dropbear.input.MouseButtonCodes import com.dropbear.logging.Logger -import com.dropbear.math.Transform -import com.dropbear.math.Vector2D -import com.dropbear.physics.Collider -import com.dropbear.physics.Index -import com.dropbear.physics.RigidBody -import com.dropbear.scene.SceneLoadHandle -import com.dropbear.scene.SceneLoadStatus -import com.dropbear.utils.Progress actual class NativeEngine { - private var worldHandle: Long = 0L - private var inputHandle: Long = 0L - private var commandBufferHandle: Long = 0L - private var assetHandle: Long = 0L - private var sceneLoaderHandle: Long = 0L - private var physicsEngineHandle: Long = 0L + internal var worldHandle: Long = 0L + internal var inputHandle: Long = 0L + internal var commandBufferHandle: Long = 0L + internal var assetHandle: Long = 0L + internal var sceneLoaderHandle: Long = 0L + internal var physicsEngineHandle: Long = 0L @JvmName("init") fun init(ctx: DropbearContext) { @@ -50,434 +19,29 @@ actual class NativeEngine { this.sceneLoaderHandle = ctx.sceneLoaderHandle this.physicsEngineHandle = ctx.physicsEngineHandle - if (this.worldHandle < 0L) { + if (this.worldHandle <= 0L) { Logger.error("NativeEngine: Error - Invalid world handle received!") return } - if (this.inputHandle < 0L) { + if (this.inputHandle <= 0L) { Logger.error("NativeEngine: Error - Invalid input handle received!") return } - if (this.commandBufferHandle < 0L) { + if (this.commandBufferHandle <= 0L) { Logger.error("NativeEngine: Error - Invalid graphics handle received!") return } - if (this.assetHandle < 0L) { + if (this.assetHandle <= 0L) { Logger.error("NativeEngine: Error - Invalid asset handle received!") return } - if (this.sceneLoaderHandle < 0L) { + if (this.sceneLoaderHandle <= 0L) { Logger.error("NativeEngine: Error - Invalid scene loader handle received!") return } - if (this.physicsEngineHandle < 0L) { + if (this.physicsEngineHandle <= 0L) { Logger.error("NativeEngine: Error - Invalid physics handle received!") return } } - - actual fun getEntityLabel(entityHandle: Long) : String? { - val result = getEntityLabel(worldHandle, entityHandle) ?: if (exceptionOnError) { - throw DropbearNativeException("Unable to get entity label for entity $entityHandle") - } else { - return null - } - return result - } - - actual fun getEntity(label: String): Long? { - val result = getEntity(worldHandle, label) - return if (result == -1L) { - if (exceptionOnError) { - throw DropbearNativeException("Unable to get entity: returned -1") - } else { - null - } - } else if (result == 0L) { - null - } else { - result - } - } - - - actual fun getTransform(entityId: EntityId): EntityTransform? { - return getTransform(worldHandle, entityId.id) - } - - actual fun propagateTransform(entityId: EntityId): Transform? { - return propagateTransform(worldHandle, entityId.id) - } - - actual fun setTransform(entityId: EntityId, transform: EntityTransform) { - return setTransform(worldHandle, entityId.id, transform) - } - - actual fun printInputState() { - return printInputState(inputHandle) - } - - actual fun isKeyPressed(key: KeyCode): Boolean { - return isKeyPressed(inputHandle, key.ordinal) - } - - actual fun getMousePosition(): Vector2D? { - val result = getMousePosition(inputHandle); - return Vector2D(result[0].toDouble(), result[1].toDouble()) - } - - actual fun isMouseButtonPressed(button: MouseButton): Boolean { - val buttonCode: Int = when (button) { - MouseButton.Left -> MouseButtonCodes.LEFT - MouseButton.Right -> MouseButtonCodes.RIGHT - MouseButton.Middle -> MouseButtonCodes.MIDDLE - MouseButton.Back -> MouseButtonCodes.BACK - MouseButton.Forward -> MouseButtonCodes.FORWARD - is MouseButton.Other -> button.value - } - - return isMouseButtonPressed(inputHandle, buttonCode) - } - - actual fun getConnectedGamepads(): List<Gamepad> { - val result = getConnectedGamepads(inputHandle) - return result.toList() - } - - actual fun isGamepadButtonPressed(id: Long, button: GamepadButton): Boolean { - return isGamepadButtonPressed(inputHandle, id, button.ordinal) - } - - actual fun getMouseDelta(): Vector2D? { - val result = getMouseDelta(inputHandle); - return Vector2D(result[0].toDouble(), result[1].toDouble()) - } - - actual fun isCursorLocked(): Boolean { - return isCursorLocked(inputHandle) - } - - actual fun setCursorLocked(locked: Boolean) { - setCursorLocked(inputHandle, commandBufferHandle, locked) - } - - actual fun getLastMousePos(): Vector2D? { - val result = getLastMousePos(inputHandle); - return Vector2D(result[0].toDouble(), result[1].toDouble()) - } - - actual fun getStringProperty(entityHandle: Long, label: String): String? { - return getStringProperty(worldHandle, entityHandle, label) - } - - actual fun getIntProperty(entityHandle: Long, label: String): Int? { - val result = getIntProperty(worldHandle, entityHandle, label) - return if (result == 650911) { - if (exceptionOnError) { - throw DropbearNativeException("Unable to get integer property for entity $label") - } else { - null - } - } else { - result - } - } - - actual fun getLongProperty(entityHandle: Long, label: String): Long? { - val result = getLongProperty(worldHandle, entityHandle, label) - return if (result == 6509112938) { - if (exceptionOnError) { - throw DropbearNativeException("Unable to get long property for entity $label") - } else { - null - } - } else { - result - } - } - - actual fun getFloatProperty(entityHandle: Long, label: String): Float? { - val result = getFloatProperty(worldHandle, entityHandle, label) - return if (result.isNaN()) { - if (exceptionOnError) { - throw DropbearNativeException("Unable to get float property for entity $label") - } else { - null - } - } else { - result.toFloat() - } - } - - actual fun getDoubleProperty(entityHandle: Long, label: String): Double? { - val result = getFloatProperty(worldHandle, entityHandle, label) - return if (result.isNaN()) { - if (exceptionOnError) { - throw DropbearNativeException("Unable to get double (float) property") - } else { - null - } - } else { - result - } - } - - actual fun getBoolProperty(entityHandle: Long, label: String): Boolean? { - return getBoolProperty(worldHandle, entityHandle, label) - } - - actual fun getVec3Property(entityHandle: Long, label: String): FloatArray? { - return getVec3Property(worldHandle, entityHandle, label) - } - - actual fun setStringProperty(entityHandle: Long, label: String, value: String) { - setStringProperty(worldHandle, entityHandle, label, value) - } - - actual fun setIntProperty(entityHandle: Long, label: String, value: Int) { - setIntProperty(worldHandle, entityHandle, label, value) - } - - actual fun setLongProperty(entityHandle: Long, label: String, value: Long) { - setLongProperty(worldHandle, entityHandle, label, value) - } - - actual fun setFloatProperty(entityHandle: Long, label: String, value: Double) { - setFloatProperty(worldHandle, entityHandle, label, value) - } - - actual fun setBoolProperty(entityHandle: Long, label: String, value: Boolean) { - setBoolProperty(worldHandle, entityHandle, label, value) - } - - actual fun setVec3Property(entityHandle: Long, label: String, value: FloatArray) { - setVec3Property(worldHandle, entityHandle, label, value) - } - - actual fun getCamera(label: String): Camera? { - return getCamera(worldHandle, label) - } - - actual fun getAttachedCamera(entityId: EntityId): Camera? { - return getAttachedCamera(worldHandle, entityId.id) - } - - actual fun setCamera(camera: Camera) { - setCamera(worldHandle, camera) - } - - actual fun isCursorHidden(): Boolean { - return isCursorHidden(inputHandle) - } - - actual fun setCursorHidden(hidden: Boolean) { - setCursorHidden(inputHandle, commandBufferHandle, hidden) - } - - actual fun getModel(entityHandle: Long): Long? { - val result = getModel(worldHandle, entityHandle) - return if (result == -1L) { - if (exceptionOnError) { - throw DropbearNativeException("Unable to get model for entity $entityHandle") - } else { - null - } - } else if (result == 0L) { - null - } else { - result - } - } - - actual fun setModel(entityHandle: Long, modelHandle: Long) { - setModel(worldHandle, assetHandle, entityHandle, modelHandle) - } - - actual fun getTexture(entityHandle: Long, name: String): Long? { - val result = getTexture(worldHandle, assetHandle, entityHandle, name) - return if (result == -1L) { - if (exceptionOnError) { - throw DropbearNativeException("Unable to get texture for entity $entityHandle") - } else { - null - } - } else if (result == 0L) { - null - } else { - result - } - } - - actual fun setTextureOverride(entityHandle: Long, oldMaterialName: String, newTextureHandle: TextureHandle) { - return setTexture( - worldHandle, - assetHandle, - entityHandle, - oldMaterialName, - newTextureHandle.raw() - ) - } - - actual fun getTextureName(textureHandle: Long): String? { - return getTextureName(assetHandle, textureHandle) - } - - actual fun isUsingModel(entityHandle: Long, modelHandle: Long): Boolean { - return isUsingModel(worldHandle, entityHandle, modelHandle) - } - - actual fun isUsingTexture(entityHandle: Long, textureHandle: Long): Boolean { - return isUsingTexture(worldHandle, entityHandle, textureHandle) - } - - actual fun getAsset(eucaURI: String): Long? { - val result = getAsset(assetHandle, eucaURI) - return if (result == -1L) { - if (exceptionOnError) { - throw DropbearNativeException("Unable to get asset for URI $eucaURI") - } else { - null - } - } else if (result == 0L) { - // no asset found - null - } else { - result - } - } - - actual fun isModelHandle(id: Long): Boolean { - return isModelHandle(assetHandle, id) - } - - actual fun isTextureHandle(id: Long): Boolean { - return isTextureHandle(assetHandle, id) - } - - actual fun getAllTextures(entityHandle: Long): Array<String> { - return getAllTextures(worldHandle, entityHandle) ?: emptyArray() - } - - actual fun getChildren(entityId: EntityId): Array<EntityRef>? { - val result = getChildren(worldHandle, entityId.id) - // i shouldn't expect it to return null unless an error, otherwise it must - // return an empty array - if (result == null) { - if (exceptionOnError) { - throw DropbearNativeException("Unable to query for all children for entity ${entityId.id}") - } else { - return null - } - } else { - val entityRefs = mutableListOf<EntityRef>() - result.forEach { e -> - entityRefs.add(EntityRef(EntityId(e))) - } - return entityRefs.toTypedArray() // must be an array so it cannot be mutated - } - } - - actual fun getChildByLabel(entityId: EntityId, label: String): EntityRef? { - val result = getChildByLabel(worldHandle, entityId.id, label) - return if (result == -1L) { - if (exceptionOnError) { - throw DropbearNativeException("Unable to get child by label $entityId $label") - } else { - null - } - } else if (result == -2L) { - null - } else { - EntityRef(EntityId(result)) - } - } - - actual fun getParent(entityId: EntityId): EntityRef? { - val result = getParent(worldHandle, entityId.id) - return if (result == -1L) { - if (exceptionOnError) { - throw DropbearNativeException("Unable to get parent of entity $entityId") - } else { - null - } - } else if (result == -2L) { - null - } else { - EntityRef(EntityId(result)) - } - } - - actual fun quit() { - quit(commandBufferHandle) - } - - actual fun switchToSceneImmediate(sceneName: String) { - SceneNative.switchToSceneImmediate(commandBufferHandle, sceneName) - } - - actual fun loadSceneAsync(sceneName: String): SceneLoadHandle? { - return SceneNative.loadSceneAsync(commandBufferHandle, sceneLoaderHandle, sceneName, this) - } - - actual fun loadSceneAsync(sceneName: String, loadingScene: String): SceneLoadHandle? { - return SceneNative.loadSceneAsync(commandBufferHandle, sceneLoaderHandle, sceneName, loadingScene, this) - } - - actual fun switchToSceneAsync(sceneLoadHandle: SceneLoadHandle) { - SceneNative.switchToSceneAsync(commandBufferHandle, sceneLoadHandle) // will throw exception from JNI interface - } - - actual fun getSceneLoadProgress(sceneLoadHandle: SceneLoadHandle): Progress { - return SceneNative.getSceneLoadProgress(sceneLoaderHandle, sceneLoadHandle) - } - - actual fun getSceneLoadStatus(sceneLoadHandle: SceneLoadHandle): SceneLoadStatus { - return SceneNative.getSceneLoadStatus(sceneLoaderHandle, sceneLoadHandle) - } - - actual fun setPhysicsEnabled(entityId: Long, enabled: Boolean) { - return PhysicsNative.setPhysicsEnabled(worldHandle, physicsEngineHandle, entityId, enabled) - } - - actual fun isPhysicsEnabled(entityId: Long): Boolean { - return PhysicsNative.isPhysicsEnabled(worldHandle, physicsEngineHandle, entityId) - } - - actual fun getRigidBody(entityId: Long): RigidBody? { - val result = PhysicsNative.getRigidBody(worldHandle, physicsEngineHandle, entityId) - result.native = this@NativeEngine - return result - } - - actual fun getAllColliders(entityId: Long): List<Collider> { - val result = PhysicsNative.getAllColliders(worldHandle, physicsEngineHandle, entityId) - result.forEach { - it.native = this@NativeEngine - } - return result.toList() - } - - actual fun applyImpulse(index: Index, x: Double, y: Double, z: Double) { - return RigidBodyNative.applyImpulse(physicsEngineHandle, index, x, y, z) - } - - actual fun applyTorqueImpulse(index: Index, x: Double, y: Double, z: Double) { - return RigidBodyNative.applyTorqueImpulse(physicsEngineHandle, index, x, y, z) - } - - actual fun setRigidbody(rigidBody: RigidBody) { - return RigidBodyNative.setRigidBody(worldHandle, physicsEngineHandle, rigidBody) - } - - actual fun getChildColliders(index: Index): List<Collider> { - val result = RigidBodyNative.getChildColliders(worldHandle, physicsEngineHandle, index) - result.forEach { - it.native = this@NativeEngine - } - return result.toList() - } - - actual fun setCollider(collider: Collider) { - return ColliderNative.setCollider(worldHandle, physicsEngineHandle, collider) - } -} +} @@ -1,7 +1,7 @@ package com.dropbear.host import com.dropbear.DropbearEngine -import com.dropbear.System +import com.dropbear.ecs.System import com.dropbear.logging.LogLevel import com.dropbear.logging.LogWriter import com.dropbear.logging.Logger @@ -0,0 +1,35 @@ +package com.dropbear.input + +import com.dropbear.DropbearEngine +import com.dropbear.math.Vector2d + +actual fun Gamepad.isGamepadButtonPressed( + id: Long, + button: GamepadButton +): Boolean { + return GamepadNative.isGamepadButtonPressed( + DropbearEngine.native.inputHandle, + id, + button.ordinal + ) +} + +@Suppress("UNCHECKED_CAST") +actual fun Gamepad.getLeftStickPosition(id: Long): Vector2d { + val result = GamepadNative.getLeftStickPosition( + DropbearEngine.native.inputHandle, + id + ) + + return result ?: Vector2d.zero() +} + +@Suppress("UNCHECKED_CAST") +actual fun Gamepad.getRightStickPosition(id: Long): Vector2d { + val result = GamepadNative.getRightStickPosition( + DropbearEngine.native.inputHandle, + id + ) + + return result ?: Vector2d.zero() +} @@ -0,0 +1,51 @@ +package com.dropbear.input + +import com.dropbear.DropbearEngine +import com.dropbear.math.Vector2d + +actual class InputState actual constructor() { + actual fun printInputState() { + return InputStateNative.printInputState(DropbearEngine.native.inputHandle) + } + + actual fun isKeyPressed(key: KeyCode): Boolean { + return InputStateNative.isKeyPressed(DropbearEngine.native.inputHandle, key.ordinal) + } + + actual fun getMousePosition(): Vector2d { + return InputStateNative.getMousePosition(DropbearEngine.native.inputHandle) + } + + actual fun isMouseButtonPressed(button: MouseButton): Boolean { + return InputStateNative.isMouseButtonPressed(DropbearEngine.native.inputHandle, button) + } + + actual fun getMouseDelta(): Vector2d { + return InputStateNative.getMouseDelta(DropbearEngine.native.inputHandle) + } + + actual fun isCursorLocked(): Boolean { + return InputStateNative.isCursorLocked(DropbearEngine.native.inputHandle) + } + + actual fun setCursorLocked(locked: Boolean) { + return InputStateNative.setCursorLocked(DropbearEngine.native.commandBufferHandle, DropbearEngine.native.inputHandle, locked) + } + + actual fun getLastMousePos(): Vector2d { + return InputStateNative.getLastMousePos(DropbearEngine.native.inputHandle) + } + + actual fun isCursorHidden(): Boolean { + return InputStateNative.isCursorHidden(DropbearEngine.native.inputHandle) + } + + actual fun setCursorHidden(hidden: Boolean) { + return InputStateNative.setCursorHidden(DropbearEngine.native.commandBufferHandle, DropbearEngine.native.inputHandle, hidden) + } + + actual fun getConnectedGamepads(): List<Gamepad> { + val result = InputStateNative.getConnectedGamepads(DropbearEngine.native.inputHandle) + return result.map { Gamepad(it) }.toList() + } +} @@ -0,0 +1,91 @@ +package com.dropbear.physics + +import com.dropbear.DropbearEngine +import com.dropbear.math.Vector3d + +actual fun Collider.getColliderShape(collider: Collider): ColliderShape { + return ColliderNative.getColliderShape(DropbearEngine.native.physicsEngineHandle, this) +} + +actual fun Collider.setColliderShape( + collider: Collider, + shape: ColliderShape +) { + ColliderNative.setColliderShape(DropbearEngine.native.physicsEngineHandle, this, shape) +} + +actual fun Collider.getColliderDensity(collider: Collider): Double { + return ColliderNative.getColliderDensity(DropbearEngine.native.physicsEngineHandle, this) +} + +actual fun Collider.setColliderDensity( + collider: Collider, + density: Double +) { + ColliderNative.setColliderDensity(DropbearEngine.native.physicsEngineHandle, this, density) +} + +actual fun Collider.getColliderFriction(collider: Collider): Double { + return ColliderNative.getColliderFriction(DropbearEngine.native.physicsEngineHandle, this) +} + +actual fun Collider.setColliderFriction( + collider: Collider, + friction: Double +) { + ColliderNative.setColliderFriction(DropbearEngine.native.physicsEngineHandle, this, friction) +} + +actual fun Collider.getColliderRestitution(collider: Collider): Double { + return ColliderNative.getColliderRestitution(DropbearEngine.native.physicsEngineHandle, this) +} + +actual fun Collider.setColliderRestitution( + collider: Collider, + restitution: Double +) { + ColliderNative.setColliderRestitution(DropbearEngine.native.physicsEngineHandle, this, restitution) +} + +actual fun Collider.getColliderIsSensor(collider: Collider): Boolean { + return ColliderNative.getColliderIsSensor(DropbearEngine.native.physicsEngineHandle, this) +} + +actual fun Collider.setColliderIsSensor( + collider: Collider, + isSensor: Boolean +) { + ColliderNative.setColliderIsSensor(DropbearEngine.native.physicsEngineHandle, this, isSensor) +} + +actual fun Collider.getColliderTranslation(collider: Collider): Vector3d { + return ColliderNative.getColliderTranslation(DropbearEngine.native.physicsEngineHandle, this) + ?: Vector3d.zero() +} + +actual fun Collider.setColliderTranslation( + collider: Collider, + translation: Vector3d +) { + ColliderNative.setColliderTranslation(DropbearEngine.native.physicsEngineHandle, this, translation) +} + +actual fun Collider.getColliderRotation(collider: Collider): Vector3d { + return ColliderNative.getColliderRotation(DropbearEngine.native.physicsEngineHandle, this) + ?: Vector3d.zero() +} + +actual fun Collider.setColliderRotation( + collider: Collider, + rotation: Vector3d +) { + ColliderNative.setColliderRotation(DropbearEngine.native.physicsEngineHandle, this, rotation) +} + +actual fun Collider.getColliderMass(collider: Collider): Double { + return ColliderNative.getColliderMass(DropbearEngine.native.physicsEngineHandle, this) +} + +actual fun Collider.setColliderMass(collider: Collider, mass: Double) { + ColliderNative.setColliderMass(DropbearEngine.native.physicsEngineHandle, this, mass) +} @@ -0,0 +1,12 @@ +package com.dropbear.physics + +import com.dropbear.DropbearEngine +import com.dropbear.EntityId + +actual fun ColliderGroup.getColliderGroupColliders(colliderGroup: ColliderGroup): List<Collider> { + return ColliderGroupNative.getColliderGroupColliders(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, colliderGroup.parentEntity.raw).toList() +} + +actual fun colliderGroupExistsForEntity(entityId: EntityId): Boolean { + return ColliderGroupNative.colliderGroupExistsForEntity(DropbearEngine.native.worldHandle, entityId.raw) +} @@ -0,0 +1,12 @@ +package com.dropbear.physics + +import com.dropbear.DropbearEngine +import com.dropbear.math.Vector3d + +internal actual fun getGravity(): Vector3d { + return PhysicsNative.getGravity(DropbearEngine.native.physicsEngineHandle) ?: Vector3d(0.0, -9.81, 0.0) +} + +internal actual fun setGravity(gravity: Vector3d) { + return PhysicsNative.setGravity(DropbearEngine.native.physicsEngineHandle, gravity) +} @@ -0,0 +1,149 @@ +package com.dropbear.physics + +import com.dropbear.DropbearEngine +import com.dropbear.EntityId +import com.dropbear.math.Vector3d + +actual fun RigidBody.getRigidbodyMode(rigidBody: RigidBody): RigidBodyMode { + val result = RigidBodyNative.getRigidBodyMode(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this) + return RigidBodyMode.entries[result] +} + +actual fun RigidBody.setRigidbodyMode( + rigidBody: RigidBody, + mode: RigidBodyMode +) { + RigidBodyNative.setRigidBodyMode(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, mode.ordinal) +} + +actual fun RigidBody.getRigidbodyGravityScale(rigidBody: RigidBody): Double { + return RigidBodyNative.getRigidBodyGravityScale(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this) +} + +actual fun RigidBody.setRigidbodyGravityScale( + rigidBody: RigidBody, + gravityScale: Double +) { + RigidBodyNative.setRigidBodyGravityScale(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, gravityScale) +} + +actual fun RigidBody.getRigidBodySleep(rigidBody: RigidBody): Boolean { + return RigidBodyNative.getRigidBodySleep(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this) +} + +actual fun RigidBody.setRigidBodySleep( + rigidBody: RigidBody, + canSleep: Boolean +) { + RigidBodyNative.setRigidBodySleep(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, canSleep) +} + +actual fun RigidBody.getRigidbodyCcdEnabled(rigidBody: RigidBody): Boolean { + return RigidBodyNative.getRigidBodyCcdEnabled(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this) +} + +actual fun RigidBody.setRigidbodyCcdEnabled( + rigidBody: RigidBody, + ccdEnabled: Boolean +) { + RigidBodyNative.setRigidBodyCcdEnabled(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, ccdEnabled) +} + +actual fun RigidBody.getRigidbodyLinearVelocity(rigidBody: RigidBody): Vector3d { + return RigidBodyNative.getRigidBodyLinearVelocity(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this) + ?: Vector3d.zero() +} + +actual fun RigidBody.setRigidbodyLinearVelocity( + rigidBody: RigidBody, + linearVelocity: Vector3d +) { + RigidBodyNative.setRigidBodyLinearVelocity(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, linearVelocity) +} + +actual fun RigidBody.getRigidbodyAngularVelocity(rigidBody: RigidBody): Vector3d { + return RigidBodyNative.getRigidBodyAngularVelocity(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this) + ?: Vector3d.zero() +} + +actual fun RigidBody.setRigidbodyAngularVelocity( + rigidBody: RigidBody, + angularVelocity: Vector3d +) { + RigidBodyNative.setRigidBodyAngularVelocity(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, angularVelocity) +} + +actual fun RigidBody.getRigidbodyLinearDamping(rigidBody: RigidBody): Double { + return RigidBodyNative.getRigidBodyLinearDamping(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this) +} + +actual fun RigidBody.setRigidbodyLinearDamping( + rigidBody: RigidBody, + linearDamping: Double +) { + RigidBodyNative.setRigidBodyLinearDamping(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, linearDamping) +} + +actual fun RigidBody.getRigidbodyAngularDamping(rigidBody: RigidBody): Double { + return RigidBodyNative.getRigidBodyAngularDamping(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this) +} + +actual fun RigidBody.setRigidbodyAngularDamping( + rigidBody: RigidBody, + angularDamping: Double +) { + RigidBodyNative.setRigidBodyAngularDamping(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, angularDamping) +} + +actual fun RigidBody.getRigidbodyLockTranslation(rigidBody: RigidBody): AxisLock { + return RigidBodyNative.getRigidBodyLockTranslation(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this) +} + +actual fun RigidBody.setRigidbodyLockTranslation( + rigidBody: RigidBody, + lockTranslation: AxisLock +) { + RigidBodyNative.setRigidBodyLockTranslation(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, lockTranslation) +} + +actual fun RigidBody.getRigidbodyLockRotation(rigidBody: RigidBody): AxisLock { + return RigidBodyNative.getRigidBodyLockRotation(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this) +} + +actual fun RigidBody.setRigidbodyLockRotation( + rigidBody: RigidBody, + lockRotation: AxisLock +) { + RigidBodyNative.setRigidBodyLockRotation(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, lockRotation) +} + +actual fun RigidBody.getRigidbodyChildren(rigidBody: RigidBody): List<Collider> { + val result = RigidBodyNative.getRigidBodyChildren(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this) + return result.toList() +} + +actual fun RigidBody.applyImpulse( + index: Index, + x: Double, + y: Double, + z: Double +) { + RigidBodyNative.applyImpulse(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, x, y, z) +} + +actual fun RigidBody.applyTorqueImpulse( + index: Index, + x: Double, + y: Double, + z: Double +) { + RigidBodyNative.applyTorqueImpulse(DropbearEngine.native.worldHandle, DropbearEngine.native.physicsEngineHandle, this, x, y, z) +} + +actual fun rigidBodyExistsForEntity(entityId: EntityId): Index? { + return RigidBodyNative.rigidBodyExistsForEntity( + DropbearEngine.native.worldHandle, + DropbearEngine.native.physicsEngineHandle, + entityId.raw + ) +} @@ -0,0 +1,21 @@ +package com.dropbear.scene + +import com.dropbear.DropbearEngine +import com.dropbear.utils.Progress + +actual fun SceneLoadHandle.getSceneLoadHandleSceneName(id: Long): String { + return SceneLoadHandleNative.getSceneLoadHandleSceneName(DropbearEngine.native.sceneLoaderHandle, id) +} + +actual fun SceneLoadHandle.switchToSceneAsync() { + return SceneLoadHandleNative.switchToSceneAsync(DropbearEngine.native.commandBufferHandle, this.id) +} + +actual fun SceneLoadHandle.getSceneLoadProgress(): Progress { + return SceneLoadHandleNative.getSceneLoadProgress(DropbearEngine.native.sceneLoaderHandle, this.id) +} + +actual fun SceneLoadHandle.getSceneLoadStatus(): SceneLoadStatus { + val result = SceneLoadHandleNative.getSceneLoadStatus(DropbearEngine.native.sceneLoaderHandle, this.id) + return SceneLoadStatus.entries[result] +} @@ -0,0 +1,32 @@ +package com.dropbear.scene + +import com.dropbear.DropbearEngine + +actual fun SceneManager.loadSceneAsyncNative(sceneName: String): SceneLoadHandle? { + val result = SceneManagerNative.loadSceneAsyncNative( + DropbearEngine.native.commandBufferHandle, + DropbearEngine.native.sceneLoaderHandle, + sceneName + ) + return if (result != null) SceneLoadHandle(result) else null +} + +actual fun SceneManager.loadSceneAsyncNative( + sceneName: String, + loadingScene: String +): SceneLoadHandle? { + val result = SceneManagerNative.loadSceneAsyncNative( + DropbearEngine.native.commandBufferHandle, + DropbearEngine.native.sceneLoaderHandle, + sceneName, + loadingScene + ) + return if (result != null) SceneLoadHandle(result) else null +} + +actual fun SceneManager.switchToSceneImmediateNative(sceneName: String) { + SceneManagerNative.switchToSceneImmediateNative( + DropbearEngine.native.commandBufferHandle, + sceneName + ) +} @@ -0,0 +1,14 @@ +package com.dropbear + +import com.dropbear.components.Camera + +actual fun getEntity(label: String): Long? { + TODO("Not yet implemented") +} + +actual fun getAsset(eucaURI: String): Long? { + TODO("Not yet implemented") +} + +actual fun quit() { +} @@ -0,0 +1,20 @@ +package com.dropbear + +actual fun EntityRef.getEntityLabel(entity: EntityId): String { + TODO("Not yet implemented") +} + +actual fun EntityRef.getChildren(entityId: EntityId): Array<EntityRef>? { + TODO("Not yet implemented") +} + +actual fun EntityRef.getChildByLabel( + entityId: EntityId, + label: String +): EntityRef? { + TODO("Not yet implemented") +} + +actual fun EntityRef.getParent(entityId: EntityId): EntityRef? { + TODO("Not yet implemented") +} @@ -0,0 +1,9 @@ +package com.dropbear.asset + +actual fun isModelHandle(id: Long): Boolean { + TODO("Not yet implemented") +} + +actual fun isTextureHandle(id: Long): Boolean { + TODO("Not yet implemented") +} @@ -0,0 +1,5 @@ +package com.dropbear.asset + +actual fun TextureHandle.getTextureName(id: Long): String? { + TODO("Not yet implemented") +} @@ -0,0 +1,82 @@ +package com.dropbear.components + +import com.dropbear.EntityId +import com.dropbear.math.Vector3d + +actual fun Camera.getCameraEye(entity: EntityId): Vector3d { + TODO("Not yet implemented") +} + +actual fun Camera.setCameraEye(entity: EntityId, value: Vector3d) { +} + +actual fun Camera.getCameraTarget(entity: EntityId): Vector3d { + TODO("Not yet implemented") +} + +actual fun Camera.setCameraTarget(entity: EntityId, value: Vector3d) { +} + +actual fun Camera.getCameraUp(entity: EntityId): Vector3d { + TODO("Not yet implemented") +} + +actual fun Camera.setCameraUp(entity: EntityId, value: Vector3d) { +} + +actual fun Camera.getCameraAspect(entity: EntityId): Double { + TODO("Not yet implemented") +} + +actual fun Camera.getCameraFovY(entity: EntityId): Double { + TODO("Not yet implemented") +} + +actual fun Camera.setCameraFovY(entity: EntityId, value: Double) { +} + +actual fun Camera.getCameraZNear(entity: EntityId): Double { + TODO("Not yet implemented") +} + +actual fun Camera.setCameraZNear(entity: EntityId, value: Double) { +} + +actual fun Camera.getCameraZFar(entity: EntityId): Double { + TODO("Not yet implemented") +} + +actual fun Camera.setCameraZFar(entity: EntityId, value: Double) { +} + +actual fun Camera.getCameraYaw(entity: EntityId): Double { + TODO("Not yet implemented") +} + +actual fun Camera.getCameraPitch(entity: EntityId): Double { + TODO("Not yet implemented") +} + +actual fun Camera.setCameraYaw(entity: EntityId, value: Double) { +} + +actual fun Camera.setCameraPitch(entity: EntityId, value: Double) { +} + +actual fun Camera.getCameraSpeed(entity: EntityId): Double { + TODO("Not yet implemented") +} + +actual fun Camera.setCameraSpeed(entity: EntityId, value: Double) { +} + +actual fun Camera.getCameraSensitivity(entity: EntityId): Double { + TODO("Not yet implemented") +} + +actual fun Camera.setCameraSensitivity(entity: EntityId, value: Double) { +} + +actual fun cameraExistsForEntity(entity: EntityId): Boolean { + TODO("Not yet implemented") +} @@ -0,0 +1,96 @@ +package com.dropbear.components + +import com.dropbear.EntityId +import com.dropbear.math.Vector3d + +actual fun CustomProperties.getStringProperty( + entityHandle: Long, + label: String +): String? { + TODO("Not yet implemented") +} + +actual fun CustomProperties.getIntProperty(entityHandle: Long, label: String): Int? { + TODO("Not yet implemented") +} + +actual fun CustomProperties.getLongProperty( + entityHandle: Long, + label: String +): Long? { + TODO("Not yet implemented") +} + +actual fun CustomProperties.getDoubleProperty( + entityHandle: Long, + label: String +): Double? { + TODO("Not yet implemented") +} + +actual fun CustomProperties.getFloatProperty( + entityHandle: Long, + label: String +): Float? { + TODO("Not yet implemented") +} + +actual fun CustomProperties.getBoolProperty( + entityHandle: Long, + label: String +): Boolean? { + TODO("Not yet implemented") +} + +actual fun CustomProperties.getVec3Property( + entityHandle: Long, + label: String +): Vector3d? { + TODO("Not yet implemented") +} + +actual fun CustomProperties.setStringProperty( + entityHandle: Long, + label: String, + value: String +) { +} + +actual fun CustomProperties.setIntProperty( + entityHandle: Long, + label: String, + value: Int +) { +} + +actual fun CustomProperties.setLongProperty( + entityHandle: Long, + label: String, + value: Long +) { +} + +actual fun CustomProperties.setFloatProperty( + entityHandle: Long, + label: String, + value: Double +) { +} + +actual fun CustomProperties.setBoolProperty( + entityHandle: Long, + label: String, + value: Boolean +) { +} + +actual fun CustomProperties.setVec3Property( + entityHandle: Long, + label: String, + value: Vector3d +) { +} + +actual fun customPropertiesExistsForEntity(entityId: EntityId): Boolean { + TODO("Not yet implemented") +} @@ -0,0 +1,32 @@ +package com.dropbear.components + +import com.dropbear.EntityId +import com.dropbear.math.Transform + +actual fun EntityTransform.getLocalTransform(entityId: EntityId): Transform { + TODO("Not yet implemented") +} + +actual fun EntityTransform.setLocalTransform( + entityId: EntityId, + transform: Transform +) { +} + +actual fun EntityTransform.getWorldTransform(entityId: EntityId): Transform { + TODO("Not yet implemented") +} + +actual fun EntityTransform.setWorldTransform( + entityId: EntityId, + transform: Transform +) { +} + +actual fun EntityTransform.propagateTransform(entityId: EntityId): Transform? { + TODO("Not yet implemented") +} + +actual fun entityTransformExistsForEntity(entityId: EntityId): Boolean { + TODO("Not yet implemented") +} @@ -0,0 +1,31 @@ +package com.dropbear.components + +import com.dropbear.EntityId +import com.dropbear.asset.ModelHandle +import com.dropbear.asset.TextureHandle + +actual fun MeshRenderer.getModel(id: EntityId): ModelHandle? { + TODO("Not yet implemented") +} + +actual fun MeshRenderer.setModel(id: EntityId, model: ModelHandle?) { +} + +actual fun MeshRenderer.getAllTextureIds(id: EntityId): List<TextureHandle>? { + TODO("Not yet implemented") +} + +actual fun MeshRenderer.getTexture(id: EntityId, materialName: String): Long? { + TODO("Not yet implemented") +} + +actual fun MeshRenderer.setTextureOverride( + id: EntityId, + materialName: String, + textureHandle: Long +) { +} + +actual fun meshRendererExistsForEntity(entityId: EntityId): Boolean { + TODO("Not yet implemented") +} @@ -1,197 +0,0 @@ -@file:OptIn(ExperimentalForeignApi::class, ExperimentalNativeApi::class) -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - -package com.dropbear.ffi - -import com.dropbear.EntityId -import com.dropbear.exception.DropbearNativeException -import com.dropbear.ffi.generated.ColliderShape -import com.dropbear.ffi.generated.ColliderShape_Box -import com.dropbear.ffi.generated.ColliderShape_Capsule -import com.dropbear.ffi.generated.ColliderShape_Cone -import com.dropbear.ffi.generated.ColliderShape_Cylinder -import com.dropbear.ffi.generated.ColliderShape_Sphere -import com.dropbear.ffi.generated.RigidBodyMode -import com.dropbear.ffi.generated.SceneLoadResult -import com.dropbear.ffi.generated.Vector3D -import com.dropbear.physics.AxisLock -import com.dropbear.physics.Collider -import com.dropbear.physics.Index -import com.dropbear.physics.RigidBody -import com.dropbear.scene.SceneLoadStatus -import kotlinx.cinterop.ExperimentalForeignApi -import kotlin.experimental.ExperimentalNativeApi - -internal fun SceneLoadResult.fromNative(): SceneLoadStatus { - return when (this) { - SceneLoadResult.SCENE_LOAD_PENDING -> SceneLoadStatus.PENDING - SceneLoadResult.SCENE_LOAD_SUCCESS -> SceneLoadStatus.READY - SceneLoadResult.SCENE_LOAD_ERROR -> SceneLoadStatus.FAILED - } -} - -internal fun Vector3D.toKotlin(): com.dropbear.math.Vector3D { - return com.dropbear.math.Vector3D(this.x, this.y, this.z) -} - -internal fun RigidBodyMode.toKotlin(): com.dropbear.physics.RigidBodyMode { - return when (this) { - RigidBodyMode.RIGIDBODY_MODE_DYNAMIC -> com.dropbear.physics.RigidBodyMode.Dynamic - RigidBodyMode.RIGIDBODY_MODE_FIXED -> com.dropbear.physics.RigidBodyMode.Fixed - RigidBodyMode.RIGIDBODY_MODE_KINEMATIC_POSITION -> com.dropbear.physics.RigidBodyMode.KinematicPosition - RigidBodyMode.RIGIDBODY_MODE_KINEMATIC_VELOCITY -> com.dropbear.physics.RigidBodyMode.KinematicVelocity - } -} - -internal fun com.dropbear.ffi.generated.AxisLock.toKotlin(): AxisLock { - return AxisLock( - this.x, - this.y, - this.z, - ) -} - -internal fun com.dropbear.ffi.generated.RigidBody.toKotlin(nativeEngine: NativeEngine): RigidBody { - return RigidBody( - index = Index(this.index.index, this.index.generation), - entity = EntityId(this.entity), - rigidBodyMode = this.mode.toKotlin(), - gravityScale = this.gravity_scale, - canSleep = this.can_sleep, - ccdEnabled = this.ccd_enabled, - linearVelocity = this.linear_velocity.toKotlin(), - angularVelocity = this.angualar_velocity.toKotlin(), - linearDamping = this.linear_damping, - angularDamping = this.angular_damping, - lockTranslation = this.lock_translation.toKotlin(), - lockRotation = this.lock_rotation.toKotlin(), - native = nativeEngine - ) -} - -internal fun RigidBody.populateCStruct(cBody: com.dropbear.ffi.generated.RigidBody) { - cBody.index.index = this.index.index - cBody.index.generation = this.index.generation - cBody.entity = this.entity.id - cBody.mode = when (this.rigidBodyMode) { - com.dropbear.physics.RigidBodyMode.Dynamic -> RigidBodyMode.RIGIDBODY_MODE_DYNAMIC - com.dropbear.physics.RigidBodyMode.Fixed -> RigidBodyMode.RIGIDBODY_MODE_FIXED - com.dropbear.physics.RigidBodyMode.KinematicPosition -> RigidBodyMode.RIGIDBODY_MODE_KINEMATIC_POSITION - com.dropbear.physics.RigidBodyMode.KinematicVelocity -> RigidBodyMode.RIGIDBODY_MODE_KINEMATIC_VELOCITY - } - cBody.gravity_scale = this.gravityScale - cBody.can_sleep = this.canSleep - cBody.ccd_enabled = this.ccdEnabled - - cBody.linear_velocity.x = this.linearVelocity.x - cBody.linear_velocity.y = this.linearVelocity.y - cBody.linear_velocity.z = this.linearVelocity.z - - cBody.angualar_velocity.x = this.angularVelocity.x - cBody.angualar_velocity.y = this.angularVelocity.y - cBody.angualar_velocity.z = this.angularVelocity.z - - cBody.linear_damping = this.linearDamping - cBody.angular_damping = this.angularDamping - - cBody.lock_translation.x = this.lockTranslation.x - cBody.lock_translation.y = this.lockTranslation.y - cBody.lock_translation.z = this.lockTranslation.z - - cBody.lock_rotation.x = this.lockRotation.x - cBody.lock_rotation.y = this.lockRotation.y - cBody.lock_rotation.z = this.lockRotation.z -} - -internal fun ColliderShape.toKotlin(): com.dropbear.physics.ColliderShape { - return when (this.tag) { - ColliderShape_Box -> { - com.dropbear.physics.ColliderShape.Box( - halfExtents = com.dropbear.math.Vector3D( - this.data.box.half_extents.x, - this.data.box.half_extents.y, - this.data.box.half_extents.z - ) - ) - } - ColliderShape_Sphere -> { - com.dropbear.physics.ColliderShape.Sphere(this.data.sphere.radius) - } - ColliderShape_Capsule -> { - com.dropbear.physics.ColliderShape.Capsule(this.data.capsule.half_height, this.data.capsule.radius) - } - ColliderShape_Cylinder -> { - com.dropbear.physics.ColliderShape.Cylinder(this.data.cylinder.half_height, this.data.cylinder.radius) - } - ColliderShape_Cone -> { - com.dropbear.physics.ColliderShape.Cone(this.data.cone.half_height, this.data.cone.radius) - } - else -> throw DropbearNativeException("Unknown collider tag: ${this.tag}") - } -} - -internal fun com.dropbear.ffi.generated.Collider.toKotlin(nativeEngine: NativeEngine): Collider { - return Collider( - index = Index(this.index.index, this.index.generation), - entity = EntityId(this.entity), - colliderShape = this.collider_shape.toKotlin(), - density = this.density, - friction = this.friction, - restitution = this.restitution, - isSensor = this.is_sensor, - translation = this.translation.toKotlin(), - rotation = this.rotation.toKotlin(), - native = nativeEngine, - id = this.id, - ) -} - -internal fun Collider.populateCStruct(struct: com.dropbear.ffi.generated.Collider) { - struct.index.index = this.index.index - struct.index.generation = this.index.generation - struct.entity = this.entity.id - struct.density = this.density - struct.friction = this.friction - struct.restitution = this.restitution - struct.is_sensor = this.isSensor - - struct.translation.x = this.translation.x - struct.translation.y = this.translation.y - struct.translation.z = this.translation.z - - struct.rotation.x = this.rotation.x - struct.rotation.y = this.rotation.y - struct.rotation.z = this.rotation.z - - this.colliderShape.populateCStruct(struct.collider_shape) -} - -internal fun com.dropbear.physics.ColliderShape.populateCStruct(struct: ColliderShape) { - when (this) { - is com.dropbear.physics.ColliderShape.Box -> { - struct.tag = ColliderShape_Box - struct.data.box.half_extents.x = this.halfExtents.x - struct.data.box.half_extents.y = this.halfExtents.y - struct.data.box.half_extents.z = this.halfExtents.z - } - is com.dropbear.physics.ColliderShape.Sphere -> { - struct.tag = ColliderShape_Sphere - struct.data.sphere.radius = this.radius - } - is com.dropbear.physics.ColliderShape.Capsule -> { - struct.tag = ColliderShape_Capsule - struct.data.capsule.half_height = this.halfHeight - struct.data.capsule.radius = this.radius - } - is com.dropbear.physics.ColliderShape.Cylinder -> { - struct.tag = ColliderShape_Cylinder - struct.data.cylinder.half_height = this.halfHeight - struct.data.cylinder.radius = this.radius - } - is com.dropbear.physics.ColliderShape.Cone -> { - struct.tag = ColliderShape_Cone - struct.data.cone.half_height = this.halfHeight - struct.data.cone.radius = this.radius - } - } -} @@ -1,36 +1,15 @@ @file:OptIn(ExperimentalForeignApi::class, ExperimentalNativeApi::class) @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") -/// guys how do i remove the reinterpret error its genuinely pmo and intellij keeps -/// on catching it. - package com.dropbear.ffi -import com.dropbear.Camera -import com.dropbear.EntityId -import com.dropbear.EntityRef -import com.dropbear.EntityTransform -import com.dropbear.asset.TextureHandle import com.dropbear.exception.DropbearNativeException -import com.dropbear.exception.PrematureSceneSwitchException import com.dropbear.exceptionOnError -import com.dropbear.ffi.generated.* -import com.dropbear.input.Gamepad -import com.dropbear.input.GamepadButton -import com.dropbear.input.KeyCode -import com.dropbear.input.MouseButton -import com.dropbear.input.MouseButtonCodes +import com.dropbear.ffi.generated.DropbearContext import com.dropbear.logging.Logger -import com.dropbear.math.Transform -import com.dropbear.math.Vector2D -import com.dropbear.physics.AxisLock -import com.dropbear.physics.Collider -import com.dropbear.physics.Index -import com.dropbear.physics.RigidBody -import com.dropbear.scene.SceneLoadHandle -import com.dropbear.scene.SceneLoadStatus -import com.dropbear.utils.Progress -import kotlinx.cinterop.* +import kotlinx.cinterop.COpaquePointer +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.interpretCPointer import kotlin.experimental.ExperimentalNativeApi actual class NativeEngine { @@ -88,1563 +67,4 @@ actual class NativeEngine { } } } - - actual fun getEntity(label: String): Long? { - val world = worldHandle ?: return null - memScoped { - val outEntity = alloc<LongVar>() - val result = dropbear_get_entity( - label = label, - world_ptr = world.reinterpret(), - out_entity = outEntity.ptr - ) - return if (result == 0) outEntity.value else if (exceptionOnError) { - throw DropbearNativeException("getEntity failed with code: $result") - } else { - println("getEntity failed with code: $result") - null - } - } - } - - actual fun getEntityLabel(entityHandle: Long): String? { - val world = worldHandle ?: return null - memScoped { - val bufferSize = 256 - val outLabel = allocArray<ByteVar>(bufferSize) - - val result = dropbear_get_entity_name( - world_ptr = world.reinterpret(), - entity_id = entityHandle, - out_name = outLabel, - max_len = bufferSize.toULong() - ) - - if (result == 0) { - return outLabel.toKString() - } else { - if (exceptionOnError) { - throw DropbearNativeException("getEntityLabel failed with code for entity '$entityHandle': $result") - } else { - println("getEntityLabel failed with code: $result") - return null - } - } - } - } - - actual fun getTransform(entityId: EntityId): EntityTransform? { - val world = worldHandle ?: return null - memScoped { - val outTransform = alloc<NativeEntityTransform>() - val result = dropbear_get_transform( - world_ptr = world.reinterpret(), - entity_handle = entityId.id, - out_transform = outTransform.ptr - ) - if (result == 0) { - return EntityTransform( - local = Transform( - position = com.dropbear.math.Vector3D( - outTransform.local.position_x, - outTransform.local.position_y, - outTransform.local.position_z - ), - rotation = com.dropbear.math.QuaternionD( - outTransform.local.rotation_x, - outTransform.local.rotation_y, - outTransform.local.rotation_z, - outTransform.local.rotation_w - ), - scale = com.dropbear.math.Vector3D( - outTransform.local.scale_x, outTransform.local.scale_y, - outTransform.local.scale_z - ) - ), - world = Transform( - position = com.dropbear.math.Vector3D( - outTransform.world.position_x, - outTransform.world.position_y, - outTransform.world.position_z - ), - rotation = com.dropbear.math.QuaternionD( - outTransform.world.rotation_x, - outTransform.world.rotation_y, - outTransform.world.rotation_z, - outTransform.world.rotation_w - ), - scale = com.dropbear.math.Vector3D( - outTransform.world.scale_x, - outTransform.world.scale_y, - outTransform.world.scale_z - ) - ) - ) - } else { - if (exceptionOnError) { - throw DropbearNativeException("getTransform failed with code: $result") - } else { - println("getTransform failed with code: $result") - return null - } - } - } - } - - actual fun propagateTransform(entityId: EntityId): Transform? { - val world = worldHandle ?: return null - memScoped { - val outTransform = alloc<NativeTransform>() - val result = dropbear_propagate_transform( - world_ptr = world.reinterpret(), - entity_id = entityId.id, - out_transform = outTransform.ptr - ) - if (result == 0) { - return Transform( - position = com.dropbear.math.Vector3D( - outTransform.position_x, - outTransform.position_y, - outTransform.position_z - ), - rotation = com.dropbear.math.QuaternionD( - outTransform.rotation_x, - outTransform.rotation_y, - outTransform.rotation_z, - outTransform.rotation_w - ), - scale = com.dropbear.math.Vector3D( - outTransform.scale_x, - outTransform.scale_y, - outTransform.scale_z - ) - ) - } else { - if (exceptionOnError) { - throw DropbearNativeException("propagateTransform failed with code: $result") - } else { - println("propagateTransform failed with code: $result") - return null - } - } - } - } - - actual fun setTransform(entityId: EntityId, transform: EntityTransform) { - val worldHandle = worldHandle ?: return - memScoped { - val nativeTransform = cValue<NativeEntityTransform> { - local.position_x = transform.local.position.x - local.position_y = transform.local.position.y - local.position_z = transform.local.position.z - local.rotation_w = transform.local.rotation.w - local.rotation_x = transform.local.rotation.x - local.rotation_y = transform.local.rotation.y - local.rotation_z = transform.local.rotation.z - local.scale_x = transform.local.scale.x - local.scale_y = transform.local.scale.y - local.scale_z = transform.local.scale.z - - world.position_x = transform.world.position.x - world.position_y = transform.world.position.y - world.position_z = transform.world.position.z - world.rotation_w = transform.world.rotation.w - world.rotation_x = transform.world.rotation.x - world.rotation_y = transform.world.rotation.y - world.rotation_z = transform.world.rotation.z - world.scale_x = transform.world.scale.x - world.scale_y = transform.world.scale.y - world.scale_z = transform.world.scale.z - } - - val result = dropbear_set_transform( - world_ptr = worldHandle.reinterpret(), - entity_id = entityId.id, - transform = nativeTransform - ) - - if (result != 0) { - if (exceptionOnError) { - throw DropbearNativeException("setTransform failed with code: $result") - } else { - println("setTransform failed with code: $result") - } - } - } - } - - actual fun printInputState() { - val input = inputHandle ?: return - dropbear_print_input_state(input_ptr = input.reinterpret()) - } - - actual fun isKeyPressed(key: KeyCode): Boolean { - val input = inputHandle ?: return false - memScoped { - val out = alloc<IntVar>() - val result = dropbear_is_key_pressed( - input.reinterpret(), - key.ordinal, - out.ptr - ) - return if (result == 0) out.value != 0 else if (exceptionOnError) { - throw DropbearNativeException("isKeyPressed failed with code: $result") - } else { - println("isKeyPressed failed with code: $result") - false - } - } - } - - actual fun getMousePosition(): Vector2D? { - val input = inputHandle ?: return null - memScoped { - val xVar = alloc<FloatVar>() - val yVar = alloc<FloatVar>() - - val result = dropbear_get_mouse_position( - input.reinterpret(), - xVar.ptr, - yVar.ptr - ) - - if (result == 0) { - val x = xVar.value.toDouble() - val y = yVar.value.toDouble() - return Vector2D(x, y) - } else { - if (exceptionOnError) { - throw DropbearNativeException("getMousePosition failed with code: $result") - } else { - println("getMousePosition failed with code: $result") - return null - } - } - } - } - - actual fun isMouseButtonPressed(button: MouseButton): Boolean { - val buttonCode: Int = when (button) { - MouseButton.Left -> MouseButtonCodes.LEFT - MouseButton.Right -> MouseButtonCodes.RIGHT - MouseButton.Middle -> MouseButtonCodes.MIDDLE - MouseButton.Back -> MouseButtonCodes.BACK - MouseButton.Forward -> MouseButtonCodes.FORWARD - is MouseButton.Other -> button.value - } - - val input = inputHandle ?: return false - - memScoped { - val pressedVar = alloc<IntVar>() - - val result = dropbear_is_mouse_button_pressed( - input.reinterpret(), - buttonCode, - pressedVar.ptr - ) - - if (result == 0) { - return pressedVar.value != 0 - } else { - if (exceptionOnError) { - throw DropbearNativeException("isMouseButtonPressed failed with code: $result") - } else { - println("isMouseButtonPressed failed with code: $result") - return false - } - } - } - } - - actual fun getMouseDelta(): Vector2D? { - val input = inputHandle ?: return null - memScoped { - val deltaXVar = alloc<FloatVar>() - val deltaYVar = alloc<FloatVar>() - - val result = dropbear_get_mouse_delta( - input.reinterpret(), - deltaXVar.ptr, - deltaYVar.ptr - ) - - if (result == 0) { - val deltaX = deltaXVar.value.toDouble() - val deltaY = deltaYVar.value.toDouble() - return Vector2D(deltaX, deltaY) - } else { - if (exceptionOnError) { - throw DropbearNativeException("getMouseDelta failed with code: $result") - } else { - println("getMouseDelta failed with code: $result") - return null - } - } - } - } - - actual fun isCursorLocked(): Boolean { - val input = inputHandle ?: return false - memScoped { - val lockedVar = alloc<IntVar>() - - val result = dropbear_is_cursor_locked( - input.reinterpret(), - lockedVar.ptr - ) - - if (result == 0) { - return lockedVar.value != 0 - } else { - if (exceptionOnError) { - throw DropbearNativeException("isCursorLocked failed with code: $result") - } else { - println("isCursorLocked failed with code: $result") - return false - } - } - } - } - - actual fun setCursorLocked(locked: Boolean) { - val lockedInt = if (locked) 1 else 0 - val input = inputHandle ?: return - val graphics = commandBufferHandle ?: return - - val result = dropbear_set_cursor_locked( - input.reinterpret(), - graphics.reinterpret(), - lockedInt - ) - - if (result != 0) { - if (exceptionOnError) { - throw DropbearNativeException("setCursorLocked failed with code: $result") - } else { - println("setCursorLocked failed with code: $result") - } - } - } - - actual fun getLastMousePos(): Vector2D? { - val input = inputHandle ?: return null - memScoped { - val xVar = alloc<FloatVar>() - val yVar = alloc<FloatVar>() - - val result = dropbear_get_last_mouse_pos( - input.reinterpret(), - xVar.ptr, - yVar.ptr - ) - - if (result == 0) { - val x = xVar.value.toDouble() - val y = yVar.value.toDouble() - return Vector2D(x, y) - } else { - if (exceptionOnError) { - throw DropbearNativeException("getLastMousePos failed with code: $result") - } else { - println("getLastMousePos failed with code: $result") - return null - } - } - } - } - - actual fun isCursorHidden(): Boolean { - val input = inputHandle ?: return false - memScoped { - val hiddenVar = alloc<IntVar>() - - val result = dropbear_is_cursor_hidden( - input.reinterpret(), - hiddenVar.ptr - ) - - if (result == 0) { - return hiddenVar.value != 0 - } else { - if (exceptionOnError) { - throw DropbearNativeException("isCursorHidden failed with code: $result") - } else { - println("isCursorHidden failed with code: $result") - return false - } - } - } - } - - actual fun setCursorHidden(hidden: Boolean) { - val hiddenInt = if (hidden) 1 else 0 - val input = inputHandle ?: return - val graphics = commandBufferHandle ?: return - - val result = dropbear_set_cursor_hidden( - input.reinterpret(), - graphics.reinterpret(), - hiddenInt - ) - - if (result != 0) { - if (exceptionOnError) { - throw DropbearNativeException("setCursorHidden failed with code: $result") - } else { - println("setCursorHidden failed with code: $result") - } - } - } - - actual fun getConnectedGamepads(): List<Gamepad> { - val input = inputHandle ?: return emptyList() - memScoped { - val gamepadsPtr = alloc<CPointerVar<com.dropbear.ffi.generated.Gamepad>>() - val count = alloc<IntVar>() - - val result = dropbear_get_connected_gamepads( - input.reinterpret(), - gamepadsPtr.ptr, - count.ptr - ) - - if (result == 0) { - val gamepadArray = gamepadsPtr.value - val gamepadCount = count.value - - if (gamepadArray == null || gamepadCount == 0) { - return emptyList() - } - - return List(gamepadCount) { index -> - val nativeGamepad = gamepadArray[index] - Gamepad( - id = nativeGamepad.id.toLong(), - leftStickPosition = Vector2D( - x = nativeGamepad.left_stick_pos.x, - y = nativeGamepad.left_stick_pos.y - ), - rightStickPosition = Vector2D( - x = nativeGamepad.right_stick_pos.x, - y = nativeGamepad.right_stick_pos.y - ), - native = this@NativeEngine - ) - } - } else { - if (exceptionOnError) { - throw DropbearNativeException("getConnectedGamepads failed with code: $result") - } else { - println("getConnectedGamepads failed with code: $result") - return emptyList() - } - } - } - } - - actual fun isGamepadButtonPressed(id: Long, button: GamepadButton): Boolean { - val input = inputHandle ?: return false - memScoped { - val outPressed = alloc<IntVar>() - - val result = dropbear_is_gamepad_button_pressed( - input_ptr = input.reinterpret(), - gamepad_id = id, - ordinal = button.ordinal, - out_pressed = outPressed.ptr - ) - - return if (result == 0) { - outPressed.value != 0 - } else if (exceptionOnError) { - throw DropbearNativeException( - "isGamepadButtonPressed failed for gamepadId='$id' button='${button.name}' with code: $result" - ) - } else { - println( - "isGamepadButtonPressed failed for gamepadId='$id' button='${button.name}' with code: $result" - ) - false - } - } - } - - actual fun getStringProperty(entityHandle: Long, label: String): String? { - val world = worldHandle ?: return null - memScoped { - val output = alloc<CPointerVar<ByteVar>>() - - val result = dropbear_get_string_property( - world.reinterpret(), - entityHandle, - label, - output.ptr - ) - - if (result == 0) { - val string = output.value?.toKString() - return string - } else { - if (exceptionOnError) { - throw DropbearNativeException("getStringProperty [$label] failed with code: $result") - } else { - println("getStringProperty [$label] failed with code: $result") - return null - } - } - } - } - - actual fun getIntProperty(entityHandle: Long, label: String): Int? { - val world = worldHandle ?: return null - memScoped { - val output = alloc<IntVar>() - - val result = dropbear_get_int_property( - world.reinterpret(), - entityHandle, - label, - output.ptr, - ) - - if (result == 0) { - return output.value - } else { - if (exceptionOnError) { - throw DropbearNativeException("getIntProperty [$label] failed with code: $result") - } else { - println("getIntProperty [$label] failed with code: $result") - return null - } - } - } - } - - actual fun getLongProperty(entityHandle: Long, label: String): Long? { - val world = worldHandle ?: return null - memScoped { - val output = alloc<LongVar>() - - val result = dropbear_get_long_property( - world.reinterpret(), - entityHandle, - label, - output.ptr - ) - - if (result == 0) { - return output.value - } else { - if (exceptionOnError) { - throw DropbearNativeException("getLongProperty [$label] failed with code: $result") - } else { - println("getLongProperty [$label] failed with code: $result") - return null - } - } - } - } - - actual fun getFloatProperty(entityHandle: Long, label: String): Float? { - val world = worldHandle ?: return null - memScoped { - val output = alloc<DoubleVar>() - - val result = dropbear_get_float_property( - world.reinterpret(), - entityHandle, - label, - output.ptr - ) - - if (result == 0) { - return output.value.toFloat() - } else { - if (exceptionOnError) { - throw DropbearNativeException("getFloatProperty [$label] failed with code: $result") - } else { - println("getFloatProperty [$label] failed with code: $result") - return null - } - } - } - } - - actual fun getDoubleProperty(entityHandle: Long, label: String): Double? { - val world = worldHandle ?: return null - memScoped { - val output = alloc<DoubleVar>() - - val result = dropbear_get_float_property( - world.reinterpret(), - entityHandle, - label, - output.ptr - ) - - if (result == 0) { - return output.value - } else { - if (exceptionOnError) { - throw DropbearNativeException("getDoubleProperty [$label] failed with code: $result") - } else { - println("getDoubleProperty [$label] failed with code: $result") - return null - } - } - } - } - - actual fun getBoolProperty(entityHandle: Long, label: String): Boolean? { - val world = worldHandle ?: return null - memScoped { - val output = alloc<IntVar>() - - val result = dropbear_get_bool_property( - world.reinterpret(), - entityHandle, - label, - output.ptr - ) - - if (result == 0) { - return output.value != 0 - } else { - if (exceptionOnError) { - throw DropbearNativeException("getBoolProperty [$label] failed with code: $result") - } else { - println("getBoolProperty [$label] failed with code: $result") - return null - } - } - } - } - - actual fun getVec3Property(entityHandle: Long, label: String): FloatArray? { - val world = worldHandle ?: return null - memScoped { - val outVec = alloc<Vector3D>() - - val result = dropbear_get_vec3_property( - world.reinterpret(), - entityHandle, - label, - outVec.ptr - ) - - if (result == 0) { - return floatArrayOf(outVec.x.toFloat(), outVec.y.toFloat(), outVec.z.toFloat()) - } else { - if (exceptionOnError) { - throw DropbearNativeException("getVec3Property [$label] failed with code: $result") - } else { - println("getVec3Property [$label] failed with code: $result") - return null - } - } - } - } - - actual fun setStringProperty(entityHandle: Long, label: String, value: String) { - val world = worldHandle ?: return - - val result = dropbear_set_string_property( - world.reinterpret(), - entityHandle, - label, - value - ) - - if (result != 0) { - if (exceptionOnError) { - throw DropbearNativeException("setStringProperty [$label] failed with code: $result") - } else { - println("setStringProperty [$label] failed with code: $result") - } - } - } - - actual fun setIntProperty(entityHandle: Long, label: String, value: Int) { - val world = worldHandle ?: return - - val result = dropbear_set_int_property( - world.reinterpret(), - entityHandle, - label, - value - ) - - if (result != 0) { - if (exceptionOnError) { - throw DropbearNativeException("setIntProperty [$label] failed with code: $result") - } else { - println("setIntProperty [$label] failed with code: $result") - } - } - } - - actual fun setLongProperty(entityHandle: Long, label: String, value: Long) { - val world = worldHandle ?: return - - val result = dropbear_set_long_property( - world.reinterpret(), - entityHandle, - label, - value - ) - - if (result != 0) { - if (exceptionOnError) { - throw DropbearNativeException("setLongProperty [$label] failed with code: $result") - } else { - println("setLongProperty [$label] failed with code: $result") - } - } - } - - actual fun setFloatProperty(entityHandle: Long, label: String, value: Double) { - val world = worldHandle ?: return - - val result = dropbear_set_float_property( - world.reinterpret(), - entityHandle, - label, - value - ) - - if (result != 0) { - if (exceptionOnError) { - throw DropbearNativeException("setFloatProperty [$label] failed with code: $result") - } else { - println("setFloatProperty [$label] failed with code: $result") - } - } - } - - actual fun setBoolProperty(entityHandle: Long, label: String, value: Boolean) { - val world = worldHandle ?: return - val intValue = if (value) 1 else 0 - - val result = dropbear_set_bool_property( - world.reinterpret(), - entityHandle, - label, - intValue - ) - - if (result != 0) { - if (exceptionOnError) { - throw DropbearNativeException("setBoolProperty [$label] failed with code: $result") - } else { - println("setBoolProperty [$label] failed with code: $result") - } - } - } - - actual fun setVec3Property(entityHandle: Long, label: String, value: FloatArray) { - val world = worldHandle ?: return - - if (value.size < 3) { - if (exceptionOnError) { - throw DropbearNativeException("setVec3Property: FloatArray must have at least 3 elements") - } else { - println("setVec3Property: FloatArray must have at least 3 elements") - return - } - } - - memScoped { - val vec = cValue<Vector3D> { - x = value[0].toDouble() - y = value[1].toDouble() - z = value[2].toDouble() - } - - val result = dropbear_set_vec3_property( - world.reinterpret(), - entityHandle, - label, - vec - ) - - if (result != 0) { - if (exceptionOnError) { - throw DropbearNativeException("setVec3Property [$label] failed with code: $result") - } else { - println("setVec3Property [$label] failed with code: $result") - } - } - } - } - - actual fun getCamera(label: String): Camera? { - val world = worldHandle ?: return null - memScoped { - val outCamera = alloc<NativeCamera>() - - val result = dropbear_get_camera( - world.reinterpret(), - label, - outCamera.ptr - ) - - if (result == 0) { - return Camera( - label = outCamera.label?.toKString() ?: "", - id = EntityId(outCamera.entity_id), - eye = com.dropbear.math.Vector3D( - outCamera.eye.x, - outCamera.eye.y, - outCamera.eye.z - ), - target = com.dropbear.math.Vector3D( - outCamera.target.x, - outCamera.target.y, - outCamera.target.z - ), - up = com.dropbear.math.Vector3D( - outCamera.up.x, - outCamera.up.y, - outCamera.up.z - ), - aspect = outCamera.aspect, - fov_y = outCamera.fov_y, - znear = outCamera.znear, - zfar = outCamera.zfar, - yaw = outCamera.yaw, - pitch = outCamera.pitch, - speed = outCamera.speed, - sensitivity = outCamera.sensitivity - ) - } else { - if (exceptionOnError) { - throw DropbearNativeException("getCamera failed with code: $result") - } else { - println("getCamera failed with code: $result") - return null - } - } - } - } - - actual fun getAttachedCamera(entityId: EntityId): Camera? { - val world = worldHandle ?: return null - memScoped { - val outCamera = alloc<NativeCamera>() - - val result = dropbear_get_attached_camera( - world.reinterpret(), - entityId.id, - outCamera.ptr - ) - - if (result == 0) { - return Camera( - label = outCamera.label?.toKString() ?: "", - id = EntityId(outCamera.entity_id), - eye = com.dropbear.math.Vector3D( - outCamera.eye.x, - outCamera.eye.y, - outCamera.eye.z - ), - target = com.dropbear.math.Vector3D( - outCamera.target.x, - outCamera.target.y, - outCamera.target.z - ), - up = com.dropbear.math.Vector3D( - outCamera.up.x, - outCamera.up.y, - outCamera.up.z - ), - aspect = outCamera.aspect, - fov_y = outCamera.fov_y, - znear = outCamera.znear, - zfar = outCamera.zfar, - yaw = outCamera.yaw, - pitch = outCamera.pitch, - speed = outCamera.speed, - sensitivity = outCamera.sensitivity - ) - } else { - if (exceptionOnError) { - throw DropbearNativeException("getAttachedCamera failed with code: $result") - } else { - println("getAttachedCamera failed with code: $result") - return null - } - } - } - } - - actual fun setCamera(camera: Camera) { - val world = worldHandle ?: return - memScoped { - val nativeCamera = cValue<NativeCamera> { - label = camera.label.cstr.ptr - entity_id = camera.id.id - - eye.x = camera.eye.x - eye.y = camera.eye.y - eye.z = camera.eye.z - - target.x = camera.target.x - target.y = camera.target.y - target.z = camera.target.z - - up.x = camera.up.x - up.y = camera.up.y - up.z = camera.up.z - - aspect = camera.aspect - fov_y = camera.fov_y - znear = camera.znear - zfar = camera.zfar - - yaw = camera.yaw - pitch = camera.pitch - speed = camera.speed - sensitivity = camera.sensitivity - } - - val result = dropbear_set_camera( - world.reinterpret(), - nativeCamera - ) - - if (result != 0) { - if (exceptionOnError) { - throw DropbearNativeException("setCamera failed with code: $result") - } else { - println("setCamera failed with code: $result") - } - } - } - } - - actual fun getModel(entityHandle: Long): Long? { - val world = worldHandle ?: return null - val asset = assetHandle ?: return null - memScoped { - val outModel = alloc<LongVar>() - val result = dropbear_get_model( - world.reinterpret(), - asset.reinterpret(), - entityHandle, - outModel.ptr - ) - return if (result == 0) outModel.value else if (exceptionOnError) throw DropbearNativeException("getModel failed with code: $result") else null - } - } - - actual fun setModel(entityHandle: Long, modelHandle: Long) { - val world = worldHandle ?: return - val asset = assetHandle ?: return - - val result = dropbear_set_model( - world.reinterpret(), - asset.reinterpret(), - entityHandle, - modelHandle - ) - - if (result != 0) { - if (exceptionOnError) { - throw DropbearNativeException("setModel failed with code: $result") - } else { - println("setModel failed with code: $result") - } - } - } - - actual fun getTexture(entityHandle: Long, name: String): Long? { - val world = worldHandle ?: return null - val asset = assetHandle ?: return null - memScoped { - val outTexture = alloc<LongVar>() - val result = dropbear_get_texture( - world.reinterpret(), - asset.reinterpret(), - entityHandle, - name, - outTexture.ptr - ) - return if (result == 0) outTexture.value else if (exceptionOnError) throw DropbearNativeException("getTexture failed with code: $result") else null - } - } - - actual fun isUsingModel(entityHandle: Long, modelHandle: Long): Boolean { - val world = worldHandle ?: return false - memScoped { - val outUsing = alloc<IntVar>() - val result = dropbear_is_using_model( - world.reinterpret(), - entityHandle, - modelHandle, - outUsing.ptr - ) - return if (result == 0) outUsing.value != 0 else false - } - } - - actual fun isUsingTexture(entityHandle: Long, textureHandle: Long): Boolean { - val world = worldHandle ?: return false - memScoped { - val outUsing = alloc<IntVar>() - val result = dropbear_is_using_texture( - world.reinterpret(), - entityHandle, - textureHandle, - outUsing.ptr - ) - return if (result == 0) outUsing.value != 0 else false - } - } - - actual fun getAsset(eucaURI: String): Long? { - val asset = assetHandle ?: return null - memScoped { - val outAsset = alloc<LongVar>() - val result = dropbear_get_asset( - asset.reinterpret(), - eucaURI, - outAsset.ptr - ) - return if (result == 0) outAsset.value else null - } - } - - actual fun isModelHandle(id: Long): Boolean { - val asset = assetHandle ?: return false - memScoped { - val outIsModel = alloc<IntVar>() - val result = dropbear_is_model_handle( - asset.reinterpret(), - id, - outIsModel.ptr - ) - return if (result == 0) outIsModel.value != 0 else false - } - } - - actual fun isTextureHandle(id: Long): Boolean { - val asset = assetHandle ?: return false - memScoped { - val outIsTexture = alloc<IntVar>() - val result = dropbear_is_texture_handle( - asset.reinterpret(), - id, - outIsTexture.ptr - ) - return if (result == 0) outIsTexture.value != 0 else false - } - } - - actual fun setTextureOverride(entityHandle: Long, oldMaterialName: String, newTextureHandle: TextureHandle) { - val world = worldHandle ?: return - val asset = assetHandle ?: return - - val result = dropbear_set_texture( - world.reinterpret(), - asset.reinterpret(), - entityHandle, - oldMaterialName, - newTextureHandle.raw() - ) - - if (result != 0) { - if (exceptionOnError) { - throw DropbearNativeException("setTextureOverride failed with code: $result") - } else { - println("setTextureOverride failed with code: $result") - } - } - } - - actual fun getTextureName(textureHandle: Long): String? { - val asset = assetHandle ?: return null - memScoped { - val outName = alloc<CPointerVar<ByteVar>>() - val result = dropbear_get_texture_name( - asset.reinterpret(), - textureHandle, - outName.ptr - ) - return if (result == 0) outName.value?.toKString() else if (exceptionOnError) throw DropbearNativeException("getTextureName failed with code: $result") else null - } - } - - actual fun getAllTextures(entityHandle: Long): Array<String> { - val world = worldHandle ?: return emptyArray() - val asset = assetHandle ?: return emptyArray() - memScoped { - val outTextures = alloc<CPointerVar<CPointerVar<ByteVar>>>() - val outCount = alloc<ULongVar>() - - val result = dropbear_get_all_textures( - asset.reinterpret(), - world.reinterpret(), - entityHandle, - outTextures.ptr, - outCount.ptr - ) - - if (result == 0) { - val count = outCount.value.toInt() - val textureArray = Array(count) { i -> - outTextures.value!![i]?.toKString() ?: "" - } - return textureArray - } else { - if (exceptionOnError) { - throw DropbearNativeException("getAllTextures failed with code: $result") - } else { - println("getAllTextures failed with code: $result") - return emptyArray() - } - } - } - } - - actual fun getChildren(entityId: EntityId): Array<EntityRef>? { - val world = worldHandle ?: return null - memScoped { - val outChildren = alloc<CPointerVar<LongVar>>() - val outCount = alloc<ULongVar>() - - val result = dropbear_get_children( - world.reinterpret(), - entityId.id, - outChildren.ptr, - outCount.ptr - ) - - if (result == 0) { - val count = outCount.value.toInt() - val childArray = Array(count) { i -> - EntityRef(EntityId(outChildren.value!![i])) - } - return childArray - } else { - if (exceptionOnError) { - throw DropbearNativeException("getChildren failed with code: $result") - } else { - println("getChildren failed with code: $result") - return null - } - } - } - } - - actual fun getChildByLabel(entityId: EntityId, label: String): EntityRef? { - val world = worldHandle ?: return null - memScoped { - val outChild = alloc<LongVar>() - val result = dropbear_get_child_by_label( - world.reinterpret(), - entityId.id, - label, - outChild.ptr - ) - return if (result == 0) EntityRef(EntityId(outChild.value)) else if (exceptionOnError) throw DropbearNativeException("getChildByLabel failed with code: $result") else null - } - } - - actual fun getParent(entityId: EntityId): EntityRef? { - val world = worldHandle ?: return null - memScoped { - val outParent = alloc<LongVar>() - val result = dropbear_get_parent( - world.reinterpret(), - entityId.id, - outParent.ptr - ) - return if (result == 0) EntityRef(EntityId(outParent.value)) else if (exceptionOnError) throw DropbearNativeException("getParent failed with code: $result") else null - } - } - - actual fun quit() { - val command = commandBufferHandle ?: if (exceptionOnError) throw DropbearNativeException("Unable to quit: graphicsHandle does not exist") else return - dropbear_quit(command.reinterpret()) - } - - actual fun loadSceneAsync(sceneName: String): SceneLoadHandle? { - val sceneLoader = sceneLoaderHandle ?: if (exceptionOnError) throw DropbearNativeException("sceneLoaderHandle is empty") else return null - val commandBuffer = commandBufferHandle ?: if (exceptionOnError) throw DropbearNativeException("commandBufferHandle is empty") else return null - memScoped { - val outHandle = alloc<com.dropbear.ffi.generated.SceneLoadHandle>() - val result = dropbear_load_scene_async_1( - command_ptr = commandBuffer.reinterpret(), - scene_loader_ptr = sceneLoader.reinterpret(), - name = sceneName, - sceneLoadHandle = outHandle.ptr - ) - - if (result != 0) { - if (exceptionOnError) { - throw DropbearNativeException("loadSceneAsync failed with code: $result") - } else { - println("loadSceneAsync failed with code: $result") - } - } - - return SceneLoadHandle( - id = outHandle.id, - sceneName = outHandle.name?.toKString() ?: throw Exception("loadSceneAsync failed: sceneName is null"), - native = this@NativeEngine, - ) - } - } - - actual fun loadSceneAsync(sceneName: String, loadingScene: String): SceneLoadHandle? { - val sceneLoader = sceneLoaderHandle ?: if (exceptionOnError) throw DropbearNativeException("sceneLoaderHandle is empty") else return null - val commandBuffer = commandBufferHandle ?: if (exceptionOnError) throw DropbearNativeException("commandBufferHandle is empty") else return null - memScoped { - val outHandle = alloc<com.dropbear.ffi.generated.SceneLoadHandle>() - val result = dropbear_load_scene_async_2( - command_ptr = commandBuffer.reinterpret(), - scene_loader_ptr = sceneLoader.reinterpret(), - name = sceneName, - loadingScene = loadingScene, - sceneLoadHandle = outHandle.ptr - ) - - if (result != 0) { - if (exceptionOnError) { - throw DropbearNativeException("loadSceneAsync with loading scene failed with code: $result") - } else { - println("loadSceneAsync with loading scene failed with code: $result") - } - } - - return SceneLoadHandle( - id = outHandle.id, - sceneName = outHandle.name?.toKString() ?: throw Exception("loadSceneAsync with loading scene failed: sceneName is null"), - native = this@NativeEngine, - ) - } - } - - actual fun switchToSceneAsync(sceneLoadHandle: SceneLoadHandle) { - val command = commandBufferHandle ?: if (exceptionOnError) throw DropbearNativeException("commandBufferHandle is empty") else return - memScoped { - val nativeHandle = alloc<com.dropbear.ffi.generated.SceneLoadHandle>() - nativeHandle.id = sceneLoadHandle.id - nativeHandle.name = sceneLoadHandle.sceneName.cstr.ptr - - val result = dropbear_switch_to_scene_async(command.reinterpret(), nativeHandle.readValue()) - - if (result == -10) { - throw PrematureSceneSwitchException("Attempted to switch to scene before it finished loading") - } - - if (result != 0) { - if (exceptionOnError) { - throw DropbearNativeException("switchToSceneAsync failed with code: $result") - } else { - println("switchToSceneAsync failed with code: $result") - } - } - } - } - - actual fun switchToSceneImmediate(sceneName: String) { - val command = commandBufferHandle ?: if (exceptionOnError) throw DropbearNativeException("commandBufferHandle is empty") else return - val result = dropbear_switch_to_scene_immediate( - command_ptr = command.reinterpret(), - name = sceneName, - ) - - if (result != 0) { - if (exceptionOnError) { - throw DropbearNativeException("switchToSceneImmediate failed with code: $result") - } else { - println("switchToSceneImmediate failed with code: $result") - } - } - } - - actual fun getSceneLoadProgress(sceneLoadHandle: SceneLoadHandle): Progress { - val sceneLoader = sceneLoaderHandle ?: if (exceptionOnError) throw DropbearNativeException("sceneLoaderHandle is empty") else return Progress.nothing("Error: commandBufferHandle is empty") - memScoped { - val nativeHandle = alloc<com.dropbear.ffi.generated.SceneLoadHandle>() - nativeHandle.id = sceneLoadHandle.id - nativeHandle.name = sceneLoadHandle.sceneName.cstr.ptr - - val outProgress = alloc<com.dropbear.ffi.generated.Progress>() - - val result = dropbear_get_scene_load_progress( - scene_loader_ptr = sceneLoader.reinterpret(), - handle = nativeHandle.readValue(), - progress = outProgress.ptr - ) - - if (result != 0) { - if (exceptionOnError) { - throw DropbearNativeException("getSceneLoadProgress failed with code: $result") - } else { - println("getSceneLoadProgress failed with code: $result") - } - } - - val progress = Progress(outProgress.current, outProgress.total, outProgress.message?.toKString()); - - return progress - } - } - - actual fun getSceneLoadStatus(sceneLoadHandle: SceneLoadHandle): SceneLoadStatus { - val sceneLoader = sceneLoaderHandle ?: if (exceptionOnError) throw DropbearNativeException("sceneLoaderHandle is empty") else return SceneLoadStatus.FAILED - memScoped { - val nativeHandle = alloc<com.dropbear.ffi.generated.SceneLoadHandle>() - nativeHandle.id = sceneLoadHandle.id - nativeHandle.name = sceneLoadHandle.sceneName.cstr.ptr - - val nativeStatus = alloc<SceneLoadResult.Var>() - - val result = dropbear_get_scene_load_status( - scene_loader_ptr = sceneLoader.reinterpret(), - handle = nativeHandle.readValue(), - result = nativeStatus.ptr - ) - if (result == 0) { - return nativeStatus.value.fromNative() - } else { - if (exceptionOnError) { - throw DropbearNativeException("getSceneLoadStatus failed with code: $result") - } else { - println("getSceneLoadStatus failed with code: $result") - return SceneLoadStatus.FAILED - } - } - } - } - - actual fun setPhysicsEnabled(entityId: Long, enabled: Boolean) { - val world = worldHandle ?: if (exceptionOnError) throw DropbearNativeException("world handle null") else return - val pe = physicsEngineHandle ?: if (exceptionOnError) throw DropbearNativeException("Physics engine handle is null") else return - - val result = dropbear_set_physics_enabled( - world.reinterpret(), - pe.reinterpret(), - entityId, - enabled - ) - - handleResult(result, "setPhysicsEnabled") - } - - actual fun isPhysicsEnabled(entityId: Long): Boolean { - val world = worldHandle ?: if (exceptionOnError) throw DropbearNativeException("world handle null") else return false - val pe = physicsEngineHandle ?: if (exceptionOnError) throw DropbearNativeException("Physics engine handle is null") else return false - - memScoped { - val outEnabled = alloc<BooleanVar>() - - val result = dropbear_is_physics_enabled( - world.reinterpret(), - pe.reinterpret(), - entityId, - outEnabled.ptr - ) - - if (result != 0) { - handleResult(result, "isPhysicsEnabled") - return false - } - - return outEnabled.value - } - } - - actual fun getRigidBody(entityId: Long): RigidBody? { - val world = worldHandle ?: if (exceptionOnError) throw DropbearNativeException("world handle null") else return null - val pe = physicsEngineHandle ?: if (exceptionOnError) throw DropbearNativeException("Physics engine handle is null") else return null - - memScoped { - val outRigidBody = alloc<com.dropbear.ffi.generated.RigidBody>() - - val result = dropbear_get_rigidbody( - world.reinterpret(), - pe.reinterpret(), - entityId, - outRigidBody.ptr - ) - - if (result != 0) { - handleResult(result, "getRigidBody") - return null - } - - return outRigidBody.toKotlin(this@NativeEngine) - } - } - - actual fun getAllColliders(entityId: Long): List<Collider> { - val world = worldHandle ?: if (exceptionOnError) throw DropbearNativeException("world handle null") else return emptyList() - val pe = physicsEngineHandle ?: if (exceptionOnError) throw DropbearNativeException("Physics engine handle is null") else return emptyList() - - memScoped { - val outCollidersPtr = alloc<CPointerVar<com.dropbear.ffi.generated.Collider>>() - val outCount = alloc<UIntVar>() - - val result = dropbear_get_all_colliders( - world.reinterpret(), - pe.reinterpret(), - entityId, - outCollidersPtr.ptr, - outCount.ptr - ) - - if (result != 0) { - handleResult(result, "getAllColliders") - return emptyList() - } - - val count = outCount.value.toInt() - val cArray = outCollidersPtr.value - - if (cArray == null || count == 0) { - return emptyList() - } - - val list = ArrayList<Collider>(count) - for (i in 0 until count) { - val cCollider = cArray[i] - list.add(cCollider.toKotlin(this@NativeEngine)) - } - - dropbear_free_colliders(cArray, outCount.value) - - return list - } - } - - actual fun applyImpulse(index: Index, x: Double, y: Double, z: Double) { - val pe = physicsEngineHandle ?: return - - memScoped { - val cIndex = alloc<com.dropbear.ffi.generated.Index>() - cIndex.index = index.index - cIndex.generation = index.generation - - val cImpulse = alloc<com.dropbear.ffi.generated.Vector3D>() - cImpulse.x = x - cImpulse.y = y - cImpulse.z = z - - dropbear_apply_impulse( - pe.reinterpret(), - cIndex.readValue(), - cImpulse.readValue() - ) - } - } - - actual fun applyTorqueImpulse(index: Index, x: Double, y: Double, z: Double) { - val pe = physicsEngineHandle ?: return - - memScoped { - val cIndex = alloc<com.dropbear.ffi.generated.Index>() - cIndex.index = index.index - cIndex.generation = index.generation - - val cTorque = alloc<Vector3D>() - cTorque.x = x - cTorque.y = y - cTorque.z = z - - dropbear_apply_torque_impulse( - pe.reinterpret(), - cIndex.readValue(), - cTorque.readValue() - ) - } - } - - actual fun setRigidbody(rigidBody: RigidBody) { - val world = worldHandle ?: if (exceptionOnError) throw DropbearNativeException("world handle null") else return - val pe = physicsEngineHandle ?: if (exceptionOnError) throw DropbearNativeException("Handle null") else return - - memScoped { - val cBody = alloc<com.dropbear.ffi.generated.RigidBody>() - rigidBody.populateCStruct(cBody) - - val result = dropbear_set_rigidbody( - world.reinterpret(), - pe.reinterpret(), - cBody.readValue() - ) - handleResult(result, "setRigidbody") - } - } - - actual fun getChildColliders(index: Index): List<Collider> { - val world = worldHandle ?: if (exceptionOnError) throw DropbearNativeException("world handle null") else return emptyList() - val pe = physicsEngineHandle ?: if (exceptionOnError) throw DropbearNativeException("Handle null") else return emptyList() - - memScoped { - val cIndex = alloc<com.dropbear.ffi.generated.Index>() - cIndex.index = index.index - cIndex.generation = index.generation - - val outCollidersPtr = alloc<CPointerVar<com.dropbear.ffi.generated.Collider>>() - val outCount = alloc<UIntVar>() - - val result = dropbear_get_child_colliders( - world.reinterpret(), - pe.reinterpret(), - cIndex.readValue(), - outCollidersPtr.ptr, - outCount.ptr - ) - - if (result != 0) { - handleResult(result, "getChildColliders") - return emptyList() - } - - val count = outCount.value.toInt() - val cArray = outCollidersPtr.value - - if (cArray == null || count == 0) return emptyList() - - val list = ArrayList<Collider>(count) - for (i in 0 until count) { - list.add(cArray[i].toKotlin(this@NativeEngine)) - } - - dropbear_free_colliders(cArray, outCount.value) - return list - } - } - - actual fun setCollider(collider: Collider) { - val world = worldHandle ?: if (exceptionOnError) throw DropbearNativeException("world handle null") else return - val pe = physicsEngineHandle ?: if (exceptionOnError) throw DropbearNativeException("Handle null") else return - - memScoped { - val cCollider = alloc<com.dropbear.ffi.generated.Collider>() - collider.populateCStruct(cCollider) - - val result = dropbear_set_collider( - world.reinterpret(), - pe.reinterpret(), - cCollider.readValue() - ) - handleResult(result, "setCollider") - } - } -} - -private fun handleResult(result: Int, funcName: String) { - if (result != 0) { - val msg = "$funcName failed with code: $result" - if (exceptionOnError) { - throw DropbearNativeException(msg) - } else { - Logger.error(msg) - } - } } @@ -0,0 +1,19 @@ +package com.dropbear.input + +import com.dropbear.math.Vector2d + +actual fun Gamepad.isGamepadButtonPressed( + id: Long, + button: GamepadButton +): Boolean { + TODO("Not yet implemented") +} + +actual fun Gamepad.getLeftStickPosition(id: Long): Vector2d { + TODO("Not yet implemented") +} + +actual fun Gamepad.getRightStickPosition(id: Long): Vector2d { + TODO("Not yet implemented") +} + @@ -0,0 +1,46 @@ +package com.dropbear.input + +import com.dropbear.math.Vector2d + +actual class InputState actual constructor() { + actual fun printInputState() { + } + + actual fun isKeyPressed(key: KeyCode): Boolean { + TODO("Not yet implemented") + } + + actual fun getMousePosition(): Vector2d { + TODO("Not yet implemented") + } + + actual fun isMouseButtonPressed(button: MouseButton): Boolean { + TODO("Not yet implemented") + } + + actual fun getMouseDelta(): Vector2d { + TODO("Not yet implemented") + } + + actual fun isCursorLocked(): Boolean { + TODO("Not yet implemented") + } + + actual fun setCursorLocked(locked: Boolean) { + } + + actual fun getLastMousePos(): Vector2d { + TODO("Not yet implemented") + } + + actual fun isCursorHidden(): Boolean { + TODO("Not yet implemented") + } + + actual fun setCursorHidden(hidden: Boolean) { + } + + actual fun getConnectedGamepads(): List<Gamepad> { + TODO("Not yet implemented") + } +} @@ -0,0 +1,81 @@ +package com.dropbear.physics + +import com.dropbear.math.Vector3d + +actual fun Collider.getColliderShape(collider: Collider): ColliderShape { + TODO("Not yet implemented") +} + +actual fun Collider.setColliderShape( + collider: Collider, + shape: ColliderShape +) { +} + +actual fun Collider.setColliderDensity( + collider: Collider, + density: Double +) { +} + +actual fun Collider.getColliderFriction(collider: Collider): Double { + TODO("Not yet implemented") +} + +actual fun Collider.setColliderFriction( + collider: Collider, + friction: Double +) { +} + +actual fun Collider.getColliderRestitution(collider: Collider): Double { + TODO("Not yet implemented") +} + +actual fun Collider.setColliderRestitution( + collider: Collider, + restitution: Double +) { +} + +actual fun Collider.getColliderIsSensor(collider: Collider): Boolean { + TODO("Not yet implemented") +} + +actual fun Collider.setColliderIsSensor( + collider: Collider, + isSensor: Boolean +) { +} + +actual fun Collider.getColliderTranslation(collider: Collider): Vector3d { + TODO("Not yet implemented") +} + +actual fun Collider.setColliderTranslation( + collider: Collider, + translation: Vector3d +) { +} + +actual fun Collider.getColliderRotation(collider: Collider): Vector3d { + TODO("Not yet implemented") +} + +actual fun Collider.setColliderRotation( + collider: Collider, + rotation: Vector3d +) { +} + +actual fun Collider.getColliderMass(collider: Collider): Double { + TODO("Not yet implemented") +} + +actual fun Collider.setColliderMass(collider: Collider, mass: Double) { +} + +actual fun Collider.getColliderDensity(collider: Collider): Double { + TODO("Not yet implemented") +} + @@ -0,0 +1,11 @@ +package com.dropbear.physics + +import com.dropbear.EntityId + +actual fun ColliderGroup.getColliderGroupColliders(colliderGroup: ColliderGroup): List<Collider> { + TODO("Not yet implemented") +} + +actual fun colliderGroupExistsForEntity(entityId: EntityId): Boolean { + TODO("Not yet implemented") +} @@ -0,0 +1,10 @@ +package com.dropbear.physics + +import com.dropbear.math.Vector3d + +internal actual fun getGravity(): Vector3d { + TODO("Not yet implemented") +} + +internal actual fun setGravity(gravity: Vector3d) { +} @@ -0,0 +1,128 @@ +package com.dropbear.physics + +import com.dropbear.EntityId +import com.dropbear.math.Vector3d + +actual fun RigidBody.setRigidbodyMode( + rigidBody: RigidBody, + mode: RigidBodyMode +) { +} + +actual fun RigidBody.getRigidbodyMode(rigidBody: RigidBody): RigidBodyMode { + TODO("Not yet implemented") +} + +actual fun RigidBody.getRigidbodyGravityScale(rigidBody: RigidBody): Double { + TODO("Not yet implemented") +} + +actual fun RigidBody.setRigidbodyGravityScale( + rigidBody: RigidBody, + gravityScale: Double +) { +} + +actual fun RigidBody.getRigidBodySleep(rigidBody: RigidBody): Boolean { + TODO("Not yet implemented") +} + +actual fun RigidBody.setRigidBodySleep( + rigidBody: RigidBody, + canSleep: Boolean +) { +} + +actual fun RigidBody.getRigidbodyCcdEnabled(rigidBody: RigidBody): Boolean { + TODO("Not yet implemented") +} + +actual fun RigidBody.setRigidbodyCcdEnabled( + rigidBody: RigidBody, + ccdEnabled: Boolean +) { +} + +actual fun RigidBody.getRigidbodyLinearVelocity(rigidBody: RigidBody): Vector3d { + TODO("Not yet implemented") +} + +actual fun RigidBody.setRigidbodyLinearDamping( + rigidBody: RigidBody, + linearDamping: Double +) { +} + +actual fun RigidBody.setRigidbodyLinearVelocity( + rigidBody: RigidBody, + linearVelocity: Vector3d +) { +} + +actual fun RigidBody.getRigidbodyAngularVelocity(rigidBody: RigidBody): Vector3d { + TODO("Not yet implemented") +} + +actual fun RigidBody.setRigidbodyAngularVelocity( + rigidBody: RigidBody, + angularVelocity: Vector3d +) { +} + +actual fun RigidBody.getRigidbodyLinearDamping(rigidBody: RigidBody): Double { + TODO("Not yet implemented") +} + +actual fun RigidBody.getRigidbodyAngularDamping(rigidBody: RigidBody): Double { + TODO("Not yet implemented") +} + +actual fun RigidBody.setRigidbodyAngularDamping( + rigidBody: RigidBody, + angularDamping: Double +) { +} + +actual fun RigidBody.getRigidbodyLockTranslation(rigidBody: RigidBody): AxisLock { + TODO("Not yet implemented") +} + +actual fun RigidBody.setRigidbodyLockTranslation( + rigidBody: RigidBody, + lockTranslation: AxisLock +) { +} + +actual fun RigidBody.getRigidbodyLockRotation(rigidBody: RigidBody): AxisLock { + TODO("Not yet implemented") +} + +actual fun RigidBody.setRigidbodyLockRotation( + rigidBody: RigidBody, + lockRotation: AxisLock +) { +} + +actual fun RigidBody.getRigidbodyChildren(rigidBody: RigidBody): List<Collider> { + TODO("Not yet implemented") +} + +actual fun RigidBody.applyImpulse( + index: Index, + x: Double, + y: Double, + z: Double +) { +} + +actual fun RigidBody.applyTorqueImpulse( + index: Index, + x: Double, + y: Double, + z: Double +) { +} + +actual fun rigidBodyExistsForEntity(entityId: EntityId): Index? { + TODO("Not yet implemented") +} @@ -0,0 +1,19 @@ +package com.dropbear.scene + +import com.dropbear.utils.Progress + +actual fun SceneLoadHandle.switchToSceneAsync() { + TODO("Not yet implemented") +} + +actual fun SceneLoadHandle.getSceneLoadProgress(): Progress { + TODO("Not yet implemented") +} + +actual fun SceneLoadHandle.getSceneLoadStatus(): SceneLoadStatus { + TODO("Not yet implemented") +} + +actual fun SceneLoadHandle.getSceneLoadHandleSceneName(id: Long): String { + TODO("Not yet implemented") +} @@ -0,0 +1,15 @@ +package com.dropbear.scene + +actual fun SceneManager.loadSceneAsyncNative(sceneName: String): SceneLoadHandle? { + TODO("Not yet implemented") +} + +actual fun SceneManager.loadSceneAsyncNative( + sceneName: String, + loadingScene: String +): SceneLoadHandle? { + TODO("Not yet implemented") +} + +actual fun SceneManager.switchToSceneImmediateNative(sceneName: String) { +}