kitgit

tirbofish/dropbear · commit

2993ca2170331eeee4b6982cbf84501017e46fd2

started on getCamera function, handing it over now

Unverified

tk <4tkbytes@pm.me> · 2025-10-21 08:28

view full diff

diff --git a/Cargo.toml b/Cargo.toml
index 2d688e0..ecfbbed 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -65,6 +65,7 @@ indexmap = "2.11"
 rand = "0.9"
 num_enum = "0.7"
 sha2 = "0.10"
+stdext = "0.3"
 
 [workspace.dependencies.image]
 version = "0.25"
diff --git a/eucalyptus-core/Cargo.toml b/eucalyptus-core/Cargo.toml
index 64567d9..71d3281 100644
--- a/eucalyptus-core/Cargo.toml
+++ b/eucalyptus-core/Cargo.toml
@@ -32,7 +32,7 @@ sha2.workspace = true
 libloading.workspace = true
 crossbeam-channel.workspace = true
 app_dirs2.workspace = true
-num_enum.workspace = true
+stdext.workspace = true
 
 [features]
 # editor only stuff
diff --git a/eucalyptus-core/src/lib.rs b/eucalyptus-core/src/lib.rs
index 0c2bfe9..e18ae6b 100644
--- a/eucalyptus-core/src/lib.rs
+++ b/eucalyptus-core/src/lib.rs
@@ -7,6 +7,7 @@ pub mod spawn;
 pub mod states;
 pub mod utils;
 pub mod ptr;
+mod result;
 
 pub use egui;
 
diff --git a/eucalyptus-core/src/result.rs b/eucalyptus-core/src/result.rs
new file mode 100644
index 0000000..f576625
--- /dev/null
+++ b/eucalyptus-core/src/result.rs
@@ -0,0 +1,80 @@
+use std::ptr;
+use jni::objects::{JClass, JObject, JString};
+
+/// Trait used by the `jni` crate for easier error matching.
+pub trait ResultToNull {
+    type Output;
+
+    /// If the output is of a type [`jni::Error`](jni::errors::Error),
+    /// it will return a null pointer. Pretty useful for when you don't want to have
+    /// to deal with error matching.
+    ///
+    /// Specifically: converts result to the inner value on [`Ok`], or a null pointer on [`Err`]
+    fn or_null(self) -> Self::Output;
+}
+
+impl ResultToNull for Result<JObject<'_>, jni::errors::Error> {
+    type Output = JObject<'static>;
+
+    fn or_null(self) -> Self::Output {
+        match self {
+            Ok(val) => unsafe { JObject::from_raw(val.into_raw()) },
+            Err(_) => JObject::null(),
+        }
+    }
+}
+
+impl ResultToNull for anyhow::Result<JObject<'_>> {
+    type Output = JObject<'static>;
+
+    fn or_null(self) -> Self::Output {
+        match self {
+            Ok(val) => unsafe { JObject::from_raw(val.into_raw()) },
+            Err(_) => JObject::null(),
+        }
+    }
+}
+
+impl ResultToNull for Result<JClass<'_>, jni::errors::Error> {
+    type Output = JClass<'static>;
+
+    fn or_null(self) -> Self::Output {
+        match self {
+            Ok(val) => unsafe { JClass::from_raw(val.into_raw()) },
+            Err(_) => unsafe { JClass::from_raw(ptr::null_mut()) },
+        }
+    }
+}
+
+impl ResultToNull for anyhow::Result<JClass<'_>> {
+    type Output = JClass<'static>;
+
+    fn or_null(self) -> Self::Output {
+        match self {
+            Ok(val) => unsafe { JClass::from_raw(val.into_raw()) },
+            Err(_) => unsafe { JClass::from_raw(ptr::null_mut()) },
+        }
+    }
+}
+
+impl ResultToNull for Result<JString<'_>, jni::errors::Error> {
+    type Output = JString<'static>;
+
+    fn or_null(self) -> Self::Output {
+        match self {
+            Ok(val) => unsafe { JString::from_raw(val.into_raw()) },
+            Err(_) => unsafe { JString::from_raw(ptr::null_mut()) },
+        }
+    }
+}
+
+impl ResultToNull for anyhow::Result<JString<'_>> {
+    type Output = JString<'static>;
+
+    fn or_null(self) -> Self::Output {
+        match self {
+            Ok(val) => unsafe { JString::from_raw(val.into_raw()) },
+            Err(_) => unsafe { JString::from_raw(ptr::null_mut()) },
+        }
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/jni/exports.rs b/eucalyptus-core/src/scripting/jni/exports.rs
index 3c8aafc..2541c40 100644
--- a/eucalyptus-core/src/scripting/jni/exports.rs
+++ b/eucalyptus-core/src/scripting/jni/exports.rs
@@ -1,12 +1,14 @@
 use crate::ptr::InputStatePtr;
-use crate::scripting::jni::utils::{java_button_to_rust, new_float_array};
+use crate::scripting::jni::utils::{create_vector3, java_button_to_rust, new_float_array};
 use crate::utils::keycode_from_ordinal;
 use dropbear_engine::entity::{AdoptedEntity, Transform};
 use glam::{DQuat, DVec3};
 use hecs::World;
-use jni::objects::{JClass, JObject, JPrimitiveArray, JString};
-use jni::sys::{jboolean, jclass, jdouble, jfloatArray, jint, jlong, jstring};
+use jni::objects::{JClass, JObject, JPrimitiveArray, JString, JValue};
+use jni::sys::{jboolean, jclass, jdouble, jfloatArray, jint, jlong, jobject, jstring};
 use jni::JNIEnv;
+use dropbear_engine::camera::Camera;
+use crate::camera::{CameraComponent, CameraType};
 use crate::states::{ModelProperties, Value};
 
 // JNIEXPORT jlong JNICALL Java_com_dropbear_ffi_JNINative_getEntity
@@ -955,8 +957,126 @@ pub fn Java_com_dropbear_ffi_JNINative_setVec3Property(
 
     if let Ok((_, props)) = world.query_one_mut::<(&AdoptedEntity, &mut ModelProperties)>(entity)
     {
-        props.set_property(key, Value::Vec3([values[0], values[1], values[2]]));
+        props.set_property(key, Value::Vec3(values));
     } else {
         eprintln!("[Java_com_dropbear_ffi_JNINative_setVec3Property] [ERROR] Failed to query entity for model properties");
     }
+}
+
+// JNIEXPORT jobject JNICALL Java_com_dropbear_ffi_JNINative_getCamera
+//   (JNIEnv *, jclass, jlong, jstring);
+#[unsafe(no_mangle)]
+pub fn Java_com_dropbear_ffi_JNINative_getCamera(
+    mut env: JNIEnv,
+    _class: JClass,
+    world_handle: jlong,
+    camera_name: JString,
+) -> jobject {
+    let world = world_handle as *mut World;
+    if world.is_null() {
+        eprintln!("[Java_com_dropbear_ffi_JNINative_getCamera] [ERROR] World pointer is null");
+        return std::ptr::null_mut();
+    }
+
+    let world = unsafe { &*world };
+
+    let label = env.get_string(&camera_name);
+    let label: String = if let Ok(str) = label {
+        str.to_string_lossy().to_string()
+    } else {
+        eprintln!("[Java_com_dropbear_ffi_JNINative_getCamera] [ERROR] Failed to get camera name");
+        return std::ptr::null_mut();
+    };
+
+    if let Some((id, (cam, comp))) = world.query::<(&Camera, &CameraComponent)>().iter().next() {
+        return if cam.label == label {
+            if matches!(comp.camera_type, CameraType::Debug) {
+                eprintln!("[Java_com_dropbear_ffi_JNINative_getCamera] [WARN] Querying a CameraType::Debug is illegal, returning null");
+                std::ptr::null_mut()
+            } else {
+                let entity_id = if let Ok(v) = env.find_class("com/dropbear/EntityId") {
+                    v
+                } else {
+                    eprintln!("[Java_com_dropbear_ffi_JNINative_getCamera] [ERROR] Unable to find EntityId class");
+                    return std::ptr::null_mut();
+                };
+                let entity_id = if let Ok(v) = env.new_object(
+                    entity_id,
+                    "(J)V",
+                    &[JValue::Long(id.id() as i64)],
+                ) {
+                    v
+                } else {
+                    eprintln!("[Java_com_dropbear_ffi_JNINative_getCamera] [ERROR] Unable to create new entity_id object");
+                    return std::ptr::null_mut();
+                };
+
+                let label = if let Ok(v) = env.new_string(cam.label.as_str()) {
+                    v
+                } else {
+                    eprintln!("[Java_com_dropbear_ffi_JNINative_getCamera] [ERROR] Unable to create new string for label");
+                    return std::ptr::null_mut();
+                };
+
+                let eye = if let Ok(v) = create_vector3(&mut env, cam.eye.x, cam.eye.y, cam.eye.z) {
+                    v
+                } else {
+                    eprintln!("[Java_com_dropbear_ffi_JNINative_getCamera] [ERROR] Unable to create vector3 for eye");
+                    return std::ptr::null_mut();
+                };
+
+                let target = if let Ok(v) = create_vector3(&mut env, cam.target.x, cam.target.y, cam.target.z) {
+                    v
+                } else {
+                    eprintln!("[Java_com_dropbear_ffi_JNINative_getCamera] [ERROR] Unable to create vector3 for target");
+                    return std::ptr::null_mut();
+                };
+
+                let up = if let Ok(v) = create_vector3(&mut env, cam.up.x, cam.up.y, cam.up.z) {
+                    v
+                } else {
+                    eprintln!("[Java_com_dropbear_ffi_JNINative_getCamera] [ERROR] Unable to create vector3 for up");
+                    return std::ptr::null_mut();
+                };
+
+                let class = if let Ok(v) = env.find_class("com/dropbear/Camera") {
+                    v
+                } else {
+                    eprintln!("[Java_com_dropbear_ffi_JNINative_getCamera] [ERROR] Unable to locate camera class");
+                    return std::ptr::null_mut();
+                };
+
+                let camera_obj = if let Ok(v) = env.new_object(
+                    class,
+                    "(Ljava/lang/String;Lcom/dropbear/EntityId;Lcom/dropbear/math/Vector3<Ljava/lang/Double;>;Lcom/dropbear/math/Vector3<Ljava/lang/Double;>;Lcom/dropbear/math/Vector3<Ljava/lang/Double;>;DDDDDDDD)V",
+                    &[
+                        JValue::Object(&label),
+                        JValue::Object(&entity_id),
+                        JValue::Object(&eye),
+                        JValue::Object(&target),
+                        JValue::Object(&up),
+                        JValue::Double(cam.aspect),
+                        JValue::Double(cam.fov_y),
+                        JValue::Double(cam.znear),
+                        JValue::Double(cam.zfar),
+                        JValue::Double(cam.yaw),
+                        JValue::Double(cam.pitch),
+                        JValue::Double(cam.speed),
+                        JValue::Double(cam.sensitivity),
+                    ]
+                ) {
+                    v
+                } else {
+                    eprintln!("[Java_com_dropbear_ffi_JNINative_getCamera] [ERROR] Unable to create the camera object");
+                    return std::ptr::null_mut();
+                };
+
+                camera_obj.as_raw()
+            }
+        } else {
+            std::ptr::null_mut()
+        }
+    }
+
+    std::ptr::null_mut()
 }
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/jni/utils.rs b/eucalyptus-core/src/scripting/jni/utils.rs
index 3fd5e72..5a4042b 100644
--- a/eucalyptus-core/src/scripting/jni/utils.rs
+++ b/eucalyptus-core/src/scripting/jni/utils.rs
@@ -1,5 +1,5 @@
 use jni::JNIEnv;
-use jni::objects::JFloatArray;
+use jni::objects::{JFloatArray, JObject, JValue};
 use jni::sys::{jfloatArray, jint};
 
 pub fn new_float_array(env: &mut JNIEnv, x: f32, y: f32) -> jfloatArray {
@@ -39,4 +39,41 @@ pub fn java_button_to_rust(button_code: jint) -> Option<winit::event::MouseButto
         other if other >= 0 => Some(winit::event::MouseButton::Other(other as u16)), // Assuming Other uses the int directly
         _ => 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/Double;Ljava/lang/Double;Ljava/lang/Double;)V",
+        &[
+            JValue::Object(&x_obj),
+            JValue::Object(&y_obj),
+            JValue::Object(&z_obj),
+        ]
+    )?;
+
+    Ok(vector3)
 }
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/Camera.kt b/src/commonMain/kotlin/com/dropbear/Camera.kt
new file mode 100644
index 0000000..03ff09b
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/Camera.kt
@@ -0,0 +1,28 @@
+package com.dropbear
+
+import com.dropbear.math.Vector3D
+
+class Camera(
+    private val label: String,
+    private 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
+) {
+    lateinit var engine: DropbearEngine
+
+    /**
+     * Pushes the camera values to the world to be updated.
+     */
+    fun setCamera() {
+        engine.native.setCamera(this)
+    }
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/DropbearEngine.kt b/src/commonMain/kotlin/com/dropbear/DropbearEngine.kt
index e92b872..d006b18 100644
--- a/src/commonMain/kotlin/com/dropbear/DropbearEngine.kt
+++ b/src/commonMain/kotlin/com/dropbear/DropbearEngine.kt
@@ -8,14 +8,22 @@ import com.dropbear.math.Transform
 class DropbearEngine(val native: NativeEngine) {
     private var inputState: InputState? = null
 
-    public fun getEntity(label: String): EntityRef? {
+    fun getEntity(label: String): EntityRef? {
         val entityId = native.getEntity(label)
         val entityRef = if (entityId != null) EntityRef(EntityId(entityId)) else null
         entityRef?.engine = this
         return entityRef
     }
 
-    public fun getInputState(): InputState {
+    fun getCamera(label: String): Camera? {
+        val result = native.getCamera(label)
+        if (result != null) {
+            result.engine = this
+        }
+        return result
+    }
+
+    fun getInputState(): InputState {
         if (this.inputState == null) {
             Logger.trace("InputState not initialised, creating new one")
             this.inputState = InputState(this)
diff --git a/src/commonMain/kotlin/com/dropbear/EntityRef.kt b/src/commonMain/kotlin/com/dropbear/EntityRef.kt
index 86d9ee7..79b1c66 100644
--- a/src/commonMain/kotlin/com/dropbear/EntityRef.kt
+++ b/src/commonMain/kotlin/com/dropbear/EntityRef.kt
@@ -47,4 +47,12 @@ class EntityRef(val id: EntityId = EntityId(0L)) {
             else -> throw IllegalArgumentException("Unsupported property type: ${value::class}")
         }
     }
+
+    fun getAttachedCamera(): Camera? {
+        val result = engine.native.getAttachedCamera(id)
+        if (result != null) {
+            result.engine = this.engine
+        }
+        return result
+    }
 }
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/ffi/NativeEngine.kt b/src/commonMain/kotlin/com/dropbear/ffi/NativeEngine.kt
index e3de4e8..9b1d74d 100644
--- a/src/commonMain/kotlin/com/dropbear/ffi/NativeEngine.kt
+++ b/src/commonMain/kotlin/com/dropbear/ffi/NativeEngine.kt
@@ -1,5 +1,6 @@
 package com.dropbear.ffi
 
+import com.dropbear.Camera
 import com.dropbear.EntityId
 import com.dropbear.input.KeyCode
 import com.dropbear.input.MouseButton
@@ -12,6 +13,10 @@ expect class NativeEngine {
      */
     fun getEntity(label: String): Long?
 
+    fun getCamera(label: String): Camera?
+    fun getAttachedCamera(entityId: EntityId): Camera?
+    fun setCamera(camera: Camera);
+
     /**
      * Fetches the [Transform] component of an entity by it's ID
      */
diff --git a/src/jvmMain/java/com/dropbear/ffi/JNINative.java b/src/jvmMain/java/com/dropbear/ffi/JNINative.java
index e8cc232..57e4174 100644
--- a/src/jvmMain/java/com/dropbear/ffi/JNINative.java
+++ b/src/jvmMain/java/com/dropbear/ffi/JNINative.java
@@ -1,5 +1,6 @@
 package com.dropbear.ffi;
 
+import com.dropbear.Camera;
 import com.dropbear.math.Transform;
 
 public class JNINative {
@@ -9,6 +10,9 @@ public class JNINative {
 
     // entity and ecs
     public static native long getEntity(long handle, String label);
+    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);
 
     // transformations
     public static native Transform getTransform(long handle, long entityHandle);
diff --git a/src/jvmMain/kotlin/com/dropbear/ffi/NativeEngine.jvm.kt b/src/jvmMain/kotlin/com/dropbear/ffi/NativeEngine.jvm.kt
index 3a6441b..af7fda6 100644
--- a/src/jvmMain/kotlin/com/dropbear/ffi/NativeEngine.jvm.kt
+++ b/src/jvmMain/kotlin/com/dropbear/ffi/NativeEngine.jvm.kt
@@ -1,5 +1,6 @@
 package com.dropbear.ffi
 
+import com.dropbear.Camera
 import com.dropbear.EntityId
 import com.dropbear.input.KeyCode
 import com.dropbear.input.MouseButton
@@ -133,4 +134,16 @@ actual class NativeEngine {
     actual fun setVec3Property(entityHandle: Long, label: String, value: FloatArray) {
         JNINative.setVec3Property(worldHandle, entityHandle, label, value)
     }
+
+    actual fun getCamera(label: String): Camera? {
+        return JNINative.getCamera(worldHandle, label)
+    }
+
+    actual fun getAttachedCamera(entityId: EntityId): Camera? {
+        return JNINative.getAttachedCamera(worldHandle, entityId.id)
+    }
+
+    actual fun setCamera(camera: Camera) {
+        JNINative.setCamera(worldHandle, camera)
+    }
 }
\ No newline at end of file
diff --git a/src/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt b/src/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt
index f965478..0071502 100644
--- a/src/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt
+++ b/src/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt
@@ -3,6 +3,7 @@
 
 package com.dropbear.ffi
 
+import com.dropbear.Camera
 import com.dropbear.EntityId
 import com.dropbear.ffi.generated.*
 import com.dropbear.input.KeyCode
@@ -298,4 +299,15 @@ actual class NativeEngine {
 
     actual fun setVec3Property(entityHandle: Long, label: String, value: FloatArray) {
     }
+
+    actual fun getCamera(label: String): Camera? {
+        TODO("Not yet implemented")
+    }
+
+    actual fun getAttachedCamera(entityId: EntityId): Camera? {
+        TODO("Not yet implemented")
+    }
+
+    actual fun setCamera(camera: Camera) {
+    }
 }
\ No newline at end of file