Import Oxide engine (Stages 0–10) under MIT license

Full project snapshot migrated to new Gitea remote without history:
engine, editor, physics, script, examples, tests, docs, and assets.
Relicensed from GPLv3 to MIT and updated repo URLs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Homer Simpson
2026-07-05 20:41:02 +02:00
parent 2afb56b329
commit f56a1eea3b
128 changed files with 40493 additions and 2 deletions
+159
View File
@@ -0,0 +1,159 @@
//! The [`RigidBody`] component and its [`RigidBodyKind`].
//!
//! A rigid body is the dynamics half of a physics object: it says *how* an
//! entity moves under the simulation (or that it does not). The *shape* it
//! collides with is a separate [`Collider`](crate::Collider) component, so the
//! two concerns compose — a body with no collider is a point mass, a collider
//! with no body is static world geometry.
//!
//! Like every Oxide component these are plain serializable data with
//! `#[derive(Reflect)]`, so they are editable from the inspector and from
//! scripts with no per-type editor code. The simulation reads them when it
//! builds the rapier world; the
//! authored fields here are inputs, never live simulation state (velocities and
//! poses live on the [`Transform`](oxide_engine::math::Transform) and inside
//! rapier).
use oxide_engine::reflect::{Reflect, ReflectEnum};
use serde::{Deserialize, Serialize};
/// How a [`RigidBody`] participates in the simulation.
///
/// Mirrors rapier's body types, named for engine users:
/// - [`Dynamic`](Self::Dynamic) — fully simulated; moved by gravity, forces,
/// and contacts.
/// - [`Kinematic`](Self::Kinematic) — moved only by the game (its
/// [`Transform`](oxide_engine::math::Transform)); pushes dynamic bodies but is
/// never pushed back. The basis for the character controller (Stage 9 later
/// piece) and for moving platforms.
/// - [`Static`](Self::Static) — never moves; immovable world geometry (floors,
/// walls). The cheapest body — no integration cost.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize, ReflectEnum)]
pub enum RigidBodyKind {
/// Fully simulated — gravity, forces, and contacts move it.
#[default]
Dynamic,
/// Moved only by the game; affects dynamics but is not affected by them.
Kinematic,
/// Immovable world geometry.
Static,
}
impl RigidBodyKind {
/// Every kind, for editor menus.
pub const ALL: [RigidBodyKind; 3] = [
RigidBodyKind::Dynamic,
RigidBodyKind::Kinematic,
RigidBodyKind::Static,
];
/// A human-readable label.
pub fn label(self) -> &'static str {
match self {
RigidBodyKind::Dynamic => "Dynamic",
RigidBodyKind::Kinematic => "Kinematic",
RigidBodyKind::Static => "Static",
}
}
}
/// Component: the dynamics of a physics object.
///
/// Attach alongside a [`Collider`](crate::Collider) to make an entity take part
/// in the simulation. The fields are authoring inputs consumed when the rapier
/// body is created; runtime velocity/pose are not stored here (the
/// [`Transform`](oxide_engine::math::Transform) is the authoritative pose, which
/// the simulation writes back each step).
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect)]
pub struct RigidBody {
/// How the body participates in the simulation.
pub kind: RigidBodyKind,
/// Mass in kilograms. Ignored for non-dynamic bodies. `0` means "derive the
/// mass from the collider's [`density`](crate::Collider::density)".
pub mass: f32,
/// Linear velocity damping (drag), `0` = none. Slows translation over time.
pub linear_damping: f32,
/// Angular velocity damping, `0` = none. Slows rotation over time.
pub angular_damping: f32,
/// Per-body multiplier on global gravity (`1` = normal, `0` = floats,
/// negative = anti-gravity). Lets some bodies ignore gravity individually.
pub gravity_scale: f32,
/// Enable continuous collision detection — prevents fast bodies tunnelling
/// through thin geometry, at extra cost. Off by default.
pub ccd: bool,
}
impl Default for RigidBody {
fn default() -> Self {
Self {
kind: RigidBodyKind::Dynamic,
// 0 = derive mass from collider density (rapier's default behaviour).
mass: 0.0,
linear_damping: 0.0,
angular_damping: 0.0,
gravity_scale: 1.0,
ccd: false,
}
}
}
impl RigidBody {
/// A static (immovable) body — convenience for world geometry.
pub fn static_body() -> Self {
Self {
kind: RigidBodyKind::Static,
..Self::default()
}
}
/// A kinematic body — moved by the game, not by the simulation.
pub fn kinematic() -> Self {
Self {
kind: RigidBodyKind::Kinematic,
..Self::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_a_dynamic_body() {
let b = RigidBody::default();
assert_eq!(b.kind, RigidBodyKind::Dynamic);
assert_eq!(b.gravity_scale, 1.0);
assert!(!b.ccd);
}
#[test]
fn constructors_set_the_kind() {
assert_eq!(RigidBody::static_body().kind, RigidBodyKind::Static);
assert_eq!(RigidBody::kinematic().kind, RigidBodyKind::Kinematic);
}
#[test]
fn round_trips_through_ron() {
let b = RigidBody {
kind: RigidBodyKind::Kinematic,
mass: 2.5,
linear_damping: 0.1,
angular_damping: 0.2,
gravity_scale: 0.0,
ccd: true,
};
let ron = ron::to_string(&b).unwrap();
let back: RigidBody = ron::from_str(&ron).unwrap();
assert_eq!(b, back);
}
#[test]
fn kind_variants_are_reflected_for_a_combo() {
// The editor lists these in a dropdown via ReflectEnum.
assert_eq!(
RigidBodyKind::variants(),
&["Dynamic", "Kinematic", "Static"]
);
}
}