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:
@@ -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"]
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user