6c47dd8
· Thribhu K
feature: physics shape cast.
fix: implement the shadowJar for creating farJars to package
jarvis, run github actions
Unverified
diff --git a/.github/workflows/build_fatty.yaml b/.github/workflows/build_fatty.yaml
index 730147a..c83d8f4 100644
--- a/.github/workflows/build_fatty.yaml
+++ b/.github/workflows/build_fatty.yaml
@@ -25,7 +25,7 @@ jobs:
run: chmod +x gradlew
- name: Build with Gradle
- run: ./gradlew build fatJar
+ run: ./gradlew build shadowJar
- name: Upload JAR artifact
uses: actions/upload-artifact@v4
diff --git a/.github/workflows/create_executable.yaml b/.github/workflows/create_executable.yaml
index 0ecba95..2849e23 100644
--- a/.github/workflows/create_executable.yaml
+++ b/.github/workflows/create_executable.yaml
@@ -41,8 +41,8 @@ jobs:
- name: give gradle permissions
if: matrix.os != 'windows-latest'
run: chmod +x gradlew
- - name: build fatjar
- run: ./gradlew fatJar
+ - name: build shadowJar
+ run: ./gradlew shadowJar
- name: install libudev-dev (Linux)
if: matrix.os == 'ubuntu-latest'
run: sudo apt-get update && sudo apt-get install -y libudev-dev
diff --git a/.run/Run eucalyptus-editor.run.xml b/.run/Run eucalyptus-editor.run.xml
index 8a0a48b..d7393d3 100644
--- a/.run/Run eucalyptus-editor.run.xml
+++ b/.run/Run eucalyptus-editor.run.xml
@@ -13,8 +13,6 @@
<option name="backtrace" value="FULL" />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
- <method v="2">
- <option name="RunConfigurationTask" enabled="true" run_configuration_name="fatJar" run_configuration_type="GradleRunConfiguration" />
- </method>
+ <method v="2" />
</configuration>
</component>
\ No newline at end of file
diff --git a/.run/fatJar.run.xml b/.run/fatJar.run.xml
index cb795fa..49a5db4 100644
--- a/.run/fatJar.run.xml
+++ b/.run/fatJar.run.xml
@@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
- <configuration default="false" name="fatJar" type="GradleRunConfiguration" factoryName="Gradle">
+ <configuration default="false" name="shadowJar" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
@@ -10,7 +10,7 @@
</option>
<option name="taskNames">
<list>
- <option value="fatJar" />
+ <option value="shadowJar" />
</list>
</option>
<option name="vmOptions" />
diff --git a/README.md b/README.md
index 03c84ea..581fb3d 100644
--- a/README.md
+++ b/README.md
@@ -78,7 +78,7 @@ git clone git@github.com:tirbofish/dropbear
cd dropbear
# eucalyptus-editor requires dropbear-1.0-SNAPSHOT-all.jar to be built first
-./gradlew fatJar
+./gradlew shadowJar
# this will build all the projects in the workspace
cargo build
```
diff --git a/build.gradle.kts b/build.gradle.kts
index 5ba1f2c..ced3d17 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -3,6 +3,7 @@ plugins {
alias(libs.plugins.kotlinxSerialization)
`maven-publish`
id("org.jetbrains.dokka") version "2.1.0"
+ id("com.gradleup.shadow") version "9.2.2"
}
group = "com.dropbear"
@@ -126,6 +127,12 @@ kotlin {
}
}
+ java {
+ sourceSets.getByName("jvmMain") {
+ java.srcDirs("scripting/jvmMain/java")
+ }
+ }
+
targets.all {
compilations.all {
compileTaskProvider.configure {
@@ -207,21 +214,4 @@ publishing {
}
}
}
-}
-
-tasks.register<Jar>("fatJar") {
- archiveClassifier.set("all")
- duplicatesStrategy = DuplicatesStrategy.EXCLUDE
-
- from(kotlin.jvm().compilations["main"].output)
-
- configurations.named("jvmRuntimeClasspath").get().forEach { file ->
- if (file.name.endsWith(".jar")) {
- from(zipTree(file))
- } else {
- from(file)
- }
- }
-
- manifest {}
}
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/physics.rs b/crates/eucalyptus-core/src/physics.rs
index 3fe01f6..e5d3df1 100644
--- a/crates/eucalyptus-core/src/physics.rs
+++ b/crates/eucalyptus-core/src/physics.rs
@@ -334,7 +334,7 @@ pub mod jni {
use crate::physics::nalgebra;
use crate::physics::PhysicsState;
use crate::scripting::jni::utils::{FromJObject, ToJObject};
- use crate::types::{ColliderFFI, IndexNative, RayHit, Vector3};
+ use crate::types::{ColliderFFI, IndexNative, RayHit, ShapeCastHitFFI, Vector3};
use hecs::Entity;
use jni::objects::{JClass, JObject};
use jni::sys::{jboolean, jdouble, jlong, jobject};
@@ -342,6 +342,9 @@ pub mod jni {
use rapier3d::parry::query::DefaultQueryDispatcher;
use rapier3d::pipeline::QueryFilter;
use rapier3d::prelude::{point, vector, Ray};
+ use rapier3d::parry::query::ShapeCastOptions;
+ use rapier3d::math::{Pose3, Vec3};
+ use crate::physics::collider::ColliderShape;
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_dropbear_physics_PhysicsNative_getGravity(
@@ -475,6 +478,161 @@ pub mod jni {
}
}
+ fn collider_ffi_from_handle(physics: &PhysicsState, handle: rapier3d::prelude::ColliderHandle) -> ColliderFFI {
+ let (idx, generation) = handle.into_raw_parts();
+
+ let mut found_label = None;
+ for (label, colliders) in physics.colliders_entity_map.iter() {
+ for (_, c) in colliders {
+ if c.0 == handle.0 {
+ found_label = Some(label);
+ break;
+ }
+ }
+ if found_label.is_some() {
+ break;
+ }
+ }
+
+ let entity_id = if let Some(label) = found_label {
+ physics
+ .entity_label_map
+ .iter()
+ .find(|(_, l)| *l == label)
+ .map(|(e, _)| e.to_bits().get())
+ .unwrap_or(Entity::DANGLING.to_bits().get())
+ } else {
+ Entity::DANGLING.to_bits().get()
+ };
+
+ ColliderFFI {
+ index: IndexNative { index: idx, generation },
+ entity_id,
+ id: idx,
+ }
+ }
+
+ fn shared_shape_from_collider_shape(shape: &ColliderShape) -> rapier3d::geometry::SharedShape {
+ match shape {
+ ColliderShape::Box { half_extents } => {
+ rapier3d::geometry::SharedShape::cuboid(half_extents[0], half_extents[1], half_extents[2])
+ }
+ ColliderShape::Sphere { radius } => rapier3d::geometry::SharedShape::ball(*radius),
+ ColliderShape::Capsule { half_height, radius } => {
+ rapier3d::geometry::SharedShape::capsule_y(*half_height, *radius)
+ }
+ ColliderShape::Cylinder { half_height, radius } => {
+ rapier3d::geometry::SharedShape::cylinder(*half_height, *radius)
+ }
+ ColliderShape::Cone { half_height, radius } => {
+ rapier3d::geometry::SharedShape::cone(*half_height, *radius)
+ }
+ }
+ }
+
+ #[unsafe(no_mangle)]
+ pub extern "system" fn Java_com_dropbear_physics_PhysicsNative_shapeCast(
+ mut env: JNIEnv,
+ _: JClass,
+ physics_handle: jlong,
+ origin: JObject,
+ direction: JObject,
+ shape: JObject,
+ time_of_impact: jdouble,
+ solid: jboolean,
+ ) -> jobject {
+ let physics = crate::convert_ptr!(mut physics_handle => PhysicsState);
+
+ let qp = physics.broad_phase.as_query_pipeline(
+ &DefaultQueryDispatcher,
+ &physics.bodies,
+ &physics.colliders,
+ QueryFilter::new(),
+ );
+
+ let origin = match Vector3::from_jobject(&mut env, &origin) {
+ Ok(v) => v,
+ Err(e) => {
+ let _ = env.throw_new(
+ "java/lang/RuntimeException",
+ format!("Unable to convert origin to Vector3: {e}"),
+ );
+ return std::ptr::null_mut();
+ }
+ };
+
+ let direction = match Vector3::from_jobject(&mut env, &direction) {
+ Ok(v) => v,
+ Err(e) => {
+ let _ = env.throw_new(
+ "java/lang/RuntimeException",
+ format!("Unable to convert direction to Vector3: {e}"),
+ );
+ return std::ptr::null_mut();
+ }
+ };
+
+ let shape = match ColliderShape::from_jobject(&mut env, &shape) {
+ Ok(v) => v,
+ Err(e) => {
+ let _ = env.throw_new(
+ "java/lang/RuntimeException",
+ format!("Unable to convert shape to ColliderShape: {e}"),
+ );
+ return std::ptr::null_mut();
+ }
+ };
+
+ let dir_len = ((direction.x * direction.x) + (direction.y * direction.y) + (direction.z * direction.z)).sqrt();
+ if dir_len <= f64::EPSILON {
+ return std::ptr::null_mut();
+ }
+
+ let dir_unit = Vector3 {
+ x: direction.x / dir_len,
+ y: direction.y / dir_len,
+ z: direction.z / dir_len,
+ };
+
+ let cast_shape = shared_shape_from_collider_shape(&shape);
+ let iso: Pose3 = nalgebra::Isometry3::translation(origin.x as f32, origin.y as f32, origin.z as f32).into();
+ let vel: Vec3 = vector![dir_unit.x as f32, dir_unit.y as f32, dir_unit.z as f32].into();
+
+ let options = ShapeCastOptions {
+ max_time_of_impact: time_of_impact as f32,
+ target_distance: 0.0,
+ stop_at_penetration: solid != 0,
+ compute_impact_geometry_on_penetration: true,
+ };
+
+ let Some((hit_handle, toi)) = qp.cast_shape(&iso, vel, cast_shape.as_ref(), options) else {
+ return std::ptr::null_mut();
+ };
+
+ let collider = collider_ffi_from_handle(&physics, hit_handle);
+
+ let hit = ShapeCastHitFFI {
+ collider,
+ distance: toi.time_of_impact as f64,
+ witness1: Vector3::from([toi.witness1.x, toi.witness1.y, toi.witness1.z]),
+ witness2: Vector3::from([toi.witness2.x, toi.witness2.y, toi.witness2.z]),
+ normal1: Vector3::from([toi.normal1.x, toi.normal1.y, toi.normal1.z]),
+ normal2: Vector3::from([toi.normal2.x, toi.normal2.y, toi.normal2.z]),
+ status: toi.status,
+ };
+
+ match hit.to_jobject(&mut env) {
+ Ok(v) => v.into_raw(),
+ Err(e) => {
+ let _ = env.throw_new(
+ "java/lang/RuntimeException",
+ format!("Unable to create ShapeCastHit object: {e}"),
+ );
+ std::ptr::null_mut()
+ }
+ }
+ }
+
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_dropbear_physics_PhysicsNative_isOverlapping(
mut env: JNIEnv,
diff --git a/crates/eucalyptus-core/src/scripting.rs b/crates/eucalyptus-core/src/scripting.rs
index 0b5b333..f2a6965 100644
--- a/crates/eucalyptus-core/src/scripting.rs
+++ b/crates/eucalyptus-core/src/scripting.rs
@@ -723,15 +723,15 @@ pub async fn build_jvm(
let gradle_cmd = get_gradle_command(project_root);
- let _ = status_sender.send(BuildStatus::Building(format!("Running: {} fatJar", gradle_cmd)));
+ let _ = status_sender.send(BuildStatus::Building(format!("Running: {} shadowJar", gradle_cmd)));
let mut child = Command::new(&gradle_cmd)
.current_dir(project_root)
- .args(["--console=plain", "fatJar"])
+ .args(["--console=plain", "shadowJar"])
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
- .context(format!("Failed to spawn `{} fatJar`", gradle_cmd))?;
+ .context(format!("Failed to spawn `{} shadowJar`", gradle_cmd))?;
let stdout = child.stdout.take().expect("Stdout was piped");
let stderr = child.stderr.take().expect("Stderr was piped");
diff --git a/crates/eucalyptus-core/src/types.rs b/crates/eucalyptus-core/src/types.rs
index 75f5788..0860283 100644
--- a/crates/eucalyptus-core/src/types.rs
+++ b/crates/eucalyptus-core/src/types.rs
@@ -510,6 +510,59 @@ impl ToJObject for RayHit {
#[repr(C)]
#[derive(Clone, Copy)]
+pub struct ShapeCastHitFFI {
+ pub collider: ColliderFFI,
+ pub distance: f64,
+ pub witness1: Vector3,
+ pub witness2: Vector3,
+ pub normal1: Vector3,
+ pub normal2: Vector3,
+ pub status: rapier3d::parry::query::ShapeCastStatus,
+}
+
+impl ToJObject for ShapeCastHitFFI {
+ fn to_jobject<'a>(&self, env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> {
+ use jni::sys::jdouble;
+
+ let collider = self.collider.to_jobject(env)?;
+ let witness1 = self.witness1.to_jobject(env)?;
+ let witness2 = self.witness2.to_jobject(env)?;
+ let normal1 = self.normal1.to_jobject(env)?;
+ let normal2 = self.normal2.to_jobject(env)?;
+ let status = self.status.to_jobject(env)?;
+
+ let distance = self.distance as jdouble;
+
+ let class = env.find_class("com/dropbear/physics/ShapeCastHit").map_err(|e| {
+ eprintln!("[JNI Error] Failed to find ShapeCastHit class: {:?}", e);
+ DropbearNativeError::JNIClassNotFound
+ })?;
+
+ let object = env
+ .new_object(
+ class,
+ "(Lcom/dropbear/physics/Collider;DLcom/dropbear/math/Vector3d;Lcom/dropbear/math/Vector3d;Lcom/dropbear/math/Vector3d;Lcom/dropbear/math/Vector3d;Lcom/dropbear/physics/ShapeCastStatus;)V",
+ &[
+ JValue::Object(&collider),
+ JValue::Double(distance),
+ JValue::Object(&witness1),
+ JValue::Object(&witness2),
+ JValue::Object(&normal1),
+ JValue::Object(&normal2),
+ JValue::Object(&status),
+ ],
+ )
+ .map_err(|e| {
+ eprintln!("[JNI Error] Failed to create ShapeCastHit object: {:?}", e);
+ DropbearNativeError::JNIFailedToCreateObject
+ })?;
+
+ Ok(object)
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
/// Class: `com.dropbear.physics.CollisionEventType`
pub enum CollisionEventType {
Started,
diff --git a/scripting/commonMain/kotlin/com/dropbear/math/Math.kt b/scripting/commonMain/kotlin/com/dropbear/math/Math.kt
index 5979e49..ee34a7f 100644
--- a/scripting/commonMain/kotlin/com/dropbear/math/Math.kt
+++ b/scripting/commonMain/kotlin/com/dropbear/math/Math.kt
@@ -35,4 +35,12 @@ fun normalizeRadians(radians: Double): Double {
normalized += 2 * PI
}
return normalized
+}
+
+
+/**
+ * Compute the linear interpolation between two points x and y with a factor of t.
+ */
+fun lerp(x: Double, y: Double, t: Double): Double {
+ return x*(1-t)+y*t
}
\ No newline at end of file
diff --git a/scripting/commonMain/kotlin/com/dropbear/physics/Physics.kt b/scripting/commonMain/kotlin/com/dropbear/physics/Physics.kt
index a78dadb..1e329c4 100644
--- a/scripting/commonMain/kotlin/com/dropbear/physics/Physics.kt
+++ b/scripting/commonMain/kotlin/com/dropbear/physics/Physics.kt
@@ -57,6 +57,30 @@ class Physics {
fun touching(entity1: EntityRef, entity2: EntityRef): Boolean {
return isTouching(entity1, entity2)
}
+
+ /**
+ * Casts a [shape] from the [origin] in a specific [direction]. Returns a nullable [ShapeCastHit] object.
+ *
+ * @param origin The origin of the cast.
+ * @param direction The direction of the cast. Prefer unit vectors.
+ * @param shape The shape to cast.
+ * @param maxDistance The maximum distance before quitting operation. Set to `null` if no maxDistance.
+ * @param solid If true, detects hits even if the cast starts inside a shape.
+ * If false, the cast "passes through" from the inside until it exits.
+ */
+ fun shapeCast(
+ origin: Vector3d,
+ direction: Vector3d,
+ shape: ColliderShape,
+ maxDistance: Double?,
+ solid: Boolean,
+ ): ShapeCastHit? {
+ return if (maxDistance != null) {
+ shapeCast(origin, direction, shape, toi = maxDistance, solid)
+ } else {
+ shapeCast(origin, direction, shape, toi = Double.MAX_VALUE, solid)
+ }
+ }
}
}
@@ -66,4 +90,6 @@ internal expect fun setGravity(gravity: Vector3d)
internal expect fun raycast(origin: Vector3d, direction: Vector3d, toi: Double, solid: Boolean): RayHit?
internal expect fun isOverlapping(collider1: Collider, collider2: Collider): Boolean
internal expect fun isTriggering(collider1: Collider, collider2: Collider): Boolean
-internal expect fun isTouching(entity1: EntityRef, entity2: EntityRef): Boolean
\ No newline at end of file
+internal expect fun isTouching(entity1: EntityRef, entity2: EntityRef): Boolean
+
+internal expect fun shapeCast(origin: Vector3d, direction: Vector3d, shape: ColliderShape, toi: Double, solid: Boolean): ShapeCastHit?
\ No newline at end of file
diff --git a/scripting/commonMain/kotlin/com/dropbear/physics/ShapeCastHit.kt b/scripting/commonMain/kotlin/com/dropbear/physics/ShapeCastHit.kt
new file mode 100644
index 0000000..c3d70d8
--- /dev/null
+++ b/scripting/commonMain/kotlin/com/dropbear/physics/ShapeCastHit.kt
@@ -0,0 +1,28 @@
+package com.dropbear.physics
+
+import com.dropbear.math.Vector3d
+
+/**
+ * Defines a hit from a shape-cast (sweeping a volume through space).
+ *
+ * @param collider The first collider that is hit.
+ * @param distance The distance travelled along the cast direction at the time of impact.
+ * @param witness1 Contact point on the world collider.
+ * @param witness2 Contact point on the casted shape.
+ * @param normal1 Normal pointing from the world collider toward the casted shape.
+ * @param normal2 Normal pointing from the casted shape toward the world collider.
+ * @param status Status of the cast result.
+ */
+class ShapeCastHit(
+ val collider: Collider,
+ val distance: Double,
+ val witness1: Vector3d,
+ val witness2: Vector3d,
+ val normal1: Vector3d,
+ val normal2: Vector3d,
+ val status: ShapeCastStatus,
+) {
+ override fun toString(): String {
+ return "ShapeCastHit(collider=$collider, distance=$distance, status=$status)"
+ }
+}
diff --git a/scripting/jvmMain/java/com/dropbear/physics/KinematicCharacterControllerNative.java b/scripting/jvmMain/java/com/dropbear/physics/KinematicCharacterControllerNative.java
index c324a60..fbf3e1a 100644
--- a/scripting/jvmMain/java/com/dropbear/physics/KinematicCharacterControllerNative.java
+++ b/scripting/jvmMain/java/com/dropbear/physics/KinematicCharacterControllerNative.java
@@ -2,7 +2,6 @@ package com.dropbear.physics;
import com.dropbear.EucalyptusCoreLoader;
import com.dropbear.math.Vector3d;
-import org.jetbrains.annotations.Nullable;
// fuck, this got a long ass name
public class KinematicCharacterControllerNative {
diff --git a/scripting/jvmMain/java/com/dropbear/physics/PhysicsNative.java b/scripting/jvmMain/java/com/dropbear/physics/PhysicsNative.java
index 078e3ba..389a510 100644
--- a/scripting/jvmMain/java/com/dropbear/physics/PhysicsNative.java
+++ b/scripting/jvmMain/java/com/dropbear/physics/PhysicsNative.java
@@ -12,6 +12,7 @@ public class PhysicsNative {
public static native void setGravity(long physicsHandle, Vector3d gravity);
public static native RayHit raycast(long physicsHandle, Vector3d origin, Vector3d direction, double toi, boolean solid);
+ public static native ShapeCastHit shapeCast(long physicsHandle, Vector3d origin, Vector3d direction, ColliderShape shape, double toi, boolean solid);
public static native boolean isOverlapping(long physicsHandle, Collider collider1, Collider collider2);
public static native boolean isTriggering(long physicsHandle, Collider collider1, Collider collider2);
public static native boolean isTouching(long physicsHandle, long entity1, long entity2);
diff --git a/scripting/jvmMain/kotlin/com/dropbear/physics/Physics.jvm.kt b/scripting/jvmMain/kotlin/com/dropbear/physics/Physics.jvm.kt
index 844c9a1..61dc248 100644
--- a/scripting/jvmMain/kotlin/com/dropbear/physics/Physics.jvm.kt
+++ b/scripting/jvmMain/kotlin/com/dropbear/physics/Physics.jvm.kt
@@ -31,4 +31,14 @@ internal actual fun isTriggering(collider1: Collider, collider2: Collider): Bool
internal actual fun isTouching(entity1: EntityRef, entity2: EntityRef): Boolean {
return PhysicsNative.isTouching(DropbearEngine.native.physicsEngineHandle, entity1.id.raw, entity2.id.raw)
+}
+
+internal actual fun shapeCast(
+ origin: Vector3d,
+ direction: Vector3d,
+ shape: ColliderShape,
+ toi: Double,
+ solid: Boolean
+): ShapeCastHit? {
+ return PhysicsNative.shapeCast(DropbearEngine.native.physicsEngineHandle, origin, direction, shape, toi, solid)
}
\ No newline at end of file
diff --git a/scripting/nativeMain/kotlin/com/dropbear/physics/Physics.native.kt b/scripting/nativeMain/kotlin/com/dropbear/physics/Physics.native.kt
index 5298951..22500c9 100644
--- a/scripting/nativeMain/kotlin/com/dropbear/physics/Physics.native.kt
+++ b/scripting/nativeMain/kotlin/com/dropbear/physics/Physics.native.kt
@@ -29,4 +29,14 @@ internal actual fun isTriggering(collider1: Collider, collider2: Collider): Bool
internal actual fun isTouching(entity1: EntityRef, entity2: EntityRef): Boolean {
TODO("Not implemented yet")
+}
+
+internal actual fun shapeCast(
+ origin: Vector3d,
+ direction: Vector3d,
+ shape: ColliderShape,
+ toi: Double,
+ solid: Boolean
+): ShapeCastHit? {
+ TODO("Not yet implemented")
}
\ No newline at end of file