use bevy_ecs::prelude::{Component, Entity};
use bevy_math::Vec2 as BevyVec2;
use boxdd::{
ApiError, ApiResult, BodyId, BodyType, Filter, JointId, MotionLocks, ShapeDef, ShapeId,
SurfaceMaterial,
};
pub const MAX_COLLIDER_POLYGON_VERTICES: usize = boxdd::MAX_POLYGON_VERTICES;
#[derive(Component, Copy, Clone, Debug, Default, Eq, PartialEq)]
pub enum RigidBody {
Static,
Kinematic,
#[default]
Dynamic,
}
impl From<RigidBody> for BodyType {
fn from(value: RigidBody) -> Self {
match value {
RigidBody::Static => BodyType::Static,
RigidBody::Kinematic => BodyType::Kinematic,
RigidBody::Dynamic => BodyType::Dynamic,
}
}
}
#[derive(Component, Copy, Clone, Debug, PartialEq)]
pub struct BodySettings {
pub gravity_scale: f32,
pub linear_damping: f32,
pub angular_damping: f32,
pub sleep_enabled: bool,
pub bullet: bool,
pub motion_locks: MotionLocks,
}
impl BodySettings {
pub fn bullet() -> Self {
Self {
bullet: true,
..Default::default()
}
}
pub fn validate(self) -> ApiResult<()> {
validate_scalar(self.gravity_scale)?;
validate_nonnegative_scalar(self.linear_damping)?;
validate_nonnegative_scalar(self.angular_damping)
}
}
impl Default for BodySettings {
fn default() -> Self {
Self {
gravity_scale: 1.0,
linear_damping: 0.0,
angular_damping: 0.0,
sleep_enabled: true,
bullet: false,
motion_locks: MotionLocks::default(),
}
}
}
#[derive(Component, Copy, Clone, Debug, PartialEq)]
pub enum Collider {
Circle {
radius: f32,
center: BevyVec2,
},
Capsule {
point1: BevyVec2,
point2: BevyVec2,
radius: f32,
},
Segment {
point1: BevyVec2,
point2: BevyVec2,
},
Rectangle {
half_extents: BevyVec2,
},
RoundedRectangle {
half_extents: BevyVec2,
radius: f32,
},
ConvexPolygon {
vertices: [BevyVec2; MAX_COLLIDER_POLYGON_VERTICES],
count: u8,
radius: f32,
},
}
impl Collider {
pub const fn circle(radius: f32) -> Self {
Self::Circle {
radius,
center: BevyVec2::ZERO,
}
}
pub const fn circle_at(center: BevyVec2, radius: f32) -> Self {
Self::Circle { radius, center }
}
pub const fn rectangle(half_width: f32, half_height: f32) -> Self {
Self::Rectangle {
half_extents: BevyVec2::new(half_width, half_height),
}
}
pub const fn square(half_extent: f32) -> Self {
Self::Rectangle {
half_extents: BevyVec2::splat(half_extent),
}
}
pub const fn rounded_rectangle(half_width: f32, half_height: f32, radius: f32) -> Self {
Self::RoundedRectangle {
half_extents: BevyVec2::new(half_width, half_height),
radius,
}
}
pub const fn capsule_y(half_height: f32, radius: f32) -> Self {
Self::Capsule {
point1: BevyVec2::new(0.0, -half_height),
point2: BevyVec2::new(0.0, half_height),
radius,
}
}
pub const fn segment(point1: BevyVec2, point2: BevyVec2) -> Self {
Self::Segment { point1, point2 }
}
pub fn convex_polygon<I>(points: I, radius: f32) -> ApiResult<Self>
where
I: IntoIterator<Item = BevyVec2>,
{
let mut vertices = [BevyVec2::ZERO; MAX_COLLIDER_POLYGON_VERTICES];
let mut count = 0usize;
for point in points {
if count == MAX_COLLIDER_POLYGON_VERTICES {
return Err(ApiError::InvalidArgument);
}
vertices[count] = point;
count += 1;
}
if count == 0 || count > u8::MAX as usize {
return Err(ApiError::InvalidArgument);
}
let collider = Self::ConvexPolygon {
vertices,
count: count as u8,
radius,
};
collider.validate()?;
Ok(collider)
}
pub fn validate(self) -> ApiResult<()> {
match self {
Self::Circle { radius, center } => {
validate_vec2(center)?;
validate_positive_scalar(radius)
}
Self::Capsule {
point1,
point2,
radius,
} => {
validate_vec2(point1)?;
validate_vec2(point2)?;
validate_distinct_points(point1, point2)?;
validate_positive_scalar(radius)
}
Self::Segment { point1, point2 } => {
validate_vec2(point1)?;
validate_vec2(point2)?;
validate_distinct_points(point1, point2)
}
Self::Rectangle { half_extents } => validate_positive_vec2(half_extents),
Self::RoundedRectangle {
half_extents,
radius,
} => {
validate_positive_vec2(half_extents)?;
validate_nonnegative_scalar(radius)
}
Self::ConvexPolygon {
vertices,
count,
radius,
} => {
let count = count as usize;
if count == 0 || count > MAX_COLLIDER_POLYGON_VERTICES {
return Err(ApiError::InvalidArgument);
}
validate_nonnegative_scalar(radius)?;
for point in &vertices[..count] {
validate_vec2(*point)?;
}
Ok(())
}
}
}
}
#[derive(Component, Copy, Clone, Debug, PartialEq)]
pub struct PhysicsMaterial {
pub density: f32,
pub friction: f32,
pub restitution: f32,
pub rolling_resistance: f32,
pub tangent_speed: f32,
pub user_material_id: u64,
pub is_sensor: bool,
pub enable_contact_events: bool,
pub enable_sensor_events: bool,
pub enable_hit_events: bool,
pub enable_pre_solve_events: bool,
pub filter: Filter,
}
impl PhysicsMaterial {
pub fn shape_def(self) -> ShapeDef {
let material = SurfaceMaterial::default()
.with_friction(self.friction)
.with_restitution(self.restitution)
.with_rolling_resistance(self.rolling_resistance)
.with_tangent_speed(self.tangent_speed)
.with_user_material_id(self.user_material_id);
ShapeDef::builder()
.material(material)
.density(self.density)
.sensor(self.is_sensor)
.enable_contact_events(self.enable_contact_events)
.enable_sensor_events(self.enable_sensor_events)
.enable_hit_events(self.enable_hit_events)
.enable_pre_solve_events(self.enable_pre_solve_events)
.filter(self.filter)
.build()
}
pub fn validate(self) -> ApiResult<()> {
validate_nonnegative_scalar(self.density)?;
self.shape_def().validate()
}
}
impl Default for PhysicsMaterial {
fn default() -> Self {
Self {
density: 1.0,
friction: 0.6,
restitution: 0.0,
rolling_resistance: 0.0,
tangent_speed: 0.0,
user_material_id: 0,
is_sensor: false,
enable_contact_events: false,
enable_sensor_events: false,
enable_hit_events: false,
enable_pre_solve_events: false,
filter: Filter::default(),
}
}
}
#[derive(Component, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct BoxddBody(pub BodyId);
impl BoxddBody {
pub const fn id(self) -> BodyId {
self.0
}
}
#[derive(Component, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct BoxddShape(pub ShapeId);
impl BoxddShape {
pub const fn id(self) -> ShapeId {
self.0
}
}
#[derive(Component, Copy, Clone, Debug, PartialEq)]
pub struct JointDescriptor {
pub entity_a: Entity,
pub entity_b: Entity,
pub kind: JointKind,
pub collide_connected: bool,
pub force_threshold: f32,
pub torque_threshold: f32,
pub constraint_hertz: f32,
pub constraint_damping_ratio: f32,
pub draw_scale: f32,
}
impl JointDescriptor {
pub const fn distance(
entity_a: Entity,
entity_b: Entity,
anchor_a: BevyVec2,
anchor_b: BevyVec2,
) -> Self {
Self::new(
entity_a,
entity_b,
JointKind::Distance(DistanceJointDescriptor {
anchor_a,
anchor_b,
length: None,
}),
)
}
pub const fn revolute(entity_a: Entity, entity_b: Entity, anchor: BevyVec2) -> Self {
Self::new(
entity_a,
entity_b,
JointKind::Revolute(RevoluteJointDescriptor { anchor }),
)
}
pub const fn new(entity_a: Entity, entity_b: Entity, kind: JointKind) -> Self {
Self {
entity_a,
entity_b,
kind,
collide_connected: false,
force_threshold: f32::MAX,
torque_threshold: f32::MAX,
constraint_hertz: 60.0,
constraint_damping_ratio: 2.0,
draw_scale: 1.0,
}
}
pub const fn with_collide_connected(mut self, flag: bool) -> Self {
self.collide_connected = flag;
self
}
pub const fn with_event_thresholds(mut self, force: f32, torque: f32) -> Self {
self.force_threshold = force;
self.torque_threshold = torque;
self
}
pub const fn with_constraint_tuning(mut self, hertz: f32, damping_ratio: f32) -> Self {
self.constraint_hertz = hertz;
self.constraint_damping_ratio = damping_ratio;
self
}
pub const fn with_draw_scale(mut self, scale: f32) -> Self {
self.draw_scale = scale;
self
}
pub fn validate(self) -> ApiResult<()> {
if self.entity_a == self.entity_b {
return Err(ApiError::InvalidArgument);
}
validate_nonnegative_scalar(self.force_threshold)?;
validate_nonnegative_scalar(self.torque_threshold)?;
validate_positive_scalar(self.constraint_hertz)?;
validate_nonnegative_scalar(self.constraint_damping_ratio)?;
validate_positive_scalar(self.draw_scale)?;
self.kind.validate()
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum JointKind {
Distance(DistanceJointDescriptor),
Revolute(RevoluteJointDescriptor),
}
impl JointKind {
fn validate(self) -> ApiResult<()> {
match self {
Self::Distance(descriptor) => descriptor.validate(),
Self::Revolute(descriptor) => descriptor.validate(),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct DistanceJointDescriptor {
pub anchor_a: BevyVec2,
pub anchor_b: BevyVec2,
pub length: Option<f32>,
}
impl DistanceJointDescriptor {
pub const fn with_length(mut self, length: f32) -> Self {
self.length = Some(length);
self
}
fn validate(self) -> ApiResult<()> {
validate_vec2(self.anchor_a)?;
validate_vec2(self.anchor_b)?;
if let Some(length) = self.length {
validate_positive_scalar(length)?;
}
Ok(())
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct RevoluteJointDescriptor {
pub anchor: BevyVec2,
}
impl RevoluteJointDescriptor {
fn validate(self) -> ApiResult<()> {
validate_vec2(self.anchor)
}
}
#[derive(Component, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct BoxddJoint(pub JointId);
impl BoxddJoint {
pub const fn id(self) -> JointId {
self.0
}
}
#[derive(Component, Copy, Clone, Debug, Default, Eq, PartialEq)]
pub enum TransformSyncMode {
#[default]
PhysicsToBevy,
BevyToPhysics,
None,
}
#[derive(Component, Copy, Clone, Debug, Default, PartialEq)]
pub struct LinearVelocity(pub BevyVec2);
#[derive(Component, Copy, Clone, Debug, Default, PartialEq)]
pub struct AngularVelocity(pub f32);
#[derive(Component, Copy, Clone, Debug, PartialEq)]
pub struct LinearImpulse {
pub impulse: BevyVec2,
pub wake: bool,
}
impl LinearImpulse {
pub const fn new(impulse: BevyVec2) -> Self {
Self {
impulse,
wake: true,
}
}
}
#[derive(Component, Copy, Clone, Debug, PartialEq)]
pub struct AngularImpulse {
pub impulse: f32,
pub wake: bool,
}
impl AngularImpulse {
pub const fn new(impulse: f32) -> Self {
Self {
impulse,
wake: true,
}
}
}
fn validate_vec2(value: BevyVec2) -> ApiResult<()> {
if value.is_finite() {
Ok(())
} else {
Err(ApiError::InvalidArgument)
}
}
fn validate_scalar(value: f32) -> ApiResult<()> {
if value.is_finite() {
Ok(())
} else {
Err(ApiError::InvalidArgument)
}
}
fn validate_positive_scalar(value: f32) -> ApiResult<()> {
if value.is_finite() && value > 0.0 {
Ok(())
} else {
Err(ApiError::InvalidArgument)
}
}
fn validate_nonnegative_scalar(value: f32) -> ApiResult<()> {
if value.is_finite() && value >= 0.0 {
Ok(())
} else {
Err(ApiError::InvalidArgument)
}
}
fn validate_positive_vec2(value: BevyVec2) -> ApiResult<()> {
if value.is_finite() && value.x > 0.0 && value.y > 0.0 {
Ok(())
} else {
Err(ApiError::InvalidArgument)
}
}
fn validate_distinct_points(a: BevyVec2, b: BevyVec2) -> ApiResult<()> {
if a.distance_squared(b) > 0.0 {
Ok(())
} else {
Err(ApiError::InvalidArgument)
}
}